Support mTLS Authentication in Webhooks (#9777)

master
Praveen raj Mani 5 years ago committed by GitHub
parent c7599d323b
commit 2ce2e88adf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 12
      cmd/config/notify/help.go
  2. 8
      cmd/config/notify/legacy.go
  3. 19
      cmd/config/notify/parse.go
  4. 6
      docs/bucket/notifications/README.md
  5. 8
      pkg/certs/certs.go
  6. 10
      pkg/certs/certs_test.go
  7. 23
      pkg/event/target/webhook.go

@ -59,6 +59,18 @@ var (
Optional: true, Optional: true,
Type: "sentence", Type: "sentence",
}, },
config.HelpKV{
Key: target.WebhookClientCert,
Description: "client cert for Webhook mTLS auth",
Optional: true,
Type: "string",
},
config.HelpKV{
Key: target.WebhookClientKey,
Description: "client cert key for Webhook mTLS auth",
Optional: true,
Type: "string",
},
} }
HelpAMQP = config.HelpKVS{ HelpAMQP = config.HelpKVS{

@ -281,6 +281,14 @@ func SetNotifyWebhook(s config.Config, whName string, cfg target.WebhookArgs) er
Key: target.WebhookQueueLimit, Key: target.WebhookQueueLimit,
Value: strconv.Itoa(int(cfg.QueueLimit)), Value: strconv.Itoa(int(cfg.QueueLimit)),
}, },
config.KV{
Key: target.WebhookClientCert,
Value: cfg.ClientCert,
},
config.KV{
Key: target.WebhookClientKey,
Value: cfg.ClientKey,
},
} }
return nil return nil

