Skip to content

feat(unleash): send the pre-shared key to bifrost - #499

Merged
Starefossen merged 4 commits into
mainfrom
feat/bifrost-psk-auth
Aug 19, 2026
Merged

feat(unleash): send the pre-shared key to bifrost#499
Starefossen merged 4 commits into
mainfrom
feat/bifrost-psk-auth

Conversation

@Starefossen

@Starefossen Starefossen commented Aug 18, 2026

Copy link
Copy Markdown
Member

Draft — not for merge yet. Part of the nais/bifrost#576 rollout.

The key now exists in fasit: nais/nais-terraform-modules#1561 is merged.

Context

bifrost added pre-shared-key authentication (nais/bifrost#549) and runs in its accept phase: unauthenticated calls are allowed but logged and counted. Verified in production:

warning  Request without a valid API key allowed (authentication not enforced)
bifrost_api_auth_requests_total{outcome="unauthenticated_allowed"}

nais-api is bifrost's only caller, so it is the gate on enforcement.

The chain

Step Where State
Key minted and written to fasit nais/nais-terraform-modules#1561 merged
bifrost reads it Feature.yamlbackend.auth.apiKeys already wired
nais-api reads it Feature.yamlunleash.bifrostApiKey this PR
Becomes an env var secret.yamlUNLEASH_BIFROST_API_KEY via existing envFrom this PR
Client sends it Authorization: Bearer <key> on every request this PR
Enforcement toggle nais/bifrost#579 draft

The key lands in the release Secret rather than as a plain value in deployment.yaml, alongside HOOKD_PSK and REST_PRE_SHARED_KEY — same kind of value, same handling. It still reaches the container as an env var.

Per-environment staging

unleash.bifrostApiKey defaults to the management value but can be overridden or cleared per environment, following the aiven.token pattern in the same file. Clearing it sends no credential, which bifrost accepts until it enforces — so one environment can be taken out of the rollout without touching the others.

Together with nais/bifrost#579, which makes backend.auth.enforced a per-environment toggle, that is what allows dev to go first: send the key in dev, confirm the unauthenticated counter goes flat there, enforce in dev only, then repeat.

Three commits

2deb514c threads the key to both places a real client is constructed — the GraphQL dataloader and the issue checker — so no path is left unauthenticated. The fake client used locally and in tests is untouched.

An unset key sends no header at all, not an empty one. A present-but-empty credential is worse than none: bifrost would record a failed authentication rather than an unauthenticated caller, which is harder to read during a rollout. Startup logs a warning when the key is missing, since that is the state that breaks when enforcement is flipped.

4c9309e9 wires the chart. The first commit read the env var but nothing set it — the client would have logged "No bifrost API key configured" and kept sending unauthenticated requests, so the rollout would have looked complete while changing nothing.

8012824b adds the per-environment override described above.

Behaviourally inert today

bifrost accepts both authenticated and unauthenticated calls, so merging this changes nothing user-visible. It only moves nais-api out of the warning log — the signal nais/bifrost#576 step 4 waits for.

Verification

go build ./... clean, internal/unleash tests pass. New tests assert the header is sent with the configured key and that no header is sent without one; the first fails if the request editor is removed. Chart renders with the key present; Feature.yaml parses.

bifrost added pre-shared-key authentication (nais/bifrost#549) and is
running in its accept phase: unauthenticated calls are allowed but logged
as "Request without a valid API key allowed (authentication not
enforced)". Every call nais-api makes is currently in that log.

Sends the key as "Authorization: Bearer <key>" on every bifrost request,
read from UNLEASH_BIFROST_API_KEY and provisioned through fasit to both
deployments.

An unset key sends no header at all rather than an empty one — a
present-but-empty credential is worse than none, since it looks like a
failed authentication rather than an unauthenticated caller. Startup logs
a warning in that case, because it is the state that breaks the moment
bifrost sets auth.enforced.

This is step 3 of nais/bifrost#576. It must be deployed and confirmed
before that flag is flipped; until then nothing changes behaviourally,
since bifrost accepts both.

Tests cover that the header is sent with the configured key and that no
header is sent without one; the first fails if the request editor is
removed.
The previous commit reads UNLEASH_BIFROST_API_KEY but nothing set it. The
chart wires UNLEASH_BIFROST_API_URL and no key, so the client would have
logged "No bifrost API key configured" and kept sending unauthenticated
requests — the rollout would have looked done while changing nothing.

Wires it end to end: the value is sourced from the fasit management value
bifrost_api_key, lands in the release Secret alongside the other
pre-shared keys, and reaches the container through the existing envFrom.

Same pattern as HOOKD_PSK and REST_PRE_SHARED_KEY, which is where a
secret belongs rather than a plain env value in the deployment.
The key is computed from the management value. It is deliberately not
overridable per environment: staging the rollout is done with bifrost's
enforcement toggle, not by varying who holds the credential. One key,
one place it comes from.

Refs nais/bifrost#576
Three review findings.

The integration harness did not compile. api.ConfigureGraph gained a
parameter and internal/integration/manager.go was not updated. The
package sits behind //go:build integration_test, so go build ./... and
go vet ./... both passed while CI — which runs go test -tags
integration_test ./... — would have failed.

Key rotation could not work. bifrost and nais-api read the same fasit
value, and bifrost accepts a comma-separated list so keys can be rotated
without downtime. nais-api sent the list verbatim, so a rotation value of
"new,old" produced a credential matching neither key. Before enforcement
that shows up as unauthenticated_allowed; after it, a full outage at the
worst possible moment. The client now presents the first entry, so
rotation is: set "new,old", let both sides settle, then drop the old one.

The missing-key warning fired per request. NewBifrostClient is
constructed per GraphQL request via the dataloader, so the warning would
have emitted thousands of lines an hour in every environment until the
key was provisioned. It is reported once at startup instead.
@Starefossen
Starefossen marked this pull request as ready for review August 19, 2026 05:30
@Starefossen
Starefossen force-pushed the feat/bifrost-psk-auth branch from c6cfc7d to bc58f44 Compare August 19, 2026 05:30
@Starefossen
Starefossen requested a review from a team as a code owner August 19, 2026 05:30
@Starefossen
Starefossen merged commit d1bfcd3 into main Aug 19, 2026
10 checks passed
@Starefossen
Starefossen deleted the feat/bifrost-psk-auth branch August 19, 2026 05:43
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