Skip to content

Commit

Permalink
Feature: tlsPassClientCertificates filter (#2912)
Browse files Browse the repository at this point in the history
* feature: new filter tlsPassClientCertificates

the new filter passes client cert as header to the backend

Signed-off-by: Sandor Szücs <[email protected]>
  • Loading branch information
szuecs authored Feb 8, 2024
1 parent 1045666 commit cbc4e2d
Show file tree
Hide file tree
Showing 5 changed files with 411 additions and 0 deletions.
16 changes: 16 additions & 0 deletions docs/reference/filters.md
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,22 @@ The second filter will set `Authorization` header to the
`access_token` query param with a prefix value `Bearer ` and will
not override the value if the header exists already.

## TLS

Filters that provide access to TLS data of a request.

### tlsPassClientCertificates

This filter copies TLS client certificates encoded as pem into the
X-Forwarded-Tls-Client-Cert header. Multiple certificates are
separated by `,`.

Example:

```
* -> tlsPassClientCertificates() -> "http://10.2.5.21:8080";
```

## Diagnostics

These filters are meant for diagnostic or load testing purposes.
Expand Down
2 changes: 2 additions & 0 deletions filters/builtin/builtin.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"github.com/zalando/skipper/filters/scheduler"
"github.com/zalando/skipper/filters/sed"
"github.com/zalando/skipper/filters/tee"
"github.com/zalando/skipper/filters/tls"
"github.com/zalando/skipper/filters/tracing"
"github.com/zalando/skipper/filters/xforward"
"github.com/zalando/skipper/script"
Expand Down Expand Up @@ -228,6 +229,7 @@ func Filters() []filters.Spec {
fadein.NewEndpointCreated(),
consistenthash.NewConsistentHashKey(),
consistenthash.NewConsistentHashBalanceFactor(),
tls.New(),
}
}

Expand Down
1 change: 1 addition & 0 deletions filters/filters.go
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,7 @@ const (
ConsistentHashBalanceFactorName = "consistentHashBalanceFactor"
OpaAuthorizeRequestName = "opaAuthorizeRequest"
OpaServeResponseName = "opaServeResponse"
TLSName = "tlsPassClientCertificates"

// Undocumented filters
HealthCheckName = "healthcheck"
Expand Down
79 changes: 79 additions & 0 deletions filters/tls/pass_client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package tls

import (
"crypto/x509"
"encoding/pem"
"strings"

"github.com/zalando/skipper/filters"
)

type tlsSpec struct{}
type tlsFilter struct{}

func New() filters.Spec {
return &tlsSpec{}
}

func (*tlsSpec) Name() string {
return filters.TLSName
}

func (c *tlsSpec) CreateFilter(args []interface{}) (filters.Filter, error) {
if len(args) != 0 {
return nil, filters.ErrInvalidFilterParameters
}

return &tlsFilter{}, nil
}

const (
certSeparator = ","
certHeaderName = "X-Forwarded-Tls-Client-Cert"
)

var (
replacer = strings.NewReplacer(
"-----BEGIN CERTIFICATE-----", "",
"-----END CERTIFICATE-----", "",
"\n", "",
)
)

// sanitize the raw certificates, remove the useless data and make it http request compliant.
func sanitize(cert []byte) string {
return replacer.Replace(string(cert))
}

// getCertificates Build a string with the client certificates.
func getCertificates(certs []*x509.Certificate) string {
var headerValues []string

for _, peerCert := range certs {
headerValues = append(headerValues, extractCertificate(peerCert))
}

return strings.Join(headerValues, certSeparator)
}

// extractCertificate extract the certificate from the request.
func extractCertificate(cert *x509.Certificate) string {
certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw})
if certPEM == nil {
return ""
}

return sanitize(certPEM)
}

// Request passes cert information via X-Forwarded-Tls-Client-Cert header to the backend.
// Largely inspired by traefik, see also https://github.com/traefik/traefik/blob/6c19a9cb8fb9e41a274bf712580df3712b69dc3e/pkg/middlewares/passtlsclientcert/pass_tls_client_cert.go#L146
func (f *tlsFilter) Request(ctx filters.FilterContext) {
if t := ctx.Request().TLS; t != nil {
if len(t.PeerCertificates) > 0 {
ctx.Request().Header.Set(certHeaderName, getCertificates(ctx.Request().TLS.PeerCertificates))
}
}
}

func (f *tlsFilter) Response(ctx filters.FilterContext) {}
Loading

0 comments on commit cbc4e2d

Please sign in to comment.