diff --git a/cmd/admin-handlers-config-kv.go b/cmd/admin-handlers-config-kv.go index 1813d1714..21323b966 100644 --- a/cmd/admin-handlers-config-kv.go +++ b/cmd/admin-handlers-config-kv.go @@ -417,6 +417,8 @@ func (a adminAPIHandlers) GetConfigHandler(w http.ResponseWriter, r *http.Reques off = !openid.Enabled(kv) case config.IdentityLDAPSubSys: off = !xldap.Enabled(kv) + case config.IdentityTLSSubSys: + off = !globalSTSTLSConfig.Enabled } if off { s.WriteString(config.KvComment) diff --git a/cmd/config-current.go b/cmd/config-current.go index 20c71b2a9..0e4698d67 100644 --- a/cmd/config-current.go +++ b/cmd/config-current.go @@ -34,6 +34,7 @@ import ( "github.com/minio/minio/internal/config/heal" xldap "github.com/minio/minio/internal/config/identity/ldap" "github.com/minio/minio/internal/config/identity/openid" + xtls "github.com/minio/minio/internal/config/identity/tls" "github.com/minio/minio/internal/config/notify" "github.com/minio/minio/internal/config/policy/opa" "github.com/minio/minio/internal/config/scanner" @@ -54,6 +55,7 @@ func initHelp() { config.CompressionSubSys: compress.DefaultKVS, config.IdentityLDAPSubSys: xldap.DefaultKVS, config.IdentityOpenIDSubSys: openid.DefaultKVS, + config.IdentityTLSSubSys: xtls.DefaultKVS, config.PolicyOPASubSys: opa.DefaultKVS, config.RegionSubSys: config.DefaultRegionKVS, config.APISubSys: api.DefaultKVS, @@ -98,6 +100,10 @@ func initHelp() { Key: config.IdentityLDAPSubSys, Description: "enable LDAP SSO support", }, + config.HelpKV{ + Key: config.IdentityTLSSubSys, + Description: "enable X.509 TLS certificate SSO support", + }, config.HelpKV{ Key: config.PolicyOPASubSys, Description: "[DEPRECATED] enable external OPA for policy enforcement", @@ -202,6 +208,7 @@ func initHelp() { config.ScannerSubSys: scanner.Help, config.IdentityOpenIDSubSys: openid.Help, config.IdentityLDAPSubSys: xldap.Help, + config.IdentityTLSSubSys: xtls.Help, config.PolicyOPASubSys: opa.Help, config.LoggerWebhookSubSys: logger.Help, config.AuditWebhookSubSys: logger.HelpWebhook, @@ -317,6 +324,12 @@ func validateConfig(s config.Config) error { conn.Close() } } + { + _, err := xtls.Lookup(s[config.IdentityTLSSubSys][config.Default]) + if err != nil { + return err + } + } if _, err := opa.LookupConfig(s[config.PolicyOPASubSys][config.Default], NewGatewayHTTPTransport(), xhttp.DrainBody); err != nil { @@ -469,6 +482,11 @@ func lookupConfigs(s config.Config, objAPI ObjectLayer) { logger.Fatal(errors.New("no KMS configured"), "MINIO_KMS_AUTO_ENCRYPTION requires a valid KMS configuration") } + globalSTSTLSConfig, err = xtls.Lookup(s[config.IdentityTLSSubSys][config.Default]) + if err != nil { + logger.LogIf(ctx, fmt.Errorf("Unable to initialize X.509/TLS STS API: %w", err)) + } + globalOpenIDConfig, err = openid.LookupConfig(s[config.IdentityOpenIDSubSys][config.Default], NewGatewayHTTPTransport(), xhttp.DrainBody) if err != nil { diff --git a/cmd/globals.go b/cmd/globals.go index cd6d05ec0..6a83b93fd 100644 --- a/cmd/globals.go +++ b/cmd/globals.go @@ -38,6 +38,7 @@ import ( "github.com/minio/minio/internal/config/dns" xldap "github.com/minio/minio/internal/config/identity/ldap" "github.com/minio/minio/internal/config/identity/openid" + xtls "github.com/minio/minio/internal/config/identity/tls" "github.com/minio/minio/internal/config/policy/opa" "github.com/minio/minio/internal/config/storageclass" xhttp "github.com/minio/minio/internal/http" @@ -188,6 +189,7 @@ var ( globalStorageClass storageclass.Config globalLDAPConfig xldap.Config globalOpenIDConfig openid.Config + globalSTSTLSConfig xtls.Config // CA root certificates, a nil value means system certs pool will be used globalRootCAs *x509.CertPool diff --git a/cmd/sts-datatypes.go b/cmd/sts-datatypes.go index aeb4517b7..5c20472d6 100644 --- a/cmd/sts-datatypes.go +++ b/cmd/sts-datatypes.go @@ -191,3 +191,15 @@ type AssumeRoleWithLDAPResponse struct { type LDAPIdentityResult struct { Credentials auth.Credentials `xml:",omitempty"` } + +// AssumeRoleWithCertificateResponse contains the result of +// a successful AssumeRoleWithCertificate request. +type AssumeRoleWithCertificateResponse struct { + XMLName xml.Name `xml:"https://sts.amazonaws.com/doc/2011-06-15/ AssumeRoleWithCertificateResponse" json:"-"` + Result struct { + Credentials auth.Credentials `xml:"Credentials,omitempty"` + } `xml:"AssumeRoleWithCertificateResult"` + Metadata struct { + RequestID string `xml:"RequestId,omitempty"` + } `xml:"ResponseMetadata,omitempty"` +} diff --git a/cmd/sts-errors.go b/cmd/sts-errors.go index b87f080e1..73fc1029e 100644 --- a/cmd/sts-errors.go +++ b/cmd/sts-errors.go @@ -93,6 +93,8 @@ const ( ErrSTSClientGrantsExpiredToken ErrSTSInvalidClientGrantsToken ErrSTSMalformedPolicyDocument + ErrSTSInsecureConnection + ErrSTSInvalidClientCertificate ErrSTSNotInitialized ErrSTSInternalError ) @@ -145,6 +147,16 @@ var stsErrCodes = stsErrorCodeMap{ Description: "The request was rejected because the policy document was malformed.", HTTPStatusCode: http.StatusBadRequest, }, + ErrSTSInsecureConnection: { + Code: "InsecureConnection", + Description: "The request was made over a plain HTTP connection. A TLS connection is required.", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrSTSInvalidClientCertificate: { + Code: "InvalidClientCertificate", + Description: "The provided client certificate is invalid. Retry with a different certificate.", + HTTPStatusCode: http.StatusBadRequest, + }, ErrSTSNotInitialized: { Code: "STSNotInitialized", Description: "STS API not initialized, please try again.", diff --git a/cmd/sts-handlers.go b/cmd/sts-handlers.go index 3eb477da7..1633af95c 100644 --- a/cmd/sts-handlers.go +++ b/cmd/sts-handlers.go @@ -20,11 +20,13 @@ package cmd import ( "bytes" "context" + "crypto/x509" "encoding/base64" "errors" "fmt" "net/http" "strings" + "time" "github.com/gorilla/mux" "github.com/minio/minio/internal/auth" @@ -48,10 +50,11 @@ const ( stsLDAPPassword = "LDAPPassword" // STS API action constants - clientGrants = "AssumeRoleWithClientGrants" - webIdentity = "AssumeRoleWithWebIdentity" - ldapIdentity = "AssumeRoleWithLDAPIdentity" - assumeRole = "AssumeRole" + clientGrants = "AssumeRoleWithClientGrants" + webIdentity = "AssumeRoleWithWebIdentity" + ldapIdentity = "AssumeRoleWithLDAPIdentity" + clientCertificate = "AssumeRoleWithCertificate" + assumeRole = "AssumeRole" stsRequestBodyLimit = 10 * (1 << 20) // 10 MiB @@ -124,6 +127,12 @@ func registerSTSRouter(router *mux.Router) { Queries(stsVersion, stsAPIVersion). Queries(stsLDAPUsername, "{LDAPUsername:.*}"). Queries(stsLDAPPassword, "{LDAPPassword:.*}") + + // AssumeRoleWithCertificate + stsRouter.Methods(http.MethodPost).HandlerFunc(httpTraceAll(sts.AssumeRoleWithCertificate)). + Queries(stsAction, clientCertificate). + Queries(stsVersion, stsAPIVersion) + } func checkAssumeRoleAuth(ctx context.Context, r *http.Request) (user auth.Credentials, isErrCodeSTS bool, stsErr STSErrorCode) { @@ -649,3 +658,100 @@ func (sts *stsAPIHandlers) AssumeRoleWithLDAPIdentity(w http.ResponseWriter, r * writeSuccessResponseXML(w, encodedSuccessResponse) } + +// AssumeRoleWithCertificate implements user authentication with client certificates. +// It verifies the client-provided X.509 certificate, maps the certificate to an S3 policy +// and returns temp. S3 credentials to the client. +// +// API endpoint: https://minio:9000?Action=AssumeRoleWithCertificate&Version=2011-06-15 +func (sts *stsAPIHandlers) AssumeRoleWithCertificate(w http.ResponseWriter, r *http.Request) { + var ctx = newContext(r, w, "AssumeRoleWithCertificate") + + if !globalSTSTLSConfig.Enabled { + writeSTSErrorResponse(ctx, w, true, ErrSTSNotInitialized, errors.New("STS API 'AssumeRoleWithCertificate' is disabled")) + return + } + + // We have to establish a TLS connection and the + // client must provide exactly one client certificate. + // Otherwise, we don't have a certificate to verify or + // the policy lookup would ambigious. + if r.TLS == nil { + writeSTSErrorResponse(ctx, w, true, ErrSTSInsecureConnection, errors.New("No TLS connection attempt")) + return + } + if len(r.TLS.PeerCertificates) == 0 { + writeSTSErrorResponse(ctx, w, true, ErrSTSMissingParameter, errors.New("No client certificate provided")) + return + } + if len(r.TLS.PeerCertificates) > 1 { + writeSTSErrorResponse(ctx, w, true, ErrSTSInvalidParameterValue, errors.New("More than one client certificate provided")) + return + } + + var certificate = r.TLS.PeerCertificates[0] + if !globalSTSTLSConfig.InsecureSkipVerify { + _, err := certificate.Verify(x509.VerifyOptions{ + KeyUsages: []x509.ExtKeyUsage{ + x509.ExtKeyUsageClientAuth, + }, + Roots: globalRootCAs, + }) + if err != nil { + writeSTSErrorResponse(ctx, w, true, ErrSTSInvalidClientCertificate, err) + return + } + } + + // We map the X.509 subject common name to the policy. So, a client + // with the common name "foo" will be associated with the policy "foo". + // Other mapping functions - e.g. public-key hash based mapping - are + // possible but not implemented. + // + // Group mapping is not possible with standard X.509 certificates. + if certificate.Subject.CommonName == "" { + writeSTSErrorResponse(ctx, w, true, ErrSTSMissingParameter, errors.New("certificate subject CN cannot be empty")) + return + } + + expiry, err := globalSTSTLSConfig.GetExpiryDuration(r.Form.Get(stsDurationSeconds)) + if err != nil { + writeSTSErrorResponse(ctx, w, true, ErrSTSMissingParameter, err) + return + } + + // We set the expiry of the temp. credentials to the minimum of the + // configured expiry and the duration until the certificate itself + // expires. + // We must not issue credentials that out-live the certificate. + if validUntil := time.Until(certificate.NotAfter); validUntil < expiry { + expiry = validUntil + } + + // Associate any service accounts to the certificate CN + parentUser := "tls:" + certificate.Subject.CommonName + + tmpCredentials, err := auth.GetNewCredentialsWithMetadata(map[string]interface{}{ + expClaim: time.Now().UTC().Add(expiry).Unix(), + parentClaim: parentUser, + subClaim: certificate.Subject.CommonName, + audClaim: certificate.Subject.Organization, + issClaim: certificate.Issuer.CommonName, + }, globalActiveCred.SecretKey) + if err != nil { + writeSTSErrorResponse(ctx, w, true, ErrSTSInternalError, err) + return + } + + tmpCredentials.ParentUser = parentUser + err = globalIAMSys.SetTempUser(tmpCredentials.AccessKey, tmpCredentials, certificate.Subject.CommonName) + if err != nil { + writeSTSErrorResponse(ctx, w, true, ErrSTSInternalError, err) + return + } + + var response = new(AssumeRoleWithCertificateResponse) + response.Result.Credentials = tmpCredentials + response.Metadata.RequestID = w.Header().Get(xhttp.AmzRequestID) + writeSuccessResponseXML(w, encodeResponse(response)) +} diff --git a/cmd/stserrorcode_string.go b/cmd/stserrorcode_string.go index 8b96ab7a5..7a1ede2ad 100644 --- a/cmd/stserrorcode_string.go +++ b/cmd/stserrorcode_string.go @@ -16,13 +16,15 @@ func _() { _ = x[ErrSTSClientGrantsExpiredToken-5] _ = x[ErrSTSInvalidClientGrantsToken-6] _ = x[ErrSTSMalformedPolicyDocument-7] - _ = x[ErrSTSNotInitialized-8] - _ = x[ErrSTSInternalError-9] + _ = x[ErrSTSInsecureConnection-8] + _ = x[ErrSTSInvalidClientCertificate-9] + _ = x[ErrSTSNotInitialized-10] + _ = x[ErrSTSInternalError-11] } -const _STSErrorCode_name = "STSNoneSTSAccessDeniedSTSMissingParameterSTSInvalidParameterValueSTSWebIdentityExpiredTokenSTSClientGrantsExpiredTokenSTSInvalidClientGrantsTokenSTSMalformedPolicyDocumentSTSNotInitializedSTSInternalError" +const _STSErrorCode_name = "STSNoneSTSAccessDeniedSTSMissingParameterSTSInvalidParameterValueSTSWebIdentityExpiredTokenSTSClientGrantsExpiredTokenSTSInvalidClientGrantsTokenSTSMalformedPolicyDocumentSTSInsecureConnectionSTSInvalidClientCertificateSTSNotInitializedSTSInternalError" -var _STSErrorCode_index = [...]uint8{0, 7, 22, 41, 65, 91, 118, 145, 171, 188, 204} +var _STSErrorCode_index = [...]uint8{0, 7, 22, 41, 65, 91, 118, 145, 171, 192, 219, 236, 252} func (i STSErrorCode) String() string { if i < 0 || i >= STSErrorCode(len(_STSErrorCode_index)-1) { diff --git a/docs/sts/tls.md b/docs/sts/tls.md new file mode 100644 index 000000000..50dedfc35 --- /dev/null +++ b/docs/sts/tls.md @@ -0,0 +1,107 @@ +# AssumeRoleWithCertificate [![Slack](https://slack.min.io/slack?type=svg)](https://slack.min.io) + +## Introduction +MinIO provides a custom STS API that allows authentication with client X.509 / TLS certificates. + +A major advantage of certificate-based authentication compared to other STS authentication methods, like OpenID Connect or LDAP/AD, is that client authentication works without any additional/external component that must be constantly available. Therefore, certificate-based authentication may provide better availability / lower operational complexity. + +The MinIO TLS STS API can be configured via MinIO's standard configuration API (i.e. using `mc admin config set/get`). Further, it can be configured via the following environment variables: + +``` +mc admin config set myminio identity_tls --env +KEY: +identity_tls enable X.509 TLS certificate SSO support + +ARGS: +MINIO_IDENTITY_TLS_SKIP_VERIFY (on|off) trust client certificates without verification. Defaults to "off" (verify) +``` + +The MinIO TLS STS API is enabled by default. However, it can be completely *disabled* by setting: +``` +MINIO_IDENTITY_TLS_ENABLE=off +``` + +## Example +MinIO exposes a custom S3 STS API endpoint as `Action=AssumeRoleWithCertificate`. A client has to send an HTTP `POST` request to `https://:?Action=AssumeRoleWithCertificate&Version=2011-06-15`. Since the authentication and authorization happens via X.509 certificates the client has to send the request over **TLS** and has to provide +a client certificate. + +The following curl example shows how to authenticate to a MinIO server with client certificate and obtain STS access credentials. + +```curl +curl -X POST --key private.key --cert public.crt "https://minio:9000?Action=AssumeRoleWithCertificate&Version=2011-06-15&DurationSeconds=3600" +``` + +```xml + + + + + YC12ZBHUVW588BQAE5BM + Zgl9+zdE0pZ88+hLqtfh0ocLN+WQTJixHouCkZkW + 2021-07-19T20:10:45ZeyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJhY2Nlc3NLZXkiOiJZQzEyWkJIVVZXNTg4QlFBRTVCTSIsImV4cCI6MTYyNjcyNTQ0NX0.wvMUf3w_x16qpVWgua8WxnV1Sgtv1jOnSu03vbrwOMzV3cI4q3_9WZD9LwlP-34DTsvbsg7gCBGh6YNriMMiQw + + + + 169339CD8B3A6948 + + +``` + +## Authentication Flow + +A client can request temp. S3 credentials via the STS API. It can authenticate via a client certificate and obtain a access/secret key pair as well as a session token. These credentials are associated to an S3 policy at the MinIO server. + +In case of certificate-based authentication, MinIO has to map the client-provided certificate to an S3 policy. MinIO does this via the subject common name field of the X.509 certificate. So, MinIO will associate a certificate with a subject `CN = foobar` to a S3 policy named `foobar`. + +The following self-signed certificate is issued for `consoleAdmin`. So, MinIO would associate it with the pre-defined `consoleAdmin` policy. +``` +Certificate: + Data: + Version: 3 (0x2) + Serial Number: + 35:ac:60:46:ad:8d:de:18:dc:0b:f6:98:14:ee:89:e8 + Signature Algorithm: ED25519 + Issuer: CN = consoleAdmin + Validity + Not Before: Jul 19 15:08:44 2021 GMT + Not After : Aug 18 15:08:44 2021 GMT + Subject: CN = consoleAdmin + Subject Public Key Info: + Public Key Algorithm: ED25519 + ED25519 Public-Key: + pub: + 5a:91:87:b8:77:fe:d4:af:d9:c7:c7:ce:55:ae:74: + aa:f3:f1:fe:04:63:9b:cb:20:97:61:97:90:94:fa: + 12:8b + X509v3 extensions: + X509v3 Key Usage: critical + Digital Signature + X509v3 Extended Key Usage: + TLS Web Client Authentication + X509v3 Basic Constraints: critical + CA:FALSE + Signature Algorithm: ED25519 + 7e:aa:be:ed:47:4d:b9:2f:fc:ed:7f:5a:fc:6b:c0:05:5b:f5: + a0:31:fe:86:e3:8e:3f:49:af:6d:d5:ac:c7:c4:57:47:ce:97: + 7d:ab:b8:e9:75:ec:b4:39:fb:c8:cf:53:16:5b:1f:15:b6:7f: + 5a:d1:35:2d:fc:31:3a:10:e7:0c +``` +> Observe the `Subject: CN = consoleAdmin` field. + +Also, note that the certificate has to contain the `Extended Key Usage: TLS Web Client Authentication`. Otherwise, MinIO would not accept the certificate as client certificate. + +Now, the STS certificate-based authentication happens in 4 steps: + +- Client sends HTTP `POST` request over a TLS connection hitting the MinIO TLS STS API. +- MinIO verifies that the client certificate is valid. +- MinIO tries to find a policy that matches the `CN` of the client certificate. +- MinIO returns temp. S3 credentials associated to the found policy. + +The returned credentials expiry after a certain period of time that can be configured via `&DurationSeconds=3600`. By default, the STS credentials are valid for 1 hour. The minimum expiration allowed is 15 minutes. + +Further, the temp. S3 credentials will never out-live the client certificate. For example, if the `MINIO_IDENTITY_TLS_STS_EXPIRY` is 7 days but the certificate itself is only valid for the next 3 days, then MinIO will return S3 credentials that are valid for 3 days only. + +## Explore Further +- [MinIO Admin Complete Guide](https://docs.min.io/docs/minio-admin-complete-guide.html) +- [The MinIO documentation website](https://docs.min.io) diff --git a/internal/config/config.go b/internal/config/config.go index e732c5190..736719fa2 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -66,6 +66,7 @@ const ( PolicyOPASubSys = "policy_opa" IdentityOpenIDSubSys = "identity_openid" IdentityLDAPSubSys = "identity_ldap" + IdentityTLSSubSys = "identity_tls" CacheSubSys = "cache" RegionSubSys = "region" EtcdSubSys = "etcd" @@ -113,6 +114,7 @@ var SubSystems = set.CreateStringSet( PolicyOPASubSys, IdentityLDAPSubSys, IdentityOpenIDSubSys, + IdentityTLSSubSys, ScannerSubSys, HealSubSys, NotifyAMQPSubSys, @@ -147,6 +149,7 @@ var SubSystemsSingleTargets = set.CreateStringSet([]string{ PolicyOPASubSys, IdentityLDAPSubSys, IdentityOpenIDSubSys, + IdentityTLSSubSys, HealSubSys, ScannerSubSys, }...) diff --git a/internal/config/identity/tls/config.go b/internal/config/identity/tls/config.go new file mode 100644 index 000000000..ad8522863 --- /dev/null +++ b/internal/config/identity/tls/config.go @@ -0,0 +1,123 @@ +// Copyright (c) 2015-2021 MinIO, Inc. +// +// This file is part of MinIO Object Storage stack +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package tls + +import ( + "strconv" + "time" + + "github.com/minio/minio/internal/auth" + "github.com/minio/minio/internal/config" + "github.com/minio/pkg/env" +) + +const ( + // EnvEnabled is an environment variable that controls whether the X.509 + // TLS STS API is enabled. By default, if not set, it is enabled. + EnvEnabled = "MINIO_IDENTITY_TLS_ENABLE" + + // EnvSkipVerify is an environment variable that controls whether + // MinIO verifies the client certificate present by the client + // when requesting temp. credentials. + // By default, MinIO always verify the client certificate. + // + // The client certificate verification should only be skipped + // when debugging or testing a setup since it allows arbitrary + // clients to obtain temp. credentials with arbitrary policy + // permissions - including admin permissions. + EnvSkipVerify = "MINIO_IDENTITY_TLS_SKIP_VERIFY" +) + +// Config contains the STS TLS configuration for generating temp. +// credentials and mapping client certificates to S3 policies. +type Config struct { + Enabled bool `json:"enabled"` + + // InsecureSkipVerify, if set to true, disables the client + // certificate verification. It should only be set for + // debugging or testing purposes. + InsecureSkipVerify bool `json:"skip_verify"` +} + +const ( + defaultExpiry time.Duration = 1 * time.Hour + minExpiry time.Duration = 15 * time.Minute + maxExpiry time.Duration = 365 * 24 * time.Hour +) + +// GetExpiryDuration - return parsed expiry duration. +func (l Config) GetExpiryDuration(dsecs string) (time.Duration, error) { + if dsecs == "" { + return defaultExpiry, nil + } + + d, err := strconv.Atoi(dsecs) + if err != nil { + return 0, auth.ErrInvalidDuration + } + + dur := time.Duration(d) * time.Second + + if dur < minExpiry || dur > maxExpiry { + return 0, auth.ErrInvalidDuration + } + return dur, nil +} + +// Lookup returns a new Config by merging the given K/V config +// system with environment variables. +func Lookup(kvs config.KVS) (Config, error) { + if err := config.CheckValidKeys(config.IdentityTLSSubSys, kvs, DefaultKVS); err != nil { + return Config{}, err + } + insecureSkipVerify, err := config.ParseBool(env.Get(EnvSkipVerify, kvs.Get(skipVerify))) + if err != nil { + return Config{}, err + } + enabled, err := config.ParseBool(env.Get(EnvEnabled, "on")) + if err != nil { + return Config{}, err + } + return Config{ + Enabled: enabled, + InsecureSkipVerify: insecureSkipVerify, + }, nil +} + +const ( + skipVerify = "skip_verify" +) + +// DefaultKVS is the the default K/V config system for +// the STS TLS API. +var DefaultKVS = config.KVS{ + config.KV{ + Key: skipVerify, + Value: "off", + }, +} + +// Help is the help and description for the STS API K/V configuration. +var Help = config.HelpKVS{ + config.HelpKV{ + Key: skipVerify, + Description: `trust client certificates without verification. Defaults to "off" (verify)`, + Optional: true, + Type: "on|off", + }, +} diff --git a/internal/http/server.go b/internal/http/server.go index ace99a574..ad953d2ea 100644 --- a/internal/http/server.go +++ b/internal/http/server.go @@ -172,6 +172,7 @@ func NewServer(addrs []string, handler http.Handler, getCert certs.GetCertificat MinVersion: tls.VersionTLS12, NextProtos: []string{"http/1.1", "h2"}, GetCertificate: getCert, + ClientAuth: tls.RequestClientCert, } if secureCiphers || fips.Enabled { tlsConfig.CipherSuites = fips.CipherSuitesTLS()