docs(design): evaluate Authorized Integrations against our static-PAT surface #312

Sammanfogat
supernaut sammanfogade 1 incheckning från docs/authorized-integrations-eval in i main 2026-08-01 16:55:48 +00:00
Ägare

Closes #80.

The evaluation note is at docs/design/authorized-integrations-jwt-eval.md, which is where this repo
already keeps non-ADR technical notes. Per the issue, no ADR was written (and ADRs live in
bitborg-docs anyway).

The issue's premise was wrong, and the note says so

The issue was filed from the v16.0 release announcement and asks whether "non-interactive service
accounts can use it". There is no consent screen and no per-use approval — v16 ships
forgejo admin user create-authorized-integration, the same shape as the forgejo admin user create
our role already runs, and the owner declares a trust rule once; anything presenting a matching JWT
then authenticates unattended.

Protocol coverage was verified in the v16.0.1 router wiring rather than taken from prose: /api/v1,
git HTTP, LFS, and the container registry /v2 (where a basic-auth password is permitted specifically
because docker login cannot send a bearer). The local issuer works on our instance —
/api/actions/.well-known/openid-configuration and /.well-known/keys both return 200, RS256.

Verdict: mostly no

Verdict Consumers
GO (scoped proof of concept) bitborg-web deploy.yml registry push as gitborg-ci (write:package)
GO (second, confirmatory) infra ci.yml mirror-pull login as gitborg-bot (read:package)
NO-GO reconciler, runner-controller, renovate, registry-mirror, token-audit, system-webhook verify, access-review script, MCP token
N/A the web portal (holds no Forgejo PAT); runner registration tokens (already single-use)

The dividing line is not interactivity — that was the question the issue asked, and it turns out to be
the wrong one. It is whether the consumer runs inside Forgejo Actions and therefore gets an OIDC ID token
for free. Everything else is a systemd timer or host container with no issuer, and giving it one (Kanidm,
or our own signer) relocates the static secret rather than removing it, while adding a runtime
dependency to the auth path.

Two automation gaps the release announcement does not mention

  • No REST API at all — zero paths in our 16.0.1 swagger. So integrations are invisible to the
    token-audit scrape, and deletable only from the owning user's web UI or by SQL. For a no-login bot
    account that resurrects the password-reset dance ADR 0024 was glad to be rid of.
  • The CLI is create-only and non-idempotent, so it does not fit a converge.

Net effect on monitoring is negative: a JWT grant leaves our token-age monitoring entirely (last-used
lives in authorized_integration.updated_unix, reachable only by SQL). A feature that removes a
long-lived secret but loses auditability is not automatically an improvement.

Rotation interaction

Proceed with the rotation work unchanged. Adoption would obviate only REGISTRY_TOKEN and
REGISTRY_READ_TOKEN. All admin-scoped rotation stays.

Four things that could not be determined, each with what would settle it

Stated as open questions rather than guessed: end-to-end registry acceptance of an Actions JWT; whether
the workflow identity claim is workflow or workflow_ref and its exact value (needed so a claim rule
does not silently allow more than intended); whether Kanidm can mint a single caller-chosen aud; and
whether the ID-token signing kid survives a Forgejo restart under our tmpfs config layout.

Filed separately

  • A hardening item on [authorized_integration] ALLOWED_DOMAINS, which is unset and defaults to allowing
    every domain — #313. That one is independent of adoption.
  • ADR 0024 drift found while mapping the token inventory — #314, which includes a write:admin PAT that is invisible to token-audit.
