Skip to content
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
52e3d39
feature: mtlsSAN() filter
szuecs Jun 1, 2026
572e245
fix merge conflict errors
szuecs Jun 2, 2026
8a64118
fix: copy audit log flag from spec to filter
szuecs Jun 3, 2026
f8d3d83
make sure agents run linter
szuecs Jun 3, 2026
4e9896c
feature: mtlsSAN() support URI lookup that we need for SPIFFE/SPIRE i…
szuecs Jun 3, 2026
703720e
feature: SPIFFE/SPIRE authz filter mtlsSAN()
szuecs Jun 3, 2026
fd567e9
fix: mtlsAuthn() filter is able to load an *x509.CertPool tht will be…
szuecs Jun 3, 2026
c7a6ef4
fix: review finding: we should not check issuer but subject of a cert…
szuecs Jun 3, 2026
850e850
fix: use only leaf cet to validate mtls authnz filters
szuecs Jun 3, 2026
99e35c4
fix: no peer certs respond with 401
szuecs Jun 3, 2026
4540606
split mtlsSAN() filters into 4 specialized filters mtlsSanCIDR(), mtl…
szuecs Jun 3, 2026
87a03cf
fix: config tests
szuecs Jun 3, 2026
34a7392
fix: tests in proxy, because of the changed behavior of mtlsSAN()
szuecs Jun 3, 2026
bd5f860
comment unused
szuecs Jun 4, 2026
8a9724a
fix: use map[] instead of sync.Map
szuecs Jun 5, 2026
66c4aa1
refactor: remove mtlsSAN() and fix all tests and benchmarls related t…
szuecs Jun 5, 2026
d57aa17
fix: build
szuecs Jun 5, 2026
a7561d4
doc: fix filter docs
szuecs Jun 8, 2026
fee0a26
fix etcd install for test deps
szuecs Jun 8, 2026
6764773
feature: handle intermediate CAs
szuecs Jun 17, 2026
9057c2c
test: empty CN is ok for mtlsCN("") filter
szuecs Jun 17, 2026
422770d
doc: add operations docs for mTLS
szuecs Jun 17, 2026
f9750a2
benchmark passing intermediate to the filter spec (static loading) an…
szuecs Jun 17, 2026
24c3a02
drop constraints after reviewing a comment and Go code I think we sho…
szuecs Jun 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ Backend is the thing to which skipper should proxy, for example:
- Start example proxy with one route: `./bin/skipper -inline-routes='r: * -> latency("1ms") -> status(201) -> <shunt>' -address :9001`
Call the proxy: curl http://localhost:9001/
- Run tests by package for example proxy: `go test ./proxy`
- Run linter: `make lint`
- Run all tests: `make check`
- Run all tests with race detector: `make check-race`

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

## Testing instructions

Expand Down
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ CURRENT_VERSION = $(shell git describe --tags --always --dirty)
VERSION ?= $(CURRENT_VERSION)
COMMIT_HASH = $(shell git rev-parse --short HEAD)
LIMIT_FDS = $(shell ulimit -n)
TEST_ETCD_VERSION ?= v3.5.11
TEST_ETCD_CHECKSUM ?= 4fb304f384dd4d6e491e405fed8375a09ea1c6c2596b93f97cb31844202e620df160f87f18611e84f17675e7b7245e40d1aa23571ecdb507cb094ba04d378171
TEST_ETCD_VERSION ?= v3.6.12
TEST_ETCD_CHECKSUM ?= d2564bb50b58e52fbaf3bde4a9561a1d04e20cdc761f795580e4d615d5d41355
TEST_PLUGINS = _test_plugins/filter_noop.so \
_test_plugins/predicate_match_none.so \
_test_plugins/dataclient_noop.so \
Expand Down
29 changes: 29 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package config

