Zum Hauptinhalt springen

Secret-Attribute in der API

Diese Seite beschreibt, wie die GraphQL-API des Asset Repository, die REST-API der Identity Provider, MCP und octo-cli Attribute vom Werttyp Secret behandeln. Die Regel in einem Satz: Sie können ein Secret schreiben und sehen, ob es gesetzt ist — keine API gibt den Wert zurück.

Verfügbarkeit

Verfügbar ab dem System-CK-Modell 2.5.0 und dem Engine- und Asset-Repository-Release, das den Werttyp Secret ausliefert. Die CK-Modell-API meldet den Werttyp als SECRET.

Lesen​

Typisierte Felder​

Ein Secret-Attribut wird als Feld vom Typ OctoSecretState statt String bereitgestellt:

type OctoSecretState {
isSet: Boolean! # false, wenn nicht gesetzt ODER wenn der gespeicherte Wert nicht lesbar ist
keyMissing: Boolean! # true: ein Wert ist gespeichert, aber seine Key-ID fehlt im Key Ring dieser Umgebung
setAt: DateTime # wann der aktuelle Wert gesetzt wurde (UTC); null, wenn nicht gesetzt oder vor diesem Feature gesetzt
}
AttributFeldtypErgebnis, wenn nicht gesetzt
Pflicht-Secret-AttributOctoSecretState!{ "isSet": false, "keyMissing": false, "setAt": null }
Optionales Secret-AttributOctoSecretStateDasselbe Objekt — das Feld ist im Schema nullable, liefert aber immer ein Objekt

keyMissing: true (zusammen mit isSet: false) kennzeichnet einen Wert, der mit einem Schlüssel verschlüsselt gespeichert ist, den die Umgebung nicht hat — typischerweise nach einem Restore aus einer anderen Umgebung. Siehe Nicht lesbare Secrets.

query {
runtime {
systemCommunicationEMailSenderConfiguration(rtId: "65f1c0a2b3d4e5f601234567") {
items {
rtId
name
userName
password {
isSet
keyMissing
setAt
}
}
}
}
}
{
"rtId": "65f1c0a2b3d4e5f601234567",
"name": "Notifications",
"userName": "service@example.com",
"password": { "isSet": true, "keyMissing": false, "setAt": "2026-10-06T08:15:00Z" }
}

Bestehende Dokumente, die ein Secret-Feld als Skalar auswählen (password ohne Sub-Selektion), scheitern an der GraphQL-Validierung. Das ist beabsichtigt: Ein Client, der einen String erwartet, scheitert laut, statt einen Wert zu erhalten.

Generische Projektion​

Die generische Attribut-Projektion liefert für ein Secret-Attribut value: null und meldet den Zustand in secretIsSet, secretKeyMissing und secretSetAt. Alle drei sind für jedes Attribut, das kein Secret ist, null.

type RtEntityAttribute {
attributeName: String
value: SimpleScalar # für ein Secret-Attribut immer null
secretIsSet: Boolean # null für Nicht-Secret-Attribute, true/false für Secrets
secretKeyMissing: Boolean # null für Nicht-Secret-Attribute; true, wenn gespeichert, aber nicht lesbar (unbekannte Key-ID)
secretSetAt: DateTime # null für Nicht-Secret-Attribute, Legacy-Werte und nicht gesetzte Secrets
}
query {
runtime {
runtimeEntities(ckId: "System.Communication/EMailSenderConfiguration", rtId: "65f1c0a2b3d4e5f601234567") {
items {
attributes {
items {
attributeName
value
secretIsSet
}
}
}
}
}
}
{ "attributeName": "password", "value": null, "secretIsSet": true }

Eine solche Projektion in einem Update zurückzusenden ist sicher — sie bedeutet „unverändert“ (siehe unten).

Schreiben​

Der Eingabetyp eines Secret-Attributs bleibt String (typisierte Inputs: password: String; Record-Member: token: String). Jede nicht leere Eingabe wird vor dem Speichern mit dem aktiven Schlüssel verschlüsselt; ein verschlüsselter Wert kann daher über die API nie von einer Entität auf eine andere kopiert werden.

EingabeWirkung
Nicht leerer StringSetzt oder rotiert das Secret (der Server verschlüsselt es)
Feld weggelassen, null, ""Unverändert
Zurückgesendetes Lese-Objekt ({ isSet }) oder secretIsSet im generischen InputUnverändert
Name in clearSecretAttributesGelöscht (Fehler, wenn das Secret Pflicht ist)
In Records: Member weggelassen, null oder ""Aus dem gespeicherten Element mit demselben Record-Key übernommen

