Skip to content

fix: wire --encryption-key, honour false booleans, match 2.4.0 MFA default - #7

Open
lakhansamani wants to merge 6 commits into
mainfrom
fix/encryption-key-and-unsettable-booleans
Open

fix: wire --encryption-key, honour false booleans, match 2.4.0 MFA default#7
lakhansamani wants to merge 6 commits into
mainfrom
fix/encryption-key-and-unsettable-booleans

Conversation

@lakhansamani

Copy link
Copy Markdown
Contributor

Three defects found by deploying this chart to a real k3d cluster (not by reading it).

1. --encryption-key was not wired — RSA/ECDSA installs cannot boot

Server 2.4.0 refuses to start when both --encryption-key and --jwt-secret are empty, which is the normal state of an RS*/ES* install. The chart had no way to set it, so every RSA/ECDSA deployment would crash-loop. Added through values.yaml, a Secret, and the container arg.

2. Twelve booleans could not be set to false

{{ .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 and the flag stayed true.

Affected, among others:

Flag Consequence
enable_playground GraphQL playground cannot be disabled
enable_graphql_introspection introspection cannot be disabled
enable_grpc_reflection gRPC reflection cannot be disabled
enable_signup signup cannot be closed
app_cookie_secure / admin_cookie_secure cannot be relaxed for local HTTP

These are exactly the flags an operator turns off to harden a production deployment. Replaced with an authorizer.bool helper that substitutes only a genuinely absent (nil) value.

3. enforce_mfa still carried the pre-2.4.0 default

The server flipped --enforce-mfa from true to false in 2.4.0. The chart still defaulted it to true, so Helm deployments silently forced mandatory MFA enrollment — and because of defect 2, setting it false did nothing.

Verification on k3d

  • RS256 install with an --encryption-key boots and serves /.well-known/openid-configurationHTTP 200
  • meta.is_mfa_enforcedfalse
  • --set authorizer.enable_playground=false now renders value: "false"
  • helm lint clean

Also worth knowing

sqlite is unusable with this chart: readOnlyRootFilesystem: true and there is no volume option (still open in TODO.md), so the DB file cannot be written. I used Postgres. Worth either documenting or adding a volume.

@netlify

netlify Bot commented Aug 4, 2026

Copy link
Copy Markdown

Deploy Preview for authorizer-helm-chart ready!

Name Link
🔨 Latest commit bc0ad6a
🔍 Latest deploy log https://app.netlify.com/projects/authorizer-helm-chart/deploys/6a76034ae32492000867c777
😎 Deploy Preview https://deploy-preview-7--authorizer-helm-chart.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

--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.
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".
@lakhansamani
lakhansamani force-pushed the fix/encryption-key-and-unsettable-booleans branch from ebd3c9b to c670dbc Compare August 6, 2026 15:00
@lakhansamani

Copy link
Copy Markdown
Contributor Author

Rebased onto main (now at chart 2.2.1). One conflict, Chart.yaml's version: line — resolved in favour of this branch's 2.3.0. Nothing else moved; #9/#10/#11/#12 touched charts/, index.yaml, the publish workflow and the README only.

Do not merge yet — this crash-loops on the image it pins.

Installed the rebased branch on kind:

Error: unknown flag: --encryption-key
NAME                          READY   STATUS             RESTARTS
authorizer-75946df77d-qvp4d   0/1     CrashLoopBackOff   6

--encryption-key does not exist in quay.io/authorizer/authorizer:2.4.0-rc.7, which is what this branch sets as appVersion. I checked every published tag — ./authorizer --help in both 2.4.0-rc.7 and 2.4.0-rc.14 shows --enforce-mfa but no --encryption-key.

The flag is real, just unreleased: it was added in authorizerdev/authorizer@c98b6f3e (security(crypto)!: split at-rest encryption key from JWT secret, 2026-08-04), and git tag --contains c98b6f3e is empty. The newest tag, 2.4.0-rc.14, was built 2026-08-03 — a day before the flag landed. So the k3d verification in the description was presumably against a locally built server, not a published image.

Note the deployment renders args unconditionally, so the flag is passed even when encryption_key is empty — every install breaks, not just RSA/ECDSA ones.

Two ways forward:

  1. Wait for a server release containing c98b6f3e, then set appVersion to that tag. Cleanest.
  2. Render --encryption-key only when .Values.authorizer.encryption_key is non-empty, and pin appVersion to 2.4.0-rc.14. Ships the boolean and enforce_mfa fixes now; the encryption-key path stays dormant until the server catches up.

The other two defects verify clean on the rebased branch — with enable_playground=false and enable_signup=false the running pod has ENABLE_PLAYGROUND=false, ENABLE_SIGNUP=false, and ENFORCE_MFA=false by default. On main those --sets emit no env var at all, so ${ENABLE_PLAYGROUND:-true} wins — the reported bug, confirmed.

Also worth folding in: README.md still says Chart version: **2.2.1** | App version: **2.3.0**, which this branch makes stale.

--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.
@lakhansamani

Copy link
Copy Markdown
Contributor Author

Rebased onto main (chart 2.2.1, 1106a1c) and updated. Rebase was clean this time — the earlier Chart.yaml version conflict is already resolved on the branch.

The crash-loop is fixed. --encryption-key landed in the server on 2026-08-04 and was in no published image, which is why this branch's appVersion: 2.4.0-rc.7 produced unknown flag: --encryption-key on every install. Two changes:

  1. appVersion2.4.0-rc.15, the first release cut from main after the flag landed.
  2. The arg is now gated on the value, matching how the env var was already gated:
{{- if .Values.authorizer.encryption_key }}
--encryption-key="${ENCRYPTION_KEY}" \
{{- end }}

Without the gate the chart hard-requires a 2.4.0+ binary; anyone pinning an older image.tag breaks. With it, the flag only appears when someone actually sets a key.

Rendering verified:

encryption_key unset flag absent (0 occurrences)
encryption_key set flag rendered, ENCRYPTION_KEY wired from the encryption-key Secret
default image quay.io/authorizer/authorizer:2.4.0-rc.15

helm lint clean.

Also updated README.md: the version line now reads chart 2.3.0 / app 2.4.0-rc.15, and the values table documents authorizer.encryption_key — required with RS*/ES*, generate once with openssl rand -hex 32, and do not rotate casually since existing TOTP enrolments become undecryptable.

Worth knowing about the requirement, since it is broader than the description says: any config with no --jwt-secret now refuses to start, not only RS*/ES* ones. A minimal install that sets no JWT flags at all exits with the same error, and the mcp subcommand enforces it too.

I will run the kind install once 2.4.0-rc.15 finishes publishing to quay (release build is still in flight) and report back here before this is merged.

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.
@lakhansamani

Copy link
Copy Markdown
Contributor Author

Pushed a6111c5 — 2.4.0 config coverage on top of this PR's flag work.

Three new 2.4.0 flags had no chart surface

Value Default Why it matters
microsoft_allowed_tenants null Restricts which Entra tenants may sign in when microsoft_tenant_id is a multi-tenant alias. Empty allows any tenant, but an untrusted tenant's email will not link to an existing account.
fga_allow_unconstrained_agents false A delegated FGA check against a model with no type agent now denies instead of authorizing as the user alone.
oauth_allow_unverified_provider_email false nOAuth escape hatch — a social login whose provider did not attest the email no longer reaches an existing account.

Both booleans default false, matching the server's secure-by-default posture. They go through the authorizer.bool helper this PR added, so an explicit false is honoured rather than falling back to the default.

Render-time guard for a combination that now crash-loops

2.4.0 turned --enable-email-verification with no SMTP from a per-user quirk into a fatal boot error — every recovery route terminates at the same mailbox, so without a mail path a user is created unverified and can never recover.

In-cluster that presents as a CrashLoopBackOff whose cause is one line in the container log. The chart now refuses to render it instead.

The condition mirrors the server's IsEmailServiceEnabled exactlysmtp_host set, smtp_port > 0, smtp_sender_email set — so it cannot reject a config the server would accept. That mattered: a looser guard checking only smtp_host would have passed a config missing the port, which the server still rejects.

Verified

helm lint clean, plus four render cases with real exit codes:

Case Expected Got
Default values renders exit 0
Verification on, no SMTP fails exit 1, with the guidance message
Verification on, complete SMTP renders exit 0
Verification on, SMTP missing only port fails exit 1

The third case is the one that matters — a guard that blocks valid configs is worse than no guard.

Also confirmed end-to-end wiring rather than just eyeballing the template: each flag reaches the container args, each env var carries the set value (MICROSOFT_ALLOWED_TENANTS="t1,t2" survives the comma), and both booleans render "false" by default rather than empty.

@lakhansamani

Copy link
Copy Markdown
Contributor Author

⚠️ Do not merge yet — the pinned image cannot run this config

Checking my own flag wiring against the actual published images turned up a blocker that predates this change but which my commit makes worse. Reporting it rather than quietly reverting.

1. The pinned tag does not exist

2.4.0-rc.15 is not published. The newest tag on quay is 2.4.0-rc.14:

$ docker run --rm quay.io/authorizer/authorizer:2.4.0-rc.15
manifest for quay.io/authorizer/authorizer:2.4.0-rc.15 not found: manifest unknown

$ curl -s 'https://quay.io/api/v1/repository/authorizer/authorizer/tag/?onlyActiveTags=true'
2.4.0 tags: rc.0, rc.2 … rc.13, rc.14      # no rc.15

So as pinned, this is an image-pull failure before any flag is parsed.

2. rc.14 does not have the new flags either — and unknown flags are fatal

$ docker run --rm ...:2.4.0-rc.14 -c './authorizer --help | grep -E "fga-allow-unconstrained|microsoft-allowed-tenants|oauth-allow-unverified|encryption-key"'
(no output)

--help itself works — the flags genuinely are not in that build, including --encryption-key from the earlier commit. And cobra refuses to start on an unrecognised flag:

$ docker run --rm ...:2.4.0-rc.14 -c './authorizer --fga-allow-unconstrained-agents=false ...'
Error: unknown flag: --fga-allow-unconstrained-agents
Usage:
  authorizer [flags]

So downgrading the pin to rc.14 as a workaround would turn a pull failure into a crash loop. Both halves are broken.

What this means

The flag wiring here is correct for the server as it now stands on main (PRs authorizerdev/authorizer#751 and #753), but no published image contains it yet. This should merge only after an rc that includes those two PRs is published, and the pin updated to it.

I have not reverted anything — the change is right, the image just has not caught up. Flagging so this does not merge into a deploy that cannot boot.

For the helm chart specifically, the render-time SMTP guard has the same dependency: it blocks a configuration that the currently-pinned image would still accept, since that image lacks the boot-time check.

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.
@lakhansamani

Copy link
Copy Markdown
Contributor Author

✅ Blocker cleared — re-pinned to 2.4.0-rc.16

2.4.0-rc.16 is cut and published, and it is the first RC that actually carries this configuration.

What was wrong with rc.15

The tag and GitHub release existed, which is why it got pinned — but its build was cancelled, so no image was ever pushed. It was also cut from 7f34a651, a commit predating the audit, so even a successful build would not have had these flags.

Verified against the published rc.16 image, not the source

All four flags are present:

$ docker run --rm quay.io/authorizer/authorizer:2.4.0-rc.16 -c './authorizer --help | grep -E ...'
--encryption-key string
--fga-allow-unconstrained-agents
--microsoft-allowed-tenants strings
--oauth-allow-unverified-provider-email

And the boot-time check this configuration assumes actually fires:

$ ./authorizer --enable-email-verification=true  (no SMTP)
{"level":"fatal","message":"--enable-email-verification=true requires a working email service,
 but SMTP is not configured. Users would be created unverified with no way to ever verify.
 Set --smtp-host, --smtp-port and --smtp-sender-email, or disable email verification."}

That second check matters here specifically: the render-time guard in this chart exists to catch that combination early, and it would have been guarding against behaviour the pinned image did not have.

No longer blocked from my side.

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.
@lakhansamani

Copy link
Copy Markdown
Contributor Author

Re-pinned to 2.4.0-rc.17 — full test gate passed

Moved off rc.16, which was built one commit too early to include the verify-email decision core and the empty-subject token rejection (authorizerdev/authorizer#755).

Both suites green against the exact rc.17 commit

Suite Result
make test-all-db exit 0 — 42 packages, 0 failures, all seven backends (internal/storage 215s + arangodb, cassandradb, couchbase, dynamodb, mongodb, sql, schemas)
make e2e-playground exit 086 passed (37.7s)

Run sequentially rather than in parallel: no port overlap, but Scylla plus six authorizer instances and browsers contend for CPU, and a false red there costs more than it saves. Both stacks tore down cleanly — zero leaked containers either time.

rc.17 provenance verified two independent ways

Tag SHAs:

rc.17 -> 0ada7ef4   (the #755 merge commit, == main)
rc.16 -> 3f17f68a   (one commit earlier)

And in the published images themselves:

ConsumeEmailVerificationToken:  rc.17 → 3 matches
                                rc.16 → 0 matches

Plus all four new flags present in the rc.17 image (--encryption-key, --fga-allow-unconstrained-agents, --microsoft-allowed-tenants, --oauth-allow-unverified-provider-email).

helm lint clean; the chart renders image: quay.io/authorizer/authorizer:2.4.0-rc.17.

Ready to merge from my side.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant