Skip to content

Commit d67c526

Browse files
committed
Add path exclusion support to mTLS authentication
Signed-off-by: Kacper Rzetelski <[email protected]>
1 parent 6ea5d3c commit d67c526

8 files changed

+676
-34
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIIB3DCCAWGgAwIBAgIUJVN8KehL1MmccvLb/mHthSMfnnswCgYIKoZIzj0EAwIw
3+
EDEOMAwGA1UEAwwFdGVzdDMwIBcNMjMwMTEwMTgxMTAwWhgPMjEyMjEyMTcxODEx
4+
MDBaMBAxDjAMBgNVBAMMBXRlc3QzMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEf8wC
5+
qU9e4lPZZqJMA4nJ84rLPdfryoUI8tquBAHtae4yfXP3z6Hz92XdPaS4ZAFDjTLt
6+
Jsl45KYixNb7y9dtbVoNxNxdDC4ywaoklqkpBGY0I9GEpNzaBll/4DIJvGcgo3ow
7+
eDAdBgNVHQ4EFgQUvyvu/TnJyRS7OGdujTbWM/W07yMwHwYDVR0jBBgwFoAUvyvu
8+
/TnJyRS7OGdujTbWM/W07yMwDwYDVR0TAQH/BAUwAwEB/zAQBgNVHREECTAHggV0
9+
ZXN0MzATBgNVHSUEDDAKBggrBgEFBQcDAjAKBggqhkjOPQQDAgNpADBmAjEAt7HK
10+
knE2MzwZ2B2dgn1/q3ikWDiO20Hbd97jo3tmv87FcF2vMqqJpHjcldJqplfsAjEA
11+
sfAz49y6Sf6LNlNS+Fc/lbOOwcrlzC+J5GJ8OmNoQPsvvDvhzGbwFiVw1M2uMqtG
12+
-----END CERTIFICATE-----
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIIBxzCCAU2gAwIBAgIUGCNnsX0qd0HD7UaQsx67ze0UaNowCgYIKoZIzj0EAwIw
3+
DzENMAsGA1UEAwwEdGVzdDAgFw0yMTA4MjAxNDQ5MTRaGA8yMTIxMDcyNzE0NDkx
4+
NFowDzENMAsGA1UEAwwEdGVzdDB2MBAGByqGSM49AgEGBSuBBAAiA2IABLFRLjQB
5+
XViHUAEIsKglwb0HxPC/+CDa1TTOp1b0WErYW7Xcx5mRNEksVWAXOWYKPej10hfy
6+
JSJE/2NiRAbrAcPjiRv01DgDt+OzwM4A0ZYqBj/3qWJKH/Kc8oKhY41bzKNoMGYw
7+
HQYDVR0OBBYEFPRbKtRBgw+AZ0b6T8oWw/+QoyjaMB8GA1UdIwQYMBaAFPRbKtRB
8+
gw+AZ0b6T8oWw/+QoyjaMA8GA1UdEwEB/wQFMAMBAf8wEwYDVR0lBAwwCgYIKwYB
9+
BQUHAwIwCgYIKoZIzj0EAwIDaAAwZQIwZqwXMJiTycZdmLN+Pwk/8Sb7wQazbocb
10+
16Zw5mZXqFJ4K+74OQMZ33i82hYohtE/AjEAn0a8q8QupgiXpr0I/PvGTRKqLQRM
11+
0mptBvpn/DcB2p3Hi80GJhtchz9Z0OqbMX4S
12+
-----END CERTIFICATE-----

Diff for: web/internal/authentication/x509/x509.go

+115
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
// Copyright 2023 The Prometheus Authors
2+
// Licensed under the Apache License, Version 2.0 (the "License");
3+
// you may not use this file except in compliance with the License.
4+
// You may obtain a copy of the License at
5+
//
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
14+
package x509
15+
16+
import (
17+
"crypto/tls"
18+
"crypto/x509"
19+
"errors"
20+
"net/http"
21+
22+
"github.com/prometheus/exporter-toolkit/web/internal/authentication"
23+
)
24+
25+
const (
26+
denyReasonCertificateRequired = "certificate required"
27+
denyReasonBadCertificate = "bad certificate"
28+
denyReasonUnknownCA = "unknown certificate authority"
29+
denyReasonCertificateExpired = "expired certificate"
30+
)
31+
32+
// X509Authenticator allows for client certificate verification at HTTP level for X.509 certificates.
33+
// The purpose behind it is to delegate or extend the TLS certificate verification beyond the standard TLS handshake.
34+
type X509Authenticator struct {
35+
// requireClientCerts specifies whether client certificates are required.
36+
// This vaguely corresponds to crypto/tls ClientAuthType: https://pkg.go.dev/crypto/tls#ClientAuthType.
37+
// If true, it is equivalent to RequireAnyClientCert or RequireAndVerifyClientCert.
38+
requireClientCerts bool
39+
40+
// verifyOptions returns VerifyOptions used to obtain parameters for Certificate.Verify.
41+
// Optional: if not provided, the client cert is not verified and hence it does not have to be valid.
42+
verifyOptions func() x509.VerifyOptions
43+
44+
// verifyPeerCertificate corresponds to `VerifyPeerCertificate` from crypto/tls Config: https://pkg.go.dev/crypto/tls#Config.
45+
// It bears the same semantics.
46+
// Optional: if not provided, it is not invoked on any of the peer certificates.
47+
verifyPeerCertificate func([][]byte, [][]*x509.Certificate) error
48+
}
49+
50+
// Authenticate performs client cert verification by mimicking the steps the server would normally take during the standard TLS handshake in crypto/tls.
51+
// https://cs.opensource.google/go/go/+/refs/tags/go1.23.2:src/crypto/tls/handshake_server.go;l=874-950
52+
func (x *X509Authenticator) Authenticate(r *http.Request) (bool, string, *authentication.HTTPChallenge, error) {
53+
if r.TLS == nil {
54+
return false, "", nil, errors.New("no tls connection state in request")
55+
}
56+
57+
if len(r.TLS.PeerCertificates) == 0 && x.requireClientCerts {
58+
if r.TLS.Version == tls.VersionTLS13 {
59+
return false, denyReasonCertificateRequired, nil, nil
60+
}
61+
62+
return false, denyReasonBadCertificate, nil, nil
63+
}
64+
65+
var verifiedChains [][]*x509.Certificate
66+
if len(r.TLS.PeerCertificates) > 0 && x.verifyOptions != nil {
67+
opts := x.verifyOptions()
68+
if opts.Intermediates == nil && len(r.TLS.PeerCertificates) > 1 {
69+
opts.Intermediates = x509.NewCertPool()
70+
for _, cert := range r.TLS.PeerCertificates[1:] {
71+
opts.Intermediates.AddCert(cert)
72+
}
73+
}
74+
75+
chains, err := r.TLS.PeerCertificates[0].Verify(opts)
76+
if err != nil {
77+
if errors.As(err, &x509.UnknownAuthorityError{}) {
78+
return false, denyReasonUnknownCA, nil, nil
79+
}
80+
81+
var errCertificateInvalid x509.CertificateInvalidError
82+
if errors.As(err, &errCertificateInvalid) && errCertificateInvalid.Reason == x509.Expired {
83+
return false, denyReasonCertificateExpired, nil, nil
84+
}
85+
86+
return false, denyReasonBadCertificate, nil, nil
87+
}
88+
89+
verifiedChains = chains
90+
}
91+
92+
if x.verifyPeerCertificate != nil {
93+
rawCerts := make([][]byte, 0, len(r.TLS.PeerCertificates))
94+
for _, c := range r.TLS.PeerCertificates {
95+
rawCerts = append(rawCerts, c.Raw)
96+
}
97+
98+
err := x.verifyPeerCertificate(rawCerts, verifiedChains)
99+
if err != nil {
100+
return false, denyReasonBadCertificate, nil, nil
101+
}
102+
}
103+
104+
return true, "", nil, nil
105+
}
106+
107+
func NewX509Authenticator(requireClientCerts bool, verifyOptions func() x509.VerifyOptions, verifyPeerCertificate func([][]byte, [][]*x509.Certificate) error) authentication.Authenticator {
108+
return &X509Authenticator{
109+
requireClientCerts: requireClientCerts,
110+
verifyOptions: verifyOptions,
111+
verifyPeerCertificate: verifyPeerCertificate,
112+
}
113+
}
114+
115+
var _ authentication.Authenticator = &X509Authenticator{}

0 commit comments

Comments
 (0)