Der generische Input RtEntityAttributeInput { attributeName, value, secretIsSet } akzeptiert secretIsSet und ignoriert es; das Zurücksenden des Gelesenen ist daher sicher. Das Anlegen einer Entität erfordert einen Wert für jedes Pflicht-Secret-Attribut. Ein gespeichertes, aber nicht lesbares Secret (keyMissing) gilt für diese Prüfung als vorhanden.

Platzhalter haben keine Bedeutung: Ein String wie <client-secret> oder TODO_SET_CLIENT_SECRET, der über eine beliebige API gesendet wird, ist ein gewöhnlicher Wert und wird wie jeder andere verschlüsselt. Nur die einmalige Migration von Legacy-Klartext behandelt solche Strings besonders (siehe Sweep).

Ein Secret setzen oder rotieren​

mutation {
runtime {
systemCommunicationEMailSenderConfigurations {
update(entities: [{ rtId: "65f1c0a2b3d4e5f601234567", item: { password: "example-password" } }]) {
rtId
password {
isSet
}
}
}
}
}

Andere Secrets der Entität bleiben unberührt. Das Ergebnis der Mutation folgt den Leseregeln: Es liefert { isSet }, nie den Wert.

Ein Secret löschen​

Da null „unverändert“ bedeutet, wird explizit gelöscht. clearSecretAttributes: [String!] (Attributnamen in camelCase) steht am Update-Eintrag — neben rtId und item, nicht in item. Sowohl das typisierte <Type>InputUpdate als auch das generische RtEntityUpdate haben es:

mutation {
runtime {
systemCommunicationEMailSenderConfigurations {
update(entities: [{ rtId: "65f1c0a2b3d4e5f601234567", item: {}, clearSecretAttributes: ["password"] }]) {
rtId
password {
isSet
}
}
}
}
}
  • Das Löschen eines Pflicht-Secret-Attributs ist ein Fehler.
  • Dasselbe Secret in einem Update zu setzen und zu löschen ist ein Fehler.
  • MutationDto.ClearSecretAttributes im SDK trägt dieselbe Liste.

Abfragen​

OperationSecret-Attribute
FeldfilterNur IS_NULL und IS_NOT_NULL („gesetzt“ = das Feld existiert und ist nicht null)
SortierungAbgelehnt
Attribut- (Text-)SucheAbgelehnt
Aggregation, Group-byAbgelehnt
Query-Spalten, Zellen von Persistent-Query-ZeilenAbgelehnt
Archiv-Spaltenpfade, CrateDB-SpaltenNicht verfügbar

Entitäten mit konfiguriertem Secret zählen:

query {
runtime {
systemCommunicationEMailSenderConfiguration(
fieldFilter: [{ attributePath: "password", operator: IS_NOT_NULL }]
) {
totalCount
}
}
}

Fehlercodes​

CodeWannDetails
SecretAttributeNotQueryableEin anderer Filter als IS_NULL / IS_NOT_NULL, Sortierung, Suche, Aggregation, Group-by, Query-Spalte oder Query-Zeilen-Zelle auf einem Secret-Attributextensions.attributePath, extensions.operation
ASSET1004 (Validierung)Pflicht-Secret fehlt beim Anlegen (Meldung 2), Löschen eines Nicht-Secrets oder unbekannten Namens (21), Löschen eines Pflicht-Secrets (22), gleichzeitig gesetzt und gelöscht (23), Pflicht-Secret fehlt nach der Record-Übernahme (24), Nicht-String-EingabeMeldungsnummern in OctoDetails als "NN: Text" — nie Werte
SecretEncryptionNotConfiguredDer Server hat keinen Key Ring—

Berechtigungen​

Datenberechtigungen bleiben Zeilenfilter auf Typebene:

  • Lesezugriff auf eine Entität zeigt, ob ihre Secrets gesetzt sind.
  • Das Schreiben eines Secrets erfordert Schreibzugriff auf die Entität.
  • Keine Berechtigung erlaubt das Entschlüsseln über die öffentliche API. Es gibt keinen Entschlüsselungs-Endpunkt.

Zwei Tenant-Rollen regeln die Secret-Verwaltung (der initiale Tenant-Administrator hat beide):

RolleErlaubt
AdminPanelManagementSecrets-Übersicht, Sweep-Reports und die Liste der Sweep-Läufe
SecretManagementSweeps starten und einen Pre-Sweep-Dump vorzeitig löschen (siehe Sweep). Angelegt von System.Identity.Bootstrap; Konstante CommonConstants.SecretManagementRole

Secrets werden nur in vertrauenswürdigen Services entschlüsselt: im Communication Controller, wenn er die Adapter-Konfiguration erstellt, und im Mesh Adapter, wenn eine Pipeline RevealSecret@1 ausführt. Jede Entschlüsselung wird gezählt (octo.secrets.decrypt); Logs enthalten nie den Wert.

Secrets in Records​