import (
"crypto/tls"
"crypto/x509"
"flag"
"fmt"
"net/http"
Expand Down Expand Up @@ -256,6 +257,9 @@ type Config struct {
ClientCertRefreshInterval time.Duration `yaml:"client-tls-cert-refresh-interval"`
Certificates []tls.Certificate `yaml:"-"`
EnableMTLS bool `yaml:"enable-mtls"`
MtlsAuthnAppendCA bool `yaml:"mtls-authn-append-ca"`
MtlsAuthnCaFile string `yaml:"mtls-authn-ca"`
MtlsAuthnCA *x509.CertPool `yaml:"-"`

// TLS version
TLSMinVersion string `yaml:"tls-min-version"`
Expand Down Expand Up @@ -633,6 +637,8 @@ func NewConfig() *Config {
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")
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.")
// MTLS
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")
flag.BoolVar(&cfg.MtlsAuthnAppendCA, "mtls-authn-append-ca", false, "If set to true -mtls-authn-ca will load system CAs, too.")
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!")

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

if c.MtlsAuthnAppendCA {
pool, err := x509.SystemCertPool()
if err != nil {
return fmt.Errorf("failed to load system cert pool: %v", err)
} else {
c.MtlsAuthnCA = pool
}
} else {
c.MtlsAuthnCA = x509.NewCertPool()
}
if c.MtlsAuthnCaFile != "" {
for f := range strings.SplitSeq(c.MtlsAuthnCaFile, ",") {
pem, err := os.ReadFile(f)
if err != nil {
return fmt.Errorf("failed to read %q: %v", f, err)
}
if !c.MtlsAuthnCA.AppendCertsFromPEM(pem) {
return fmt.Errorf("failed to append CA cert %q", f)
}
}
}

if c.NormalizeHost || c.KubernetesIngress {
c.HostPatch = net.HostPatch{
ToLower: true,
Expand Down Expand Up @@ -1209,6 +1237,7 @@ func (c *Config) ToOptions() skipper.Options {
options.ClientCertFile = c.ClientCertFile
options.ClientKeyFile = c.ClientKeyFile
options.ClientCertRefreshInterval = c.ClientCertRefreshInterval
options.MtlsAuthnCA = c.MtlsAuthnCA

var wrappers []func(handler http.Handler) http.Handler
options.CustomHttpHandlerWrap = func(handler http.Handler) http.Handler {
Expand Down
4 changes: 4 additions & 0 deletions config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package config

import (
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"os"
Expand Down Expand Up @@ -190,6 +191,9 @@ func defaultConfig(with func(*Config)) *Config {
ProxyAllowListCIDRs: commaListFlag(),
ProxyDenyListCIDRs: commaListFlag(),
ProxySkipListCIDRs: commaListFlag(),
MtlsAuthnAppendCA: false,
MtlsAuthnCaFile: "",
MtlsAuthnCA: x509.NewCertPool(),
}
with(cfg)
return cfg
Expand Down
103 changes: 103 additions & 0 deletions docs/reference/filters.md
Original file line number Diff line number Diff line change
Expand Up @@ -561,6 +561,109 @@ Example:
* -> tlsPassClientCertificates() -> "http://10.2.5.21:8080";
```

### mtlsAuthn

This filter validates the client certificate provided by verifying
with the configured system CA certificates.

Example:

```
* -> mtlsAuthn() -> "http://10.2.5.21:8080";
Comment thread
szuecs marked this conversation as resolved.
```

### mtlsIssuerDN

This authz filter checks the DN value of the issuer of the provided certificate. You have
to use `mtlsAuthn()` to verify validity.

Parameters:

* DN (string)

Example:

```
* -> mtlsAuthn() -> mtlsIssuerDN("CN=My CA,O=My Org,C=DE") -> "http://10.2.5.21:8080";
```

### mtlsCN

This authz filter checks the CN value of the subject of the provided
certificate. You have to use `mtlsAuthn()` to verify validity.

Parameters:

* CN (string)

Example:

```
* -> mtlsAuthn() -> mtlsCN("My CA") -> "http://10.2.5.21:8080";
```

### mtlsSanCIDR

This authz filter checks CIDRs of the SAN value of the provided certificate. You have
to use `mtlsAuthn()` to verify validity.

Parameters are one or more:

* CIDR (string)

Example:

```
* -> mtlsAuthn() -> mtlsSanCIDR("2a05:aec0::/29", "10.0.5.0/15") -> "http://10.2.5.21:8080";
```

### mtlsSanDNS

This authz filter checks DNS of the SAN value of the provided certificate. You have
to use `mtlsAuthn()` to verify validity.

Parameters are one or more:

* DNS hostnames (string)

Example:

```
* -> mtlsAuthn() -> mtlsSanDNS("my.host.example") -> "http://10.2.5.21:8080";
```

### mtlsSanIP

This authz filter checks IPs of the SAN value of the provided certificate. You have
to use `mtlsAuthn()` to verify validity.

Parameters are one or more:

* IP (string)

Example:

```
* -> mtlsAuthn() -> mtlsSanIP("2a05:aec0::5", "10.0.5.10") -> "http://10.2.5.21:8080";
```

### mtlsSanURI

This authz filter checks URIs of the SAN value of the provided certificate. You have
to use `mtlsAuthn()` to verify validity.

Parameters are one or more:

* URI (string)
Comment thread
szuecs marked this conversation as resolved.

Example:

```
* -> mtlsAuthn() -> mtlsSanURI("spiffe://my-service.example/app1") -> "http://10.2.5.21:8080";
```



## Diagnostics

These filters are meant for diagnostic or load testing purposes.
Expand Down
2 changes: 1 addition & 1 deletion etcd/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ mkdir -p .bin

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

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

tar -xzf .bin/etcd.tar.gz --strip-components=1 \
-C ./.bin "etcd-${ETCD_VERSION}-linux-amd64/etcd"
Expand Down
6 changes: 6 additions & 0 deletions filters/builtin/builtin.go
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,12 @@ func Filters() []filters.Spec {
consistenthash.NewConsistentHashKey(),
consistenthash.NewConsistentHashBalanceFactor(),
tls.New(),
tls.NewMtlsCN(),
tls.NewMtlsIssuerDN(),
tls.NewMtlsSanCIDR(),
tls.NewMtlsSanDNS(),
tls.NewMtlsSanIP(),
tls.NewMtlsSanURI(),
}
}

Expand Down
7 changes: 7 additions & 0 deletions filters/filters.go
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,13 @@ const (
OpaServeResponseName = "opaServeResponse"
OpaServeResponseWithReqBodyName = "opaServeResponseWithReqBody"
TLSName = "tlsPassClientCertificates"
MtlsIssuerDN = "mtlsIssuerDN"
MtlsSanCIDR = "mtlsSanCIDR"
MtlsSanDNS = "mtlsSanDNS"
MtlsSanIP = "mtlsSanIP"
MtlsSanURI = "mtlsSanURI"
MtlsCN = "mtlsCN"
MtlsAuthn = "mtlsAuthn"
AWSSigV4Name = "awsSigv4"
LoopbackIfStatus = "loopbackIfStatus"
CacheName = "cache"
Expand Down
Loading
Loading