Service Account Model (Pre-Implementation Baseline)¶
Purpose¶
Define machine identity and authorization shape before service-account coding begins. This document is contract-first guidance for the upcoming implementation phase.
Scope¶
- Service account ownership and lifecycle.
- Auth token model.
- Authorization boundaries.
- Audit and security controls.
- Minimal schema contract.
Core Principles¶
- Service accounts are non-human identities scoped to a single project.
- Service accounts inherit tenant boundary through project ownership.
- Tokens minted for service accounts are short-lived and audience-scoped.
- Service-account actions are fully auditable with identity attribution.
- No static long-lived bearer secrets in application code or UI.
Important current limitation:
- this baseline is sufficient for project-scoped app controllers,
- it is not yet the final model for tenant-owned shared app runtimes,
- those require a tenant-bounded machine-identity model that does not widen into
platform-global authority,
- see:
- doc/architecture/App_Tenant_Shared_Attachment_Model_v1.md
- doc/architecture/IAM_Token_Issuer_v1.md
Ownership Model¶
Rules:
1. service_accounts.project_id is required.
2. service_accounts.org_id is required (denormalized to avoid project-join fanout on every policy/billing/audit query; consistency enforced by project/org FK invariant).
3. Cross-project use is not allowed; access is project-scoped.
4. Cross-tenant use is forbidden.
Minimal Schema Shape (Planned)¶
service_accounts¶
id uuid pkorg_id uuid not null references organizations(id)project_id uuid not null references projects(id)name text not nullslug text not nulldescription text nullstate text not null check (state in ('active','disabled','deleted'))created_by_user_id uuid not null references users(id)created_at timestamptz not null default now()disabled_at timestamptz nulldeleted_at timestamptz null
Constraints:
- unique (project_id, slug)
- invariant: project.org_id == service_accounts.org_id
service_account_credentials¶
id uuid pkservice_account_id uuid not null references service_accounts(id)key_id text not nullpublic_jwk jsonb not nullprivate_key_enc jsonb not nullalgorithm text not null(MVP:RS256orEdDSA, choose one in implementation ADR)state text not null check (state in ('active','rotated','revoked'))created_at timestamptz not null default now()expires_at timestamptz nullrevoked_at timestamptz null
Constraints:
- unique (service_account_id, key_id)
- at most one active primary credential per service account in MVP
Auth Model (MVP)¶
- Human users authenticate with OIDC as today.
- Service accounts use a dedicated machine-token endpoint (client-credentials style).
- Machine access token claims include:
sub: service account idactor_type:service_accountorg_idproject_idscope/permissionsexp,iat,iss,aud,jti- Token TTL is short (default 15m; policy-controlled).
- Refresh tokens are not issued for service accounts in MVP.
Follow-on consolidation:
- doc/architecture/IAM_Token_Issuer_v1.md defines the shared issuer model for
service-account tokens, delegated shared-runtime operator tokens, future
workload-bound tokens, and provider credential brokering.
- Service-account endpoint contracts remain stable; the issuer is an internal
issuer/policy/audit consolidation, not a new broad public token endpoint.
Authorization Model¶
- Requests from service-account tokens are evaluated against project-scoped permissions.
- Service accounts cannot call admin-only endpoints.
- Service accounts cannot manage IAM/billing/tenant settings.
- Endpoint allowlist for service accounts must be explicit in contract docs.
ownership_requiredchecks resolve by project scope in token claims.
MVP allowlist (implemented):
- GET /api/v1/skus
- GET /api/v1/nodes
- GET /api/v1/projects/{project_id}/app-instances
- POST /api/v1/projects/{project_id}/app-instances
- GET /api/v1/projects/{project_id}/app-instances/{app_instance_id}
- DELETE /api/v1/projects/{project_id}/app-instances/{app_instance_id}
- POST /api/v1/projects/{project_id}/app-instances/{app_instance_id}/upgrade
- POST /api/v1/projects/{project_id}/app-instances/{app_instance_id}/rollback
- POST /api/v1/projects/{project_id}/app-instances/{app_instance_id}/decommission
- POST /api/v1/projects/{project_id}/access-credentials/{credential_id}/deliver
- GET /api/v1/storage/list
- GET /api/v1/storage/download
- PUT /api/v1/storage/upload
- POST /api/v1/storage/mkdir
- POST /api/v1/storage/rename
- DELETE /api/v1/storage/delete
For project-scoped app-instance endpoints, {project_id} must equal token claim project_id.
For storage endpoints, X-Project-ID is required and must equal token claim project_id.
Any non-allowlisted endpoint returns 403 insufficient_permissions.
API Surface (Planned)¶
Tenant/project admin managed endpoints:
1. POST /api/v1/projects/{project_id}/service-accounts
2. GET /api/v1/projects/{project_id}/service-accounts
3. POST /api/v1/projects/{project_id}/service-accounts/{id}/disable
4. POST /api/v1/projects/{project_id}/service-accounts/{id}/rotate-key
5. DELETE /api/v1/projects/{project_id}/service-accounts/{id} (soft delete)
Token issuance endpoint:
1. POST /api/v1/auth/service-account/token
Audit Requirements¶
Every service-account lifecycle mutation must write audit_logs with:
- actor (human admin user),
- action (service_account.create|disable|rotate|delete),
- target (service_account id),
- result,
- correlation_id.
Every machine action should log actor context:
- actor_type=service_account
- actor_id=<service_account_id>
- org_id, project_id
- correlation_id.
Security Controls¶
- Private keys for service-account credentials are always stored encrypted (
*_encenvelope format). - Rotation is mandatory and policy-driven.
- Revocation must be immediate (deny-list by
jtior credential state check). - Tokens never via query parameters.
- Scope minimization: per-service-account endpoint permissions only.
MVP Key Custody Assumption¶
- MVP allows encrypted private-key material to be stored in Postgres (
private_key_enc). - Production hardening target is external key custody (KMS/Vault reference model) with rotation and revocation parity.
- This assumption is tracked in
doc/governance/Assumptions_Register.md(A-019). - Vault-first custody baseline is defined in
doc/architecture/Platform_Vault_Secrets_Baseline_v1.md.
Policy Keys (Planned)¶
auth.service_account_token_ttl_secondsauth.service_account_max_active_keysauth.service_account_rotation_daysauth.service_account_allowed_scopes(json)
Non-Goals (Initial Rollout)¶
- Cross-project or cross-tenant service accounts.
- Service-account UI in MVP if API-first rollout is sufficient.
- External IdP-managed machine identities.
Follow-on note: - tenant-owned shared runtimes may require tenant-scoped machine identity or an equivalent delegated model, but that should be added as an explicit next contract rather than weakening the project-scoped baseline in place.
Acceptance Gate Before Coding¶
- OpenAPI endpoints and token schema added.
- Async/event contracts added if lifecycle events are emitted.
- ERD and db schema include service account tables/constraints.
- Authorization matrix updated for actor_type = service_account.
- Security controls mapped in
Security_Control_Verification.md.
References¶
doc/architecture/Tenant_Project_Ownership_Baseline.mddoc/architecture/adrs/ADR-004-identity-authz-model.mddoc/governance/Security_Control_Verification.mddoc/api/openapi.draft.yamldoc/architecture/App_Tenant_Shared_Attachment_Model_v1.mddoc/architecture/IAM_Token_Issuer_v1.md