1
0
mirror of https://github.com/distribution/distribution synced 2024-12-25 15:05:51 +01:00

feat(configuration): support mtls auth mod (#4537)

This commit is contained in:
Milos Gajdos 2024-12-17 14:00:36 +00:00 committed by GitHub
commit 1c62898144
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 84 additions and 19 deletions

@ -109,6 +109,10 @@ type Configuration struct {
// A file may contain multiple CA certificates encoded as PEM // A file may contain multiple CA certificates encoded as PEM
ClientCAs []string `yaml:"clientcas,omitempty"` ClientCAs []string `yaml:"clientcas,omitempty"`
// Client certificate authentication mode
// One of: request-client-cert, require-any-client-cert, verify-client-cert-if-given, require-and-verify-client-cert
ClientAuth ClientAuth `yaml:"clientauth,omitempty"`
// Specifies the lowest TLS version allowed // Specifies the lowest TLS version allowed
MinimumTLS string `yaml:"minimumtls,omitempty"` MinimumTLS string `yaml:"minimumtls,omitempty"`
@ -899,3 +903,35 @@ func setFieldValue(field reflect.Value, value interface{}) error {
} }
return nil return nil
} }
const (
ClientAuthRequestClientCert = "request-client-cert"
ClientAuthRequireAnyClientCert = "require-any-client-cert"
ClientAuthVerifyClientCertIfGiven = "verify-client-cert-if-given"
ClientAuthRequireAndVerifyClientCert = "require-and-verify-client-cert"
)
type ClientAuth string
// UnmarshalYAML implements the yaml.Umarshaler interface
// Unmarshals a string into a ClientAuth, validating that it represents a valid ClientAuth mod
func (clientAuth *ClientAuth) UnmarshalYAML(unmarshal func(interface{}) error) error {
var clientAuthString string
err := unmarshal(&clientAuthString)
if err != nil {
return err
}
switch clientAuthString {
case ClientAuthRequestClientCert:
case ClientAuthRequireAnyClientCert:
case ClientAuthVerifyClientCertIfGiven:
case ClientAuthRequireAndVerifyClientCert:
default:
return fmt.Errorf("invalid ClientAuth %s Must be one of: %s, %s, %s, %s", clientAuthString, ClientAuthRequestClientCert, ClientAuthRequireAnyClientCert, ClientAuthVerifyClientCertIfGiven, ClientAuthRequireAndVerifyClientCert)
}
*clientAuth = ClientAuth(clientAuthString)
return nil
}

