Skip to content

Commit 13a38aa

Browse files
authored
feature: proxy mTLS support (#4037)
feature: proxy mTLS support implementation based on net.Client and net.CertReloader Added test cases by ai and validated the code that it is testing as expected. --------- Signed-off-by: Sandor Szücs <sandor.szuecs@zalando.de>
1 parent 30ad48e commit 13a38aa

5 files changed

Lines changed: 929 additions & 5 deletions

File tree

config/config.go

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -251,9 +251,11 @@ type Config struct {
251251
EnableAdvancedValidation bool `yaml:"enable-advanced-validation"`
252252

253253
// TLS client certs
254-
ClientKeyFile string `yaml:"client-tls-key"`
255-
ClientCertFile string `yaml:"client-tls-cert"`
256-
Certificates []tls.Certificate `yaml:"-"`
254+
ClientKeyFile string `yaml:"client-tls-key"`
255+
ClientCertFile string `yaml:"client-tls-cert"`
256+
ClientCertRefreshInterval time.Duration `yaml:"client-tls-cert-refresh-interval"`
257+
Certificates []tls.Certificate `yaml:"-"`
258+
EnableMTLS bool `yaml:"enable-mtls"`
257259

258260
// TLS version
259261
TLSMinVersion string `yaml:"tls-min-version"`
@@ -629,6 +631,9 @@ func NewConfig() *Config {
629631
// TLS client certs
630632
flag.StringVar(&cfg.ClientKeyFile, "client-tls-key", "", "TLS Key file for backend connections, multiple keys may be given comma separated - the order must match the certs")
631633
flag.StringVar(&cfg.ClientCertFile, "client-tls-cert", "", "TLS certificate files for backend connections, multiple keys may be given comma separated - the order must match the keys")
634+
flag.DurationVar(&cfg.ClientCertRefreshInterval, "client-tls-cert-refresh-interval", 0, "How often to reload client TLS certificate and key files for backend connections. Defaults to 5 minutes if certificate files are set.")
635+
// MTLS
636+
flag.BoolVar(&cfg.EnableMTLS, "enable-mtls", false, "Enables MTLS support in the proxy. It uses -client-tls-cert and -client-tls-key as files and rotates the client cert every -client-tls-cert-refresh-interval time.Duration. It only supports one cert and one key file!")
632637

633638
// TLS version
634639
flag.StringVar(&cfg.TLSMinVersion, "tls-min-version", defaultMinTLSVersion, "minimal TLS Version to be used in server, proxy and client connections")
@@ -838,7 +843,8 @@ func (c *Config) ParseArgs(progname string, args []string) error {
838843
c.ResponseSizeBuckets, _ = c.parseHistogramBuckets(c.ResponseSizeBucketsString, metrics.DefaultResponseSizeBuckets)
839844
c.RequestSizeBuckets, _ = c.parseHistogramBuckets(c.RequestSizeBucketsString, metrics.DefaultRequestSizeBuckets)
840845

841-
if c.ClientKeyFile != "" && c.ClientCertFile != "" {
846+
// static client certs
847+
if !c.EnableMTLS && c.ClientKeyFile != "" && c.ClientCertFile != "" {
842848
certsFiles := strings.Split(c.ClientCertFile, ",")
843849
keyFiles := strings.Split(c.ClientKeyFile, ",")
844850

@@ -1190,13 +1196,20 @@ func (c *Config) ToOptions() skipper.Options {
11901196
options.ProxyFlags |= proxy.PatchPath
11911197
}
11921198

1199+
// static client certs
11931200
if len(c.Certificates) > 0 {
11941201
options.ClientTLS = &tls.Config{
11951202
Certificates: c.Certificates,
11961203
MinVersion: c.getMinTLSVersion(),
11971204
}
11981205
}
11991206

1207+
// mtls support
1208+
options.EnableMTLS = c.EnableMTLS
1209+
options.ClientCertFile = c.ClientCertFile
1210+
options.ClientKeyFile = c.ClientKeyFile
1211+
options.ClientCertRefreshInterval = c.ClientCertRefreshInterval
1212+
12001213
var wrappers []func(handler http.Handler) http.Handler
12011214
options.CustomHttpHandlerWrap = func(handler http.Handler) http.Handler {
12021215
for _, wrapper := range wrappers {

proxy/proxy.go

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,24 @@ type Params struct {
361361
// ClientTLS configuration to connect to Backends
362362
ClientTLS *tls.Config
363363

364+
// ClientCertFile is the path to a PEM-encoded client certificate for mTLS to backends.
365+
// Must be set together with ClientKeyFile. When set, GetClientCertificate is used for cert rotation.
366+
ClientCertFile string
367+
368+
// ClientKeyFile is the path to a PEM-encoded private key for mTLS to backends.
369+
// Must be set together with ClientCertFile.
370+
ClientKeyFile string
371+
372+
// ClientCertRefreshInterval is how often ClientCertFile/ClientKeyFile are re-read.
373+
// Defaults to 5 minutes if zero.
374+
ClientCertRefreshInterval time.Duration
375+
376+
// EnableMTLS enables MTLS support in the proxy if we have
377+
// ClientCertFile, ClientKeyFile and uses
378+
// ClientCertRefreshInterval to update rotated x509 cert from
379+
// the provided files.
380+
EnableMTLS bool
381+
364382
// OpenTracing contains parameters related to OpenTracing instrumentation. For default values
365383
// check OpenTracingParams
366384
OpenTracing *OpenTracingParams
@@ -477,6 +495,7 @@ type Proxy struct {
477495
clientTLS *tls.Config
478496
hostname string
479497
onPanicSometimes rate.Sometimes
498+
cr *snet.CertReloader
480499
}
481500

482501
// proxyError is used to wrap errors during proxying and to indicate
@@ -845,6 +864,26 @@ func WithParams(p Params) *Proxy {
845864
}
846865
}
847866

867+
log := &logging.DefaultLog{}
868+
var cr *snet.CertReloader
869+
if p.EnableMTLS && p.ClientCertFile != "" && p.ClientKeyFile != "" {
870+
interval := p.ClientCertRefreshInterval
871+
if interval == 0 {
872+
interval = 5 * time.Minute
873+
}
874+
var err error
875+
cr, err = snet.NewCertReloader(p.ClientCertFile, p.ClientKeyFile, interval, log)
876+
if err != nil {
877+
log.Errorf("Failed to initialize cert reloader in proxy: %v", err)
878+
os.Exit(2)
879+
} else {
880+
if tr.TLSClientConfig == nil {
881+
tr.TLSClientConfig = &tls.Config{}
882+
}
883+
tr.TLSClientConfig.GetClientCertificate = cr.GetClientCertificate
884+
}
885+
}
886+
848887
m := p.Metrics
849888
if m == nil {
850889
m = metrics.Default
@@ -897,7 +936,7 @@ func WithParams(p Params) *Proxy {
897936
maxLoops: p.MaxLoopbacks,
898937
breakers: p.CircuitBreakers,
899938
limiters: p.RateLimiters,
900-
log: &logging.DefaultLog{},
939+
log: log,
901940
accessLogger: p.AccessLogger,
902941
defaultHTTPStatus: defaultHTTPStatus,
903942
tracing: newProxyTracing(p.OpenTracing),
@@ -908,6 +947,7 @@ func WithParams(p Params) *Proxy {
908947
clientTLS: tr.TLSClientConfig,
909948
hostname: hostname,
910949
onPanicSometimes: rate.Sometimes{First: 3, Interval: 1 * time.Minute},
950+
cr: cr,
911951
}
912952
}
913953

@@ -1866,6 +1906,9 @@ func (p *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
18661906
func (p *Proxy) Close() error {
18671907
close(p.quit)
18681908
p.registry.Close()
1909+
if p.cr != nil {
1910+
p.cr.Close()
1911+
}
18691912
return nil
18701913
}
18711914

0 commit comments

Comments
 (0)