Skip to content

Commit 9f512f7

Browse files
authored
Feature: mtls filters (#4044)
1 parent 9b35db1 commit 9f512f7

13 files changed

Lines changed: 2805 additions & 6 deletions

File tree

AGENTS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ Backend is the thing to which skipper should proxy, for example:
2222
- Start example proxy with one route: `./bin/skipper -inline-routes='r: * -> latency("1ms") -> status(201) -> <shunt>' -address :9001`
2323
Call the proxy: curl http://localhost:9001/
2424
- Run tests by package for example proxy: `go test ./proxy`
25+
- Run linter: `make lint`
2526
- Run all tests: `make check`
2627
- Run all tests with race detector: `make check-race`
2728

@@ -32,6 +33,7 @@ Backend is the thing to which skipper should proxy, for example:
3233
- package docs in doc.go
3334
- no comments in code if they are not critical
3435
- no kubernetes client-go dependencies
36+
- run linter `make lint` and fix all findings
3537

3638
## Testing instructions
3739

Makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ CURRENT_VERSION = $(shell git describe --tags --always --dirty)
44
VERSION ?= $(CURRENT_VERSION)
55
COMMIT_HASH = $(shell git rev-parse --short HEAD)
66
LIMIT_FDS = $(shell ulimit -n)
7-
TEST_ETCD_VERSION ?= v3.5.11
8-
TEST_ETCD_CHECKSUM ?= 4fb304f384dd4d6e491e405fed8375a09ea1c6c2596b93f97cb31844202e620df160f87f18611e84f17675e7b7245e40d1aa23571ecdb507cb094ba04d378171
7+
TEST_ETCD_VERSION ?= v3.6.12
8+
TEST_ETCD_CHECKSUM ?= d2564bb50b58e52fbaf3bde4a9561a1d04e20cdc761f795580e4d615d5d41355
99
TEST_PLUGINS = _test_plugins/filter_noop.so \
1010
_test_plugins/predicate_match_none.so \
1111
_test_plugins/dataclient_noop.so \

config/config.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package config
22

33
import (
44
"crypto/tls"
5+
"crypto/x509"
56
"flag"
67
"fmt"
78
"net/http"
@@ -256,6 +257,9 @@ type Config struct {
256257
ClientCertRefreshInterval time.Duration `yaml:"client-tls-cert-refresh-interval"`
257258
Certificates []tls.Certificate `yaml:"-"`
258259
EnableMTLS bool `yaml:"enable-mtls"`
260+
MtlsAuthnAppendCA bool `yaml:"mtls-authn-append-ca"`
261+
MtlsAuthnCaFile string `yaml:"mtls-authn-ca"`
262+
MtlsAuthnCA *x509.CertPool `yaml:"-"`
259263

260264
// TLS version
261265
TLSMinVersion string `yaml:"tls-min-version"`
@@ -633,6 +637,8 @@ func NewConfig() *Config {
633637
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")
634638
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.")
635639
// MTLS
640+
flag.StringVar(&cfg.MtlsAuthnCaFile, "mtls-authn-ca", "", "PEM encoded CA files to use in mtlsAuthn() filter to validate client certificates, multiple files may be given comma separated")
641+
flag.BoolVar(&cfg.MtlsAuthnAppendCA, "mtls-authn-append-ca", false, "If set to true -mtls-authn-ca will load system CAs, too.")
636642
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!")
637643

638644
// TLS version
@@ -861,6 +867,28 @@ func (c *Config) ParseArgs(progname string, args []string) error {
861867
c.Certificates = certificates
862868
}
863869

870+
if c.MtlsAuthnAppendCA {
871+
pool, err := x509.SystemCertPool()
872+
if err != nil {
873+
return fmt.Errorf("failed to load system cert pool: %v", err)
874+
} else {
875+
c.MtlsAuthnCA = pool
876+
}
877+
} else {
878+
c.MtlsAuthnCA = x509.NewCertPool()
879+
}
880+
if c.MtlsAuthnCaFile != "" {
881+
for f := range strings.SplitSeq(c.MtlsAuthnCaFile, ",") {
882+
pem, err := os.ReadFile(f)
883+
if err != nil {
884+
return fmt.Errorf("failed to read %q: %v", f, err)
885+
}
886+
if !c.MtlsAuthnCA.AppendCertsFromPEM(pem) {
887+
return fmt.Errorf("failed to append CA cert %q", f)
888+
}
889+
}
890+
}
891+
864892
if c.NormalizeHost || c.KubernetesIngress {
865893
c.HostPatch = net.HostPatch{
866894
ToLower: true,
@@ -1209,6 +1237,7 @@ func (c *Config) ToOptions() skipper.Options {
12091237
options.ClientCertFile = c.ClientCertFile
12101238
options.ClientKeyFile = c.ClientKeyFile
12111239
options.ClientCertRefreshInterval = c.ClientCertRefreshInterval
1240+
options.MtlsAuthnCA = c.MtlsAuthnCA
12121241

12131242
var wrappers []func(handler http.Handler) http.Handler
12141243
options.CustomHttpHandlerWrap = func(handler http.Handler) http.Handler {

config/config_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package config
22

33
import (
44
"crypto/tls"
5+
"crypto/x509"
56
"errors"
67
"fmt"
78
"os"
@@ -190,6 +191,9 @@ func defaultConfig(with func(*Config)) *Config {
190191
ProxyAllowListCIDRs: commaListFlag(),
191192
ProxyDenyListCIDRs: commaListFlag(),
192193
ProxySkipListCIDRs: commaListFlag(),
194+
MtlsAuthnAppendCA: false,
195+
MtlsAuthnCaFile: "",
196+
MtlsAuthnCA: x509.NewCertPool(),
193197
}
194198
with(cfg)
195199
return cfg

docs/operation/operation.md

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,63 @@ addresses.
191191

192192
## Authentication and Authorization
193193

194+
### mTLS
195+
196+
Skipper as a proxy supports mTLS as authentication and authorization
197+
method as client and as server and per route via filters to check the
198+
data in the validated X509 certificate.
199+
200+
#### proxy client to backend
201+
202+
To enable proxy client to backend authentication, skipper will create
203+
an mTLS connection sending its client certificate as its identity to
204+
the backend such that backends can verify the connection is ok to
205+
trust.
206+
207+
To enable this you need to use `-enable-mtls`, `-client-tls-cert` and
208+
`-client-tls-key`. Skipper supports only one identity, so only one
209+
cert+key in the mTLS case. The provided file paths for cert and key
210+
can be used to rotate cert and key. Skipper will automatically detect
211+
new data in these files and will reload the TLS client configuration
212+
to use the new cert and key. Rotation will happen every
213+
`-client-tls-cert-refresh-interval`, which defaults to `5m`.
214+
215+
This configuration will enable mTLS for all connections to all
216+
backends of the proxy. More advanced configurations are work in
217+
progress:
218+
219+
- https://github.com/zalando/skipper/issues/4072
220+
- https://github.com/zalando/skipper/issues/4042
221+
222+
#### client to proxy server
223+
224+
For mTLS to be able to work you need to terminate TLS in skipper. If
225+
you want to validate client certificates for mTLS connections in
226+
skipper, it is possible to do so per route.
227+
228+
Global configuration is not needed if the system CAs are enough to
229+
validate all client certificates. If you want to add your own CAs,
230+
you can use `-mtls-authn-ca="pem1,pem2"` to add your PEM encoded CA
231+
bundles. If you also want to add the system CAs, too, you can use
232+
`-mtls-authn-append-ca=true` to load also system bundles.
233+
234+
How mTLS authentication and authorization works per route:
235+
236+
The client presents the client certificate and Go passes it to the
237+
http.Request.TLS object that we can process in filters. For example in
238+
the [`mtlsAuthn()`](../reference/filters.md#mtlsauthn) filter to
239+
validate client certificates. In order to check more details for authorization, you can add the other mtls filters after the `mtlsAuthn()` filter, like:
240+
241+
- [`mtlsIssuerDN()`](../reference/filters.md#mtlsissuerdn)
242+
- [`mtlsSanCIDR()`](../reference/filters.md#mtlssancidr)
243+
- [`mtlsSanDNS()`](../reference/filters.md#mtlssandns)
244+
- [`mtlsSanIP()`](../reference/filters.md#mtlssanip)
245+
- [`mtlsSanURI()`](../reference/filters.md#mtlssanuri)
246+
247+
Features are also work in progress:
248+
249+
- https://github.com/zalando/skipper/issues/4073
250+
194251
### OAuth2 Tokeninfo
195252

196253
OAuth2 filters integrate with external services and have their own

docs/reference/filters.md

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -561,6 +561,109 @@ Example:
561561
* -> tlsPassClientCertificates() -> "http://10.2.5.21:8080";
562562
```
563563

564+
### mtlsAuthn
565+
566+
This filter validates the client certificate provided by verifying
567+
with the configured system CA certificates.
568+
569+
Example:
570+
571+
```
572+
* -> mtlsAuthn() -> "http://10.2.5.21:8080";
573+
```
574+
575+
### mtlsIssuerDN
576+
577+
This authz filter checks the DN value of the issuer of the provided certificate. You have
578+
to use `mtlsAuthn()` to verify validity.
579+
580+
Parameters:
581+
582+
* DN (string)
583+
584+
Example:
585+
586+
```
587+
* -> mtlsAuthn() -> mtlsIssuerDN("CN=My CA,O=My Org,C=DE") -> "http://10.2.5.21:8080";
588+
```
589+
590+
### mtlsCN
591+
592+
This authz filter checks the CN value of the subject of the provided
593+
certificate. You have to use `mtlsAuthn()` to verify validity.
594+
595+
Parameters:
596+
597+
* CN (string)
598+
599+
Example:
600+
601+
```
602+
* -> mtlsAuthn() -> mtlsCN("My CA") -> "http://10.2.5.21:8080";
603+
```
604+
605+
### mtlsSanCIDR
606+
607+
This authz filter checks CIDRs of the SAN value of the provided certificate. You have
608+
to use `mtlsAuthn()` to verify validity.
609+
610+
Parameters are one or more:
611+
612+
* CIDR (string)
613+
614+
Example:
615+
616+
```
617+
* -> mtlsAuthn() -> mtlsSanCIDR("2a05:aec0::/29", "10.0.5.0/15") -> "http://10.2.5.21:8080";
618+
```
619+
620+
### mtlsSanDNS
621+
622+
This authz filter checks DNS of the SAN value of the provided certificate. You have
623+
to use `mtlsAuthn()` to verify validity.
624+
625+
Parameters are one or more:
626+
627+
* DNS hostnames (string)
628+
629+
Example:
630+
631+
```
632+
* -> mtlsAuthn() -> mtlsSanDNS("my.host.example") -> "http://10.2.5.21:8080";
633+
```
634+
635+
### mtlsSanIP
636+
637+
This authz filter checks IPs of the SAN value of the provided certificate. You have
638+
to use `mtlsAuthn()` to verify validity.
639+
640+
Parameters are one or more:
641+
642+
* IP (string)
643+
644+
Example:
645+
646+
```
647+
* -> mtlsAuthn() -> mtlsSanIP("2a05:aec0::5", "10.0.5.10") -> "http://10.2.5.21:8080";
648+
```
649+
650+
### mtlsSanURI
651+
652+
This authz filter checks URIs of the SAN value of the provided certificate. You have
653+
to use `mtlsAuthn()` to verify validity.
654+
655+
Parameters are one or more:
656+
657+
* URI (string)
658+
659+
Example:
660+
661+
```
662+
* -> mtlsAuthn() -> mtlsSanURI("spiffe://my-service.example/app1") -> "http://10.2.5.21:8080";
663+
```
664+
665+
666+
564667
## Diagnostics
565668

566669
These filters are meant for diagnostic or load testing purposes.

etcd/install.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ mkdir -p .bin
2222

2323
curl -LsSfo ./.bin/etcd.tar.gz "${ETCD_URL}"
2424

25-
echo ${ETCD_CHECKSUM} ./.bin/etcd.tar.gz | sha512sum --check -
25+
echo ${ETCD_CHECKSUM} ./.bin/etcd.tar.gz | sha256sum --check -
2626

2727
tar -xzf .bin/etcd.tar.gz --strip-components=1 \
2828
-C ./.bin "etcd-${ETCD_VERSION}-linux-amd64/etcd"

filters/builtin/builtin.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,12 @@ func Filters() []filters.Spec {
243243
consistenthash.NewConsistentHashKey(),
244244
consistenthash.NewConsistentHashBalanceFactor(),
245245
tls.New(),
246+
tls.NewMtlsCN(),
247+
tls.NewMtlsIssuerDN(),
248+
tls.NewMtlsSanCIDR(),
249+
tls.NewMtlsSanDNS(),
250+
tls.NewMtlsSanIP(),
251+
tls.NewMtlsSanURI(),
246252
}
247253
}
248254

filters/filters.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,13 @@ const (
377377
OpaServeResponseName = "opaServeResponse"
378378
OpaServeResponseWithReqBodyName = "opaServeResponseWithReqBody"
379379
TLSName = "tlsPassClientCertificates"
380+
MtlsIssuerDN = "mtlsIssuerDN"
381+
MtlsSanCIDR = "mtlsSanCIDR"
382+
MtlsSanDNS = "mtlsSanDNS"
383+
MtlsSanIP = "mtlsSanIP"
384+
MtlsSanURI = "mtlsSanURI"
385+
MtlsCN = "mtlsCN"
386+
MtlsAuthn = "mtlsAuthn"
380387
AWSSigV4Name = "awsSigv4"
381388
LoopbackIfStatus = "loopbackIfStatus"
382389
CacheName = "cache"

0 commit comments

Comments
 (0)