Ein Record-Array wird immer als Ganzes geschrieben. Bei Update und Replace, in jeder Verschachtelungstiefe, wird ein Secret-Member, der in einem eingehenden Element weggelassen, null oder "" ist, aus dem gespeicherten Element mit demselben Record-Key übernommen (der recordKey des Record-Typs). Ein einzelnes Record-Attribut übernimmt über die Position. Keys werden über den Wert verglichen (Ganzzahlen über numerische Typen hinweg, sonst ordinaler Text). Ein Record-Array ohne Key (Modelle von vor der recordKey-Regel) erhält keine Übernahme.

Beispiel: Der gespeicherte Wert hat zwei Elemente mit den Keys smtp und imap; das Update sendet:

[
{ "path": "smtp", "secretValue": null },
{ "path": "imap", "secretValue": "example-password-1" },
{ "path": "pop3", "secretValue": "example-password-2" }
]

Ergebnis: smtp behält sein gespeichertes Secret, imap erhält das neue Secret, pop3 wird hinzugefügt. Ein Element, das nicht mehr gesendet wird, wird mitsamt seinem Secret entfernt. Ein Secret-Member in einem Record kann nicht einzeln gelöscht werden — clearSecretAttributes benennt nur Attribute der obersten Ebene; lassen Sie das Element weg, um es zu entfernen, oder senden Sie einen neuen Wert, um es zu ersetzen.

In der Projektion zeigt der Secret-Member eines Record-Elements { isSet } wie ein typisiertes Feld (endpoints { key label token { isSet } }).

Identity Provider (REST)​

Der Identity Service speichert das ClientSecret der OAuth-Provider als Secret-Attribut (System.Identity 2.23). Für GoogleIdentityProviderDto, MicrosoftIdentityProviderDto, FacebookIdentityProviderDto und AzureEntraIdProviderDto auf {tenantId}/v1/identityProviders gilt:

OperationclientSecret
Antworten auf GET (Liste und per ID), POST und PUTImmer null (nur schreibbar). Die PUT-Antwort wird aus der gespeicherten Entität erstellt, nicht zurückgespiegelt
POSTPflicht, nicht leer
PUTNicht leer rotiert; null, "" oder weggelassen behalten das gespeicherte Secret. Es kann nicht gelöscht werden (das Secret ist Pflicht)

Ein Wert, der wie ein Platzhalter aussieht (<…>, TODO_SET_*), ist ein gewöhnliches Client Secret. Die Antworten tragen statt des Werts den Zustand (jedes Feld entfällt bei null):

FeldBedeutung
clientSecretIsSet: booleanEin lesbares Client Secret ist gespeichert
clientSecretKeyMissing: booleanEin Client Secret ist gespeichert, aber seine Key-ID fehlt im Key Ring (clientSecretIsSet ist dann false). Der Provider wird bei der Anmeldung mit einem Fehler-Log übersprungen, bis das Secret erneut eingegeben wird
clientSecretSetAt: string (ISO-8601)Wann das aktuelle Client Secret gesetzt wurde; null für Legacy-Werte

octo-cli -c UpdateIdentityProvider und das MCP-Tool update_identity_provider senden ein Client Secret nur, wenn ein neues angegeben wird (siehe Identity Provider).

Nicht lesbare Secrets (Key Missing)​

Ein geschützter Wert, dessen Key-ID nicht im Key Ring der Umgebung ist — typischerweise nach dem Restore eines Dumps aus einer anderen Umgebung oder eines Child-Tenants — bleibt verschlüsselt gespeichert:

  • Leser sehen isSet: false und keyMissing: true (generisch: secretIsSet: false, secretKeyMissing: true); die Secrets-Übersicht meldet die Form KEY_MISSING und führt den Wert als Neueingabe-Aufgabe.
  • Services, die den Wert brauchen (Adapter-Konfiguration, RevealSecret@1, Identity Provider, AI-Services), behandeln ihn als nicht gesetzt und loggen einen Fehler ohne den Wert.
  • Er wird automatisch wieder lesbar, sobald seine Key-ID dem Key Ring hinzugefügt wird.
  • Er wird nur durch die Eingabe eines neuen Werts (Neueingabe), durch Löschen mit clearSecretAttributes (optionale Secrets) oder durch den Admin-Sweep CleanupUnreadable entfernt. Keine API entschlüsselt oder exportiert Klartext.

Ein gespeicherter Wert, der sich gar nicht parsen lässt, gilt ebenfalls als nicht gesetzt (Form CORRUPT, Warnung im Log).

Secrets-Übersicht (Admin-GraphQL)​

