cert_options.user
Configures issuance of user certificates: who may approve one, how long they’re valid for, and which SSH certificate extensions to grant.
require_group
Section titled “require_group”string, default empty
Has been replaced by require, which expresses the same group check as require: {group: <name>} alongside claim conditions. Setting it is a startup error rather than a silent no-op, because a gate that silently stopped applying would widen issuance.
cert_options: user: require_group: ""require
Section titled “require”The condition an approver’s identity must satisfy to approve a user certificate at all. Unset means any authenticated user may approve, which is the behavior every deployment has had so far.
Worth setting even though approval is already bound to the requester at approval: the binding answers “is this your request”, this answers “are you allowed certificates at all”.
- group: membership in an OIDC group, exactly the behaviour group tiers had before conditions existed.- claim with at_least / at_most: numeric comparison against an extra claim (see authentication.fields.extra). Both keys together express a range; boundaries are inclusive.- claim with exactly: numeric equality, shorthand for at_least and at_most set to the same value.- claim with equals / one_of: scalar string equality, or membership of the scalar in a fixed set.- claim with contains: membership of a value in a list-valued claim.- all_of / any_of: conjunction or disjunction over a list of the forms above. One level of nesting only — a nested condition may not itself carry all_of or any_of.Exactly one of group, claim, all_of, or any_of must be set. A claim condition takes exactly one comparator family. Every claim name referenced must be declared under authentication.fields.extra, checked at startup.
An absent claim is never neutral: a missing or unparseable claim value fails the condition (the floor), loudly, and can never widen what an identity receives. A claim’s value is only as fresh as the subject’s last login — see authentication.fields.extra.
require.group
Section titled “require.group”string, default empty
Names an OIDC group the identity must be a member of.
require.claim
Section titled “require.claim”string, default empty
Names an extra claim (a key under authentication.fields.extra) the comparator keys below test. The server attaches no meaning to the claim itself; it only compares the value.
require.at_least
Section titled “require.at_least”number, default 0
Passes when the claim’s numeric value is >= this bound (inclusive). May be combined with at_most to express a range.
require.at_most
Section titled “require.at_most”number, default 0
Passes when the claim’s numeric value is <= this bound (inclusive). May be combined with at_least to express a range.
require.exactly
Section titled “require.exactly”number, default 0
Passes when the claim’s numeric value equals this value. It desugars to at_least and at_most of the same value, so there is no second comparison path. Right for an integer-valued score; a computed confidence of 39.9999 does not equal 40, so a non-integral literal here draws a startup warning.
require.equals
Section titled “require.equals”string, default empty
Passes when the claim’s scalar string value equals this string.
require.one_of
Section titled “require.one_of”list, default empty
Passes when the claim’s scalar string value appears in this set.
require.contains
Section titled “require.contains”string, default empty
Passes when this value appears in a list-valued claim. A scalar claim takes the absent path — the condition fails, loudly.
require.all_of
Section titled “require.all_of”list, default empty
Passes when every listed condition passes. Listed conditions may not themselves carry all_of or any_of; nesting stops at one level.
require.any_of
Section titled “require.any_of”list, default empty
Passes when at least one listed condition passes. Listed conditions may not themselves carry all_of or any_of; nesting stops at one level.
valid_duration
Section titled “valid_duration”duration, default 30s
The ceiling on how long an issued user certificate is valid. Any lifetime policy only ever narrows from here.
cert_options: user: valid_duration: 30sextensions
Section titled “extensions”list, default permit-pty, permit-user-rc
The SSH certificate extensions a user certificate may carry, e.g. permit-pty and permit-user-rc. A request is narrowed to the intersection of what it asked for and this list.
Every extensions list in cert_options takes only the permit-* extensions or a vendor extension named name@domain. A critical option (source-address, force-command, verify-required) or no-touch-required is a startup error.
cert_options: user: extensions: [permit-pty, permit-user-rc]key_id_template
Section titled “key_id_template”string, default empty
The fallback for the service and PAM templates when either is empty, since user certificates are the common case. See https://mnestor.github.io/ssoossh/concepts/ (key ID templating).
cert_options: user: key_id_template: ""lifetime_policy
Section titled “lifetime_policy”Configures tiered certificate duration based on OIDC group membership and source network narrowing — see https://mnestor.github.io/ssoossh/operations/certificate-policy/.
lifetime_policy.default_duration
Section titled “lifetime_policy.default_duration”duration, default 0
The duration applied when no tier matches. It is required whenever any part of the lifetime policy is configured: a zero value is a startup error rather than a zero-second certificate that fails at signing, several layers from the config line that caused it.
lifetime_policy.on_absent_claim
Section titled “lifetime_policy.on_absent_claim”string, default empty
States what a missing or unparseable claim resolves to during condition evaluation. The only accepted value is “floor” (the default): the condition fails and the identity falls through to the floor. It must never mean “skip this condition”, which is how a missing claim becomes the most generous outcome, so no other value exists; the key is here to make the posture explicit in config.
cert_options: user: lifetime_policy: on_absent_claim: ""lifetime_policy.default_extensions
Section titled “lifetime_policy.default_extensions”list, default empty
The SSH certificate extensions granted when no tier matches, or when the winning tier states no grant_extensions of its own. Applies only when tiers are configured: with tiers present, extension grants are opt-in and start from nothing — an empty or omitted list grants no extensions. Every entry must appear in the enclosing type’s extensions ceiling, checked at startup. With no tiers configured, the grant axis is inactive and the type’s extensions ceiling alone bounds a request.
lifetime_policy.default_enrollment_duration
Section titled “lifetime_policy.default_enrollment_duration”duration, default 0
The enrollment-code lifetime applied when no tier matches, clamped to cert_options.service.enrollment_duration. Service certificates only — a startup error on any other type. Zero falls back to the enrollment_duration ceiling.
lifetime_policy.tiers
Section titled “lifetime_policy.tiers”list, default empty
Evaluated in order; the FIRST tier whose when condition the approver’s identity satisfies wins, and the list means what it says — tier order is the administrator’s job. Numeric thresholds are nested by construction (everyone satisfying at_least 40 also satisfies at_least 30), so write them in descending order; ascending order silently lands every high-score identity in the shortest tier. An empty tiers list means DefaultDuration is always used.
lifetime_policy.tiers[].name
Section titled “lifetime_policy.tiers[].name”string, default empty
Labels the tier for the policy explanation recorded with each approval decision — the answer to “why one hour”.
lifetime_policy.tiers[].when
Section titled “lifetime_policy.tiers[].when”The condition an identity must satisfy to take this tier. It is required: a tier without one is a startup error. Group tiers from before the condition grammar move from group: <name> to when: {group: <name>}.
- group: membership in an OIDC group, exactly the behaviour group tiers had before conditions existed.- claim with at_least / at_most: numeric comparison against an extra claim (see authentication.fields.extra). Both keys together express a range; boundaries are inclusive.- claim with exactly: numeric equality, shorthand for at_least and at_most set to the same value.- claim with equals / one_of: scalar string equality, or membership of the scalar in a fixed set.- claim with contains: membership of a value in a list-valued claim.- all_of / any_of: conjunction or disjunction over a list of the forms above. One level of nesting only — a nested condition may not itself carry all_of or any_of.Exactly one of group, claim, all_of, or any_of must be set. A claim condition takes exactly one comparator family. Every claim name referenced must be declared under authentication.fields.extra, checked at startup.
An absent claim is never neutral: a missing or unparseable claim value fails the condition (the floor), loudly, and can never widen what an identity receives. A claim’s value is only as fresh as the subject’s last login — see authentication.fields.extra.
lifetime_policy.tiers[].when.group
Section titled “lifetime_policy.tiers[].when.group”string, default empty
Names an OIDC group the identity must be a member of.
lifetime_policy.tiers[].when.claim
Section titled “lifetime_policy.tiers[].when.claim”string, default empty
Names an extra claim (a key under authentication.fields.extra) the comparator keys below test. The server attaches no meaning to the claim itself; it only compares the value.
lifetime_policy.tiers[].when.at_least
Section titled “lifetime_policy.tiers[].when.at_least”number, default 0
Passes when the claim’s numeric value is >= this bound (inclusive). May be combined with at_most to express a range.
lifetime_policy.tiers[].when.at_most
Section titled “lifetime_policy.tiers[].when.at_most”number, default 0
Passes when the claim’s numeric value is <= this bound (inclusive). May be combined with at_least to express a range.
lifetime_policy.tiers[].when.exactly
Section titled “lifetime_policy.tiers[].when.exactly”number, default 0
Passes when the claim’s numeric value equals this value. It desugars to at_least and at_most of the same value, so there is no second comparison path. Right for an integer-valued score; a computed confidence of 39.9999 does not equal 40, so a non-integral literal here draws a startup warning.
lifetime_policy.tiers[].when.equals
Section titled “lifetime_policy.tiers[].when.equals”string, default empty
Passes when the claim’s scalar string value equals this string.
lifetime_policy.tiers[].when.one_of
Section titled “lifetime_policy.tiers[].when.one_of”list, default empty
Passes when the claim’s scalar string value appears in this set.
lifetime_policy.tiers[].when.contains
Section titled “lifetime_policy.tiers[].when.contains”string, default empty
Passes when this value appears in a list-valued claim. A scalar claim takes the absent path — the condition fails, loudly.
lifetime_policy.tiers[].when.all_of
Section titled “lifetime_policy.tiers[].when.all_of”list, default empty
Passes when every listed condition passes. Listed conditions may not themselves carry all_of or any_of; nesting stops at one level.
lifetime_policy.tiers[].when.any_of
Section titled “lifetime_policy.tiers[].when.any_of”list, default empty
Passes when at least one listed condition passes. Listed conditions may not themselves carry all_of or any_of; nesting stops at one level.
lifetime_policy.tiers[].max_duration
Section titled “lifetime_policy.tiers[].max_duration”duration, default 0
The longest lifetime certificates in this tier can receive, bounded by the enclosing type’s valid_duration ceiling.
lifetime_policy.tiers[].grant_extensions
Section titled “lifetime_policy.tiers[].grant_extensions”list, default empty
The SSH certificate extensions this tier grants. Every entry must appear in the enclosing type’s extensions ceiling — a grant outside it is a startup error rather than a silent trim. An empty or omitted list falls back to the policy’s default_extensions.
lifetime_policy.tiers[].max_enrollment_duration
Section titled “lifetime_policy.tiers[].max_enrollment_duration”duration, default 0
Tiers the enrollment code’s own lifetime, clamped to cert_options.service.enrollment_duration — the lever against a code outliving the conditions that authorized it, without re-evaluating anything at retrieve. Service certificates only — a startup error on any other type. Zero falls back to default_enrollment_duration.
lifetime_policy.source_policy
Section titled “lifetime_policy.source_policy”list, default empty
Restricts certificate lifetime based on the request’s source IP address. Longest prefix match wins; ties resolve to the stricter rule. Entries are intersected with the tier-determined duration, and the final effective lifetime is clamped to the ceiling set by the enclosing CertOptions*.ValidDuration.
See https://mnestor.github.io/ssoossh/operations/certificate-policy/ section “Which address” for why the server-observed source IP is used, and why RequestedOptions.SourceAddresses is never consulted.
lifetime_policy.source_policy[].cidr
Section titled “lifetime_policy.source_policy[].cidr”string, default empty
The IPv4 or IPv6 network this rule applies to, in CIDR notation (e.g., “10.0.0.0/8” or “2001:db8::/32”).
lifetime_policy.source_policy[].max_duration
Section titled “lifetime_policy.source_policy[].max_duration”duration, default 0
The longest lifetime certificates from this network can receive. The final effective duration is min(tier_duration, source_rule_max_duration, type_ceiling).
lifetime_policy.source_policy[].extensions
Section titled “lifetime_policy.source_policy[].extensions”list, default empty
Has been replaced by removed_extensions. The old key made an empty list and an omitted field mean opposite things at one length check; the subtractive key retires that. Setting it is a startup error rather than a silently skipped narrowing.
lifetime_policy.source_policy[].removed_extensions
Section titled “lifetime_policy.source_policy[].removed_extensions”list, default empty
SSH certificate extensions requests from this network never receive, subtracted after the tier grant. An empty or omitted list removes nothing — the two spellings agree. Subtractive on purpose: identity grants, network narrows — being on the office range is not a reason to receive a capability the tier withheld.
lifetime_policy.source_policy[].pin_source_address
Section titled “lifetime_policy.source_policy[].pin_source_address”bool, default false
when true, adds a critical “source-address” SSH option pinning the certificate to this network. Valid only for service certificates; ignored for user certificates (see https://mnestor.github.io/ssoossh/operations/certificate-policy/ “Not for user certificates”). The network must be narrow enough to actually restrict — a /0 or ::/0 with PinSourceAddress=true is a warning sign (the certificate can be used anywhere, pinning is meaningless). Narrowing is enforced by the intersectExtensions helper (source-address is not an extension).