Skip to content

Commit

Permalink
Add path exclusion support to mTLS authentication
Browse files Browse the repository at this point in the history
Signed-off-by: Kacper Rzetelski <[email protected]>
  • Loading branch information
rzetelskik committed Oct 25, 2024
1 parent 6ea5d3c commit d67c526
Show file tree
Hide file tree
Showing 8 changed files with 676 additions and 34 deletions.
12 changes: 12 additions & 0 deletions web/internal/authentication/x509/testdata/client2_selfsigned.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
-----BEGIN CERTIFICATE-----
MIIB3DCCAWGgAwIBAgIUJVN8KehL1MmccvLb/mHthSMfnnswCgYIKoZIzj0EAwIw
EDEOMAwGA1UEAwwFdGVzdDMwIBcNMjMwMTEwMTgxMTAwWhgPMjEyMjEyMTcxODEx
MDBaMBAxDjAMBgNVBAMMBXRlc3QzMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEf8wC
qU9e4lPZZqJMA4nJ84rLPdfryoUI8tquBAHtae4yfXP3z6Hz92XdPaS4ZAFDjTLt
Jsl45KYixNb7y9dtbVoNxNxdDC4ywaoklqkpBGY0I9GEpNzaBll/4DIJvGcgo3ow
eDAdBgNVHQ4EFgQUvyvu/TnJyRS7OGdujTbWM/W07yMwHwYDVR0jBBgwFoAUvyvu
/TnJyRS7OGdujTbWM/W07yMwDwYDVR0TAQH/BAUwAwEB/zAQBgNVHREECTAHggV0
ZXN0MzATBgNVHSUEDDAKBggrBgEFBQcDAjAKBggqhkjOPQQDAgNpADBmAjEAt7HK
knE2MzwZ2B2dgn1/q3ikWDiO20Hbd97jo3tmv87FcF2vMqqJpHjcldJqplfsAjEA
sfAz49y6Sf6LNlNS+Fc/lbOOwcrlzC+J5GJ8OmNoQPsvvDvhzGbwFiVw1M2uMqtG
-----END CERTIFICATE-----
12 changes: 12 additions & 0 deletions web/internal/authentication/x509/testdata/client_selfsigned.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
-----BEGIN CERTIFICATE-----
MIIBxzCCAU2gAwIBAgIUGCNnsX0qd0HD7UaQsx67ze0UaNowCgYIKoZIzj0EAwIw
DzENMAsGA1UEAwwEdGVzdDAgFw0yMTA4MjAxNDQ5MTRaGA8yMTIxMDcyNzE0NDkx
NFowDzENMAsGA1UEAwwEdGVzdDB2MBAGByqGSM49AgEGBSuBBAAiA2IABLFRLjQB
XViHUAEIsKglwb0HxPC/+CDa1TTOp1b0WErYW7Xcx5mRNEksVWAXOWYKPej10hfy
JSJE/2NiRAbrAcPjiRv01DgDt+OzwM4A0ZYqBj/3qWJKH/Kc8oKhY41bzKNoMGYw
HQYDVR0OBBYEFPRbKtRBgw+AZ0b6T8oWw/+QoyjaMB8GA1UdIwQYMBaAFPRbKtRB
gw+AZ0b6T8oWw/+QoyjaMA8GA1UdEwEB/wQFMAMBAf8wEwYDVR0lBAwwCgYIKwYB
BQUHAwIwCgYIKoZIzj0EAwIDaAAwZQIwZqwXMJiTycZdmLN+Pwk/8Sb7wQazbocb
16Zw5mZXqFJ4K+74OQMZ33i82hYohtE/AjEAn0a8q8QupgiXpr0I/PvGTRKqLQRM
0mptBvpn/DcB2p3Hi80GJhtchz9Z0OqbMX4S
-----END CERTIFICATE-----
115 changes: 115 additions & 0 deletions web/internal/authentication/x509/x509.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
// Copyright 2023 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package x509

