Skip to content

feat: service accounts with scoped virtual key self-provisioning#134

Merged
farovictor merged 1 commit intomainfrom
feat/service-accounts
Apr 6, 2026
Merged

feat: service accounts with scoped virtual key self-provisioning#134
farovictor merged 1 commit intomainfrom
feat/service-accounts

Conversation

@farovictor
Copy link
Copy Markdown
Owner

Summary

  • New ServiceAccount entity — separate from User, no management access
  • POST /v1/service-token (auth via X-Service-Key) issues a short-lived virtual key without needing a user session
  • Management CRUD behind normal user auth: POST/GET /v1/serviceaccounts, DELETE /v1/serviceaccounts/{id}
  • allowed_services field: empty = allow any registered service, non-empty = restrict to listed service IDs
  • Keys issued this way carry source: "sa" for traceability in usage logs
  • Migration 011_create_service_accounts.sql

Design note

This is a flat v1 implementation — service accounts are global to the Bifrost instance. Org-scoped service accounts are documented as a future concern in pkg/serviceaccounts/serviceaccount.go.

Usage (CI/CD pipeline example)

# Admin creates a service account once
curl -X POST /v1/serviceaccounts \
  -H "X-API-Key: ..." -H "Authorization: Bearer ..." \
  -d '{"name":"my-pipeline","allowed_services":["openai"]}'
# → {"id":"...","api_key":"sa-xxx","allowed_services":["openai"]}

# Pipeline requests a key at runtime (no user session needed)
curl -X POST /v1/service-token \
  -H "X-Service-Key: sa-xxx" \
  -d '{"service":"openai","ttl_seconds":300}'
# → {"key":"vk-sa-...","expires_at":"..."}

Test plan

  • TestCreateServiceAccount_OK — generates ID + api_key automatically
  • TestCreateServiceAccount_MissingName — 400
  • TestCreateServiceAccount_Duplicate — 409
  • TestListServiceAccounts — returns all accounts
  • TestDeleteServiceAccount_OK — 204
  • TestDeleteServiceAccount_NotFound — 404
  • TestServiceToken_OK — returns key with correct source
  • TestServiceToken_MissingServiceKey — 401
  • TestServiceToken_InvalidServiceKey — 401
  • TestServiceToken_ServiceNotAllowed — 403
  • TestServiceToken_ServiceNotFound — 404
  • TestServiceToken_EmptyAllowedServicesAllowsAny — 200

🤖 Generated with Claude Code

Adds a machine identity layer so CI/CD pipelines and automated services
can request short-lived virtual keys without a full user account.

- pkg/serviceaccounts: ServiceAccount model, MemoryStore, SQLStore
  - allowed_services: empty = allow all; non-empty = restrict to list
  - NOTE: flat v1 design — org-scoping deferred, see pkg/serviceaccounts/serviceaccount.go
- POST /v1/serviceaccounts  — create (auto-generates ID + api_key)
- GET  /v1/serviceaccounts  — list (requires user auth)
- DELETE /v1/serviceaccounts/{id} — delete (requires user auth)
- POST /v1/service-token (X-Service-Key header) — returns a scoped virtual key
  - defaults: ttl=3600s, rate_limit=60rpm, scope=write, source=sa
- keys.SourceServiceAccount constant added
- migration 011_create_service_accounts.sql
- 12 tests covering CRUD + token happy/error paths

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@farovictor farovictor self-assigned this Apr 6, 2026
@farovictor farovictor merged commit 8a9cf77 into main Apr 6, 2026
8 checks passed
@farovictor farovictor deleted the feat/service-accounts branch April 6, 2026 21:51
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