Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Traffic Ops client certificate authentication #7392

Merged
merged 21 commits into from
May 8, 2023

Conversation

zrhoffman
Copy link
Member

Overview

(Largely reposting @tcfdev's PR description from #7110):

This PR implements #6622 by adding the ability for a Traffic Ops instance to accept TLS certificates from a client request and verify them against specified Root CA's certificate as a form of login. This is not to be confused with mTLS, albeit has a similar design. Should a client not send a TLS certificate as part of the request login functionality will default to standard form authentication (current implementation).

Client

The client will provide a TLS certificate (and intermediate(s)) when hitting the /user/login endpoint.

The client will need to attach a certificate (and intermediate(s)) that was provided by the Root CA. The client certificate will need to contain a UID Relative Distinguished Name in the DN for the x509 certificate. The object identifier for this field is:

0.9.2342.19200300.100.1.1

This will result in a subject that looks something like:

Subject: C=US, ST=Colorado, L=Denver, O=Apache, OU=ATC, CN=client.local/UID=userid

Traffic Ops

In cnd.conf there is a new section to define the location of the Root CA certificates that are used for verification.

cdn.conf

"client_certificate_authentication" : {
    "root_certificates_directory" : "/etc/pki/tls/certs/"
}

Additionally, to enable a line must be added to the traffic_ops_golang.tls_config section if it exists. If this section is not present, ClientAuth will be enabled (but not required) by default.

cdn.conf

"traffic_ops_golang" : {
// ... other configuration values
    "tls_config": {
        "MinVersion": 769,
        "ClientAuth" : 1 // <---- Enables requesting the client certificate
    }
}

Traffic Ops does not require the client TLS certificate to be present. And will not verify the client provided TLS certificates during the TLS handshake when establishing a connection. Only if the client sends a TLS certificate to the /user/login will it be processed. If the certificate (and intermediate(s)) provided by the client verifies correctly against the defined Root certificates, the UID field will be parsed. If there is more than 1 UID field, only the first one is accepted. Order is not guaranteed. The UID value will then be used for authentication.

Should the TLS certificate authentication fail at any point, Traffic Ops will attempt to perform form value authentication (username and password) which is the current functionality. TLS client certificate authentication is not present on Oauth or Token login endpoints currently.


Which Traffic Control components are affected by this PR?

  • Documentation
  • Traffic Ops

What is the best way to verify this PR?

Unit

Verify unit tests pass. Unit tests have been added to test TLS authentication. Existing unit tests ensure current default behavior continues to work as expected.

Manual

Test certificates can be created using the file located at trafficcontrol/experimental/certificate_auth/generate_certs.go. Running go run generate_certs.go will produce private keys and certificates for Root, Intermediate, and Client (Server cert/key is also created, but can be ignored since they too are used only in tests). Place the Root certificate in the directory location specified in the cdn.conf file.

Launch a Traffic Ops instance.

Ensure a user exists with appropriate permissions with the name userid (This can be changed in the generate_certs.go file if you want to use a different username).

Send a request to the user/login Traffic Ops with the Client and Intermediate certs. For a Go client this would look something like:

client.go A more complete version can be found at trafficcontrol/experimental/certificate_auth/example/client.go

func main() {
	// LoadX509KeyPair can also load certificate chain with intermediates
	cert, _ := tls.LoadX509KeyPair("../certs/client-intermediate-chain.crt.pem", "../certs/client.key.pem")

	transport := &http.Transport{
		TLSClientConfig: &tls.Config{
			Certificates: []tls.Certificate{cert},
		},
	}

	client := http.Client{
		Timeout:   time.Second * 60,
		Transport: transport,
	}

	req, _ := http.NewRequest(
		http.MethodPost,
		"https://server.local:8443/api/4.0/user/login",
		bytes.NewBufferString(""),
	)

	resp, _ := client.Do(req)
	defer resp.Body.Close()

	respBody, _ := ioutil.ReadAll(resp.Body)

	fmt.Println(string(respBody)) // Verify Success
	fmt.Println(resp.Cookies()) // Verify Cookie(s)
}

Upon success, a 200 OK status code will be returned along with the following body:

{
    "alerts": [
        {
            "text": "Successfully logged in.",
            "level": "success"
        }
    ]
}

Additional tests may also be performed if desired, such as sending a POST request with a username/password body and no certificate (current behavior). Or a bad cert (either malformed or not signed by the correct Root CA) with or without username/password form.

PR submission checklist

@zrhoffman zrhoffman added new feature A new feature, capability or behavior Traffic Ops related to Traffic Ops medium impact impacts a significant portion of a CDN, or has the potential to do so authentication Relating to login, registration, passwords, tokens, etc. labels Mar 9, 2023
@codecov
Copy link

codecov bot commented Mar 9, 2023

Codecov Report

Merging #7392 (73409b2) into master (41542e9) will increase coverage by 2.51%.
The diff coverage is 28.04%.

@@             Coverage Diff              @@
##             master    #7392      +/-   ##
============================================
+ Coverage     27.45%   29.97%   +2.51%     
  Complexity       98       98              
============================================
  Files           685      785     +100     
  Lines         77786    82087    +4301     
  Branches         90      790     +700     
============================================
+ Hits          21357    24604    +3247     
- Misses        54417    55413     +996     
- Partials       2012     2070      +58     
Flag Coverage Δ
golib_unit 48.56% <ø> (-0.01%) ⬇️
grove_unit 4.60% <ø> (ø)
t3c_unit 5.32% <ø> (ø)
traffic_monitor_unit 21.28% <ø> (ø)
traffic_ops_integration 69.42% <ø> (ø)
traffic_ops_unit 22.91% <28.04%> (+0.09%) ⬆️
traffic_portal_v2 76.44% <ø> (?)
traffic_stats_unit 10.14% <ø> (ø)
unit_tests 27.00% <28.04%> (+2.93%) ⬆️
v3 57.79% <ø> (ø)
v4 79.18% <ø> (ø)
v5 78.63% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

Impacted Files Coverage Δ
traffic_ops/traffic_ops_golang/auth/authorize.go 27.05% <0.00%> (ø)
traffic_ops/traffic_ops_golang/config/config.go 35.98% <ø> (ø)
...affic_ops/traffic_ops_golang/traffic_ops_golang.go 0.59% <0.00%> (-0.01%) ⬇️
traffic_ops/traffic_ops_golang/login/login.go 11.25% <14.96%> (+3.16%) ⬆️
traffic_ops/traffic_ops_golang/auth/certificate.go 44.64% <44.64%> (ø)

... and 100 files with indirect coverage changes

📣 We’re building smart automated test selection to slash your CI/CD build times. Learn more

Copy link
Contributor

@alficles alficles left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some of these need conversation and some need updating.

}
},
"client_certificate_authentication" : {
"root_certificates_directory" : "/etc/pki/tls/certs/"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a problematic default. This will default to accepting certs signed by any of the default CAs, which is not what anyone is likely to want here. I would probably leave this undefined so the default "fails closed" with no acceptable roots.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not a big fan of leaving it completely empty, because that make the feature less usable. Changed to /etc/pki/tls/traffic_ops in 3e3873e8f0, would that work?

traffic_ops/traffic_ops_golang/auth/certificate.go Outdated Show resolved Hide resolved
traffic_ops/traffic_ops_golang/auth/certificate.go Outdated Show resolved Hide resolved
traffic_ops/traffic_ops_golang/auth/certificate.go Outdated Show resolved Hide resolved
traffic_ops/traffic_ops_golang/login/login.go Outdated Show resolved Hide resolved

// LoginHandler first attempts to verify and parse user information from an optionally
// provided client TLS certificate. If it fails at any point, it will fall back and
// continue with the standard submitted form authentication.
func LoginHandler(db *sqlx.DB, cfg config.Config) http.HandlerFunc {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This handles certificates as a replacement for uid/pass credentials, but not as a replacement for the mojo cookie. Given that the certificate can be seamlessly provided on every request, asking clients to use the login method to obtain a token that is then used instead of the certificate seems to create complexity where it isn't required. Additionally, certificates can ideally enable flows that do not use tokens, which prevents tokens from being stolen. The private key of a certificate isn't transmitted, so even if the certificate were leaked, it couldn't be used.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This handles certificates as a replacement for uid/pass credentials, but not as a replacement for the mojo cookie. Given that the certificate can be seamlessly provided on every request, asking clients to use the login method to obtain a token that is then used instead of the certificate seems to create complexity where it isn't required. Additionally, certificates can ideally enable flows that do not use tokens, which prevents tokens from being stolen. The private key of a certificate isn't transmitted, so even if the certificate were leaked, it couldn't be used.

You're right that this is a good idea, I opened #7498 for it. I'll make sure we implement it, but it should not block #7392 being accepted, IMO.

@zrhoffman
Copy link
Member Author

(Rebased)

@zrhoffman
Copy link
Member Author

Rebased to get #7489

Copy link
Contributor

@alficles alficles left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is good stuff. I see the open ticket to track the larger work getting the certs to work on pages other than the login page. That's important work, but it doesn't need to block this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
authentication Relating to login, registration, passwords, tokens, etc. medium impact impacts a significant portion of a CDN, or has the potential to do so new feature A new feature, capability or behavior Traffic Ops related to Traffic Ops
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants