feat(config): per-service operator config + secrets (theme M1)
Services can declare an operator-configurable config block in /_manifest
(ordered array of single-key objects, pure schema: type cleartext|password,
description?, key = container env var name). Maestro:
- lib/secrets: AES-256-GCM FileStore; dedicated secrets.key (32 bytes,
chmod 600, separate from session.key); secrets.json rows keyed
service/field; cleartext read-write, password write-only & encrypted at
rest, decrypted only at injection. Footure key rotation = delete the key.
- handlers/config (owner-gated via auth.RequireSession):
GET /api/services/{service}/config (merged schema+set view, password
values never returned), PUT/DELETE /api/services/{service}/config/{key}
(uniform ERR_NOT_FOUND / ERR_INVALID_PAYLOAD error shape).
- start.go: injectConfigSecrets runs after injectServiceIdentity; only
operator-set rows become env on non-oneoff services; unset fields keep
the compose default.
- Manifest.Config carried onto Container/RegistryContainer/InstalledMetadata
via updateInstalledMetadataFromManifest + watcher.
- Frontend: per-service Settings dialog (ServiceConfigDialog) + api
helpers; cleartext editable, password set/unset indicator only.
- spec.yaml documents the new endpoints/schemas.
This commit is contained in:
@@ -334,6 +334,14 @@ components:
|
||||
The auth service's RS256 public key (manifest public_key); the hook
|
||||
for a future Maestro dashboard auth that validates dcntrlized-auth
|
||||
tokens.
|
||||
config:
|
||||
type: array
|
||||
description: >-
|
||||
Operator-configurable fields the service declares in its manifest
|
||||
config block (Service Config & Secrets theme). Each element is a
|
||||
single-key object: the key names the field, the value is its spec.
|
||||
items:
|
||||
$ref: '#/components/schemas/ServiceConfigDeclaration'
|
||||
docker_name:
|
||||
type: string
|
||||
image:
|
||||
@@ -357,6 +365,70 @@ components:
|
||||
source:
|
||||
type: string
|
||||
|
||||
ServiceConfigField:
|
||||
type: object
|
||||
description: >-
|
||||
The spec of one operator-configurable field declared by a service's
|
||||
manifest config block (Service Config & Secrets theme). The manifest is
|
||||
a pure schema — it never carries a value.
|
||||
required: [type, key]
|
||||
properties:
|
||||
type:
|
||||
type: string
|
||||
enum: [cleartext, password]
|
||||
description: cleartext is read-write; password is write-only and encrypted at rest.
|
||||
description:
|
||||
type: string
|
||||
description: Optional human-readable explanation shown in the UI.
|
||||
key:
|
||||
type: string
|
||||
description: The container env var name Maestro injects the value under.
|
||||
|
||||
ServiceConfigDeclaration:
|
||||
type: object
|
||||
description: >-
|
||||
A single-key config declaration inside a service manifest's config
|
||||
array. The key names the field; the value is the field spec.
|
||||
additionalProperties:
|
||||
$ref: '#/components/schemas/ServiceConfigField'
|
||||
minProperties: 1
|
||||
maxProperties: 1
|
||||
|
||||
ServiceConfigEntry:
|
||||
type: object
|
||||
description: >-
|
||||
The read-only merged view of one config field served by
|
||||
GET /api/services/{service}/config: the schema plus the operator's set
|
||||
state. Password values are never included; a cleartext value
|
||||
(read-write) is.
|
||||
required: [field, key, type, set]
|
||||
properties:
|
||||
field:
|
||||
type: string
|
||||
description: The manifest config field name (the config array element key).
|
||||
key:
|
||||
type: string
|
||||
description: The container env var name.
|
||||
type:
|
||||
type: string
|
||||
enum: [cleartext, password]
|
||||
description:
|
||||
type: string
|
||||
set:
|
||||
type: boolean
|
||||
description: Whether a value is stored. The only thing a password row reveals.
|
||||
value:
|
||||
type: string
|
||||
description: The stored value of a cleartext field; never present for passwords.
|
||||
|
||||
ServiceConfigValue:
|
||||
type: object
|
||||
description: Request body for setting a config value (PUT .../config/{key}).
|
||||
required: [value]
|
||||
properties:
|
||||
value:
|
||||
type: string
|
||||
|
||||
paths:
|
||||
/api/actions/{containerId}/{actionName}:
|
||||
parameters:
|
||||
@@ -858,3 +930,97 @@ paths:
|
||||
description: Missing service or fingerprint
|
||||
'502':
|
||||
description: Failed to reach the shared auth service
|
||||
|
||||
/api/services/{service}/config:
|
||||
parameters:
|
||||
- name: service
|
||||
in: path
|
||||
required: true
|
||||
description: >-
|
||||
The service (matched case-insensitively against the install record /
|
||||
container label, as in /api/containers).
|
||||
schema:
|
||||
type: string
|
||||
get:
|
||||
summary: Merge a service's declared config schema with its stored values
|
||||
tags: ["service-config"]
|
||||
description: >-
|
||||
Owner-gated (Service Config & Secrets theme M1). Lists each field the
|
||||
service's manifest config block declares, merged with the operator's
|
||||
set state. Password fields are only ever reported as set:false/true;
|
||||
cleartext fields (read-write) carry their stored value.
|
||||
responses:
|
||||
'200':
|
||||
description: The merged per-field view
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/ServiceConfigEntry'
|
||||
'401':
|
||||
description: ERR_UNAUTHENTICATED - no valid owner session
|
||||
'404':
|
||||
description: ERR_NOT_FOUND - unknown service
|
||||
'428':
|
||||
description: ERR_SETUP_REQUIRED - owner setup not completed
|
||||
|
||||
/api/services/{service}/config/{key}:
|
||||
parameters:
|
||||
- name: service
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
- name: key
|
||||
in: path
|
||||
required: true
|
||||
description: The manifest config field name (the config array element key).
|
||||
schema:
|
||||
type: string
|
||||
put:
|
||||
summary: Set a service config value
|
||||
tags: ["service-config"]
|
||||
description: >-
|
||||
Owner-gated (Service Config & Secrets theme M1). Stores the value for
|
||||
the field declared by the service manifest. Cleartext fields are
|
||||
stored plaintext (read-write); password fields are AES-256-GCM
|
||||
encrypted at rest, keyed by <dataRoot>/maestro/secrets.key, and never
|
||||
returned again — the response is only a ServiceConfigEntry with set:true.
|
||||
The value is injected as the field's env key on the next start.
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ServiceConfigValue'
|
||||
responses:
|
||||
'200':
|
||||
description: Value stored
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ServiceConfigEntry'
|
||||
'400':
|
||||
description: ERR_INVALID_PAYLOAD - unparseable body or empty password
|
||||
'401':
|
||||
description: ERR_UNAUTHENTICATED - no valid owner session
|
||||
'404':
|
||||
description: ERR_NOT_FOUND - unknown service or config field
|
||||
'428':
|
||||
description: ERR_SETUP_REQUIRED - owner setup not completed
|
||||
delete:
|
||||
summary: Clear a service config value
|
||||
tags: ["service-config"]
|
||||
description: >-
|
||||
Owner-gated (Service Config & Secrets theme M1). Removes the stored
|
||||
value; on the next start the compose file's own default applies.
|
||||
responses:
|
||||
'200':
|
||||
description: Value cleared
|
||||
'401':
|
||||
description: ERR_UNAUTHENTICATED - no valid owner session
|
||||
'404':
|
||||
description: ERR_NOT_FOUND - unknown service or config field
|
||||
'428':
|
||||
description: ERR_SETUP_REQUIRED - owner setup not completed
|
||||
|
||||
Reference in New Issue
Block a user