feat(s2s): bearer-auth every service-to-service call#79
Merged
Conversation
…vice call
Closes the gap where service-to-service traffic went out unauthenticated
and the receiving side's RequestTokenHasScope checks silently saw an
empty scope. Every non-core service now boots, swaps a shared secret
for its pre-seeded bearer JWT, and includes it on every outbound
sentinel-client call. The receiving end goes through the existing
JWT validation path — no special-casing.
Core
- core/jobs/init.go: initializeInternalServiceAccounts seeds an SA
per known internal service (sentinel-discord, sentinel-oauth,
sentinel-saml), each scoped sentinel:all and never-expires. The
seed path uses CreateServiceAccountForApp + MintServiceAccountToken
directly, bypassing the UI scope allow-list — internal automations
legitimately need broad access for system work. Idempotent on both
SA existence and SignedToken presence.
- core/api/bootstrap.go: POST /core/internal/bootstrap-token
exchanges INTERNAL_BOOTSTRAP_SECRET (via X-Bootstrap-Secret header,
constant-time compared) for the named SA's persisted bearer JWT.
Closed-set name allowlist via jobs.IsInternalServiceAccountName so
a leaked secret can't be used to harvest admin-created SAs. Fails
closed when the secret isn't configured server-side.
- core/config: INTERNAL_BOOTSTRAP_SECRET env var.
Per-service sentinel clients (discord/oauth/saml)
- SetBearer + sync-protected bearer state; do() automatically
attaches Authorization: Bearer <token> when set.
- Bootstrap(serviceName, secret) helper handles the exchange with
linear-backoff retries (~10s total) for the compose boot race.
- Each service's main.go calls Bootstrap after kerbecs.Init and
before any other sentinel call. Fatal on failure — compose's
restart: always handles persistent races.
- Each service's config gains InternalBootstrapSecret +
InternalServiceName (constant per service).
discord group sync — rolled in
- Fixes the duplicate-key error you've been seeing: the reconcile
now reads ALL of an entity's group memberships, not just
DISCORD-sourced ones, so the ADD path skips groups where the
entity already exists via DIRECT/CONDITIONAL. Delete loop stays
source-scoped — never touches non-DISCORD rows.
docker-compose + example.env
- INTERNAL_BOOTSTRAP_SECRET passed through to core/discord/oauth/saml
as a pure env-var reference (per the no-defaults rule).
The seeded SA used "Sentinel Core" — Title Case with a space — which stuck out next to the internal SAs that match docker container naming (sentinel-discord, sentinel-oauth, sentinel-saml). Renames the seed to "sentinel-core" and adds an idempotent UPDATE so existing DBs with the old name get migrated on next boot.
The new SA gates (requireAppOwnerOrAdmin, GetServiceAccountToken) only accepted owner/admin/creator. That broke the codebase-wide convention where sentinel:all is the universal first-party bypass — meaning an internal automation carrying sentinel:all couldn't manage SAs even though every other admin-flavored endpoint accepts it. Adding sentinel:all to the Any() in both gates so the model stays uniform: scope:sentinel:all = "I'm an internal automation, skip everything." No code path needs to add internal SAs to the Admins group as a workaround.
This was referenced Jun 17, 2026
This was referenced Jun 17, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Rolled-in fix
To test
Test plan