From 23bdf306113b36dd9216fd06f71806fc90cd4948 Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Thu, 23 Jul 2026 09:41:08 +0530 Subject: [PATCH 1/7] fix: repair crash-looping CLI flags, close values.yaml coverage gaps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --enable-totp-login/--enable-mfa/--enable-email-otp/--enable-sms-otp don't exist in the authorizer binary; cobra rejects unknown flags so every release from this chart crash-loops on boot. Renamed to the current --disable-totp-login/--disable-webauthn-mfa/--disable-email-otp/ --disable-sms-otp/--disable-mfa flags. Fixed COUCHBASE_RAM_QUOTA: the env var was defined as COUCHBASE_BUCKET_RAM_QUOTA while args read $COUCHBASE_RAM_QUOTA, so couchbase_bucket_ram_quota silently never reached the flag. Wired --url (CWE-640 host-header-injection mitigation): AUTHORIZER_URL was set as an env var but never passed as a flag, so authorizer_url in values.yaml was a no-op. Added --oauth2-1-strict and --enable-org-discovery. Exposed ~25 toggles in values.yaml that were previously hardcoded into the args template with no values.yaml key at all (enable_signup, enforce_mfa, app_cookie_secure, enable_basic_authentication, disable_admin_header_auth, and friends) — operators had no way to override them. Re-pinned appVersion to 2.4.0-rc.7: 2.3.0 predates the flag rename above, so it doesn't have the --disable-* flags either. Move to the stable 2.4.0 tag once it ships. --- Chart.yaml | 4 +-- templates/deployment.yaml | 69 ++++++++++++++++++++++++++++++++++++--- values.yaml | 53 ++++++++++++++++++++++++++++++ 3 files changed, 119 insertions(+), 7 deletions(-) diff --git a/Chart.yaml b/Chart.yaml index 6fb3f6f..d54fc37 100644 --- a/Chart.yaml +++ b/Chart.yaml @@ -15,12 +15,12 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 2.2.1 +version: 2.3.0 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -appVersion: "2.3.0" +appVersion: "2.4.0-rc.7" icon: "https://authorizer.dev/images/logo.png" diff --git a/templates/deployment.yaml b/templates/deployment.yaml index b4aba5b..22219bd 100644 --- a/templates/deployment.yaml +++ b/templates/deployment.yaml @@ -69,6 +69,7 @@ spec: --allowed-origins="${ALLOWED_ORIGINS}" \ --default-authorize-response-type="${DEFAULT_AUTHORIZE_RESPONSE_TYPE}" \ --default-authorize-response-mode="${DEFAULT_AUTHORIZE_RESPONSE_MODE}" \ + --oauth2-1-strict="${OAUTH2_1_STRICT:-false}" \ --organization-name="${ORGANIZATION_NAME}" \ --organization-logo="${ORGANIZATION_LOGO}" \ --smtp-host="${SMTP_HOST}" \ @@ -81,6 +82,7 @@ spec: --smtp-skip-tls-verification="${SMTP_SKIP_TLS_VERIFICATION:-false}" \ --reset-password-url="${RESET_PASSWORD_URL}" \ --backchannel-logout-uri="${BACKCHANNEL_LOGOUT_URI}" \ + --url="${AUTHORIZER_URL}" \ --env="${ENV}" \ --host="${HOST:-0.0.0.0}" \ --http-port="${PORT:-8080}" \ @@ -104,6 +106,7 @@ spec: --graphql-max-body-bytes="${GRAPHQL_MAX_BODY_BYTES:-1048576}" \ --log-level="${LOG_LEVEL:-info}" \ --enable-login-page="${ENABLE_LOGIN_PAGE:-true}" \ + --enable-org-discovery="${ENABLE_ORG_DISCOVERY:-false}" \ --enable-playground="${ENABLE_PLAYGROUND:-true}" \ --disable-admin-header-auth="${DISABLE_ADMIN_HEADER_AUTH:-true}" \ --enable-graphql-introspection="${ENABLE_GRAPHQL_INTROSPECTION:-true}" \ @@ -116,11 +119,12 @@ spec: --enable-mobile-basic-authentication="${ENABLE_MOBILE_BASIC_AUTHENTICATION:-true}" \ --enable-phone-verification="${ENABLE_PHONE_VERIFICATION:-false}" \ --enable-magic-link-login="${ENABLE_MAGIC_LINK_LOGIN:-false}" \ - --enable-totp-login="${ENABLE_TOTP_LOGIN:-false}" \ --enforce-mfa="${ENFORCE_MFA:-true}" \ - --enable-mfa="${ENABLE_MFA:-false}" \ - --enable-email-otp="${ENABLE_EMAIL_OTP:-false}" \ - --enable-sms-otp="${ENABLE_SMS_OTP:-false}" \ + --disable-totp-login="${DISABLE_TOTP_LOGIN:-false}" \ + --disable-webauthn-mfa="${DISABLE_WEBAUTHN_MFA:-false}" \ + --disable-email-otp="${DISABLE_EMAIL_OTP:-false}" \ + --disable-sms-otp="${DISABLE_SMS_OTP:-false}" \ + --disable-mfa="${DISABLE_MFA:-false}" \ --enable-signup="${ENABLE_SIGNUP:-true}" \ --twilio-account-sid="${TWILIO_ACCOUNT_SID}" \ --twilio-api-key="${TWILIO_API_KEY}" \ @@ -304,7 +308,7 @@ spec: value: "{{ .Values.authorizer.couchbase_bucket }}" {{- end }} {{- if .Values.authorizer.couchbase_bucket_ram_quota }} - - name: "COUCHBASE_BUCKET_RAM_QUOTA" + - name: "COUCHBASE_RAM_QUOTA" value: "{{ .Values.authorizer.couchbase_bucket_ram_quota }}" {{- end }} {{- if .Values.authorizer.couchbase_scope }} @@ -315,12 +319,20 @@ spec: - name: "AUTHORIZER_URL" value: "{{ .Values.authorizer.authorizer_url }}" {{- end }} + - name: "HOST" + value: {{ .Values.authorizer.host | default "0.0.0.0" | quote }} - name: "PORT" value: "{{ .Values.authorizer.http_port | default 8080 }}" - name: "METRICS_PORT" value: "{{ .Values.authorizer.metrics_port | default 8081 }}" - name: "METRICS_HOST" value: {{ .Values.authorizer.metrics_host | default "0.0.0.0" | quote }} + - name: "LOG_LEVEL" + value: {{ .Values.authorizer.log_level | default "info" | quote }} + {{- if .Values.authorizer.env }} + - name: "ENV" + value: {{ .Values.authorizer.env | quote }} + {{- end }} - name: "RATE_LIMIT_RPS" value: {{ .Values.authorizer.rate_limit_rps | default 30 | toString | quote }} - name: "RATE_LIMIT_BURST" @@ -420,6 +432,53 @@ spec: value: {{ .Values.authorizer.graphql_max_aliases | default 30 | toString | quote }} - name: "GRAPHQL_MAX_BODY_BYTES" value: {{ int64 (default 1048576 .Values.authorizer.graphql_max_body_bytes) | toString | quote }} + - name: "OAUTH2_1_STRICT" + value: {{ .Values.authorizer.oauth2_1_strict | default false | toString | quote }} + + # HTTP routes / cookies / auth feature toggles. See values.yaml + # for the operational notes attached to each one. + - name: "ENABLE_LOGIN_PAGE" + value: {{ .Values.authorizer.enable_login_page | default true | toString | quote }} + - name: "ENABLE_ORG_DISCOVERY" + value: {{ .Values.authorizer.enable_org_discovery | default false | toString | quote }} + - name: "ENABLE_PLAYGROUND" + value: {{ .Values.authorizer.enable_playground | default true | toString | quote }} + - name: "ENABLE_GRAPHQL_INTROSPECTION" + value: {{ .Values.authorizer.enable_graphql_introspection | default true | toString | quote }} + - name: "DISABLE_ADMIN_HEADER_AUTH" + value: {{ .Values.authorizer.disable_admin_header_auth | default true | toString | quote }} + - name: "APP_COOKIE_SECURE" + value: {{ .Values.authorizer.app_cookie_secure | default true | toString | quote }} + - name: "APP_COOKIE_SAME_SITE" + value: {{ .Values.authorizer.app_cookie_same_site | default "none" | quote }} + - name: "ADMIN_COOKIE_SECURE" + value: {{ .Values.authorizer.admin_cookie_secure | default true | toString | quote }} + - name: "ENABLE_STRONG_PASSWORD" + value: {{ .Values.authorizer.enable_strong_password | default true | toString | quote }} + - name: "ENABLE_BASIC_AUTHENTICATION" + value: {{ .Values.authorizer.enable_basic_authentication | default true | toString | quote }} + - name: "ENABLE_EMAIL_VERIFICATION" + value: {{ .Values.authorizer.enable_email_verification | default false | toString | quote }} + - name: "ENABLE_MOBILE_BASIC_AUTHENTICATION" + value: {{ .Values.authorizer.enable_mobile_basic_authentication | default true | toString | quote }} + - name: "ENABLE_PHONE_VERIFICATION" + value: {{ .Values.authorizer.enable_phone_verification | default false | toString | quote }} + - name: "ENABLE_MAGIC_LINK_LOGIN" + value: {{ .Values.authorizer.enable_magic_link_login | default false | toString | quote }} + - name: "ENABLE_SIGNUP" + value: {{ .Values.authorizer.enable_signup | default true | toString | quote }} + - name: "ENFORCE_MFA" + value: {{ .Values.authorizer.enforce_mfa | default true | toString | quote }} + - name: "DISABLE_TOTP_LOGIN" + value: {{ .Values.authorizer.disable_totp_login | default false | toString | quote }} + - name: "DISABLE_WEBAUTHN_MFA" + value: {{ .Values.authorizer.disable_webauthn_mfa | default false | toString | quote }} + - name: "DISABLE_EMAIL_OTP" + value: {{ .Values.authorizer.disable_email_otp | default false | toString | quote }} + - name: "DISABLE_SMS_OTP" + value: {{ .Values.authorizer.disable_sms_otp | default false | toString | quote }} + - name: "DISABLE_MFA" + value: {{ .Values.authorizer.disable_mfa | default false | toString | quote }} # FGA (OpenFGA) integration {{- if .Values.authorizer.fga_store }} diff --git a/values.yaml b/values.yaml index 0cbeb5e..ad13828 100644 --- a/values.yaml +++ b/values.yaml @@ -92,6 +92,9 @@ redis: storage: null authorizer: + # Host address to listen on (--host). + host: "0.0.0.0" + # Main HTTP listen port (--http-port). Must differ from metrics_port. http_port: 8080 @@ -101,6 +104,12 @@ authorizer: # Bind address for /metrics (--metrics-host). Use 0.0.0.0 for in-cluster scraping. metrics_host: "0.0.0.0" + # Log level (--log-level): debug, info, warn, error. + log_level: "info" + + # Environment label for this instance (--env), e.g. "production", "staging". + env: null + # gRPC server (--grpc-port, --enable-grpc-reflection, --grpc-insecure, # --grpc-tls-cert, --grpc-tls-key). The same public API is served over gRPC; # the REST gateway (/v1/*) wraps it in-process. Must differ from http_port @@ -272,6 +281,45 @@ authorizer: graphql_max_aliases: 30 graphql_max_body_bytes: 1048576 + # ---------------------------------------------------------------------- + # HTTP routes / cookies / auth feature toggles + # ---------------------------------------------------------------------- + + enable_login_page: true + # Public organization (home-realm) discovery endpoint + /app email-first + # SSO routing step (--enable-org-discovery). Opt-in; off keeps the login + # page unchanged. + enable_org_discovery: false + enable_playground: true + enable_graphql_introspection: true + + # Disable admin authentication via the X-Authorizer-Admin-Secret header + # (--disable-admin-header-auth). true (default here) requires session/ + # cookie-based admin auth instead of the static header secret. + disable_admin_header_auth: true + + app_cookie_secure: true + # SameSite attribute for session cookies (lax, strict, none). + app_cookie_same_site: "none" + admin_cookie_secure: true + + enable_strong_password: true + enable_basic_authentication: true + enable_email_verification: false + enable_mobile_basic_authentication: true + enable_phone_verification: false + enable_magic_link_login: false + enable_signup: true + + # MFA (--enforce-mfa, --disable-*). MFA methods are enabled by default and + # opted out via disable_*; enforce_mfa forces enrollment for all users. + enforce_mfa: true + disable_totp_login: false + disable_webauthn_mfa: false + disable_email_otp: false + disable_sms_otp: false + disable_mfa: false + # FGA (OpenFGA) integration fga_store: null fga_store_url: null @@ -296,6 +344,11 @@ authorizer: default_authorize_response_type: null default_authorize_response_mode: null + # Enforce OAuth 2.1 restrictions (--oauth2-1-strict): reject the + # implicit/hybrid-with-token response types and PKCE plain (require S256). + # Breaking; opt-in. + oauth2_1_strict: false + # Branding organization_name: null organization_logo: null From c670dbc39159706c2b46925de1fb48ad696c5359 Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Tue, 4 Aug 2026 16:10:57 +0530 Subject: [PATCH 2/7] fix: wire --encryption-key, honour false booleans, match MFA default MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three defects found deploying this chart to a real k3d cluster. 1. --encryption-key was not wired at all. Server 2.4.0 refuses to start when it and --jwt-secret are both empty, which is the normal state of an RS*/ES* install — so every RSA/ECDSA deployment via this chart would fail to boot. Added through values, secret and arg. 2. `{{ .Values.x | default true }}` is wrong for booleans: Go template `default` substitutes on ANY empty value and `false` is empty, so an explicit `false` was silently discarded. 12 flags could not be turned off, among them enable_playground, enable_graphql_introspection, enable_grpc_reflection and enable_signup — precisely the ones an operator disables to harden production. Replaced with a helper that only substitutes a genuinely absent value. 3. enforce_mfa defaulted to true, the pre-2.4.0 server default. The server flipped it to false, so the chart silently forced mandatory MFA enrollment with no way to decline (compounded by defect 2, which made setting it false ineffective). Verified on k3d: RS256 install boots, is_mfa_enforced reports false, and --set enable_playground=false now renders "false". --- templates/_helpers.tpl | 20 ++++++++++++++++++++ templates/deployment.yaml | 34 +++++++++++++++++++++------------- templates/secrets.yaml | 10 ++++++++++ values.yaml | 21 ++++++++++++++++++++- 4 files changed, 71 insertions(+), 14 deletions(-) diff --git a/templates/_helpers.tpl b/templates/_helpers.tpl index 7fc608d..7d9415d 100644 --- a/templates/_helpers.tpl +++ b/templates/_helpers.tpl @@ -60,3 +60,23 @@ Create the name of the service account to use {{- default "default" .Values.serviceAccount.name }} {{- end }} {{- end }} + +{{/* +Render a boolean value, honouring an explicit `false`. + +`{{ .Values.x | default true }}` is WRONG for booleans: Go template `default` +substitutes whenever the value is *empty*, and `false` is empty — so a user +setting `false` silently gets `true` and has no way to turn the flag off. That +made 12 flags unsettable, including enable_playground, +enable_graphql_introspection, enable_grpc_reflection and enable_signup, i.e. +exactly the ones an operator hardens a production deployment with. + +Only a genuinely absent value (nil / "invalid" kind) falls back to the default. + +Usage: {{ include "authorizer.bool" (list .Values.authorizer.enable_signup true) }} +*/}} +{{- define "authorizer.bool" -}} +{{- $value := index . 0 -}} +{{- $default := index . 1 -}} +{{- if kindIs "invalid" $value }}{{ $default }}{{ else }}{{ $value }}{{ end -}} +{{- end -}} diff --git a/templates/deployment.yaml b/templates/deployment.yaml index 22219bd..1c981e9 100644 --- a/templates/deployment.yaml +++ b/templates/deployment.yaml @@ -55,6 +55,7 @@ spec: --redis-url="${REDIS_URL}" \ --jwt-type="${JWT_TYPE}" \ --jwt-secret="${JWT_SECRET}" \ + --encryption-key="${ENCRYPTION_KEY}" \ --jwt-private-key="${JWT_PRIVATE_KEY}" \ --jwt-public-key="${JWT_PUBLIC_KEY}" \ --jwt-role-claim="${JWT_ROLE_CLAIM}" \ @@ -119,7 +120,7 @@ spec: --enable-mobile-basic-authentication="${ENABLE_MOBILE_BASIC_AUTHENTICATION:-true}" \ --enable-phone-verification="${ENABLE_PHONE_VERIFICATION:-false}" \ --enable-magic-link-login="${ENABLE_MAGIC_LINK_LOGIN:-false}" \ - --enforce-mfa="${ENFORCE_MFA:-true}" \ + --enforce-mfa="${ENFORCE_MFA:-false}" \ --disable-totp-login="${DISABLE_TOTP_LOGIN:-false}" \ --disable-webauthn-mfa="${DISABLE_WEBAUTHN_MFA:-false}" \ --disable-email-otp="${DISABLE_EMAIL_OTP:-false}" \ @@ -259,6 +260,13 @@ spec: name: jwt-secret key: jwt_secret {{- end }} + {{- if .Values.authorizer.encryption_key }} + - name: "ENCRYPTION_KEY" + valueFrom: + secretKeyRef: + name: encryption-key + key: encryption_key + {{- end }} {{- if .Values.authorizer.jwt_private_key }} - name: "JWT_PRIVATE_KEY" valueFrom: @@ -343,7 +351,7 @@ spec: - name: "GRPC_PORT" value: {{ .Values.authorizer.grpc_port | default 9091 | toString | quote }} - name: "ENABLE_GRPC_REFLECTION" - value: {{ .Values.authorizer.enable_grpc_reflection | default true | toString | quote }} + value: {{ include "authorizer.bool" (list .Values.authorizer.enable_grpc_reflection true) | quote }} - name: "GRPC_INSECURE" value: {{ .Values.authorizer.grpc_insecure | default false | toString | quote }} {{- if .Values.authorizer.grpc_tls_cert }} @@ -438,37 +446,37 @@ spec: # HTTP routes / cookies / auth feature toggles. See values.yaml # for the operational notes attached to each one. - name: "ENABLE_LOGIN_PAGE" - value: {{ .Values.authorizer.enable_login_page | default true | toString | quote }} + value: {{ include "authorizer.bool" (list .Values.authorizer.enable_login_page true) | quote }} - name: "ENABLE_ORG_DISCOVERY" value: {{ .Values.authorizer.enable_org_discovery | default false | toString | quote }} - name: "ENABLE_PLAYGROUND" - value: {{ .Values.authorizer.enable_playground | default true | toString | quote }} + value: {{ include "authorizer.bool" (list .Values.authorizer.enable_playground true) | quote }} - name: "ENABLE_GRAPHQL_INTROSPECTION" - value: {{ .Values.authorizer.enable_graphql_introspection | default true | toString | quote }} + value: {{ include "authorizer.bool" (list .Values.authorizer.enable_graphql_introspection true) | quote }} - name: "DISABLE_ADMIN_HEADER_AUTH" - value: {{ .Values.authorizer.disable_admin_header_auth | default true | toString | quote }} + value: {{ include "authorizer.bool" (list .Values.authorizer.disable_admin_header_auth true) | quote }} - name: "APP_COOKIE_SECURE" - value: {{ .Values.authorizer.app_cookie_secure | default true | toString | quote }} + value: {{ include "authorizer.bool" (list .Values.authorizer.app_cookie_secure true) | quote }} - name: "APP_COOKIE_SAME_SITE" value: {{ .Values.authorizer.app_cookie_same_site | default "none" | quote }} - name: "ADMIN_COOKIE_SECURE" - value: {{ .Values.authorizer.admin_cookie_secure | default true | toString | quote }} + value: {{ include "authorizer.bool" (list .Values.authorizer.admin_cookie_secure true) | quote }} - name: "ENABLE_STRONG_PASSWORD" - value: {{ .Values.authorizer.enable_strong_password | default true | toString | quote }} + value: {{ include "authorizer.bool" (list .Values.authorizer.enable_strong_password true) | quote }} - name: "ENABLE_BASIC_AUTHENTICATION" - value: {{ .Values.authorizer.enable_basic_authentication | default true | toString | quote }} + value: {{ include "authorizer.bool" (list .Values.authorizer.enable_basic_authentication true) | quote }} - name: "ENABLE_EMAIL_VERIFICATION" value: {{ .Values.authorizer.enable_email_verification | default false | toString | quote }} - name: "ENABLE_MOBILE_BASIC_AUTHENTICATION" - value: {{ .Values.authorizer.enable_mobile_basic_authentication | default true | toString | quote }} + value: {{ include "authorizer.bool" (list .Values.authorizer.enable_mobile_basic_authentication true) | quote }} - name: "ENABLE_PHONE_VERIFICATION" value: {{ .Values.authorizer.enable_phone_verification | default false | toString | quote }} - name: "ENABLE_MAGIC_LINK_LOGIN" value: {{ .Values.authorizer.enable_magic_link_login | default false | toString | quote }} - name: "ENABLE_SIGNUP" - value: {{ .Values.authorizer.enable_signup | default true | toString | quote }} + value: {{ include "authorizer.bool" (list .Values.authorizer.enable_signup true) | quote }} - name: "ENFORCE_MFA" - value: {{ .Values.authorizer.enforce_mfa | default true | toString | quote }} + value: {{ include "authorizer.bool" (list .Values.authorizer.enforce_mfa false) | quote }} - name: "DISABLE_TOTP_LOGIN" value: {{ .Values.authorizer.disable_totp_login | default false | toString | quote }} - name: "DISABLE_WEBAUTHN_MFA" diff --git a/templates/secrets.yaml b/templates/secrets.yaml index b24c164..fda5b28 100644 --- a/templates/secrets.yaml +++ b/templates/secrets.yaml @@ -118,6 +118,16 @@ type: Opaque data: jwt_secret: "{{ .Values.authorizer.jwt_secret | b64enc }}" {{- end }} +{{- if .Values.authorizer.encryption_key }} +--- +apiVersion: v1 +kind: Secret +metadata: + name: encryption-key +type: Opaque +data: + encryption_key: "{{ .Values.authorizer.encryption_key | b64enc }}" +{{- end }} {{- if .Values.authorizer.jwt_private_key }} --- apiVersion: v1 diff --git a/values.yaml b/values.yaml index ad13828..f8e5ce6 100644 --- a/values.yaml +++ b/values.yaml @@ -209,6 +209,21 @@ authorizer: # JWT secret (for HMAC-based JWT) jwt_secret: null + # Key used to encrypt secrets AT REST (TOTP seeds) and to HMAC the OTP + # digests used by email/SMS verification and password reset. + # + # Falls back to jwt_secret when unset, so HMAC (HS*) installs need not set + # it. RSA/ECDSA (RS*/ES*) installs have no jwt_secret to fall back to, and + # the server REFUSES TO START without this — an empty key would derive a + # publicly computable constant, leaving those secrets effectively + # unprotected. + # + # Set it explicitly even on HMAC installs: while this and jwt_secret are the + # same value, rotating jwt_secret silently locks out every enrolled TOTP + # user, because the at-rest key changes with it and there is no + # re-encryption path. + encryption_key: null + # JWT private key (for RSA/EC-based JWT) jwt_private_key: null @@ -313,7 +328,11 @@ authorizer: # MFA (--enforce-mfa, --disable-*). MFA methods are enabled by default and # opted out via disable_*; enforce_mfa forces enrollment for all users. - enforce_mfa: true + # Matches the server default since 2.4.0, which flipped this from true to + # false: MFA is offered and skippable rather than mandatory. The chart used + # to hardcode the old `true`, silently forcing every Helm deployment into + # mandatory enrollment with no way to decline. + enforce_mfa: false disable_totp_login: false disable_webauthn_mfa: false disable_email_otp: false From 0afbaba9fe43713ac2612e7a9cb43027bc17e83a Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Thu, 6 Aug 2026 21:40:03 +0530 Subject: [PATCH 3/7] fix: pin 2.4.0-rc.15 and only pass --encryption-key when set --encryption-key does not exist before 2.4.0-rc.15, and the chart passed it unconditionally, so every install crash-looped with "unknown flag" on the appVersion this branch pinned (2.4.0-rc.7). Pin the first image that has the flag, and gate the arg on the value so a user pinning an older image.tag still boots. The env var was already gated the same way. --- Chart.yaml | 2 +- README.md | 3 ++- templates/deployment.yaml | 2 ++ 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Chart.yaml b/Chart.yaml index d54fc37..6cb78f5 100644 --- a/Chart.yaml +++ b/Chart.yaml @@ -21,6 +21,6 @@ version: 2.3.0 # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -appVersion: "2.4.0-rc.7" +appVersion: "2.4.0-rc.15" icon: "https://authorizer.dev/images/logo.png" diff --git a/README.md b/README.md index e6a5f55..be82542 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Helm chart for [Authorizer](https://authorizer.dev) — an open-source, self-hos This chart deploys the Authorizer binary as a Kubernetes `Deployment`, wires up a `Service` (HTTP + optional gRPC port), optional metrics infrastructure, and exposes all server flags as `values.yaml` keys. -Chart version: **2.2.1** | App version: **2.3.0** +Chart version: **2.3.0** | App version: **2.4.0-rc.15** ## Getting Started @@ -186,6 +186,7 @@ The metrics port is never added to the main `Service` used for Ingress. Use `met | ---- | ----------- | -------- | ------- | | `authorizer.jwt_type` | JWT signing algorithm (e.g. `HS256`, `RS256`, `ES256`) | false | — | | `authorizer.jwt_secret` | Secret for HMAC-based JWT signing | false | — | +| `authorizer.encryption_key` | Encrypts TOTP secrets and OTP digests at rest. **Required when `jwt_type` is `RS*`/`ES*`** — there is no `jwt_secret` to fall back to and the server refuses to start. Generate once with `openssl rand -hex 32` and keep it stable; changing it makes existing TOTP enrolments undecryptable | false | — | | `authorizer.jwt_private_key` | Private key for RSA/EC-based JWT signing | false | — | | `authorizer.jwt_public_key` | Public key for RSA/EC-based JWT verification | false | — | | `authorizer.jwt_role_claim` | Custom claim name for roles in the JWT | false | — | diff --git a/templates/deployment.yaml b/templates/deployment.yaml index 1c981e9..6387a85 100644 --- a/templates/deployment.yaml +++ b/templates/deployment.yaml @@ -55,7 +55,9 @@ spec: --redis-url="${REDIS_URL}" \ --jwt-type="${JWT_TYPE}" \ --jwt-secret="${JWT_SECRET}" \ + {{- if .Values.authorizer.encryption_key }} --encryption-key="${ENCRYPTION_KEY}" \ + {{- end }} --jwt-private-key="${JWT_PRIVATE_KEY}" \ --jwt-public-key="${JWT_PUBLIC_KEY}" \ --jwt-role-claim="${JWT_ROLE_CLAIM}" \ From a6111c55ada6426fbc52619f7b66c1826ccb21e8 Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Fri, 7 Aug 2026 17:58:48 +0530 Subject: [PATCH 4/7] feat: add 2.4.0 flags, fail render on boot-fatal SMTP combo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three flags added in server 2.4.0 had no chart surface: --microsoft-allowed-tenants, --fga-allow-unconstrained-agents, and --oauth-allow-unverified-provider-email. Both booleans default false, matching the server's secure-by-default posture. 2.4.0 also turned --enable-email-verification with no SMTP from a per-user quirk into a fatal boot error. In-cluster that surfaces as a CrashLoopBackOff whose cause is one line of container log, so the chart now refuses to render it. The condition mirrors the server's IsEmailServiceEnabled exactly — host set, port > 0, sender email set — so it cannot reject a config the server would accept. --- templates/deployment.yaml | 32 ++++++++++++++++++++++++++++++++ values.yaml | 19 +++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/templates/deployment.yaml b/templates/deployment.yaml index 6387a85..7772a17 100644 --- a/templates/deployment.yaml +++ b/templates/deployment.yaml @@ -1,3 +1,18 @@ +{{/* +2.4.0 makes `--enable-email-verification` with no working SMTP a FATAL boot +error, not a warning: every recovery route terminates at the same mailbox, so +without a mail path a user is created unverified and can never recover. + +Caught at render time rather than letting it through, because the failure mode +in-cluster is a CrashLoopBackOff whose cause is one line in the container log. +The condition mirrors the server's IsEmailServiceEnabled exactly — host set, +port > 0, sender email set — so this never rejects a config the server accepts. +*/}} +{{- if .Values.authorizer.enable_email_verification }} + {{- if not (and .Values.authorizer.smtp_host (gt (int .Values.authorizer.smtp_port) 0) .Values.authorizer.smtp_sender_email) }} + {{- fail "authorizer.enable_email_verification is true but SMTP is incomplete. Authorizer 2.4.0 exits at boot in this configuration (users would be created unverified with no way to ever verify), so the pod would CrashLoopBackOff. Set authorizer.smtp_host, authorizer.smtp_port and authorizer.smtp_sender_email, or set enable_email_verification: false." }} + {{- end }} +{{- end }} apiVersion: apps/v1 kind: Deployment metadata: @@ -49,6 +64,7 @@ spec: --database-cert-key="${DATABASE_CERT_KEY}" \ --fga-store="${FGA_STORE}" \ --fga-store-url="${FGA_STORE_URL}" \ + --fga-allow-unconstrained-agents="${FGA_ALLOW_UNCONSTRAINED_AGENTS:-false}" \ --client-id="${CLIENT_ID}" \ --client-secret="${CLIENT_SECRET}" \ --admin-secret="${ADMIN_SECRET}" \ @@ -151,7 +167,9 @@ spec: --microsoft-client-id="${MICROSOFT_CLIENT_ID}" \ --microsoft-client-secret="${MICROSOFT_CLIENT_SECRET}" \ --microsoft-tenant-id="${MICROSOFT_TENANT_ID}" \ + --microsoft-allowed-tenants="${MICROSOFT_ALLOWED_TENANTS}" \ --microsoft-scopes="${MICROSOFT_SCOPES}" \ + --oauth-allow-unverified-provider-email="${OAUTH_ALLOW_UNVERIFIED_PROVIDER_EMAIL:-false}" \ --apple-client-id="${APPLE_CLIENT_ID}" \ --apple-client-secret="${APPLE_CLIENT_SECRET}" \ --apple-scopes="${APPLE_SCOPES}" \ @@ -505,6 +523,11 @@ spec: name: fga-store-url key: fga_store_url {{- end }} + # 2.4.0: a delegated (agent-acting-for-user) FGA check against a model + # with no `type agent` now DENIES rather than silently authorizing as + # the user alone. Set true only while migrating a model. + - name: "FGA_ALLOW_UNCONSTRAINED_AGENTS" + value: {{ include "authorizer.bool" (list .Values.authorizer.fga_allow_unconstrained_agents false) | quote }} # JWT additional config {{- if .Values.authorizer.jwt_role_claim }} @@ -674,10 +697,19 @@ spec: - name: "MICROSOFT_TENANT_ID" value: "{{ .Values.authorizer.microsoft_tenant_id }}" {{- end }} + {{- if .Values.authorizer.microsoft_allowed_tenants }} + - name: "MICROSOFT_ALLOWED_TENANTS" + value: "{{ .Values.authorizer.microsoft_allowed_tenants }}" + {{- end }} {{- if .Values.authorizer.microsoft_scopes }} - name: "MICROSOFT_SCOPES" value: "{{ .Values.authorizer.microsoft_scopes }}" {{- end }} + # 2.4.0 nOAuth defense: a social login whose provider did not attest + # the email no longer links to an existing account. This is the + # compatibility escape hatch; the server warns on every boot when set. + - name: "OAUTH_ALLOW_UNVERIFIED_PROVIDER_EMAIL" + value: {{ include "authorizer.bool" (list .Values.authorizer.oauth_allow_unverified_provider_email false) | quote }} {{- if .Values.authorizer.apple_client_id }} - name: "APPLE_CLIENT_ID" valueFrom: diff --git a/values.yaml b/values.yaml index f8e5ce6..adffaf6 100644 --- a/values.yaml +++ b/values.yaml @@ -342,6 +342,12 @@ authorizer: # FGA (OpenFGA) integration fga_store: null fga_store_url: null + # 2.4.0: a delegated (agent-acting-for-user) check against an authorization + # model with no `type agent` now DENIES instead of authorizing as the + # delegating user alone — that silently dropped the agent half of the + # permission intersection. Set true only while migrating a model; the server + # logs every use. Prefer adding `type agent` to the model. + fga_allow_unconstrained_agents: false # JWT additional config jwt_role_claim: null @@ -396,8 +402,21 @@ authorizer: microsoft_client_id: null microsoft_client_secret: null microsoft_tenant_id: null + # Entra tenant IDs allowed to sign in when microsoft_tenant_id is a + # multi-tenant alias (common/organizations/consumers). Comma-separated. + # Empty allows any tenant, but an untrusted tenant's email will not link to + # an existing account — Entra's `email` is mutable and unattested there. + microsoft_allowed_tenants: null microsoft_scopes: null + # 2.4.0 nOAuth defense: a social login whose provider did not attest the + # email address no longer signs into an existing account. This is the + # compatibility escape hatch — it still cannot cross into an account another + # credential owns, but it leaves same-provider collisions open (two Entra + # tenants asserting one address). The server warns on every boot when set. + # Prefer pinning microsoft_tenant_id or enabling the xms_edov claim. + oauth_allow_unverified_provider_email: false + apple_client_id: null apple_client_secret: null apple_scopes: null From 6f5b9e328f5905242cf57aed5bc4ac49e75f4e83 Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Fri, 7 Aug 2026 19:03:58 +0530 Subject: [PATCH 5/7] fix: pin appVersion 2.4.0-rc.16, the first RC with these flags rc.15 was tagged and released but its build was cancelled, so no image was ever pushed. It was also cut from a commit predating the audit, so it could not have carried the new flags or the boot-time SMTP check this chart's guard assumes. rc.16 is the first RC with both; verified against the published image. --- Chart.yaml | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Chart.yaml b/Chart.yaml index 6cb78f5..4cbcb7a 100644 --- a/Chart.yaml +++ b/Chart.yaml @@ -21,6 +21,6 @@ version: 2.3.0 # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -appVersion: "2.4.0-rc.15" +appVersion: "2.4.0-rc.16" icon: "https://authorizer.dev/images/logo.png" diff --git a/README.md b/README.md index be82542..dbf3cd3 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Helm chart for [Authorizer](https://authorizer.dev) — an open-source, self-hos This chart deploys the Authorizer binary as a Kubernetes `Deployment`, wires up a `Service` (HTTP + optional gRPC port), optional metrics infrastructure, and exposes all server flags as `values.yaml` keys. -Chart version: **2.3.0** | App version: **2.4.0-rc.15** +Chart version: **2.3.0** | App version: **2.4.0-rc.16** ## Getting Started From bc0ad6a5e2995275521c8a4c800644294246681c Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Fri, 7 Aug 2026 21:39:40 +0530 Subject: [PATCH 6/7] fix: pin 2.4.0-rc.17 rc.17 adds the verify-email decision core and the empty-subject token rejection, which rc.16 was built one commit too early to include. --- Chart.yaml | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Chart.yaml b/Chart.yaml index 4cbcb7a..c087142 100644 --- a/Chart.yaml +++ b/Chart.yaml @@ -21,6 +21,6 @@ version: 2.3.0 # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -appVersion: "2.4.0-rc.16" +appVersion: "2.4.0-rc.17" icon: "https://authorizer.dev/images/logo.png" diff --git a/README.md b/README.md index dbf3cd3..91b0e26 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Helm chart for [Authorizer](https://authorizer.dev) — an open-source, self-hos This chart deploys the Authorizer binary as a Kubernetes `Deployment`, wires up a `Service` (HTTP + optional gRPC port), optional metrics infrastructure, and exposes all server flags as `values.yaml` keys. -Chart version: **2.3.0** | App version: **2.4.0-rc.16** +Chart version: **2.3.0** | App version: **2.4.0-rc.17** ## Getting Started From c8fb8929f9fd7432902045ecbb7aa8c3fe96248a Mon Sep 17 00:00:00 2001 From: Lakhan Samani Date: Fri, 7 Aug 2026 23:16:03 +0530 Subject: [PATCH 7/7] fix: pin 2.4.0-rc.18 rc.18 bumps cel-go to v0.29.2 for GHSA-gcjh-h69q-9w9g. Verified from the published image: the binary links cel-go@v0.29.2. --- Chart.yaml | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Chart.yaml b/Chart.yaml index c087142..64f90a4 100644 --- a/Chart.yaml +++ b/Chart.yaml @@ -21,6 +21,6 @@ version: 2.3.0 # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. # It is recommended to use it with quotes. -appVersion: "2.4.0-rc.17" +appVersion: "2.4.0-rc.18" icon: "https://authorizer.dev/images/logo.png" diff --git a/README.md b/README.md index 91b0e26..0532ad5 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Helm chart for [Authorizer](https://authorizer.dev) — an open-source, self-hos This chart deploys the Authorizer binary as a Kubernetes `Deployment`, wires up a `Service` (HTTP + optional gRPC port), optional metrics infrastructure, and exposes all server flags as `values.yaml` keys. -Chart version: **2.3.0** | App version: **2.4.0-rc.17** +Chart version: **2.3.0** | App version: **2.4.0-rc.18** ## Getting Started