feat: add configurable mTLS identity extraction with fallback chain (#3640)

Add support for configurable identity attributes in mTLS authentication,
allowing identity extraction from CommonName, Subject DN, Email SAN,
URI SAN, or DNSName SAN with fallback chain support. Includes regex
pattern matching for URI SANs (e.g., SPIFFE workload IDs).

- Add MTLSConfig with identity attributes, URISANPattern, and index fields
- Implement extractMTLSIdentity with fallback chain logic
- Move the mtls tests in the api package to pkg/api/mtls_test.go

Signed-off-by: Andrei Aaron <andreifdaaron@gmail.com>
This commit is contained in:
Andrei Aaron
2025-12-18 19:10:47 +02:00
committed by GitHub
parent f2064c9af0
commit 79439bbf63
14 changed files with 2788 additions and 1435 deletions
+26
View File
@@ -12,6 +12,7 @@ import (
"errors"
"fmt"
"net"
"net/url"
"os"
"time"
)
@@ -67,6 +68,10 @@ type CertificateOptions struct {
// If nil, no email addresses will be included.
EmailAddresses []string
// URIs contains the URIs for the Subject Alternative Name extension.
// If nil, no URIs will be included.
URIs []string
// Hostname is the hostname or IP address for server certificates.
// For server certificates, this is required and will be added to DNSNames or IPAddresses
// based on whether it's a valid IP address or a DNS name.
@@ -404,6 +409,27 @@ func applyOptions(template *x509.Certificate, opts *CertificateOptions, certType
template.EmailAddresses = opts.EmailAddresses
}
// Apply URIs
if opts.URIs != nil {
templateURIs := make([]*url.URL, 0, len(opts.URIs))
for _, uriStr := range opts.URIs {
uri, err := url.Parse(uriStr)
if err != nil {
// Skip invalid URIs - could log error in production
continue
}
// Validate that the URI has a valid scheme (url.Parse accepts URIs without schemes)
if uri.Scheme == "" {
// Skip URIs without a scheme - could log error in production
continue
}
templateURIs = append(templateURIs, uri)
}
template.URIs = templateURIs
}
// Apply CommonName - if provided, override the default; otherwise keep default from initializeTemplate
if opts.CommonName != "" {
template.Subject.CommonName = opts.CommonName
+24
View File
@@ -240,6 +240,30 @@ func TestApplyOptionsCoverage(t *testing.T) {
So(cert.EmailAddresses, ShouldContain, "admin@example.com")
})
Convey("Test with URIs including invalid URI", func() {
// Mix of valid and invalid URIs - invalid ones should be skipped
customURIs := []string{
"spiffe://example.org/workload/test",
"not a valid uri", // Invalid URI - should be skipped
"https://example.com",
"://invalid", // Invalid URI - should be skipped
}
opts := &tls.CertificateOptions{
Hostname: "localhost",
URIs: customURIs,
}
certPEM, _, err := tls.GenerateServerCert(caCertPEM, caKeyPEM, opts)
So(err, ShouldBeNil)
certBlock, _ := pem.Decode(certPEM)
cert, err := x509.ParseCertificate(certBlock.Bytes)
So(err, ShouldBeNil)
// Should only contain valid URIs (2 out of 4)
So(len(cert.URIs), ShouldEqual, 2)
So(cert.URIs[0].String(), ShouldEqual, "spiffe://example.org/workload/test")
So(cert.URIs[1].String(), ShouldEqual, "https://example.com")
})
Convey("Test with all options combined", func() {
customNotBefore := time.Now().Add(-12 * time.Hour)
customNotAfter := time.Now().Add(365 * 24 * time.Hour)