Eine Credential-Art hinzufügen
Diese Anleitung beschreibt die vollständige Checkliste zum Hinzufügen einer neuen Credential-Art,
die durch den Postgres-AES-GCM-Envelope gesichert wird — dasselbe Muster wie bei api_key_refs,
git_credentials, claude_oauth_tokens, tracker_oauth_credentials und env_vars.
Wenn das geheime Material in OpenBao liegt (ein Vault-Pfad oder KV2-Referenz), handelt es sich
um eine OpenBao-Ref-Art (resources, mailbox_credentials). Diese Art ist hier nicht
behandelt — siehe services::secret_scope::kind_backend und ADR 0038 D6.
Hintergrund: das dreistufige Modell
Abschnitt betitelt „Hintergrund: das dreistufige Modell“Jede Postgres-Envelope-Credential-Zeile gehört genau einer Scope-Stufe an, die durch eine
XOR-CHECK-Bedingung (Migration 156) erzwungen und in CredentialScope
(persistence/db/models/credential_scope.rs) kodiert wird:
| Scope | workspace_id |
organization_id |
Gewinnt wenn |
|---|---|---|---|
| Personal | NULL | NULL | standardmäßig am spezifischsten |
| Workspace | gesetzt | NULL | überschreibt Org |
| Organisation | NULL | gesetzt | standardmäßig letzter Rang; gewinnt bei locked = true |
Die Auflösungsregel aus services::secret_scope::resolve_uniform:
if org[name].locked → Org gewinnt (org-shared-wins Kurzschluss)else → personal > workspace > org (most-specific-wins)Die Auflösung findet in SQL per ORDER BY CASE … LIMIT 1 statt — die Datenbank gibt
den einzelnen Gewinner zurück, niemals eine vollständige Kandidatenliste.
Das locked-Flag (Migration 164) ist nur bei Org-Zeilen sinnvoll. Eine Org-Credential
mit locked = true überschreibt jede Workspace- oder Personal-Credential desselben
logischen Schlüssels und ermöglicht so Abrechnungs- und Policy-Enforcement-Anwendungsfälle
ohne eine eigene Vorrangregel pro Art.
Schritte
Abschnitt betitelt „Schritte“-
Migration schreiben.
Ermittle zunächst die aktuell höchste Migrationsnummer — der Namespace ist zwischen parallelen Entwicklern und Worktrees geteilt. Die Abfrage-Anleitung findest du unter Eine Datenbankmigration hinzufügen.
Die Tabelle muss die drei Scope-Spalten, die XOR-CHECK-Bedingung, das
locked-Flag und org-bewusste RLS-Policies enthalten. Verwendeapi_key_refsund die Migrationen156und164als Vorlage:CREATE TABLE my_credentials (id UUID PRIMARY KEY DEFAULT gen_random_uuid(),user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,workspace_id UUID REFERENCES workspaces(id) ON DELETE CASCADE,organization_id UUID REFERENCES organizations(id) ON DELETE CASCADE,locked BOOLEAN NOT NULL DEFAULT false,secret_data TEXT NOT NULL, -- enthält scenc:v1: Geheimtext-- ... deine art-spezifischen Spalten ...created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),CONSTRAINT my_credentials_scope_xor_team_orgCHECK (workspace_id IS NULL OR organization_id IS NULL));-- Index für org-scoped AuflösungCREATE INDEX idx_my_credentials_orgON my_credentials (organization_id)WHERE organization_id IS NOT NULL;-- RLS: Lesen = jedes Mitglied; Schreiben = Workspace-Admin ODER Org-Owner/AdminALTER TABLE my_credentials ENABLE ROW LEVEL SECURITY;CREATE POLICY my_credentials_select ON my_credentials FOR SELECT USING (user_id = (select auth.uid())OR workspace_id IN (SELECT workspace_id FROM workspace_members WHERE user_id = (select auth.uid()))OR organization_id IN (SELECT organization_id FROM organization_members WHERE user_id = (select auth.uid())));CREATE POLICY my_credentials_insert ON my_credentials FOR INSERT WITH CHECK ((organization_id IS NULL AND user_id = (select auth.uid()))OR organization_id IN (SELECT organization_id FROM organization_members WHERE user_id = (select auth.uid()) AND role IN ('owner', 'admin')));CREATE POLICY my_credentials_update ON my_credentials FOR UPDATE USING (user_id = (select auth.uid())OR workspace_id IN (SELECT workspace_id FROM workspace_members WHERE user_id = (select auth.uid()) AND role IN ('owner', 'admin'))OR organization_id IN (SELECT organization_id FROM organization_members WHERE user_id = (select auth.uid()) AND role IN ('owner', 'admin')));CREATE POLICY my_credentials_delete ON my_credentials FOR DELETE USING (user_id = (select auth.uid())OR workspace_id IN (SELECT workspace_id FROM workspace_members WHERE user_id = (select auth.uid()) AND role IN ('owner', 'admin'))OR organization_id IN (SELECT organization_id FROM organization_members WHERE user_id = (select auth.uid()) AND role IN ('owner', 'admin'))); -
Backend in
secret_scope::kind_backendregistrieren.Öffne
server/src/services/secret_scope.rsund füge deinen Tabellennamen demPostgresEnvelope-Arm hinzu:"api_key_refs"| "env_vars"| "git_credentials"| "claude_oauth_tokens"| "tracker_oauth_credentials"| "my_credentials" // das hier hinzufügen=> Some(SecretBackend::PostgresEnvelope),Die
kind_backend-Tests erkennen eine nicht registrierte oder falsch klassifizierte Art. -
Query-Schicht mit scope-bewusstem AAD hinzufügen.
Erstelle
server/src/persistence/db/queries/my_credentials.rs. Wähle einen tabellenqualifizierten AAD-Basisstring —"my_credentials.secret_data"— und verwende die Hilfsfunktionenprotect_secret/reveal_secretausqueries::credential_secret:const SECRET_DATA_AAD: &str = "my_credentials.secret_data";pub async fn insert_my_credential(pool: &PgPool,user_id: Uuid,secret: &str,scope: CredentialScope,) -> sqlx::Result<MyCredential> {let (workspace_id, org_id) = (scope.workspace_id(), scope.organization_id());let protected = protect_secret(secret, SECRET_DATA_AAD, workspace_id, org_id)?;sqlx::query_as::<_, MyCredential>("INSERT INTO my_credentials (user_id, secret_data, workspace_id, organization_id)VALUES ($1, $2, $3, $4) RETURNING *",).bind(user_id).bind(protected).bind(workspace_id).bind(org_id).fetch_one(pool).await// beim Zurückgeben entschlüsseln:.and_then(reveal_my_credential)}fn reveal_my_credential(mut row: MyCredential) -> sqlx::Result<MyCredential> {row.secret_data = reveal_secret(&row.secret_data,SECRET_DATA_AAD,row.workspace_id,row.organization_id,)?;Ok(row)}Die Funktion
scoped_aadinapp/db_secrets.rsleitet den AAD wie folgt ab:- Workspace-Zeile:
my_credentials.secret_data.workspace.<workspace_id> - Org-Zeile:
my_credentials.secret_data.organization.<org_id> - Personal-Zeile:
my_credentials.secret_data.user
Die AES-GCM-Authentifizierung schlägt fehl, wenn ein Geheimtext in eine Zeile mit einer anderen Mandanten-ID verschoben wird — das ist der Cross-Tenant-Relokationsschutz aus ADR 0038 D7.
- Workspace-Zeile:
-
Single-Winner-Resolver schreiben.
Für die punktgenaue Auflösung (eine Credential per logischem Schlüssel abrufen) spiegle das
ORDER BY CASE … LIMIT 1-Muster. Die Ränge stammen aussecret_scope::case_rank:SELECT *FROM my_credentialsWHERE user_id = $user -- persönliche KandidatenOR workspace_id = $wsOR organization_id = $orgORDER BYCASEWHEN organization_id IS NOT NULL AND locked THEN 0WHEN user_id = $user AND workspace_id IS NULL AND organization_id IS NULL THEN 1WHEN workspace_id IS NOT NULL THEN 2ELSE 3 -- entsperrte OrgENDLIMIT 1Die Unit-Tests in
secret_scopeprüfen, dassresolve_uniformundcase_rankübereinstimmen, sodass jede Abweichung zwischen diesem SQL und der Spezifikation im CI erkannt wird. -
Schreibzugriffe nach Scope-Berechtigung absichern.
- Personal- / Workspace-Zeilen: jede authentifizierte Nutzerin / jeder authentifizierte
Nutzer darf in eigene Personal-Zeilen schreiben; für Workspace-Zeilen ist
WorkspaceAction::ManageCredentials(oder die Workspace-Admin-Rolle) erforderlich. - Organisations-Zeilen: erfordern
OrgAction::ManageCredentials(Org-owneroderadmin). Der Management-API-State-Apply-Writer muss workspace-eingeschränkt bleiben — er darf niemals eine org-scoped Zeile schreiben (ADR 0038 D3 Anti-Kontamination). locked-Toggle: nur für Org-Zeilen; erfordert Org-Admin-Berechtigung. Füge eine Query analog zuset_api_key_lockedinqueries/credentials.rshinzu.
- Personal- / Workspace-Zeilen: jede authentifizierte Nutzerin / jeder authentifizierte
Nutzer darf in eigene Personal-Zeilen schreiben; für Workspace-Zeilen ist
-
Verifizieren.
Terminal window cd server && cargo checkcargo test archcargo test secret_scopecargo test archerzwingt die 500-Zeilen-Quelldatei-Obergrenze und die Modulgrenzen-Regeln.cargo test secret_scopeführt denresolve_uniform/case_rank-Übereinstimmungstest aus.
Siehe auch
Abschnitt betitelt „Siehe auch“server/src/services/secret_scope.rs— kanonische Resolver-Spezifikationserver/src/app/db_secrets.rs—scoped_aad,protect_scoped,reveal_scopedserver/src/persistence/db/models/credential_scope.rs—CredentialScope-Enumserver/migrations/156_credential_org_scope.sql— XOR-CHECK-Vorlageserver/migrations/164_credential_locked_and_org_rls.sql—locked+ RLS-Vorlage- ADR 0038 — Begründung des einheitlichen dreistufigen Scopes
- Eine Datenbankmigration hinzufügen