From bbd1244a88bf5cf09229a5338e52e0ccccb807c1 Mon Sep 17 00:00:00 2001 From: Nitish Tiwari Date: Mon, 1 Mar 2021 22:49:13 +0530 Subject: [PATCH] Add support for mTLS for Audit log target (#11645) --- cmd/config-current.go | 2 +- cmd/config/certs.go | 10 +++++++ cmd/logger/config.go | 62 ++++++++++++++++++++++++++++++++---------- cmd/logger/help.go | 16 ++++++++++- cmd/utils.go | 20 ++++++++++++++ docs/logging/README.md | 4 ++- 6 files changed, 97 insertions(+), 17 deletions(-) diff --git a/cmd/config-current.go b/cmd/config-current.go index b624adebf..1e6f1880e 100644 --- a/cmd/config-current.go +++ b/cmd/config-current.go @@ -553,7 +553,7 @@ If you need help to migrate smoothly visit: https://min.io/pricing` http.WithAuthToken(l.AuthToken), http.WithUserAgent(loggerUserAgent), http.WithLogKind(string(logger.All)), - http.WithTransport(NewGatewayHTTPTransport()), + http.WithTransport(NewGatewayHTTPTransportWithClientCerts(l.ClientCert, l.ClientKey)), ), ); err != nil { logger.LogIf(ctx, fmt.Errorf("Unable to initialize audit HTTP target: %w", err)) diff --git a/cmd/config/certs.go b/cmd/config/certs.go index 2809be5a6..3944f63c7 100644 --- a/cmd/config/certs.go +++ b/cmd/config/certs.go @@ -23,6 +23,7 @@ import ( "crypto/tls" "crypto/x509" "encoding/pem" + "errors" "io/ioutil" "github.com/minio/minio/pkg/env" @@ -113,3 +114,12 @@ func LoadX509KeyPair(certFile, keyFile string) (tls.Certificate, error) { } return cert, nil } + +// EnsureCertAndKey checks if both client certificate and key paths are provided +func EnsureCertAndKey(ClientCert, ClientKey string) error { + if (ClientCert != "" && ClientKey == "") || + (ClientCert == "" && ClientKey != "") { + return errors.New("cert and key must be specified as a pair") + } + return nil +} diff --git a/cmd/logger/config.go b/cmd/logger/config.go index dc520f832..f792f7cd3 100644 --- a/cmd/logger/config.go +++ b/cmd/logger/config.go @@ -30,9 +30,11 @@ type Console struct { // HTTP logger target type HTTP struct { - Enabled bool `json:"enabled"` - Endpoint string `json:"endpoint"` - AuthToken string `json:"authToken"` + Enabled bool `json:"enabled"` + Endpoint string `json:"endpoint"` + AuthToken string `json:"authToken"` + ClientCert string `json:"clientCert"` + ClientKey string `json:"clientKey"` } // Config console and http logger targets @@ -44,16 +46,20 @@ type Config struct { // HTTP endpoint logger const ( - Endpoint = "endpoint" - AuthToken = "auth_token" + Endpoint = "endpoint" + AuthToken = "auth_token" + ClientCert = "client_cert" + ClientKey = "client_key" EnvLoggerWebhookEnable = "MINIO_LOGGER_WEBHOOK_ENABLE" EnvLoggerWebhookEndpoint = "MINIO_LOGGER_WEBHOOK_ENDPOINT" EnvLoggerWebhookAuthToken = "MINIO_LOGGER_WEBHOOK_AUTH_TOKEN" - EnvAuditWebhookEnable = "MINIO_AUDIT_WEBHOOK_ENABLE" - EnvAuditWebhookEndpoint = "MINIO_AUDIT_WEBHOOK_ENDPOINT" - EnvAuditWebhookAuthToken = "MINIO_AUDIT_WEBHOOK_AUTH_TOKEN" + EnvAuditWebhookEnable = "MINIO_AUDIT_WEBHOOK_ENABLE" + EnvAuditWebhookEndpoint = "MINIO_AUDIT_WEBHOOK_ENDPOINT" + EnvAuditWebhookAuthToken = "MINIO_AUDIT_WEBHOOK_AUTH_TOKEN" + EnvAuditWebhookClientCert = "MINIO_AUDIT_WEBHOOK_CLIENT_CERT" + EnvAuditWebhookClientKey = "MINIO_AUDIT_WEBHOOK_CLIENT_KEY" ) // Default KVS for loggerHTTP and loggerAuditHTTP @@ -85,6 +91,14 @@ var ( Key: AuthToken, Value: "", }, + config.KV{ + Key: ClientCert, + Value: "", + }, + config.KV{ + Key: ClientKey, + Value: "", + }, } ) @@ -251,10 +265,24 @@ func LookupConfig(scfg config.Config) (Config, error) { if target != config.Default { authTokenEnv = EnvAuditWebhookAuthToken + config.Default + target } + clientCertEnv := EnvAuditWebhookClientCert + if target != config.Default { + clientCertEnv = EnvAuditWebhookClientCert + config.Default + target + } + clientKeyEnv := EnvAuditWebhookClientKey + if target != config.Default { + clientKeyEnv = EnvAuditWebhookClientKey + config.Default + target + } + err = config.EnsureCertAndKey(env.Get(clientCertEnv, ""), env.Get(clientKeyEnv, "")) + if err != nil { + return cfg, err + } cfg.Audit[target] = HTTP{ - Enabled: true, - Endpoint: env.Get(endpointEnv, ""), - AuthToken: env.Get(authTokenEnv, ""), + Enabled: true, + Endpoint: env.Get(endpointEnv, ""), + AuthToken: env.Get(authTokenEnv, ""), + ClientCert: env.Get(clientCertEnv, ""), + ClientKey: env.Get(clientKeyEnv, ""), } } @@ -307,10 +335,16 @@ func LookupConfig(scfg config.Config) (Config, error) { if !enabled { continue } + err = config.EnsureCertAndKey(kv.Get(ClientCert), kv.Get(ClientKey)) + if err != nil { + return cfg, err + } cfg.Audit[starget] = HTTP{ - Enabled: true, - Endpoint: kv.Get(Endpoint), - AuthToken: kv.Get(AuthToken), + Enabled: true, + Endpoint: kv.Get(Endpoint), + AuthToken: kv.Get(AuthToken), + ClientCert: kv.Get(ClientCert), + ClientKey: kv.Get(ClientKey), } } diff --git a/cmd/logger/help.go b/cmd/logger/help.go index e625c4cca..52703e3e7 100644 --- a/cmd/logger/help.go +++ b/cmd/logger/help.go @@ -16,7 +16,9 @@ package logger -import "github.com/minio/minio/cmd/config" +import ( + "github.com/minio/minio/cmd/config" +) // Help template for logger http and audit var ( @@ -58,5 +60,17 @@ var ( Optional: true, Type: "sentence", }, + config.HelpKV{ + Key: ClientCert, + Description: "mTLS certificate for Audit Webhook authentication", + Optional: true, + Type: "string", + }, + config.HelpKV{ + Key: ClientKey, + Description: "mTLS certificate key for Audit Webhook authentication", + Optional: true, + Type: "string", + }, } ) diff --git a/cmd/utils.go b/cmd/utils.go index ac433566d..3e392d156 100644 --- a/cmd/utils.go +++ b/cmd/utils.go @@ -43,6 +43,7 @@ import ( "github.com/gorilla/mux" xhttp "github.com/minio/minio/cmd/http" "github.com/minio/minio/cmd/logger" + "github.com/minio/minio/pkg/certs" "github.com/minio/minio/pkg/handlers" "github.com/minio/minio/pkg/madmin" "golang.org/x/net/http2" @@ -606,6 +607,25 @@ func newCustomHTTPTransport(tlsConfig *tls.Config, dialTimeout time.Duration) fu } } +// NewGatewayHTTPTransportWithClientCerts returns a new http configuration +// used while communicating with the cloud backends. +func NewGatewayHTTPTransportWithClientCerts(clientCert, clientKey string) *http.Transport { + transport := newGatewayHTTPTransport(1 * time.Minute) + if clientCert != "" && clientKey != "" { + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + c, err := certs.NewManager(ctx, clientCert, clientKey, tls.LoadX509KeyPair) + if err != nil { + logger.LogIf(ctx, fmt.Errorf("failed to load client key and cert, please check your endpoint configuration: %s", + err.Error())) + } + if c != nil { + transport.TLSClientConfig.GetClientCertificate = c.GetClientCertificate + } + } + return transport +} + // NewGatewayHTTPTransport returns a new http configuration // used while communicating with the cloud backends. func NewGatewayHTTPTransport() *http.Transport { diff --git a/docs/logging/README.md b/docs/logging/README.md index 81cdd2b5a..8988a2b41 100644 --- a/docs/logging/README.md +++ b/docs/logging/README.md @@ -38,7 +38,7 @@ minio server /mnt/data Assuming `mc` is already [configured](https://docs.min.io/docs/minio-client-quickstart-guide.html) ``` mc admin config get myminio/ audit_webhook -audit_webhook:name1 auth_token="" endpoint="" +audit_webhook:name1 enable=off endpoint= auth_token= client_cert= client_key= ``` ``` @@ -53,6 +53,8 @@ MinIO also honors environment variable for HTTP target Audit logging as shown be export MINIO_AUDIT_WEBHOOK_ENABLE_target1="on" export MINIO_AUDIT_WEBHOOK_AUTH_TOKEN_target1="token" export MINIO_AUDIT_WEBHOOK_ENDPOINT_target1=http://localhost:8080/minio/logs +export MINIO_AUDIT_WEBHOOK_CLIENT_CERT="/tmp/cert.pem" +export MINIO_AUDIT_WEBHOOK_CLIENT_KEY=="/tmp/key.pem" minio server /mnt/data ```