feat: service accounts as FGA subjects#665
Open
lakhansamani wants to merge 2 commits into
Open
Conversation
…ects - A caller presenting a machine (client_credentials) access token now resolves to service_account:<client_id> in check_permissions and list_permissions, instead of a user subject. - Classification keys only on the login_method claim; delegated (RFC 8693, act chain) and user tokens keep resolving to user:<sub>. The delegation guard is therefore structural, not a runtime check. - Reuse the public client_id (not the internal surrogate id) as the FGA subject id so admin tuples and the client registry share one key. - Fail closed: unresolvable/non-service_account client denies; a model without the service_account type denies (engine errors on the unknown subject type) and never falls back to a user subject. - allowed_scopes stays the coarse ceiling at token issuance; FGA is additive below it. Admin tuple writes are already free-form, so no change is needed to reference service_account subjects.
Security-review LOWs: FGA is an authorization decision surface, so a deactivated service account is denied at check time even while its token remains valid elsewhere (delete already failed closed). The resolved client_id is also rejected if it ever contains tuple syntax (:#@/whitespace) — defense in depth; it is a server UUID today.
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.
What
Registered
kind=service_accountclients can now be first-class FGA subjects: an autonomousclient_credentialscaller'scheck_permissions/list_permissionsresolve toservice_account:<client_id>instead ofuser:<sub>. Design follows the adversarially-verified research brief (GCP-IAM-style resource grants for autonomous machines; industry two-layer pattern preserved).Guarantees (each enforced in code and tested)
login_method=service_account— stamped exclusively by the client_credentials mint (token.go:703). Lookup re-verifieskind=service_account; fail-closed otherwise.login_methodand structurally cannot classify as machine — user stays the effective FGA subject; theactchain semantics are unchanged.service_accounttype, machine checks fail closed (engine deny) — never fall back to a user subject.allowed_scopesremains the issuance ceiling; a machine request must pass BOTH the scope check and the FGA check.client_id(per fix: expose public client_id on Client API type #664), never the internal surrogate id — proven by a surrogate-keyed decoy-tuple test.Security review
Full security-engineer pass: no blockers. Both LOW hardening suggestions applied: deactivated (
is_active=false) service accounts are denied at FGA-check time even with a live token, and the resolved subject id is rejected if it ever contains tuple syntax (defense in depth — it's a server UUID today).Scope
One production file (
internal/service/fga.go, +~100 lines) + 380-line integration test suite. No schema/proto changes; gRPC gets parity for free via the shared service layer +authctx.Principal.LoginMethod.enforceRequiredRelations(sessionrequired_relations) intentionally still user-only — flagged as follow-up if machine callers ever need it.Testing
TestFGAServiceAccount*suites: allow/deny per tuples, fail-closed without model type, deactivation denial, surrogate-id decoy, self-pin vs probe, user-token isolation, scope-ceiling independence — all minting real tokens through/oauth/token, not hand-crafted claimsinternal/integration_testssuite passes (68s); build/vet/gofmt/golangci-lint clean