AuthPolicy
Two CRDs manage policy — ClusterAuthPolicy for cluster-wide defaults and AuthPolicy for namespace-scoped overrides. This two-CRD model leverages Kubernetes RBAC so that security teams control cluster-wide defaults while application teams can override specific settings for OidcClients within their own namespaces.
Example
ClusterAuthPolicy (Cluster-Wide Default)
apiVersion: auth.nauthera.io/v1alpha1
kind: ClusterAuthPolicy
metadata:
name: production-policy
spec:
allowedScopes:
- openid
- profile
- email
- "api:read"
- "api:write"
tokenSettings:
accessTokenTTL: 15m
refreshTokenTTL: 8h
idTokenTTL: 15m
rotateRefreshTokens: true
claimMappings:
- claim: email
fromUserAttribute: email
- claim: name
fromUserAttribute: name
- claim: groups
fromUserAttribute: groups
- claim: "https://myapp.example.com/roles"
fromUserAttribute: appRoles
conditions:
requireMfa: false
allowedNetworkCidrs:
- "10.0.0.0/8"
- "172.16.0.0/12"AuthPolicy (Namespaced Override)
A namespaced AuthPolicy overrides specific fields from the cluster-wide defaults for all OidcClients in the same namespace:
apiVersion: auth.nauthera.io/v1alpha1
kind: AuthPolicy
metadata:
name: long-lived-sessions
namespace: internal-tools
spec:
tokenSettings:
accessTokenTTL: 1h
refreshTokenTTL: 24h
idTokenTTL: 1h
conditions:
requireMfa: trueIn this example, the ClusterAuthPolicy sets a 15-minute access token TTL, but the AuthPolicy in the internal-tools namespace gives OidcClients there 1-hour tokens with mandatory MFA instead.
Spec Reference
Both ClusterAuthPolicy and AuthPolicy share the same spec fields. The scope is determined by the CRD kind — ClusterAuthPolicy is cluster-scoped and AuthPolicy is namespaced.
spec.allowedScopes
Type: []string | Required
The list of OAuth2 scopes this policy permits. An OidcClient may only request scopes that appear in the allowedScopes list of an active policy. Requests for unlisted scopes are silently dropped (not rejected, to maintain RFC 6749 compliance).
spec:
allowedScopes:
- openid
- profile
- email
- "api:read"
- "api:write"The openid scope is always required for OIDC flows and is implicitly included even if omitted.
spec.tokenSettings
Type: object | Optional
Controls the lifetime and rotation behaviour of issued tokens.
spec:
tokenSettings:
accessTokenTTL: 15m
refreshTokenTTL: 8h
idTokenTTL: 15m
rotateRefreshTokens: true| Field | Default | Description |
|---|---|---|
accessTokenTTL | 1h | Lifetime of access tokens. Accepts duration strings (e.g., 15m, 1h, 24h) |
refreshTokenTTL | 24h | Lifetime of refresh tokens |
idTokenTTL | 1h | Lifetime of ID tokens |
rotateRefreshTokens | false | If true, each use of a refresh token issues a new refresh token and invalidates the old one |
spec.claimMappings
Type: []object | Optional
Defines how the user's stored attributes are mapped into JWT claims. Mappings apply to both access tokens and ID tokens unless tokenType is specified.
spec:
claimMappings:
- claim: email
fromUserAttribute: email
- claim: name
fromUserAttribute: name
- claim: groups
fromUserAttribute: groups
- claim: "https://myapp.example.com/roles"
fromUserAttribute: appRoles
tokenType: id_tokenClaim Mapping Fields
| Field | Required | Description |
|---|---|---|
claim | Yes | The JWT claim name. Use a URL-namespaced name for custom claims |
fromUserAttribute | Yes | The attribute name as stored in the user record |
tokenType | No | Apply only to access_token or id_token. Defaults to both |
transform | No | Optional transformation: lowercase, uppercase, join |
spec.conditions
Type: object | Optional
Additional conditions that must be satisfied for authentication to succeed.
spec:
conditions:
requireMfa: true
allowedNetworkCidrs:
- "10.0.0.0/8"
deniedNetworkCidrs:
- "192.168.99.0/24"| Field | Default | Description |
|---|---|---|
requireMfa | false | If true, authentication requires a second factor (TOTP). See note below |
allowedNetworkCidrs | [] (allow all) | If non-empty, only allow authentication requests from these CIDR ranges |
deniedNetworkCidrs | [] (deny none) | Explicitly deny authentication requests from these CIDR ranges |
MFA Support (v1): When
requireMfa: trueis set, users must enroll a TOTP authenticator app (e.g., Google Authenticator, Authy) during their next login. Recovery codes are generated at enrollment time. WebAuthn/passkey support and additional MFA methods are planned for a future release.
Note: The source IP is determined from the incoming request. In environments behind load balancers or ingress controllers, the operator must be configured to trust proxy headers (
X-Forwarded-For) via the Helm valueoperator.trustedProxies. Without this configuration, CIDR conditions will match the proxy's IP rather than the client's real IP.
spec.consentScreen
Type: object | Optional
Controls the OAuth2 consent screen behaviour.
spec:
consentScreen:
mode: auto
rememberConsentDays: 30mode Value | Description |
|---|---|
always | Always show the consent screen |
auto | Show only when the client requests new scopes |
never | Never show a consent screen (implicit approval) |
Policy Composition
Policy composition happens in two layers:
1. Cluster-Wide Merge
When multiple ClusterAuthPolicy resources exist, they are merged:
allowedScopes: Union of all policies.tokenSettings: Most restrictive value wins (shortest TTL,rotateRefreshTokens: truetakes precedence).claimMappings: Union. If two policies map the same claim, the policy with the lower alphabetical name wins.conditions: Most restrictive wins.
2. Namespaced Override
After the cluster-wide merge produces a baseline, any AuthPolicy in a given namespace overrides the merged cluster defaults for OidcClients in that namespace. The override is field-level — only the fields specified in the namespaced AuthPolicy replace the cluster defaults; unspecified fields inherit from the cluster baseline.
Example: If the cluster-wide merge results in accessTokenTTL: 15m and an AuthPolicy in internal-tools sets accessTokenTTL: 1h, OidcClients in internal-tools get 1-hour access tokens while all other namespaces continue to use 15 minutes.
Enforcement Floors
Namespaced AuthPolicy overrides are clamped by the cluster-wide baseline to prevent weakening security:
requireMfa: A namespaced policy cannot setrequireMfa: falseif anyClusterAuthPolicysets it totrue. MFA enforcement can only be tightened, never relaxed, at the namespace level.- Token TTLs: Namespaced overrides cannot exceed the cluster-wide maximum. If the merged
ClusterAuthPolicysetsaccessTokenTTL: 15m, a namespaceAuthPolicysettingaccessTokenTTL: 1hwill be clamped to15m. allowedScopes: Namespaced policies can only restrict the allowed scopes — they cannot grant scopes that are not in the cluster-wide set.
This ensures that cluster-wide security invariants set by the security team cannot be weakened by namespace-level overrides.
Status
status:
conditions:
- type: Active
status: "True"
reason: PolicyApplied
lastTransitionTime: "2024-01-01T00:00:00Z"Conditions
| Condition | Description |
|---|---|
Active | The policy is active and applied to the operator's token issuance logic |
Related Resources
- OidcClient — Clients governed by this policy.
- Architecture — How policies fit into the overall system.