work
This commit is contained in:
119
docs/modules/scanner/design/surface-secrets-schema.md
Normal file
119
docs/modules/scanner/design/surface-secrets-schema.md
Normal file
@@ -0,0 +1,119 @@
|
||||
# Surface.Secrets Schema (Security-approved)
|
||||
|
||||
Status: Final · 2025-11-23
|
||||
Owners: Security Guild · Scanner Guild · Zastava Guild
|
||||
Related tasks: SURFACE-SECRETS-01/02/03/04/05/06, SCANNER-SECRETS-03, ZASTAVA-SECRETS-01/02
|
||||
|
||||
## Purpose
|
||||
Canonical schema for declaring and validating secret providers used by Surface consumers (Scanner, Zastava, Scheduler). Supersedes the draft notes in `surface-secrets.md` §4–5 and unblocks validation + integration tasks.
|
||||
|
||||
## Configuration Document
|
||||
Location: `Surface:Secrets` (appsettings, env, or offline kit manifest). JSON Schema (v2020-12):
|
||||
```json
|
||||
{
|
||||
"$id": "https://stellaops/policy/surface-secrets.schema.json",
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"type": "object",
|
||||
"required": ["provider", "providers"],
|
||||
"properties": {
|
||||
"provider": {"type": "string", "enum": ["kubernetes", "file", "inline"], "description": "active provider id"},
|
||||
"fallbackProvider": {"type": "string", "enum": ["kubernetes", "file", "inline"], "nullable": true},
|
||||
"providers": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"kubernetes": {
|
||||
"type": "object",
|
||||
"required": ["namespace", "prefix"],
|
||||
"properties": {
|
||||
"namespace": {"type": "string", "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$"},
|
||||
"prefix": {"type": "string", "default": "surface-"},
|
||||
"ttlSeconds": {"type": "integer", "minimum": 30, "default": 600},
|
||||
"allowServiceAccountFallback": {"type": "boolean", "default": false}
|
||||
}
|
||||
},
|
||||
"file": {
|
||||
"type": "object",
|
||||
"required": ["root"],
|
||||
"properties": {
|
||||
"root": {"type": "string", "pattern": "^/.+"},
|
||||
"format": {"type": "string", "enum": ["json", "yaml"], "default": "json"},
|
||||
"permissions": {"type": "string", "enum": ["0600"], "default": "0600"}
|
||||
}
|
||||
},
|
||||
"inline": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"payloadBase64": {"type": "string", "contentEncoding": "base64"},
|
||||
"enabled": {"type": "boolean", "default": false}
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"requiredSecrets": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": ["tenant", "component", "secretType"],
|
||||
"properties": {
|
||||
"tenant": {"type": "string"},
|
||||
"component": {"type": "string"},
|
||||
"secretType": {"type": "string", "enum": ["cas-access", "registry", "attestation", "tls"]},
|
||||
"name": {"type": "string"}
|
||||
}
|
||||
},
|
||||
"uniqueItems": true
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
```
|
||||
|
||||
### Secret Object Shape
|
||||
Secrets resolved by providers must conform to one of:
|
||||
- `cas-access`: `{ "accessKey": "...", "secretKey": "...", "sessionToken": "..." }`
|
||||
- `registry`: `{ "username": "...", "password": "..." }` or `{ "token": "..." }`
|
||||
- `attestation`: `{ "privateKeyPem": "...", "keyId": "...", "rekorApiKey": "..." }`
|
||||
- `tls`: `{ "certPem": "...", "keyPem": "..." }`
|
||||
Binary values may be base64 but must decode to UTF-8 cleanly; otherwise return base64 string and mark `isBinary: true` in handle metadata.
|
||||
|
||||
## Determinism & Validation
|
||||
- Deterministic ordering: providers resolved in request order; when multiple secrets share tenant/component/type, choose lexicographically smallest `name` then earliest `ingestedAt` metadata.
|
||||
- `Surface.Validation` codes: `SURFACE_SECRET_PROVIDER_UNKNOWN`, `SURFACE_SECRET_MISSING`, `SURFACE_SECRET_STALE`, `SURFACE_SECRET_FORMAT_INVALID` (new).
|
||||
- Validators must reject world-readable files (`permissions != 0600`) and inline payloads when `enabled=false` (air-gapped safety).
|
||||
|
||||
## Offline / Air-Gap Profile
|
||||
- Offline kit must ship `offline/secrets/manifest.json` matching this schema with hashes for each secret blob (SHA-256 hex) and `createdAt` in UTC.
|
||||
- Importer scripts map manifest entries to file provider layout: `<root>/<tenant>/<component>/<secretType>/<name>.json`.
|
||||
- No external network calls during validation or resolution.
|
||||
|
||||
## Examples
|
||||
Minimal Kubernetes config:
|
||||
```json
|
||||
{
|
||||
"provider": "kubernetes",
|
||||
"providers": {"kubernetes": {"namespace": "stellaops-runtime", "prefix": "surface-"}},
|
||||
"requiredSecrets": [
|
||||
{"tenant": "acme", "component": "scanner-worker", "secretType": "cas-access"},
|
||||
{"tenant": "acme", "component": "scanner-worker", "secretType": "registry", "name": "primary"}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
File provider (offline):
|
||||
```json
|
||||
{
|
||||
"provider": "file",
|
||||
"providers": {"file": {"root": "/etc/stellaops/secrets", "format": "json"}},
|
||||
"requiredSecrets": []
|
||||
}
|
||||
```
|
||||
|
||||
## Implementation Notes
|
||||
- Aligns with design doc `surface-secrets.md`; this file is the authoritative schema for SURFACE-SECRETS-01/02/03.
|
||||
- `ISurfaceSecretProvider` must emit metadata `{ "provider": "kubernetes", "ageDays": <int>, "isBinary": <bool> }` for observability.
|
||||
- Add JSON Schema to `schemas/surface/surface-secrets.schema.json` (mirrored in Offline Kit) when code lands.
|
||||
|
||||
## Acceptance
|
||||
- SECURITY sign-off requires adherence to this schema and validation rules.
|
||||
- SURFACE-SECRETS-01 moves to DONE; SURFACE-SECRETS-02 to TODO (implementation); SURFACE-VAL-01 unblocks because schema is defined.
|
||||
Reference in New Issue
Block a user