Feature - Traffic Ops Client Certificate Authentication
Overview
Add 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
- [x] This PR has tests
- [x] This PR has documentation
- [x] This PR has a CHANGELOG.md entry
- [x] This PR DOES NOT FIX A SERIOUS SECURITY VULNERABILITY (see the Apache Software Foundation's security guidelines for details)
Codecov Report
:exclamation: No coverage uploaded for pull request base (
master@3a9517c). Click here to learn what that means. The diff coverage isn/a.
@@ Coverage Diff @@
## master #7110 +/- ##
=========================================
Coverage ? 26.16%
=========================================
Files ? 621
Lines ? 75297
Branches ? 0
=========================================
Hits ? 19704
Misses ? 53787
Partials ? 1806
| Flag | Coverage Δ | |
|---|---|---|
| golib_unit | 53.15% <0.00%> (?) |
|
| grove_unit | 4.60% <0.00%> (?) |
|
| t3c_unit | 5.35% <0.00%> (?) |
|
| traffic_monitor_unit | 20.43% <0.00%> (?) |
|
| traffic_ops_integration | 69.34% <0.00%> (?) |
|
| traffic_ops_unit | 19.80% <0.00%> (?) |
|
| traffic_stats_unit | 10.41% <0.00%> (?) |
|
| unit_tests | 22.65% <0.00%> (?) |
|
| v3 | 57.79% <0.00%> (?) |
|
| v4 | 79.09% <0.00%> (?) |
|
| v5 | 78.53% <0.00%> (?) |
Flags with carried forward coverage won't be shown. Click here to find out more.
:mega: We’re building smart automated test selection to slash your CI/CD build times. Learn more
@tcfdev re-PRed this with edits at #7392