feat: secret rotation#909
Open
rohan-chaturvedi wants to merge 133 commits into
Open
Conversation
… + reference resolution
… SecretType / EnvironmentType.secrets
…o groups blend with adjacent rows
…er key prefix prefill, presets layout, persistent duration hints, tighter header
…l, switch save to onClick, persistent hints, split footer
…zone, provider-aware credential id, lifetime copy, refresh, scroll padding
…ce provider id on revoked credentials
…ap to the right style
…atus on hover and poll for engine-driven updates
…provider match on create, scope actor lookup to the rotating secret org
…stacking so Listbox clicks aren't swallowed
…der in the revocation-delay hint
… resolver on user_can_access_environment
…esn't read all-green on a failed secret
… pill open when attention needed, label retries plainly
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
Adds Rotating Secrets: credentials for third-party services that Phase mints, exposes, and revokes automatically on a schedule. The active credential surfaces alongside regular secrets in an Environment — SDKs, CLI, REST clients, and sync integrations pick up rotated values with no application-side changes.
Initial providers: LiteLLM virtual keys and OpenAI project service-account keys. The architecture is provider-agnostic; adding a new provider is a single file + a one-line registry entry.
SSE must be enabled on the App (Phase needs to encrypt the minted value with the Environment key on the server).
Motivation
Users currently manage third-party credentials (API keys, gateway keys) as static secrets and rotate them by hand. That means cron jobs in some bespoke infra, or just not rotating at all. This PR lets the Console handle the full lifecycle: mint → expose → expire → revoke, with audit trails and provider-side cleanup.
The rotated value flows through the same paths a normal secret does, so nothing in your application needs to know it's rotating — just point at the env var and Phase keeps the value fresh.
How it works
Each Rotating Secret is a config row (provider, root credentials, schedule, output key map) plus a stream of minted Credentials:
RotatingSecretCredential.encrypted_values.Secretrows are materialised — synthetic instances are constructed at read time and slot into the existing serializers.Expiringwindow. It remains valid at the provider until the revocation delay elapses, giving consumers time to pick up the new value.Revoked. A 404 is treated as success (idempotent — handles out-of-band cleanup).Every state transition writes a
RotatingSecretEventwith actor, IP, user agent, and a sanitised provider response excerpt. Reads of the active value flow through the standardSecretEventaudit trail.The engine uses
select_for_update()row locks on the parent row insidetransaction.atomic()so a manual rotate can't race a scheduled rotation, and a manual revoke can't race the scheduled-revoke job.Failure handling
CreateRotatingSecretMutation. If it fails, the transaction rolls back and the user sees the provider's error — nothing partial is left behind.60s → 5m → 30m → 2h). After 5 consecutive transient failures, or any auth/config/quota error, the schedule pauses andhealth = FAILED.REVOKE_FAILEDimmediately.ORPHANED_CREDENTIALevent is recorded with the provider-side id for manual cleanup.validate_root_credentialsagainst the provider before persisting, so bad keys never make it into the rotation config.Supported providers
LiteLLM
Mints virtual keys via
POST /key/generate, revokes viaPOST /key/delete. Supports the full LiteLLM policy surface (models, budgets, rate limits, metadata, aliases, permissions). The Create dialog has an Import config tab that fetches an existing key's policy via/key/infoand shows it as editable JSON so users can mirror an existing key.Recommends scoped management keys (proxy-admin user with
allowed_routeswhitelist) over the master key.OpenAI
Mints project service-account keys via
POST /v1/organization/projects/{project_id}/service_accounts— minted keys are scoped to one project, not org-wide. The create dialog calls/organization/projectsto render a project picker so users select by name instead of pasting aproj_xxx.Revoke deletes the service account (no separate key-revoke endpoint exists in OpenAI's API). Rotation creates a new SA each cycle and deletes the prior one — naming uses a deterministic
phase-rs-<rs_id>-<epoch>template so orphans are traceable.Permissions
Two separate resources, intentionally:
Secrets— gates reading the rotated value (the synthetic row goes through the normal secret-read path).RotatingSecrets— gates managing the config: create, edit, pause/resume, delete, manual rotate, revoke credential.This lets you grant
Secrets:readbroadly without letting every Developer pause/edit/delete rotation configs (which calls real provider APIs and can incur cost). Defaults: Owner/Admin/Manager full, Developer/Service read-only.UI
Active/Expiring in <relative time>/Revoking/Revoked/Mint failed/Revoke failed), provider id, timestamps, and Revoke now for non-terminal non-active credentials. Revoked credentials roll into an accordion.resource_type=rs.Migrations
0130_rotating_secrets— three new models (RotatingSecret,RotatingSecretCredential,RotatingSecretEvent).0131_rotation_status_renames— status terminology cleanup.0132_secret_event_rotating_fk— addsSecretEvent.rotating_secret_credential(FK hasdb_index=Falsebecause >99.99% of rows will be NULL on this column; a partial index can be added later if a per-credential read-history query lands).0133_auditevent_rotating_secret_resource_type— registers'rs'as anAuditEvent.resource_typechoice.All are metadata-only on PG 11+ (no table rewrites, no extended locks).
Tests
53 backend unit tests under
backend/tests/ee/integrations/secrets/rotation/:api_keyobject handling.Preview