Das Asset Repository bietet eine Tenant-weite Übersicht über alle Secret-Attribute, einschließlich der Client Secrets der Identity Provider (System.Identity/*) und der AI-Zugangsdaten (System.Ai/*). Sie erfordert die Tenant-Rolle AdminPanelManagement (sonst Fehlercode Forbidden) und liefert nie Werte. Es gibt keine Aggregation über Child-Tenants: Fragen Sie den Endpunkt jedes Child-Tenants ab.

type Query { secrets: SecretsQuery }

type SecretsQuery {
inventory(
first: Int = 50, after: String,
ckTypeId: String, # exakter CK-Typ (abgeleitete Typen eingeschlossen)
forms: [SecretStorageForm!],
needsReEntry: Boolean, # nur Neueingabe-Aufgaben
search: String # rtId, rtWellKnownName, Anzeigename, Attributpfad (nie Werte)
): SecretInventoryConnection!
summary: SecretInventorySummary!
usages(ckTypeId: String!, rtId: OctoObjectId!, attributePath: String!): [SecretUsage!]!
}

enum SecretStorageForm { NOT_SET, PLAINTEXT, ENC_V1, ENC_V2, KEY_MISSING, CORRUPT }

type SecretInventoryItem {
ckTypeId: String!
rtId: OctoObjectId!
rtWellKnownName: String
displayName: String
attributePath: String! # camelCase; Record-Member als "endpoints[key=prod].token" / "credentials.token"
attributeName: String! # CK-Attributname des Attributs der obersten Ebene
required: Boolean!
form: SecretStorageForm!
keyId: String # nur ENC_V2 / KEY_MISSING
setAt: DateTime
needsReEntry: Boolean! # KEY_MISSING oder CORRUPT, oder NOT_SET und Pflicht
usedBy: [SecretUsage!]!
}

type SecretUsage {
dataFlowRtId: OctoObjectId
dataFlowName: String
pipelineRtId: OctoObjectId!
pipelineName: String
nodePath: String!
match: SecretUsageMatch! # EXACT: RevealSecret@1 mit ckTypeId + rtId + attributeName; BY_TYPE: rtIdPath auf demselben Typ und Attribut
}

type SecretInventorySummary {
total: Int!, notSet: Int!, plaintext: Int!, encV1: Int!, encV2: Int!, keyMissing: Int!, corrupt: Int!, needsReEntry: Int!
encV2ByKeyId: [KeyIdCount!]!
}
FormBedeutung
NOT_SETKein Wert
PLAINTEXTLegacy-Klartext noch gespeichert (vor dem Encrypt-Sweep)
ENC_V1Legacy-Wert enc:v1 (Instance Key)
ENC_V2Geschützt, Key-ID bekannt
KEY_MISSINGGeschützt, Key-ID nicht im Key Ring dieser Umgebung
CORRUPTGespeicherter Wert lässt sich nicht parsen (gilt als nicht gesetzt)

Neueingabe-Aufgaben sind live: inventory(needsReEntry: true). Eine Aufgabe ist erledigt, wenn ein neuer Wert gesetzt wurde (→ ENC_V2), das optionale Secret gelöscht wurde oder der Sweep CleanupUnreadable es entfernt hat.

query {
secrets {
summary { total keyMissing needsReEntry encV2ByKeyId { keyId count } }
inventory(needsReEntry: true, first: 20) {
totalCount
items { ckTypeId rtId displayName attributePath form keyId setAt }
}
}
}

Exporte, Backups, MCP und octo-cli​

  • ExportRt und Deep-Graph-Exporte enthalten nie Secret-Werte (Ownership Secret).
  • Backups (Dumps) enthalten die verschlüsselten Werte; ein Restore behält sie verschlüsselt (siehe Nicht lesbare Secrets und Restore über Umgebungen hinweg).
  • Debug-Snapshots von Pipelines maskieren offengelegte Werte als *** und listen die maskierten Stellen in redactedPaths (siehe RevealSecret@1).
  • MCP (Tool-Referenz): Jedes Tool, das Entitäten liefert, zeigt value: null plus secretIsSet. create_entity und update_entity (mittleres Risiko) lehnen einen nicht leeren Secret-Wert ab; null, "" oder ein zurückgesendetes { "isSet": … } lassen das Secret unverändert, und update_entity akzeptiert clearSecretAttributes. Ein Secret wird mit dem Tool set_entity_secrets (hohes Risiko) gesetzt oder rotiert; eine Entität, deren Typ ein Pflicht-Secret hat, wird mit dem Tool create_entity_with_secrets (hohes Risiko) angelegt. Abfrage- und Aggregations-Tools lehnen Secret-Attribute mit SecretAttributeNotQueryable ab. get_secret_status und start_secret_sweep decken den Verschlüsselungsstatus und den Sweep ab.
  • octo-cli: SecretStatus, ReprotectSecrets und DeleteSecretSweepDump (siehe Secret Encryption Key Ring).

Siehe auch​