Configuration variables
The authorization server reads its settings from environment variables. The table lists every variable that src/main/resources/application.yml reads, with its default and the Spring property it sets. It is generated from that file, and the description is the comment above the property there.
Most defaults suit local development, such as PBAC_OIDC_ISSUER at http://localhost:8080. Before an instance serves real traffic, follow Harden an instance. It lists the five values the server refuses to start without and the defaults to change.
Variables
| Variable | Default | Spring property | Description |
|---|---|---|---|
HIKARI_MAX_POOL_SIZE | 19 | spring.datasource.hikari.maximum-pool-size | 19 + the cluster bus's 1 dedicated LISTEN connection = 20 per pod. |
HIKARI_MIN_IDLE | 5 | spring.datasource.hikari.minimum-idle | — |
INSTANCE_NAME | local | instance.name | — |
LOKI_URL | http://localhost:3100 | loki.url | — |
OPA_URL | http://localhost:8181 | opa.url | Dev default; production must set opa.url |
PBAC_ADMIN_API_KEY | empty | pbac.admin.api-key | Admin key: /admin/** requires it in the X-Admin-API-Key header. /access/** does not use it; AuthzenBearerFilter there takes a Bearer token or Basic auth. Required: the AS refuses to start when blank, unless allow-insecure-dev-key is on (dev profile, tests), in which case /admin/** is open. |
PBAC_ADMIN_APP_NAME | PolicyArc Portal | pbac.branding.app-name | — |
PBAC_ADMIN_ENABLED | true | pbac.admin.enabled | When false, /admin/** returns 404 to a caller with a valid admin key; a caller without one is refused first. Use PBAC_ADMIN_ENABLED=false in production. |
PBAC_ADMIN_LOGO_URI | empty | pbac.branding.logo-uri | — |
PBAC_ADMIN_PEPPER | empty | pbac.admin.pepper | Server-wide secret added to plaintext before hashing into lookup_hash. Required: the AS always refuses a known dev pepper, and refuses a blank one unless allow-insecure-dev-key is on, which then uses a public fallback. |
PBAC_ALLOW_INSECURE_DEV_KEY | false | pbac.allow-insecure-dev-key | Explicit opt-in for the public-source-tree dev fallback key. Required for local dev (./pbac create) and CI when PBAC_CREDENTIAL_KEY is unset. Production defaults to false — boot fails closed when no credential key is provided. |
PBAC_BUNDLE_API_KEY | dev-bundle-key | pbac.bundle.api-key | /bundles/** require it in the X-Bundle-API-Key header (OPA bundle fetch). Required: the default dev-bundle-key, a blank value or another known dev key refuses the start unless allow-insecure-dev-key is on (dev profile, tests). |
PBAC_BUNDLE_CONFIRM_ACTIVATION | true | pbac.bundle.confirm-activation.enabled | The batch deploy (POST /admin/api/policy-bundle/deploy) is acknowledged only once OPA reports the built bundle's manifest revision as ACTIVE; otherwise the pre-deploy snapshot is restored and the caller gets a 422. Nothing on the AS can compile Rego, so OPA's own activation is the only real compile check — without this, a broken rule returned 200 and took the instance down at the next OPA restart. The budget must exceed OPA's bundle poll interval (1-2s here). |
PBAC_BUNDLE_CONFIRM_POLL_MS | 500 | pbac.bundle.confirm-activation.poll-ms | The batch deploy (POST /admin/api/policy-bundle/deploy) is acknowledged only once OPA reports the built bundle's manifest revision as ACTIVE; otherwise the pre-deploy snapshot is restored and the caller gets a 422. Nothing on the AS can compile Rego, so OPA's own activation is the only real compile check — without this, a broken rule returned 200 and took the instance down at the next OPA restart. The budget must exceed OPA's bundle poll interval (1-2s here). |
PBAC_BUNDLE_CONFIRM_TIMEOUT_MS | 10000 | pbac.bundle.confirm-activation.timeout-ms | The batch deploy (POST /admin/api/policy-bundle/deploy) is acknowledged only once OPA reports the built bundle's manifest revision as ACTIVE; otherwise the pre-deploy snapshot is restored and the caller gets a 422. Nothing on the AS can compile Rego, so OPA's own activation is the only real compile check — without this, a broken rule returned 200 and took the instance down at the next OPA restart. The budget must exceed OPA's bundle poll interval (1-2s here). |
PBAC_CIMD_ALLOWED_INSECURE_HOSTS | localhost,127.0.0.1,[::1] | pbac.registration.cimd.allowed-insecure-hosts | Hosts allowed for insecure (http://) CIMD URLs when allow-insecure-localhost is true |
PBAC_CIMD_ALLOW_INSECURE_LOCALHOST | false | pbac.registration.cimd.allow-insecure-localhost | Allow http:// client_id URLs for listed hosts (dev only; disable in production) |
PBAC_CIMD_CACHE_TTL_SECONDS | 3600 | pbac.registration.cimd.cache-ttl-seconds | Cache TTL in seconds for resolved CIMD clients (1 hour default) |
PBAC_CIMD_FETCH_TIMEOUT_SECONDS | 10 | pbac.registration.cimd.fetch-timeout-seconds | HTTP fetch timeout in seconds |
PBAC_CIMD_MAX_DOCUMENT_SIZE_BYTES | 65536 | pbac.registration.cimd.max-document-size-bytes | Maximum size of fetched metadata document in bytes (64KB default) |
PBAC_CREDENTIAL_KEY | the value of PBAC_GATEWAY_CREDENTIAL_KEY, else empty | pbac.credential-key | Shared AES-GCM credential key for at-rest encryption of AS secrets. PBAC_CREDENTIAL_KEY is the canonical env var; PBAC_GATEWAY_CREDENTIAL_KEY is the legacy fallback. When neither is set, the AS refuses to start unless allow-insecure-dev-key is on, in which case it uses a public dev fallback key and logs a warning. |
PBAC_CREDENTIAL_KEY_DISCARD_PER_USER | false | pbac.credential-key-discard-per-user | Compromise-response only. When true, THIS BOOT DELETES every per-user encrypted store — upstream exchanged tokens, upstream identity tokens, user upstream secrets, this AS's own access and refresh tokens, and every browser session. The OIDC signing key is re-keyed, not deleted. Users re-authenticate upstream and sign in again; client_credentials clients recover on their own. The control plane sets this for exactly one restart during a credential-key rotation. It must not be left on: an ordinary restart would then log out the whole instance. |
PBAC_DOCS_URL | http://localhost:3010 | pbac.docs.url | Base URL of the PolicyArc documentation site. Used by admin portal "learn more" / Privacy / Terms links. Local dev default points at the docs container; deployed environments MUST set PBAC_DOCS_URL (the AS Helm chart wires it from authServer.docsUrl). |
PBAC_DPOP_ENABLED | true | pbac.dpop.enabled | — |
PBAC_DPOP_IAT_WINDOW_SECONDS | 60 | pbac.dpop.iat-window-seconds | — |
PBAC_DPOP_NONCE_ENABLED | false | pbac.dpop.nonce.enabled | — |
PBAC_DPOP_NONCE_TTL_SECONDS | 300 | pbac.dpop.nonce.ttl-seconds | — |
PBAC_DPOP_SIGNING_ALGS | ES256,RS256 | pbac.dpop.signing-algs | — |
PBAC_ENV | dev | management.opentelemetry.resource-attributes.deployment.environment | OpenTelemetry resource attributes. service.name is derived from spring.application.name automatically. |
PBAC_FEEDBACK_ENABLED | true | pbac.feedback.enabled | When true, AS admin portal includes a floating feedback widget that loads the widget JS and posts submissions back. Disable for on-prem deployments without a reachable feedback surface. |
PBAC_FEEDBACK_URL | http://localhost:8000 | pbac.feedback.url | Base URL of the feedback / submissions surface. The script tag loads ${url}/feedback-widget.js and the widget posts to ${url}/submissions. In dev this is the controlplane on :8000; in cluster, point it at the dashboard frontend's public BFF (e.g. https://dashboard.example.com/api/public). |
PBAC_GATEWAY_CONNECT_TIMEOUT | 5s | pbac.gateway.connect-timeout | TCP connect timeout for upstream dials. Kept short so a bad upstream URL fails fast with a 502 — see GatewayProperties for the rationale. |
PBAC_GATEWAY_CREDENTIAL_KEY | empty | pbac.gateway.credential-key | Legacy alias — CredentialEncryptor prefers pbac.credential-key (canonical) and falls back here. Kept for backward compatibility with existing deployments. |
PBAC_GATEWAY_ENABLED | true | pbac.gateway.enabled | When false, /gateway/** returns 404. Managed Resource Gateway feature flag. |
PBAC_GATEWAY_LINK_STATE_SECRET | empty | pbac.gateway.link-state-secret | HMAC secret for step-up link-state; set in prod |
PBAC_GATEWAY_MAX_BODY_SIZE | 10MB | pbac.gateway.max-body-size | Maximum request/response body size buffered by the proxy WebClient. |
PBAC_GATEWAY_RESPONSE_TIMEOUT | 30s | pbac.gateway.response-timeout | Read timeout once the upstream connection is established. Stays generous to accommodate slow legitimate APIs. |
PBAC_MAX_REDIRECT_URIS | 25 | pbac.registration.max-redirect-uris | Cap on redirect_uris per client (DCR and the admin client form). Not wired into the Helm chart values yet — set PBAC_MAX_REDIRECT_URIS directly if a deployment needs a different bound. |
PBAC_OIDC_ISSUER | http://localhost:8080 | pbac.oidc.issuer | Dev default; production must set PBAC_OIDC_ISSUER |
PBAC_OIDC_JWKS_CACHE_SECONDS | 300 | pbac.oidc.jwks-cache-seconds | max-age advertised by /.well-known/jwks.json. |
PBAC_OIDC_JWKS_PATH | empty | pbac.oidc.jwks-path | — |
PBAC_OIDC_KEY_RETIREMENT_SECONDS | 86400 | pbac.oidc.key-retirement-seconds | How long a retired signing key stays published in JWKS so tokens it signed keep verifying. Should exceed id-token-validity-seconds + jwks-cache-seconds; below that OidcKeyRotationGuard logs a WARN at boot. It does NOT refuse to start — a tuning mistake should not turn into an outage. |
PBAC_OIDC_STATEMENT_KEY_RETIREMENT_SECONDS | 34560000 | pbac.oidc.statement-key-retirement-seconds | How long a rotated-out SOFTWARE_STATEMENT key stays published. Statements default to a one-year lifetime, so this is sized against that rather than against an id_token: 400 days is a year plus a month of margin. |
PBAC_OTEL_ENABLED | false | management.tracing.enabled | OTLP export. Off by default. PBAC_OTEL_ENABLED=true is the single opt-in switch. /actuator/prometheus is unaffected either way. |
PBAC_OTEL_EXPORTER_OTLP_ENDPOINT | empty | management.otlp.tracing.endpoint | Empty by default, same as the logging/metrics endpoints below — when OtelExportEnvironmentPostProcessor derives management.otlp.tracing.export.enabled=false, OtlpTracingConfigurations.Exporters (@ConditionalOnEnabledTracing("otlp")) is filtered out of auto-configuration entirely, so OtlpHttpSpanExporterBuilder#setEndpoint never runs and never sees this value. (An earlier revision defaulted this to a syntactically- valid loopback placeholder as defense-in-depth; that made this property always non-empty, which broke the postprocessor's own "is traces configured" check — see its Javadoc.) |
PBAC_OTEL_EXPORTER_OTLP_LOGS_ENDPOINT | empty | management.otlp.logging.endpoint | e.g. http://collector:4318/v1/logs |
PBAC_OTEL_EXPORTER_OTLP_METRICS_URL | empty | management.otlp.metrics.export.url | e.g. http://collector:4318/v1/metrics |
PBAC_OTEL_TRACE_SAMPLE | 1.0 | management.tracing.sampling.probability | Always-on when enabled; enterprises sample at their own collector. |
PBAC_RATE_LIMIT_ENABLED | true | pbac.rate-limit.enabled | — |
PBAC_RATE_LIMIT_REQUESTS | 1000 | pbac.rate-limit.requests-per-window | — |
PBAC_RATE_LIMIT_WINDOW | 60 | pbac.rate-limit.window-seconds | — |
PBAC_ROTATION_ADMIN_PEPPER | empty | pbac.rotation.admin-pepper | Written by the control plane during a secret rotation and removed when it completes. DO NOT SET BY HAND — a value equal to its steady-state counterpart makes the rotation a silent no-op: every row still decrypts, the retirement assertion still passes, and the old value stays live. Boot refuses that case rather than proceeding. Comma-separated; empty is the steady state. |
PBAC_ROTATION_BUNDLE_API_KEY | empty | pbac.rotation.bundle-api-key | Written by the control plane during a secret rotation and removed when it completes. DO NOT SET BY HAND — a value equal to its steady-state counterpart makes the rotation a silent no-op: every row still decrypts, the retirement assertion still passes, and the old value stays live. Boot refuses that case rather than proceeding. Comma-separated; empty is the steady state. |
PBAC_ROTATION_CREDENTIAL_KEY | empty | pbac.rotation.credential-key | Written by the control plane during a secret rotation and removed when it completes. DO NOT SET BY HAND — a value equal to its steady-state counterpart makes the rotation a silent no-op: every row still decrypts, the retirement assertion still passes, and the old value stays live. Boot refuses that case rather than proceeding. Comma-separated; empty is the steady state. |
PBAC_ROTATION_LINK_STATE_SECRET | empty | pbac.rotation.link-state-secret | Written by the control plane during a secret rotation and removed when it completes. DO NOT SET BY HAND — a value equal to its steady-state counterpart makes the rotation a silent no-op: every row still decrypts, the retirement assertion still passes, and the old value stays live. Boot refuses that case rather than proceeding. Comma-separated; empty is the steady state. |
PBAC_SAFE_HTTP_ALLOWED_INSECURE_HOSTS | localhost,127.0.0.1,[::1],host.docker.internal | pbac.security.safe-http.allowed-insecure-hosts | — |
PBAC_SAFE_HTTP_ALLOW_INSECURE_LOCALHOST | false | pbac.security.safe-http.allow-insecure-localhost | Outbound HTTPS-only SSRF guard for IdP/JWKS/software-statement fetches. Production defaults to strict — only flip this on for dev/CI where mocks are reached over http://host.docker.internal etc. See SafeHttpProperties. |
PBAC_SESSION_CLEANUP_CRON | 0 */5 * * * * | spring.session.jdbc.cleanup-cron | Servlet HttpSession lives in Postgres, not Tomcat memory, so the authenticated subject survives a request landing on a different replica. Only /authorize and the two gateway link/connect endpoints touch a session — /token, /introspect and /gateway/** stay sessionless, so this is off the hot paths. |
PBAC_SESSION_COOKIE_SECURE | false | server.servlet.session.cookie.secure | Spring Session defaults Secure to request.isSecure(), which is false when TLS terminates at the ingress and the AS sees plain HTTP — so it has to be forced rather than inferred. Off by default so local HTTP and ./pbac instances keep working; the chart sets it true. |
PBAC_SESSION_TIMEOUT | 30m | spring.session.timeout | Servlet HttpSession lives in Postgres, not Tomcat memory, so the authenticated subject survives a request landing on a different replica. Only /authorize and the two gateway link/connect endpoints touch a session — /token, /introspect and /gateway/** stay sessionless, so this is off the hot paths. |
PBAC_SOFTWARE_STATEMENT_LIFETIME_SECONDS | 31536000 | pbac.software-statement.lifetime-seconds | — |
PBAC_SOFTWARE_STATEMENT_VERIFY | true | pbac.software-statement.verify | Verify the statement signature at /register. On by default: the trusted_issuers allow-list is keyed on iss, which is an unauthenticated claim until this is on. |
PBAC_SPIFFE_AUDIENCE | the value of PBAC_OIDC_ISSUER, else http://localhost:8080 | pbac.spiffe.audience | — |
PBAC_SPIFFE_TRUST_BUNDLE_REFRESH_SECONDS | 300 | pbac.spiffe.trust-bundle-refresh-seconds | SPIRE OIDC discovery URL is sourced from policy data (spiffe_trust_bundle_url in the pbac.operator.controls.trust row) — register a SPIRE endpoint via PATCH /admin/api/policy-data/by-key, not env vars. |
PBAC_SWAGGER_ENABLED | true | springdoc.api-docs.enabledspringdoc.swagger-ui.enabled | — |
PBAC_TRUSTED_PROXY_COUNT | 0 | pbac.trusted-proxy-count | When > 0, use X-Forwarded-For; take the Nth-from-right (the client before N trusted proxies) |
PBAC_VERSION | dev | management.opentelemetry.resource-attributes.service.version | OpenTelemetry resource attributes. service.name is derived from spring.application.name automatically. |
SCHEDULING_POOL_SIZE | 4 | spring.task.scheduling.pool.size | Boot's default is 1. That put the Spring Session cleanup, the expired-token cleanup, the MCP session sweep and the 20s MCP SSE keepalive on a single thread — so one slow bulk DELETE delays keepalives and open streams look dead to their clients. Two of those jobs are DB-bound and were added alongside the session store. |
SHUTDOWN_PHASE_TIMEOUT | 25s | spring.lifecycle.timeout-per-shutdown-phase | How long server.shutdown: graceful waits for in-flight requests before forcing the context closed. Must stay below the pod's terminationGracePeriodSeconds minus the preStop drain, or the kubelet SIGKILLs mid-drain and we are back to dropped requests. |
SPRING_DATASOURCE_PASSWORD | pbac-dev | spring.datasource.password | — |
SPRING_DATASOURCE_URL | jdbc:postgresql://localhost:5432/pbac?options=-c%20TimeZone%3DUTC | spring.datasource.url | — |
SPRING_DATASOURCE_USERNAME | pbac | spring.datasource.username | — |
Settings that are not in the table
A Spring property with no variable in application.yml can still be set from the environment. Spring maps the variable name to the property, so PBAC_OAUTH_TIMING_ID_TOKEN_VALIDITY_SECONDS sets pbac.oauth.timing.id-token-validity-seconds, the lifetime of an ID token, 3600 seconds by default. The Helm chart sets that variable from authServer.oidc.idTokenValiditySeconds.
Rate limiting
PBAC_RATE_LIMIT_ENABLED, PBAC_RATE_LIMIT_REQUESTS and PBAC_RATE_LIMIT_WINDOW control a limit per client address on /token, /authorize, /register, /introspect, /revoke, /bc-authorize, /gateway/mcp, /gateway/link and /gateway/connect. Other paths are not limited. A request over the limit gets HTTP 429.
{"error":"too_many_requests","error_description":"Rate limit exceeded. Try again later."}
We set PBAC_RATE_LIMIT_REQUESTS=3 and sent five token requests, and the fourth and fifth got this response.
The client address comes from the connection unless PBAC_TRUSTED_PROXY_COUNT is above 0. Then the server takes the address in X-Forwarded-For that sits that many entries from the right end, so with 1 it takes the last entry. Each replica counts on its own. See Run more than one replica.
Send feedback about this page