@ -1428,6 +1428,14 @@ var (
Key: target.WebhookQueueDir, Key: target.WebhookQueueDir,
Value: "", Value: "",
}, },
config.KV{
Key: target.WebhookClientCert,
Value: "",
},
config.KV{
Key: target.WebhookClientKey,
Value: "",
},
} }
) )
@ -1471,6 +1479,15 @@ func GetNotifyWebhook(webhookKVS map[string]config.KVS, transport *http.Transpor
if k != config.Default { if k != config.Default {
authEnv = authEnv + config.Default + k authEnv = authEnv + config.Default + k
} }
clientCertEnv := target.EnvWebhookClientCert
if k != config.Default {
clientCertEnv = clientCertEnv + config.Default + k
}
clientKeyEnv := target.EnvWebhookClientKey
if k != config.Default {
clientKeyEnv = clientKeyEnv + config.Default + k
}
webhookArgs := target.WebhookArgs{ webhookArgs := target.WebhookArgs{
Enable: enabled, Enable: enabled,
@ -1479,6 +1496,8 @@ func GetNotifyWebhook(webhookKVS map[string]config.KVS, transport *http.Transpor
AuthToken: env.Get(authEnv, kv.Get(target.WebhookAuthToken)), AuthToken: env.Get(authEnv, kv.Get(target.WebhookAuthToken)),
QueueDir: env.Get(queueDirEnv, kv.Get(target.WebhookQueueDir)), QueueDir: env.Get(queueDirEnv, kv.Get(target.WebhookQueueDir)),
QueueLimit: uint64(queueLimit), QueueLimit: uint64(queueLimit),
ClientCert: env.Get(clientCertEnv, kv.Get(target.WebhookClientCert)),
ClientKey: env.Get(clientKeyEnv, kv.Get(target.WebhookClientKey)),
} }
if err = webhookArgs.Validate(); err != nil { if err = webhookArgs.Validate(); err != nil {
return nil, err return nil, err

@ -1241,6 +1241,8 @@ endpoint* (url) webhook server endpoint e.g. http://localhost:8080/mini
auth_token (string) opaque string or JWT authorization token auth_token (string) opaque string or JWT authorization token
queue_dir (path) staging dir for undelivered messages e.g. '/home/events' queue_dir (path) staging dir for undelivered messages e.g. '/home/events'
queue_limit (number) maximum limit for undelivered messages, defaults to '100000' queue_limit (number) maximum limit for undelivered messages, defaults to '100000'
client_cert (string) client cert for Webhook mTLS auth
client_key (string) client cert key for Webhook mTLS auth
comment (sentence) optionally add a comment to this setting comment (sentence) optionally add a comment to this setting
``` ```
@ -1256,11 +1258,13 @@ MINIO_NOTIFY_WEBHOOK_AUTH_TOKEN (string) opaque string or JWT authorization
MINIO_NOTIFY_WEBHOOK_QUEUE_DIR (path) staging dir for undelivered messages e.g. '/home/events' MINIO_NOTIFY_WEBHOOK_QUEUE_DIR (path) staging dir for undelivered messages e.g. '/home/events'
MINIO_NOTIFY_WEBHOOK_QUEUE_LIMIT (number) maximum limit for undelivered messages, defaults to '100000' MINIO_NOTIFY_WEBHOOK_QUEUE_LIMIT (number) maximum limit for undelivered messages, defaults to '100000'
MINIO_NOTIFY_WEBHOOK_COMMENT (sentence) optionally add a comment to this setting MINIO_NOTIFY_WEBHOOK_COMMENT (sentence) optionally add a comment to this setting
MINIO_NOTIFY_WEBHOOK_CLIENT_CERT (string) client cert for Webhook mTLS auth
MINIO_NOTIFY_WEBHOOK_CLIENT_KEY (string) client cert key for Webhook mTLS auth
``` ```
```sh ```sh
$ mc admin config get myminio/ notify_webhook $ mc admin config get myminio/ notify_webhook
notify_webhook:1 queue_limit="0" endpoint="" queue_dir="" notify_webhook:1 endpoint="" auth_token="" queue_limit="0" queue_dir="" client_cert="" client_key=""
``` ```
Use `mc admin config set` command to update the configuration for the deployment. Here the endpoint is the server listening for webhook notifications. Save the settings and restart the MinIO server for changes to take effect. Note that the endpoint needs to be live and reachable when you restart your MinIO server. Use `mc admin config set` command to update the configuration for the deployment. Here the endpoint is the server listening for webhook notifications. Save the settings and restart the MinIO server for changes to take effect. Note that the endpoint needs to be live and reachable when you restart your MinIO server.

@ -185,6 +185,14 @@ func (c *Certs) GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate, er
return c.cert, nil return c.cert, nil
} }
// GetClientCertificate returns the loaded certificate for use by
// the TLSConfig fields GetClientCertificate field in a http.Server.
func (c *Certs) GetClientCertificate(_ *tls.CertificateRequestInfo) (*tls.Certificate, error) {
c.RLock()
defer c.RUnlock()
return c.cert, nil
}
// Stop tells loader to stop watching for changes to the // Stop tells loader to stop watching for changes to the
// certificate and key files. // certificate and key files.
func (c *Certs) Stop() { func (c *Certs) Stop() {

@ -93,6 +93,16 @@ func TestValidPairAfterWrite(t *testing.T) {
if !reflect.DeepEqual(gcert.Certificate, expectedCert.Certificate) { if !reflect.DeepEqual(gcert.Certificate, expectedCert.Certificate) {
t.Error("certificate doesn't match expected certificate") t.Error("certificate doesn't match expected certificate")
} }
rInfo := &tls.CertificateRequestInfo{}
gcert, err = c.GetClientCertificate(rInfo)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(gcert.Certificate, expectedCert.Certificate) {
t.Error("client certificate doesn't match expected certificate")
}
} }
func TestStop(t *testing.T) { func TestStop(t *testing.T) {

@ -19,6 +19,7 @@ package target
import ( import (
"bytes" "bytes"
"context" "context"
"crypto/tls"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
@ -30,6 +31,7 @@ import (
"path/filepath" "path/filepath"
"time" "time"
"github.com/minio/minio/pkg/certs"
"github.com/minio/minio/pkg/event" "github.com/minio/minio/pkg/event"
xnet "github.com/minio/minio/pkg/net" xnet "github.com/minio/minio/pkg/net"
) )
@ -40,12 +42,16 @@ const (
WebhookAuthToken = "auth_token" WebhookAuthToken = "auth_token"
WebhookQueueDir = "queue_dir" WebhookQueueDir = "queue_dir"
WebhookQueueLimit = "queue_limit" WebhookQueueLimit = "queue_limit"
WebhookClientCert = "client_cert"
WebhookClientKey = "client_key"
EnvWebhookEnable = "MINIO_NOTIFY_WEBHOOK_ENABLE" EnvWebhookEnable = "MINIO_NOTIFY_WEBHOOK_ENABLE"
EnvWebhookEndpoint = "MINIO_NOTIFY_WEBHOOK_ENDPOINT" EnvWebhookEndpoint = "MINIO_NOTIFY_WEBHOOK_ENDPOINT"
EnvWebhookAuthToken = "MINIO_NOTIFY_WEBHOOK_AUTH_TOKEN" EnvWebhookAuthToken = "MINIO_NOTIFY_WEBHOOK_AUTH_TOKEN"
EnvWebhookQueueDir = "MINIO_NOTIFY_WEBHOOK_QUEUE_DIR" EnvWebhookQueueDir = "MINIO_NOTIFY_WEBHOOK_QUEUE_DIR"
EnvWebhookQueueLimit = "MINIO_NOTIFY_WEBHOOK_QUEUE_LIMIT" EnvWebhookQueueLimit = "MINIO_NOTIFY_WEBHOOK_QUEUE_LIMIT"
EnvWebhookClientCert = "MINIO_NOTIFY_WEBHOOK_CLIENT_CERT"
EnvWebhookClientKey = "MINIO_NOTIFY_WEBHOOK_CLIENT_KEY"
) )
// WebhookArgs - Webhook target arguments. // WebhookArgs - Webhook target arguments.
@ -56,6 +62,8 @@ type WebhookArgs struct {
Transport *http.Transport `json:"-"` Transport *http.Transport `json:"-"`
QueueDir string `json:"queueDir"` QueueDir string `json:"queueDir"`
QueueLimit uint64 `json:"queueLimit"` QueueLimit uint64 `json:"queueLimit"`
ClientCert string `json:"clientCert"`
ClientKey string `json:"clientKey"`
} }
// Validate WebhookArgs fields // Validate WebhookArgs fields
@ -71,6 +79,9 @@ func (w WebhookArgs) Validate() error {
return errors.New("queueDir path should be absolute") return errors.New("queueDir path should be absolute")
} }
} }
if w.ClientCert != "" && w.ClientKey == "" || w.ClientCert == "" && w.ClientKey != "" {
return errors.New("cert and key must be specified as a pair")
}
return nil return nil
} }
@ -211,12 +222,18 @@ func NewWebhookTarget(id string, args WebhookArgs, doneCh <-chan struct{}, logge
target := &WebhookTarget{ target := &WebhookTarget{
id: event.TargetID{ID: id, Name: "webhook"}, id: event.TargetID{ID: id, Name: "webhook"},
args: args, args: args,
httpClient: &http.Client{
Transport: transport,
},
loggerOnce: loggerOnce, loggerOnce: loggerOnce,
} }
if target.args.ClientCert != "" && target.args.ClientKey != "" {
c, err := certs.New(target.args.ClientCert, target.args.ClientKey, tls.LoadX509KeyPair)
if err != nil {
return target, err
}
transport.TLSClientConfig.GetClientCertificate = c.GetClientCertificate
}
target.httpClient = &http.Client{Transport: transport}
if args.QueueDir != "" { if args.QueueDir != "" {
queueDir := filepath.Join(args.QueueDir, storePrefix+"-webhook-"+id) queueDir := filepath.Join(args.QueueDir, storePrefix+"-webhook-"+id)
store = NewQueueStore(queueDir, args.QueueLimit) store = NewQueueStore(queueDir, args.QueueLimit)

Loading…
Cancel
Save