Documentation
¶
Overview ¶
Code generated by apic; DO NOT EDIT.
Code generated by apic; DO NOT EDIT.
Code generated by apic; DO NOT EDIT.
Code generated by apic; DO NOT EDIT.
Code generated by apic; DO NOT EDIT.
Code generated by apic; DO NOT EDIT.
Code generated by apic; DO NOT EDIT.
Package mtlsx implements the per-route mTLS verification pipeline used by apic-generated handlers: trust store loading, issuer-label enforcement, CRL/OCSP revocation checks, and principal extraction. Code generated by apic; DO NOT EDIT.
Code generated by apic; DO NOT EDIT.
Code generated by apic; DO NOT EDIT.
Index ¶
- Variables
- func ContextWith(ctx context.Context, p *Principal) context.Context
- func ExtractUPN(c *x509.Certificate) string
- func SetVerifiedClientChain(r *http.Request, chain []*x509.Certificate)
- func VerifyIssuer(c *x509.Certificate, allowed []string) error
- type CRLChecker
- type CRLConfig
- type IssuerLabelResolver
- type OCSPChecker
- type OCSPConfig
- type Principal
- type PrincipalAdapter
- type TrustStore
- type Verifier
- type VerifierConfig
Constants ¶
This section is empty.
Variables ¶
var ( // ErrCertRevoked is returned when a CRL lists the certificate as // revoked, or when an OCSP responder reports the certificate status // as Revoked. ErrCertRevoked = errors.New("mtlsx: certificate revoked") // ErrCRLFetch is returned when none of the CRL distribution points // could be successfully fetched and parsed (and AllowSoftFail is // false). ErrCRLFetch = errors.New("mtlsx: CRL fetch failed") )
var ErrEmptyBundle = errors.New("mtlsx: CA bundle contains no certificates")
ErrEmptyBundle is returned when the supplied CA file contains no PEM-encoded certificates. Returning this instead of an empty pool keeps the contract fail-closed: a typo in the bundle path or a truncated file aborts startup rather than silently accepting no peers.
var ErrIssuerNotAllowed = errors.New("mtlsx: certificate issuer not in allow-list")
ErrIssuerNotAllowed is returned by VerifyIssuer when the certificate's Issuer CommonName is not in the per-route allow-list.
var ErrUnknownIssuerLabel = fmt.Errorf("mtlsx: unknown issuer label")
ErrUnknownIssuerLabel is returned by IssuerLabelResolver.Resolve when a requested label has no mapping. Wrapped errors give the offending label in the message.
var ErrUnsafeEndpoint = errors.New("mtlsx: endpoint URL not permitted (SSRF guard)")
ErrUnsafeEndpoint is returned when an endpoint URL fails the SSRF allow-list (scheme not http/https, or host resolves to a loopback / private / link-local address).
Functions ¶
func ContextWith ¶
ContextWith returns a new context carrying p. A nil p is a no-op: the original context is returned unchanged so generated handlers can safely call this on the optional-mTLS path.
func ExtractUPN ¶
func ExtractUPN(c *x509.Certificate) string
ExtractUPN walks the SAN extension and pulls a UPN otherName if present. Exported so the cacpiv adapter (Plan 03) can reuse the same parser without import-cycling back into mtlsx.
func SetVerifiedClientChain ¶
func SetVerifiedClientChain(r *http.Request, chain []*x509.Certificate)
SetVerifiedClientChain attaches a verified client certificate chain to r so that mtlsx.Verifier can extract the leaf, issuer, and (for CRL/OCSP signature verification) any intermediates / root.
The chain is leaf-first: chain[0] is the client cert; chain[1] (if present) is the issuer used for CRL/OCSP checks; further entries are the rest of the path up to a root.
Both r.TLS.PeerCertificates and r.TLS.VerifiedChains are populated. The runtime code path reads the leaf from PeerCertificates[0] and the issuer from VerifiedChains[0][1]; without the second field set, CRL/OCSP signature checks would fail with "no issuer in verified chain", which is the friction this helper smooths.
Intended for use in *_test.go files. SetVerifiedClientChain does NOT perform any chain verification of its own — callers are responsible for supplying a chain that represents the state the test wishes to assert.
Closes the A-NEW-3 (VerifiedChains test model) friction noted in docs/GENERATOR_BUGS.md.
func VerifyIssuer ¶
func VerifyIssuer(c *x509.Certificate, allowed []string) error
VerifyIssuer returns nil if the cert's Issuer.CommonName matches one of the entries in allowed (case-sensitive exact match). An empty allowed slice means "no issuer restriction" — every verified issuer is accepted.
This closes the gap documented in pkg/securex/mtls.go:31 where SupportedIssuers was passed through but never enforced.
Types ¶
type CRLChecker ¶
type CRLChecker struct {
// contains filtered or unexported fields
}
CRLChecker validates leaf certificates against issuer-issued CRLs.
func NewCRLChecker ¶
func NewCRLChecker(cfg CRLConfig) (*CRLChecker, error)
NewCRLChecker validates the supplied config and returns a checker.
When CRLConfig.HTTPClient is nil, a default operator client is injected that refuses to follow HTTP redirects (so an internal CRL responder cannot proxy the fetch into AWS IMDS / on-host pprof / kube-internal services). Dial-time IP filtering is intentionally NOT applied to the operator client because legitimate operator deployments run an internal CRL responder on a private IP.
The checker additionally builds an internal "safe" client that adds a dial-time DNS-rebind guard on top of the redirect refusal. The safe client is used only when fromLeaf == true (leaf-supplied URLs), so the strict policy applies precisely where the URL is attacker-influenced (A-NEW-7).
func (*CRLChecker) Check ¶
func (c *CRLChecker) Check(ctx context.Context, leaf, issuer *x509.Certificate) error
Check verifies that leaf is not revoked by any of the configured CRLs. When CRLConfig.Endpoints is non-empty, that list is authoritative. When empty AND CRLConfig.TrustLeafEndpoints is true, the runtime falls back to leaf.CRLDistributionPoints; otherwise the call returns ErrCRLFetch (FedRAMP fail-closed posture, SC-7).
type CRLConfig ¶
type CRLConfig struct {
// Endpoints is the list of CRL distribution point URLs to consult.
// When non-empty, this list is authoritative — the runtime does NOT
// fall back to attacker-controlled URLs embedded in the leaf cert
// unless TrustLeafEndpoints is also set. (SSRF defense.)
Endpoints []string
// TrustLeafEndpoints, when true, allows fallback to
// leaf.CRLDistributionPoints when Endpoints is empty. Default false
// to fail closed against a hostile mTLS client supplying its own
// CRLDP URL (FedRAMP / NIST 800-53 SC-7).
TrustLeafEndpoints bool
// TTL bounds how long a fetched CRL is cached before re-fetch.
// Zero defaults to 15 minutes.
TTL time.Duration
// HTTPClient is the client used to fetch CRLs. nil → a default
// client with a 10-second timeout. CRL responses are capped at
// maxCRLBytes regardless of the client.
HTTPClient *http.Client
// AllowSoftFail returns nil instead of ErrCRLFetch when all
// endpoints are unreachable. FedRAMP profiles MUST set this false.
AllowSoftFail bool
}
CRLConfig configures a CRLChecker.
type IssuerLabelResolver ¶
type IssuerLabelResolver struct {
// contains filtered or unexported fields
}
IssuerLabelResolver maps human-readable logical labels (e.g. "dod-id-ca-59", "partner-prod", "internal-mesh") to the real issuer CommonName strings that VerifyIssuer matches against.
Background: VerifyIssuer matches Issuer.CommonName exactly (see issuers.go). When a config-driven deployment groups CAs by logical role rather than by CN, supported_issuers does not slot in directly. This resolver bridges the gap so a deployment can keep label-based taxonomy in its config and still hand mtlsx the CN strings the runtime requires.
One label may map to many CNs (e.g., a "dod-id" label may cover "DOD ID CA-59" plus "DOD ID SW CA-66"). Resolve flattens, de-dupes, and returns a sorted slice ready to drop into VerifierConfig.SupportedIssuers.
Closes the A-NEW-3 (issuer-DN vs logical labels) friction noted in docs/GENERATOR_BUGS.md.
The zero value is ready to use; concurrent Add and Resolve are NOT safe — callers building a resolver concurrently should guard with their own sync.Mutex. The typical pattern is build-once at boot and read-many at request time, which is concurrent-safe by construction.
func NewIssuerLabelResolver ¶
func NewIssuerLabelResolver() *IssuerLabelResolver
NewIssuerLabelResolver returns an empty resolver. Equivalent to a zero-value &IssuerLabelResolver{} — the constructor exists for readability at call sites.
func (*IssuerLabelResolver) Add ¶
func (r *IssuerLabelResolver) Add(label string, cns ...string)
Add maps the given label to one or more issuer CommonName strings. Calling Add multiple times for the same label accumulates entries (it does not replace). Label and CN values are stored verbatim; VerifyIssuer is case-sensitive so callers must use the exact CN from the issuing CA certificate.
Empty or whitespace-only labels and CNs are ignored.
func (*IssuerLabelResolver) Has ¶
func (r *IssuerLabelResolver) Has(label string) bool
Has reports whether the given label has at least one mapped CN.
func (*IssuerLabelResolver) Labels ¶
func (r *IssuerLabelResolver) Labels() []string
Labels returns the sorted list of every label registered with this resolver. Useful for tests, audits, and rendering operator-facing "known labels" diagnostics.
func (*IssuerLabelResolver) Resolve ¶
func (r *IssuerLabelResolver) Resolve(labels []string) ([]string, error)
Resolve takes a slice of logical labels and returns the flattened, de-duplicated, sorted list of issuer CommonName strings suitable for VerifierConfig.SupportedIssuers.
If any label has no mapping, Resolve returns ErrUnknownIssuerLabel with the offending label in the error message. Fail-closed is deliberate: a typo in the consumer config would otherwise silently produce an empty allow-list and accept every issuer.
An empty input slice returns (nil, nil) — equivalent to "no issuer restriction" semantics in VerifyIssuer.
type OCSPChecker ¶
type OCSPChecker struct {
// contains filtered or unexported fields
}
OCSPChecker queries OCSP responders for revocation status.
func NewOCSPChecker ¶
func NewOCSPChecker(cfg OCSPConfig) (*OCSPChecker, error)
NewOCSPChecker constructs and validates the checker.
When OCSPConfig.HTTPClient is nil, a default operator client is injected that refuses to follow HTTP redirects (so an internal OCSP responder cannot proxy the request into a private-IP target). Dial-time IP filtering is intentionally NOT applied to the operator client because legitimate operator deployments run an internal OCSP responder on a private IP.
The checker additionally builds an internal "safe" client that adds a dial-time DNS-rebind guard on top of the redirect refusal. The safe client is used only when fromLeaf == true (leaf-supplied OCSPServer URLs), so the strict policy applies precisely where the URL is attacker-influenced (A-NEW-7).
func (*OCSPChecker) Check ¶
func (c *OCSPChecker) Check(ctx context.Context, leaf, issuer *x509.Certificate) error
Check returns nil when the leaf is reported Good by the OCSP responder, ErrCertRevoked when Revoked, ErrCRLFetch when unreachable (unless AllowSoftFail is set).
type OCSPConfig ¶
type OCSPConfig struct {
Endpoints []string
// TrustLeafEndpoints, when true, allows fallback to leaf.OCSPServer
// URLs when Endpoints is empty. Default false to fail closed against
// a hostile mTLS client supplying its own OCSP responder (SC-7).
TrustLeafEndpoints bool
TTL time.Duration
HTTPClient *http.Client
AllowSoftFail bool
}
OCSPConfig configures the OCSPChecker.
type Principal ¶
type Principal struct {
CommonName string
IssuerCN string
SANEmails []string
SANDNS []string
SANURIs []string
UPN string // Microsoft UPN OID 1.3.6.1.4.1.311.20.2.3
EDIPI string // populated by cacpiv adapter (Plan 03)
Classification string // populated by cacpiv adapter (Plan 03)
FASCN []byte // populated by cacpiv adapter (Plan 03)
AgencyCode string // populated by cacpiv adapter (Plan 03)
OrgCategory int // populated by cacpiv adapter (Plan 03)
Fingerprint string // sha256-hex of leaf DER
Raw *x509.Certificate
}
Principal is the identity extracted from a verified client certificate. Plan 03 (CAC/PIV) populates EDIPI/Classification/FASCN via the same struct; those fields stay zero when the cacpiv adapter is not wired.
func From ¶
From retrieves a Principal previously stored via ContextWith. The second return value distinguishes "no Principal in context" from "explicit nil-valued key" — callers that care about the difference should consult `ok`.
func PrincipalFromCertificate ¶
func PrincipalFromCertificate(c *x509.Certificate) *Principal
PrincipalFromCertificate builds a Principal from a verified leaf cert. It never fails: missing fields stay zero. Callers MUST call this AFTER chain verification has succeeded; the function does not validate the chain or revoke status.
type PrincipalAdapter ¶
PrincipalAdapter post-processes a Principal after core fields are extracted. Plan 03 (cacpiv) registers an adapter that adds EDIPI, FASC-N, and PE/NPE classification.
type TrustStore ¶
type TrustStore struct {
// contains filtered or unexported fields
}
TrustStore loads a PEM CA bundle into an *x509.CertPool and supports atomic reload without server restart. Used by the per-route VerifierConfig.CABundlePath and (via api.WithMTLS in Plan 02 Task 12) by the TLS listener itself.
func NewTrustStore ¶
func NewTrustStore(path string) (*TrustStore, error)
NewTrustStore loads the bundle at path. Returns ErrEmptyBundle if no certificates are parsed (rather than silently producing an empty pool that would later accept zero peers).
func (*TrustStore) CertPool ¶
func (ts *TrustStore) CertPool() *x509.CertPool
CertPool returns the currently active pool. The returned pointer is stable for the caller's scope; subsequent Reload calls swap in a new pool and do NOT mutate the one previously returned.
func (*TrustStore) Path ¶
func (ts *TrustStore) Path() string
Path returns the bundle's source file (useful for logging).
func (*TrustStore) Reload ¶
func (ts *TrustStore) Reload() error
Reload re-reads the underlying file and atomically swaps the pool. Safe for concurrent callers; serialized internally.
type Verifier ¶
type Verifier struct {
// contains filtered or unexported fields
}
Verifier composes trust-store + securex.VerifyMTLS + issuer/CRL/OCSP + principal extraction into a single fail-closed pipeline.
func NewVerifier ¶
func NewVerifier(cfg VerifierConfig) (*Verifier, error)
NewVerifier validates cfg, loads the trust store, and constructs optional revocation checkers.
func (*Verifier) TrustPool ¶
TrustPool returns the configured trust store pool, or nil if none was configured. The listener uses this to set tls.Config.ClientCAs.
func (*Verifier) VerifyAndExtractPrincipal ¶
func (v *Verifier) VerifyAndExtractPrincipal(ctx context.Context, r *http.Request) (*Principal, error)
VerifyAndExtractPrincipal runs the full pipeline on r and returns the extracted Principal (or an error). Steps, in order:
- securex.VerifyMTLS — TLS handshake state + EKU
- VerifyIssuer — SupportedIssuers allow-list
- CRL.Check — if CRLConfig set
- OCSP.Check — if OCSPConfig set
- PrincipalFromCertificate (+ optional PrincipalAdapter)
Fail-closed: any non-nil error from any step aborts the chain.
type VerifierConfig ¶
type VerifierConfig struct {
CABundlePath string
SupportedIssuers []string
EKUValidation bool
Required bool // mirror of securex.MTLSPolicy.Required
CRLConfig *CRLConfig
OCSPConfig *OCSPConfig
// PrincipalAdapter optionally enriches the extracted Principal. Plan 03
// (cacpiv) provides cacpiv.New(...).Apply as the canonical adapter
// for DOD CAC / federal PIV deployments.
PrincipalAdapter PrincipalAdapter
}
VerifierConfig captures one verifier's settings (typically one per-route configuration in the apic config file).