x509 Guard
x509 Guard
Defines a guard with X.509 client certificate support.
The x509 guard takes a verified peer certificate chain as its credential and produces an authorized session with an identity, named attributes, and optionally roles. It is fed in-process by the binding that terminated the mutual TLS handshake, so the chain it inspects has already been validated against a trust store.
This makes what the certificate asserts — subject, subject alternative name, issuer — available to route conditions and to ${guarded['...'].attributes.*} expressions, alongside what the handshake merely negotiated.
guards:
my_x509_guard:
type: x509
options:
identity: subject.cn
attributes:
organization: subject.o
tenant: san.uri
roles:
partner:
- issuer.cn: "Partner Issuing CA"
internal:
- issuer.cn: "Internal CA"
subject.ou: "Platform"
- issuer.cn: "Internal CA"
san.dns: "*.internal.example.com"All three options are optional. A guard configured with no options at all still produces an authorized session identified by the canonical subject distinguished name.
Credential
The credential is the verified chain as PEM, leaf-first, concatenated — the leaf certificate first, followed by any intermediates. A root is not required; the root is the trust store's business.
The tls server binding supplies it, through an options.authorization block naming this guard:
bindings:
tls_server0:
type: tls
kind: server
vault: server
options:
keys:
- localhost
trust:
- partner-ca
mutual: required
authorization:
my_x509_guard:
credentials:
certificates: pem
routes:
- guarded:
my_x509_guard:
- partner
exit: mqtt_server0Certificates are public, so a chain on its own proves nothing — what proves the claim is possession of the private key, demonstrated during the handshake and not recoverable afterward. The guard's trust in the chain is therefore structural: it is fed by a component that observed the handshake. A chain forwarded in a header by a terminating proxy is byte-identical and carries entirely different trust, so it is deliberately not accepted here.
Certificate fields
identity, attributes, and roles all address the leaf certificate through one internal field vocabulary.
| Field | Value |
|---|---|
subject.dn | Subject distinguished name, in canonical form — lowercased, normalized whitespace |
subject.<type> | Subject relative distinguished name by attribute type, e.g. subject.cn, subject.o, subject.ou, subject.c |
issuer.dn | Issuer distinguished name, in canonical form |
issuer.<type> | Issuer relative distinguished name by attribute type, e.g. issuer.cn, issuer.o |
san.dns | Subject alternative names of type dNSName |
san.uri | Subject alternative names of type uniformResourceIdentifier |
san.email | Subject alternative names of type rfc822Name |
san.ip | Subject alternative names of type iPAddress |
x5t.s256 | SHA-256 over the DER encoding of the leaf certificate, base64url encoded without padding |
A field may carry more than one value — subject.ou legitimately appears more than once in a distinguished name, and a certificate commonly carries several san.dns entries. A field that the certificate does not carry is simply absent.
Distinguished name rendering
subject.dn and issuer.dn are rendered canonically — lowercased, with normalized whitespace and no spaces around separators. RFC 1779 and RFC 2253 forms render the same certificate differently, and identity values commonly feed topic names and other derived strings, so the rendering is pinned to keep downstream state stable.
Individual relative distinguished names such as subject.cn keep the case as written in the certificate.
Configuration (* required)
store
string
The name of the store used by this guard.
options
object
The x509 specific options.
options:
identity: subject.cn
attributes:
organization: subject.o
tenant: san.uri
roles:
internal:
- issuer.cn: "Internal CA"
subject.ou: "Platform"options.identity
string| Default:subject.dn
Certificate field to use as the session identity.
Prefer a subject alternative name over the common name where the certificates permit it; CN-as-identity is deprecated by RFC 6125, though still ubiquitous in private PKI for machine identity.
If the selected field carries more than one value, the first is used.
options.attributes
objectas map of namedstringproperties
Named attributes to extract from the certificate, mapping an external attribute name to an internal certificate field. Resolvable in configuration as ${guarded['my_x509_guard'].attributes.organization}.
The indirection is the point: attribute names describe meaning rather than origin, so attributes.organization can be sourced from subject.o here and from a token claim under a different guard, and expressions referencing it survive a change of identity source.
If the selected field carries more than one value, the first is used. If the certificate does not carry the field at all, the attribute resolves to no value.
options.roles
objectas map of namedarrayofobjectproperties
Roles to grant, mapped to the certificate fields that must match for each. Roles granted here are what routes[].guarded authorizes against.
This is an explicit, operator-declared mapping, visible in configuration — deliberately not the pattern of implicitly reading roles out of OU by convention, which would be invisible and non-portable.
roles:
partner:
- issuer.cn: "Partner Issuing CA"
internal:
- issuer.cn: "Internal CA"
subject.ou: "Platform"
- issuer.cn: "Internal CA"
san.dns: "*.internal.example.com"Matching follows the same and/or reading rule as routes[].when — array members are OR'd, properties within an object are AND'd. In the example above, a chain is granted internal if it was issued by Internal CA and either carries a Platform organizational unit or a subject alternative DNS name under internal.example.com.
Matching rules
- Values are glob patterns, matching house style —
*matches any sequence of characters and?matches any single character. Not regular expressions: a loosely anchored pattern over a distinguished name can match a field the author did not intend, and DN string rendering varies between issuers. - Patterns are anchored — the whole field value must match, not a substring.
- Matching is case-insensitive, following the comparison rules conventionally applied to distinguished names and DNS names.
- Multi-valued fields match existentially —
san.dnsagainst a certificate carrying three DNS names matches if any one of them does. - An absent field never matches, rather than erroring, so evaluation is total.
- Roles are a set union across all matching entries, not first-match.
Non-goals
- Chain path validation. The trust manager has already validated the chain during the handshake. The guard inspects an already-validated chain; it does not rebuild or revalidate a path.
- CA narrowing. Restricting which issuers are acceptable for a given surface belongs in the
tlsbinding, following the convention that options declare the superset and routes select from it. - Certificate revocation. CRL and OCSP are trust manager concerns.