Closes #80. The evaluation note is at `docs/design/authorized-integrations-jwt-eval.md`, which is where this repo already keeps non-ADR technical notes. Per the issue, **no ADR was written** (and ADRs live in bitborg-docs anyway). ## The issue's premise was wrong, and the note says so The issue was filed from the v16.0 release announcement and asks whether "non-interactive service accounts can use it". There is **no consent screen and no per-use approval** — v16 ships `forgejo admin user create-authorized-integration`, the same shape as the `forgejo admin user create` our role already runs, and the owner declares a trust rule *once*; anything presenting a matching JWT then authenticates unattended. Protocol coverage was verified in the v16.0.1 router wiring rather than taken from prose: `/api/v1`, git HTTP, LFS, and the container registry `/v2` (where a basic-auth password is permitted specifically because `docker login` cannot send a bearer). The local issuer works on our instance — `/api/actions/.well-known/openid-configuration` and `/.well-known/keys` both return 200, RS256. ## Verdict: mostly no | Verdict | Consumers | | --- | --- | | **GO** (scoped proof of concept) | bitborg-web `deploy.yml` registry push as `gitborg-ci` (`write:package`) | | **GO** (second, confirmatory) | infra `ci.yml` mirror-pull login as `gitborg-bot` (`read:package`) | | **NO-GO** | reconciler, runner-controller, renovate, registry-mirror, token-audit, system-webhook verify, access-review script, MCP token | | **N/A** | the web portal (holds no Forgejo PAT); runner registration tokens (already single-use) | **The dividing line is not interactivity** — that was the question the issue asked, and it turns out to be the wrong one. It is whether the consumer runs inside Forgejo Actions and therefore gets an OIDC ID token for free. Everything else is a systemd timer or host container with no issuer, and giving it one (Kanidm, or our own signer) **relocates the static secret rather than removing it**, while adding a runtime dependency to the auth path. ## Two automation gaps the release announcement does not mention - **No REST API at all** — zero paths in our 16.0.1 swagger. So integrations are invisible to the `token-audit` scrape, and deletable only from the owning user's web UI or by SQL. For a no-login bot account that resurrects the password-reset dance ADR 0024 was glad to be rid of. - **The CLI is create-only and non-idempotent**, so it does not fit a converge. Net effect on monitoring is negative: a JWT grant leaves our token-age monitoring entirely (last-used lives in `authorized_integration.updated_unix`, reachable only by SQL). A feature that removes a long-lived secret but loses auditability is not automatically an improvement. ## Rotation interaction **Proceed with the rotation work unchanged.** Adoption would obviate only `REGISTRY_TOKEN` and `REGISTRY_READ_TOKEN`. All admin-scoped rotation stays. ## Four things that could not be determined, each with what would settle it Stated as open questions rather than guessed: end-to-end registry acceptance of an Actions JWT; whether the workflow identity claim is `workflow` or `workflow_ref` and its exact value (needed so a claim rule does not silently allow more than intended); whether Kanidm can mint a single caller-chosen `aud`; and whether the ID-token signing `kid` survives a Forgejo restart under our tmpfs config layout. ## Filed separately - A hardening item on `[authorized_integration] ALLOWED_DOMAINS`, which is unset and defaults to allowing every domain — #313. That one is independent of adoption. - ADR 0024 drift found while mapping the token inventory — #314, which includes a `write:admin` PAT that is invisible to `token-audit`.
supernaut lade till 1 incheckning 2026-08-01 15:00:05 +00:00
docs: evaluate v16 authorized integrations vs static pats (#80)
Alla kontroller lyckades
ci / ci (pull_request) Successful in 18s
f18867f39c
Findings note for the Tier 2 v16 follow-up. Verdict: no-go for every
host-based consumer (reconciler, runner-controller, renovate,
registry-mirror, token-audit, webhook verify) — they have no OIDC issuer,
and adding one relocates the static secret rather than removing it.
Conditional go for the two consumers that run inside Forgejo Actions and
therefore get an ID token for free: the gitborg-web deploy push and the
infra CI mirror-pull login.

Corrects the premise the issue was filed from: the feature needs no
interactive authorisation and is provisionable from the admin CLI, but it
has no REST API at all, so integrations are invisible to the token-audit
scrape and can only be deleted from the owning user's web UI.

Also records ADR 0024 drift found while mapping the inventory, and two
hardening items worth doing regardless of adoption: pin the
[authorized_integration] issuer allowlist, and never permit a
pull_request claim rule on a public repo.
supernaut sammanfogade incheckning 8914884c47 till main 2026-08-01 16:55:48 +00:00
supernaut tog bort grenen docs/authorized-integrations-eval 2026-08-01 16:55:48 +00:00
Logga in för att delta i denna konversation.
Inga granskare
Ingen milstolpe
Inget projekt
Inga tilldelade
1 deltagare
Notiser
Förfallodatum
Förfallodatumet är ogiltigt eller utanför gränserna. Använd formatet "åååå-mm-dd".

Inget förfallodatum satt.

Beroenden

Inga beroenden satta

Referens
bitborg/bitborg-infra!312
Ingen beskrivning angiven.