@ -78,11 +78,12 @@ var configStruct = Configuration{
RelativeURLs bool `yaml:"relativeurls,omitempty"` RelativeURLs bool `yaml:"relativeurls,omitempty"`
DrainTimeout time.Duration `yaml:"draintimeout,omitempty"` DrainTimeout time.Duration `yaml:"draintimeout,omitempty"`
TLS struct { TLS struct {
Certificate string `yaml:"certificate,omitempty"` Certificate string `yaml:"certificate,omitempty"`
Key string `yaml:"key,omitempty"` Key string `yaml:"key,omitempty"`
ClientCAs []string `yaml:"clientcas,omitempty"` ClientCAs []string `yaml:"clientcas,omitempty"`
MinimumTLS string `yaml:"minimumtls,omitempty"` ClientAuth ClientAuth `yaml:"clientauth,omitempty"`
CipherSuites []string `yaml:"ciphersuites,omitempty"` MinimumTLS string `yaml:"minimumtls,omitempty"`
CipherSuites []string `yaml:"ciphersuites,omitempty"`
LetsEncrypt struct { LetsEncrypt struct {
CacheFile string `yaml:"cachefile,omitempty"` CacheFile string `yaml:"cachefile,omitempty"`
Email string `yaml:"email,omitempty"` Email string `yaml:"email,omitempty"`
@ -106,11 +107,12 @@ var configStruct = Configuration{
} `yaml:"h2c,omitempty"` } `yaml:"h2c,omitempty"`
}{ }{
TLS: struct { TLS: struct {
Certificate string `yaml:"certificate,omitempty"` Certificate string `yaml:"certificate,omitempty"`
Key string `yaml:"key,omitempty"` Key string `yaml:"key,omitempty"`
ClientCAs []string `yaml:"clientcas,omitempty"` ClientCAs []string `yaml:"clientcas,omitempty"`
MinimumTLS string `yaml:"minimumtls,omitempty"` ClientAuth ClientAuth `yaml:"clientauth,omitempty"`
CipherSuites []string `yaml:"ciphersuites,omitempty"` MinimumTLS string `yaml:"minimumtls,omitempty"`
CipherSuites []string `yaml:"ciphersuites,omitempty"`
LetsEncrypt struct { LetsEncrypt struct {
CacheFile string `yaml:"cachefile,omitempty"` CacheFile string `yaml:"cachefile,omitempty"`
Email string `yaml:"email,omitempty"` Email string `yaml:"email,omitempty"`
@ -118,7 +120,8 @@ var configStruct = Configuration{
DirectoryURL string `yaml:"directoryurl,omitempty"` DirectoryURL string `yaml:"directoryurl,omitempty"`
} `yaml:"letsencrypt,omitempty"` } `yaml:"letsencrypt,omitempty"`
}{ }{
ClientCAs: []string{"/path/to/ca.pem"}, ClientCAs: []string{"/path/to/ca.pem"},
ClientAuth: ClientAuthVerifyClientCertIfGiven,
}, },
Headers: http.Header{ Headers: http.Header{
"X-Content-Type-Options": []string{"nosniff"}, "X-Content-Type-Options": []string{"nosniff"},
@ -202,6 +205,7 @@ http:
tls: tls:
clientcas: clientcas:
- /path/to/ca.pem - /path/to/ca.pem
clientauth: verify-client-cert-if-given
headers: headers:
X-Content-Type-Options: [nosniff] X-Content-Type-Options: [nosniff]
redis: redis:
@ -297,6 +301,7 @@ func (suite *ConfigSuite) TestParseInmemory() {
suite.expectedConfig.Storage = Storage{"inmemory": Parameters{}} suite.expectedConfig.Storage = Storage{"inmemory": Parameters{}}
suite.expectedConfig.Log.Fields = nil suite.expectedConfig.Log.Fields = nil
suite.expectedConfig.HTTP.TLS.ClientCAs = nil suite.expectedConfig.HTTP.TLS.ClientCAs = nil
suite.expectedConfig.HTTP.TLS.ClientAuth = ""
suite.expectedConfig.Redis = Redis{} suite.expectedConfig.Redis = Redis{}
config, err := Parse(bytes.NewReader([]byte(inmemoryConfigYamlV0_1))) config, err := Parse(bytes.NewReader([]byte(inmemoryConfigYamlV0_1)))
@ -318,6 +323,7 @@ func (suite *ConfigSuite) TestParseIncomplete() {
suite.expectedConfig.Notifications = Notifications{} suite.expectedConfig.Notifications = Notifications{}
suite.expectedConfig.HTTP.Headers = nil suite.expectedConfig.HTTP.Headers = nil
suite.expectedConfig.HTTP.TLS.ClientCAs = nil suite.expectedConfig.HTTP.TLS.ClientCAs = nil
suite.expectedConfig.HTTP.TLS.ClientAuth = ""
suite.expectedConfig.Redis = Redis{} suite.expectedConfig.Redis = Redis{}
suite.expectedConfig.Validation.Manifests.Indexes.Platforms = "" suite.expectedConfig.Validation.Manifests.Indexes.Platforms = ""
@ -590,6 +596,7 @@ func copyConfig(config Configuration) *Configuration {
} }
configCopy.HTTP.TLS.ClientCAs = make([]string, 0, len(config.HTTP.TLS.ClientCAs)) configCopy.HTTP.TLS.ClientCAs = make([]string, 0, len(config.HTTP.TLS.ClientCAs))
configCopy.HTTP.TLS.ClientCAs = append(configCopy.HTTP.TLS.ClientCAs, config.HTTP.TLS.ClientCAs...) configCopy.HTTP.TLS.ClientCAs = append(configCopy.HTTP.TLS.ClientCAs, config.HTTP.TLS.ClientCAs...)
configCopy.HTTP.TLS.ClientAuth = config.HTTP.TLS.ClientAuth
configCopy.Redis = config.Redis configCopy.Redis = config.Redis
configCopy.Redis.TLS.Certificate = config.Redis.TLS.Certificate configCopy.Redis.TLS.Certificate = config.Redis.TLS.Certificate

@ -229,6 +229,7 @@ http:
clientcas: clientcas:
- /path/to/ca.pem - /path/to/ca.pem
- /path/to/another/ca.pem - /path/to/another/ca.pem
clientauth: require-and-verify-client-cert
letsencrypt: letsencrypt:
cachefile: /path/to/cache-file cachefile: /path/to/cache-file
email: emailused@letsencrypt.com email: emailused@letsencrypt.com
@ -808,6 +809,7 @@ http:
clientcas: clientcas:
- /path/to/ca.pem - /path/to/ca.pem
- /path/to/another/ca.pem - /path/to/another/ca.pem
clientauth: require-and-verify-client-cert
minimumtls: tls1.2 minimumtls: tls1.2
ciphersuites: ciphersuites:
- TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 - TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
@ -848,13 +850,14 @@ for the server. If you already have a web server running on
the same host as the registry, you may prefer to configure TLS on that web server the same host as the registry, you may prefer to configure TLS on that web server
and proxy connections to the registry server. and proxy connections to the registry server.
| Parameter | Required | Description | | Parameter | Required | Description |
|-----------|----------|-------------------------------------------------------| |----------------|----------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `certificate` | yes | Absolute path to the x509 certificate file. | | `certificate` | yes | Absolute path to the x509 certificate file. |
| `key` | yes | Absolute path to the x509 private key file. | | `key` | yes | Absolute path to the x509 private key file. |
| `clientcas` | no | An array of absolute paths to x509 CA files. | | `clientcas` | no | An array of absolute paths to x509 CA files. |
| `minimumtls` | no | Minimum TLS version allowed (tls1.0, tls1.1, tls1.2, tls1.3). Defaults to tls1.2 | | `clientauth` | no | Client certificate authentication mode. This setting determines how the server handles client certificates during the TLS handshake. If clientcas is not provided, TLS Client Authentication is disabled, and the mode is ignored. Allowed (request-client-cert, require-any-client-cert, verify-client-cert-if-given, require-and-verify-client-cert). Defaults to require-and-verify-client-cert |
| `ciphersuites` | no | Cipher suites allowed. Please see below for allowed values and default. | | `minimumtls` | no | Minimum TLS version allowed (tls1.0, tls1.1, tls1.2, tls1.3). Defaults to tls1.2 |
| `ciphersuites` | no | Cipher suites allowed. Please see below for allowed values and default. |
Available cipher suites: Available cipher suites:
- TLS_RSA_WITH_RC4_128_SHA - TLS_RSA_WITH_RC4_128_SHA

@ -79,6 +79,14 @@ var tlsVersions = map[string]uint16{
"tls1.3": tls.VersionTLS13, "tls1.3": tls.VersionTLS13,
} }
// tlsClientAuth maps user-specified values to TLS Client Authentication constants.
var tlsClientAuth = map[string]tls.ClientAuthType{
configuration.ClientAuthRequestClientCert: tls.RequestClientCert,
configuration.ClientAuthRequireAnyClientCert: tls.RequireAnyClientCert,
configuration.ClientAuthVerifyClientCertIfGiven: tls.VerifyClientCertIfGiven,
configuration.ClientAuthRequireAndVerifyClientCert: tls.RequireAndVerifyClientCert,
}
// defaultLogFormatter is the default formatter to use for logs. // defaultLogFormatter is the default formatter to use for logs.
const defaultLogFormatter = "text" const defaultLogFormatter = "text"
@ -298,7 +306,18 @@ func (registry *Registry) ListenAndServe() error {
dcontext.GetLogger(registry.app).Debugf("CA Subject: %s", string(subj)) dcontext.GetLogger(registry.app).Debugf("CA Subject: %s", string(subj))
} }
tlsConf.ClientAuth = tls.RequireAndVerifyClientCert if config.HTTP.TLS.ClientAuth != "" {
tlsClientAuthMod, ok := tlsClientAuth[string(config.HTTP.TLS.ClientAuth)]
if !ok {
return fmt.Errorf("unknown client auth mod '%s' specified for http.tls.clientauth", config.HTTP.TLS.ClientAuth)
}
tlsConf.ClientAuth = tlsClientAuthMod
} else {
tlsConf.ClientAuth = tls.RequireAndVerifyClientCert
}
tlsConf.ClientCAs = pool tlsConf.ClientCAs = pool
} }