import (
"crypto/tls"
"crypto/x509"
"errors"
"net/http"

"github.com/prometheus/exporter-toolkit/web/internal/authentication"
)

const (
denyReasonCertificateRequired = "certificate required"
denyReasonBadCertificate = "bad certificate"
denyReasonUnknownCA = "unknown certificate authority"
denyReasonCertificateExpired = "expired certificate"
)

// X509Authenticator allows for client certificate verification at HTTP level for X.509 certificates.
// The purpose behind it is to delegate or extend the TLS certificate verification beyond the standard TLS handshake.
type X509Authenticator struct {
// requireClientCerts specifies whether client certificates are required.
// This vaguely corresponds to crypto/tls ClientAuthType: https://pkg.go.dev/crypto/tls#ClientAuthType.
// If true, it is equivalent to RequireAnyClientCert or RequireAndVerifyClientCert.
requireClientCerts bool

// verifyOptions returns VerifyOptions used to obtain parameters for Certificate.Verify.
// Optional: if not provided, the client cert is not verified and hence it does not have to be valid.
verifyOptions func() x509.VerifyOptions

// verifyPeerCertificate corresponds to `VerifyPeerCertificate` from crypto/tls Config: https://pkg.go.dev/crypto/tls#Config.
// It bears the same semantics.
// Optional: if not provided, it is not invoked on any of the peer certificates.
verifyPeerCertificate func([][]byte, [][]*x509.Certificate) error
}

// Authenticate performs client cert verification by mimicking the steps the server would normally take during the standard TLS handshake in crypto/tls.
// https://cs.opensource.google/go/go/+/refs/tags/go1.23.2:src/crypto/tls/handshake_server.go;l=874-950
func (x *X509Authenticator) Authenticate(r *http.Request) (bool, string, *authentication.HTTPChallenge, error) {
if r.TLS == nil {
return false, "", nil, errors.New("no tls connection state in request")
}

if len(r.TLS.PeerCertificates) == 0 && x.requireClientCerts {
if r.TLS.Version == tls.VersionTLS13 {
return false, denyReasonCertificateRequired, nil, nil
}

return false, denyReasonBadCertificate, nil, nil
}

var verifiedChains [][]*x509.Certificate
if len(r.TLS.PeerCertificates) > 0 && x.verifyOptions != nil {
opts := x.verifyOptions()
if opts.Intermediates == nil && len(r.TLS.PeerCertificates) > 1 {
opts.Intermediates = x509.NewCertPool()
for _, cert := range r.TLS.PeerCertificates[1:] {
opts.Intermediates.AddCert(cert)
}
}

chains, err := r.TLS.PeerCertificates[0].Verify(opts)
if err != nil {
if errors.As(err, &x509.UnknownAuthorityError{}) {
return false, denyReasonUnknownCA, nil, nil
}

var errCertificateInvalid x509.CertificateInvalidError
if errors.As(err, &errCertificateInvalid) && errCertificateInvalid.Reason == x509.Expired {
return false, denyReasonCertificateExpired, nil, nil
}

return false, denyReasonBadCertificate, nil, nil
}

verifiedChains = chains
}

if x.verifyPeerCertificate != nil {
rawCerts := make([][]byte, 0, len(r.TLS.PeerCertificates))
for _, c := range r.TLS.PeerCertificates {
rawCerts = append(rawCerts, c.Raw)
}

err := x.verifyPeerCertificate(rawCerts, verifiedChains)
if err != nil {
return false, denyReasonBadCertificate, nil, nil
}
}

return true, "", nil, nil
}

func NewX509Authenticator(requireClientCerts bool, verifyOptions func() x509.VerifyOptions, verifyPeerCertificate func([][]byte, [][]*x509.Certificate) error) authentication.Authenticator {
return &X509Authenticator{
requireClientCerts: requireClientCerts,
verifyOptions: verifyOptions,
verifyPeerCertificate: verifyPeerCertificate,
}
}

var _ authentication.Authenticator = &X509Authenticator{}
Loading

0 comments on commit d67c526

Please sign in to comment.