Allow adding optional Client CA files

This commit is contained in:
NicoMen 2017-11-10 10:30:04 +01:00 committed by Traefiker
parent 1691f586d7
commit 4f4491c247
12 changed files with 97 additions and 25 deletions

View file

@ -52,7 +52,10 @@ func TestDo_globalConfiguration(t *testing.T) {
{CertFile: "CertFile 1", KeyFile: "KeyFile 1"},
{CertFile: "CertFile 2", KeyFile: "KeyFile 2"},
},
ClientCAFiles: []string{"foo ClientCAFiles 1", "foo ClientCAFiles 2", "foo ClientCAFiles 3"},
ClientCA: traefikTls.ClientCA{
Files: []string{"foo ClientCAFiles 1", "foo ClientCAFiles 2", "foo ClientCAFiles 3"},
Optional: false,
},
},
Redirect: &configuration.Redirect{
Replacement: "foo Replacement",
@ -95,7 +98,10 @@ func TestDo_globalConfiguration(t *testing.T) {
{CertFile: "CertFile 1", KeyFile: "KeyFile 1"},
{CertFile: "CertFile 2", KeyFile: "KeyFile 2"},
},
ClientCAFiles: []string{"fii ClientCAFiles 1", "fii ClientCAFiles 2", "fii ClientCAFiles 3"},
ClientCA: traefikTls.ClientCA{
Files: []string{"fii ClientCAFiles 1", "fii ClientCAFiles 2", "fii ClientCAFiles 3"},
Optional: false,
},
},
Redirect: &configuration.Redirect{
Replacement: "fii Replacement",

View file

@ -308,7 +308,11 @@ func (ep *EntryPoints) Set(value string) error {
}
if len(result["ca"]) > 0 {
files := strings.Split(result["ca"], ",")
configTLS.ClientCAFiles = files
optional := toBool(result, "ca_optional")
configTLS.ClientCA = tls.ClientCA{
Files: files,
Optional: optional,
}
}
var redirect *Redirect
if len(result["redirect_entrypoint"]) > 0 || len(result["redirect_regex"]) > 0 || len(result["redirect_replacement"]) > 0 {

View file

@ -134,7 +134,7 @@ func TestEntryPoints_Set(t *testing.T) {
}{
{
name: "all parameters camelcase",
expression: "Name:foo Address::8000 TLS:goo,gii TLS CA:car Redirect.EntryPoint:RedirectEntryPoint Redirect.Regex:RedirectRegex Redirect.Replacement:RedirectReplacement Compress:true WhiteListSourceRange:Range ProxyProtocol.TrustedIPs:192.168.0.1 ForwardedHeaders.TrustedIPs:10.0.0.3/24,20.0.0.3/24",
expression: "Name:foo Address::8000 TLS:goo,gii TLS CA:car CA.Optional:false Redirect.EntryPoint:RedirectEntryPoint Redirect.Regex:RedirectRegex Redirect.Replacement:RedirectReplacement Compress:true WhiteListSourceRange:Range ProxyProtocol.TrustedIPs:192.168.0.1 ForwardedHeaders.TrustedIPs:10.0.0.3/24,20.0.0.3/24",
expectedEntryPointName: "foo",
expectedEntryPoint: &EntryPoint{
Address: ":8000",
@ -152,7 +152,10 @@ func TestEntryPoints_Set(t *testing.T) {
},
WhitelistSourceRange: []string{"Range"},
TLS: &tls.TLS{
ClientCAFiles: []string{"car"},
ClientCA: tls.ClientCA{
Files: []string{"car"},
Optional: false,
},
Certificates: tls.Certificates{
{
CertFile: tls.FileOrContent("goo"),
@ -164,7 +167,7 @@ func TestEntryPoints_Set(t *testing.T) {
},
{
name: "all parameters lowercase",
expression: "name:foo address::8000 tls:goo,gii tls ca:car redirect.entryPoint:RedirectEntryPoint redirect.regex:RedirectRegex redirect.replacement:RedirectReplacement compress:true whiteListSourceRange:Range proxyProtocol.trustedIPs:192.168.0.1 forwardedHeaders.trustedIPs:10.0.0.3/24,20.0.0.3/24",
expression: "name:foo address::8000 tls:goo,gii tls ca:car ca.optional:true redirect.entryPoint:RedirectEntryPoint redirect.regex:RedirectRegex redirect.replacement:RedirectReplacement compress:true whiteListSourceRange:Range proxyProtocol.trustedIPs:192.168.0.1 forwardedHeaders.trustedIPs:10.0.0.3/24,20.0.0.3/24",
expectedEntryPointName: "foo",
expectedEntryPoint: &EntryPoint{
Address: ":8000",
@ -182,7 +185,10 @@ func TestEntryPoints_Set(t *testing.T) {
},
WhitelistSourceRange: []string{"Range"},
TLS: &tls.TLS{
ClientCAFiles: []string{"car"},
ClientCA: tls.ClientCA{
Files: []string{"car"},
Optional: true,
},
Certificates: tls.Certificates{
{
CertFile: tls.FileOrContent("goo"),

View file

@ -62,10 +62,13 @@ And here is another example with client certificate authentication:
[entryPoints.https]
address = ":443"
[entryPoints.https.tls]
clientCAFiles = ["tests/clientca1.crt", "tests/clientca2.crt"]
[[entryPoints.https.tls.certificates]]
certFile = "tests/traefik.crt"
keyFile = "tests/traefik.key"
[entryPoints.https.tls]
[entryPoints.https.tls.ClientCA]
files = ["tests/clientca1.crt", "tests/clientca2.crt"]
optional = false
[[entryPoints.https.tls.certificates]]
certFile = "tests/traefik.crt"
keyFile = "tests/traefik.key"
```
- We enable SSL on `https` by giving a certificate and a key.

View file

@ -72,11 +72,13 @@ Define an entrypoint with SNI support.
## TLS Mutual Authentication
Only accept clients that present a certificate signed by a specified Certificate Authority (CA).
TLS Mutual Authentication can be `optional` or not.
If it's `optional`, Træfik will authorize connection with certificates not signed by a specified Certificate Authority (CA).
Otherwise, Træfik will only accept clients that present a certificate signed by a specified Certificate Authority (CA).
`ClientCAFiles` can be configured with multiple `CA:s` in the same file or use multiple files containing one or several `CA:s`.
The `CA:s` has to be in PEM format.
All clients will be required to present a valid cert.
By default, `ClientCAFiles` is not optional, all clients will be required to present a valid cert.
The requirement will apply to all server certs in the entrypoint.
In the example below both `snitest.com` and `snitest.org` will require client certs
@ -86,7 +88,9 @@ In the example below both `snitest.com` and `snitest.org` will require client ce
[entryPoints.https]
address = ":443"
[entryPoints.https.tls]
ClientCAFiles = ["tests/clientca1.crt", "tests/clientca2.crt"]
[entryPoints.https.tls.ClientCA]
files = ["tests/clientca1.crt", "tests/clientca2.crt"]
optional = false
[[entryPoints.https.tls.certificates]]
certFile = "integration/fixtures/https/snitest.com.cert"
keyFile = "integration/fixtures/https/snitest.com.key"
@ -95,6 +99,11 @@ In the example below both `snitest.com` and `snitest.org` will require client ce
keyFile = "integration/fixtures/https/snitest.org.key"
```
!!! note
The deprecated argument `ClientCAFiles` allows adding Client CA files which are mandatory.
If this parameter exists, the new ones are not checked.
## Authentication
### Basic Authentication

View file

@ -6,7 +6,9 @@ defaultEntryPoints = ["https"]
[entryPoints.https]
address = ":4443"
[entryPoints.https.tls]
ClientCAFiles = ["fixtures/https/clientca/ca1.crt"]
[entryPoints.https.tls.ClientCA]
files = ["fixtures/https/clientca/ca1.crt"]
optional = true
[[entryPoints.https.tls.certificates]]
certFile = "fixtures/https/snitest.com.cert"
keyFile = "fixtures/https/snitest.com.key"

View file

@ -6,7 +6,8 @@ defaultEntryPoints = ["https"]
[entryPoints.https]
address = ":4443"
[entryPoints.https.tls]
ClientCAFiles = ["fixtures/https/clientca/ca1and2.crt"]
[entryPoints.https.tls.ClientCA]
files = ["fixtures/https/clientca/ca1and2.crt"]
[[entryPoints.https.tls.certificates]]
certFile = "fixtures/https/snitest.com.cert"
keyFile = "fixtures/https/snitest.com.key"

View file

@ -6,7 +6,9 @@ defaultEntryPoints = ["https"]
[entryPoints.https]
address = ":4443"
[entryPoints.https.tls]
ClientCAFiles = ["fixtures/https/clientca/ca1.crt", "fixtures/https/clientca/ca2.crt"]
[entryPoints.https.tls.ClientCA]
files = ["fixtures/https/clientca/ca1.crt", "fixtures/https/clientca/ca2.crt"]
optional = false
[[entryPoints.https.tls.certificates]]
certFile = "fixtures/https/snitest.com.cert"
keyFile = "fixtures/https/snitest.com.key"

View file

@ -116,7 +116,7 @@ func (s *HTTPSSuite) TestWithSNIConfigRoute(c *check.C) {
}
// TestWithClientCertificateAuthentication
// The client has to send a certificate signed by a CA trusted by the server
// The client can send a certificate signed by a CA trusted by the server but it's optional
func (s *HTTPSSuite) TestWithClientCertificateAuthentication(c *check.C) {
cmd, display := s.traefikCmd(withConfigFile("fixtures/https/clientca/https_1ca1config.toml"))
defer display(c)
@ -135,7 +135,7 @@ func (s *HTTPSSuite) TestWithClientCertificateAuthentication(c *check.C) {
}
// Connection without client certificate should fail
_, err = tls.Dial("tcp", "127.0.0.1:4443", tlsConfig)
c.Assert(err, checker.NotNil, check.Commentf("should not be allowed to connect to server"))
c.Assert(err, checker.IsNil, check.Commentf("should be allowed to connect to server"))
// Connect with client certificate signed by ca1
cert, err := tls.LoadX509KeyPair("fixtures/https/clientca/client1.crt", "fixtures/https/clientca/client1.key")
@ -147,6 +147,16 @@ func (s *HTTPSSuite) TestWithClientCertificateAuthentication(c *check.C) {
conn.Close()
// Connect with client certificate not signed by ca1
cert, err = tls.LoadX509KeyPair("fixtures/https/snitest.org.cert", "fixtures/https/snitest.org.key")
c.Assert(err, checker.IsNil, check.Commentf("unable to load client certificate and key"))
tlsConfig.Certificates = append(tlsConfig.Certificates, cert)
conn, err = tls.Dial("tcp", "127.0.0.1:4443", tlsConfig)
c.Assert(err, checker.IsNil, check.Commentf("failed to connect to server"))
conn.Close()
// Connect with client signed by ca2 should fail
tlsConfig = &tls.Config{
InsecureSkipVerify: true,
@ -158,8 +168,7 @@ func (s *HTTPSSuite) TestWithClientCertificateAuthentication(c *check.C) {
tlsConfig.Certificates = append(tlsConfig.Certificates, cert)
_, err = tls.Dial("tcp", "127.0.0.1:4443", tlsConfig)
c.Assert(err, checker.NotNil, check.Commentf("should not be allowed to connect to server"))
c.Assert(err, checker.IsNil, check.Commentf("should be allowed to connect to server"))
}
// TestWithClientCertificateAuthentication

View file

@ -574,8 +574,13 @@ func createClientTLSConfig(entryPointName string, tlsOption *traefikTls.TLS) (*t
}
if len(tlsOption.ClientCAFiles) > 0 {
log.Warnf("Deprecated configuration found during client TLS configuration creation: %s. Please use %s (which allows to make the CA Files optional).", "tls.ClientCAFiles", "tls.ClientCA.files")
tlsOption.ClientCA.Files = tlsOption.ClientCAFiles
tlsOption.ClientCA.Optional = false
}
if len(tlsOption.ClientCA.Files) > 0 {
pool := x509.NewCertPool()
for _, caFile := range tlsOption.ClientCAFiles {
for _, caFile := range tlsOption.ClientCA.Files {
data, err := ioutil.ReadFile(caFile)
if err != nil {
return nil, err
@ -611,8 +616,13 @@ func (server *Server) createTLSConfig(entryPointName string, tlsOption *traefikT
config.NextProtos = []string{"h2", "http/1.1"}
if len(tlsOption.ClientCAFiles) > 0 {
log.Warnf("Deprecated configuration found during TLS configuration creation: %s. Please use %s (which allows to make the CA Files optional).", "tls.ClientCAFiles", "tls.ClientCA.files")
tlsOption.ClientCA.Files = tlsOption.ClientCAFiles
tlsOption.ClientCA.Optional = false
}
if len(tlsOption.ClientCA.Files) > 0 {
pool := x509.NewCertPool()
for _, caFile := range tlsOption.ClientCAFiles {
for _, caFile := range tlsOption.ClientCA.Files {
data, err := ioutil.ReadFile(caFile)
if err != nil {
return nil, err
@ -623,7 +633,11 @@ func (server *Server) createTLSConfig(entryPointName string, tlsOption *traefikT
}
}
config.ClientCAs = pool
config.ClientAuth = tls.RequireAndVerifyClientCert
if tlsOption.ClientCA.Optional {
config.ClientAuth = tls.VerifyClientCertIfGiven
} else {
config.ClientAuth = tls.RequireAndVerifyClientCert
}
}
if server.globalConfiguration.ACME != nil {

View file

@ -6,12 +6,20 @@ import (
"strings"
)
// ClientCA defines traefik CA files for a entryPoint
// and it indicates if they are mandatory or have just to be analyzed if provided
type ClientCA struct {
Files []string
Optional bool
}
// TLS configures TLS for an entry point
type TLS struct {
MinVersion string `export:"true"`
CipherSuites []string
Certificates Certificates
ClientCAFiles []string
ClientCAFiles []string // Deprecated
ClientCA ClientCA
}
// RootCAs hold the CA we want to have in root

View file

@ -445,6 +445,7 @@ type AccessLog struct {
// CA, Cert and Key can be either path or file contents
type ClientTLS struct {
CA string `description:"TLS CA"`
CAOptional bool `description:"TLS CA.Optional"`
Cert string `description:"TLS cert"`
Key string `description:"TLS key"`
InsecureSkipVerify bool `description:"TLS insecure skip verify"`
@ -458,6 +459,7 @@ func (clientTLS *ClientTLS) CreateTLSConfig() (*tls.Config, error) {
return nil, nil
}
caPool := x509.NewCertPool()
clientAuth := tls.NoClientCert
if clientTLS.CA != "" {
var ca []byte
if _, errCA := os.Stat(clientTLS.CA); errCA == nil {
@ -469,6 +471,11 @@ func (clientTLS *ClientTLS) CreateTLSConfig() (*tls.Config, error) {
ca = []byte(clientTLS.CA)
}
caPool.AppendCertsFromPEM(ca)
if clientTLS.CAOptional {
clientAuth = tls.VerifyClientCertIfGiven
} else {
clientAuth = tls.RequireAndVerifyClientCert
}
}
cert := tls.Certificate{}
@ -505,6 +512,7 @@ func (clientTLS *ClientTLS) CreateTLSConfig() (*tls.Config, error) {
Certificates: []tls.Certificate{cert},
RootCAs: caPool,
InsecureSkipVerify: clientTLS.InsecureSkipVerify,
ClientAuth: clientAuth,
}
return TLSConfig, nil
}