Maestro is the UI/CA, not the enforcement: the auth service owns its
authorized-keys trust store. This is Maestro's side of the flow.
- lib/authmgmt: mTLS management client (Maestro's own identity cert from
the instance CA) for /internal/{attestation,authorizations,revoke,
pending}; RevokeServiceBestEffort for uninstall.
- handlers/peers: GET /api/peers, /api/peers/attestation, POST
approve/revoke.
- uninstall: revoke the service's authorization in the auth trust store
alongside the CRL identity revocation.
- frontend: TrustedServices panel (pending first-contact authorizations +
approved keys with authorize/ignore/revoke actions).
- spec.yaml: document the new endpoints and schemas.
- housekeeping: untrack pki test log artifacts, gofmt fixes in pki.
549 lines
16 KiB
YAML
549 lines
16 KiB
YAML
openapi: 3.0.3
|
|
info:
|
|
title: Dcntrlized Maestro API
|
|
version: 1.0.0
|
|
description: The API of the Maestro orchestration router. Maestro manages the lifecycle of Dcntrlized service containers and routes inter-service action calls to them.
|
|
|
|
components:
|
|
schemas:
|
|
ActionRequest:
|
|
type: object
|
|
properties:
|
|
payload:
|
|
type: object
|
|
description: Action-specific parameters
|
|
ActionResponse:
|
|
type: object
|
|
properties:
|
|
status:
|
|
type: string
|
|
data:
|
|
type: object
|
|
|
|
SuccessResponse:
|
|
type: object
|
|
properties:
|
|
status:
|
|
type: string
|
|
example: "success"
|
|
message:
|
|
type: string
|
|
|
|
PendingAuthorization:
|
|
type: object
|
|
description: A first-contact authorization request awaiting the instance owner (M2 §3).
|
|
properties:
|
|
service:
|
|
type: string
|
|
example: "keepnote"
|
|
fingerprint:
|
|
type: string
|
|
example: "sha256:aa:bb:cc:dd"
|
|
first_seen:
|
|
type: string
|
|
last_seen:
|
|
type: string
|
|
|
|
AuthorizedKey:
|
|
type: object
|
|
description: An approved peer row of the auth service's trust store.
|
|
properties:
|
|
service:
|
|
type: string
|
|
fingerprint:
|
|
type: string
|
|
approved_by:
|
|
type: string
|
|
approved_at:
|
|
type: string
|
|
revoked_at:
|
|
type: string
|
|
nullable: true
|
|
|
|
PeerAction:
|
|
type: object
|
|
required: [service, fingerprint]
|
|
properties:
|
|
service:
|
|
type: string
|
|
description: The service name (cert CN / org.dcntrlized.service).
|
|
fingerprint:
|
|
type: string
|
|
description: The SHA256 public-key fingerprint (sha256:…) shown to the owner.
|
|
|
|
Attestation:
|
|
type: object
|
|
description: The auth service's public signing key statement (D3).
|
|
properties:
|
|
public_key:
|
|
type: string
|
|
fingerprint:
|
|
type: string
|
|
example: "sha256:…"
|
|
kid:
|
|
type: string
|
|
issued_at:
|
|
type: string
|
|
|
|
RegistryContainer:
|
|
type: object
|
|
required: [name, image]
|
|
properties:
|
|
name:
|
|
type: string
|
|
example: "keepnote"
|
|
version:
|
|
type: string
|
|
example: "0.0.1"
|
|
image:
|
|
type: string
|
|
example: "192.168.122.183:3000/dcntrlized/keepnote:dev"
|
|
digest:
|
|
type: string
|
|
title:
|
|
type: string
|
|
example: "KeepNote"
|
|
vendor:
|
|
type: string
|
|
author:
|
|
type: string
|
|
description:
|
|
type: string
|
|
source:
|
|
type: string
|
|
revision:
|
|
type: string
|
|
licenses:
|
|
type: string
|
|
url:
|
|
type: string
|
|
documentation:
|
|
type: string
|
|
created_at:
|
|
type: string
|
|
hidden:
|
|
type: boolean
|
|
description: Hidden infrastructure containers (e.g. the shared dcntrlized-db) are excluded from the service grid.
|
|
example: true
|
|
|
|
PortMapping:
|
|
type: object
|
|
properties:
|
|
host:
|
|
type: integer
|
|
guest:
|
|
type: integer
|
|
|
|
IPAddress:
|
|
type: object
|
|
properties:
|
|
version:
|
|
type: string
|
|
enum: [v4, v6]
|
|
address:
|
|
type: string
|
|
|
|
Network:
|
|
type: object
|
|
properties:
|
|
name:
|
|
type: string
|
|
ipAddress:
|
|
$ref: '#/components/schemas/IPAddress'
|
|
mode:
|
|
type: string
|
|
enum: [bridge, host, none, overlay, ipvlan, macvlan]
|
|
ports:
|
|
type: array
|
|
items:
|
|
$ref: '#/components/schemas/PortMapping'
|
|
|
|
Container:
|
|
type: object
|
|
properties:
|
|
id:
|
|
type: string
|
|
name:
|
|
type: string
|
|
service_name:
|
|
type: string
|
|
role:
|
|
type: string
|
|
example: "db"
|
|
hidden:
|
|
type: boolean
|
|
description: Hidden infrastructure containers (e.g. the shared dcntrlized-db) are excluded from the service grid.
|
|
capabilities:
|
|
type: array
|
|
items:
|
|
type: string
|
|
example: "database"
|
|
description: Capabilities declared by the service manifest (e.g. database).
|
|
has_auth:
|
|
type: boolean
|
|
description: >-
|
|
Whether the service consumes the shared auth service (manifest
|
|
has_auth). True for dcntrlized-auth itself and for auth-consuming
|
|
services.
|
|
public_key:
|
|
type: string
|
|
description: >-
|
|
The auth service's RS256 public key (manifest public_key); the hook
|
|
for a future Maestro dashboard auth that validates dcntrlized-auth
|
|
tokens.
|
|
docker_name:
|
|
type: string
|
|
image:
|
|
type: string
|
|
version:
|
|
type: string
|
|
status:
|
|
type: string
|
|
created_at:
|
|
type: string
|
|
network:
|
|
$ref: '#/components/schemas/Network'
|
|
title:
|
|
type: string
|
|
vendor:
|
|
type: string
|
|
author:
|
|
type: string
|
|
description:
|
|
type: string
|
|
source:
|
|
type: string
|
|
|
|
paths:
|
|
/api/actions/{containerId}/{actionName}:
|
|
parameters:
|
|
- name: actionName
|
|
in: path
|
|
required: true
|
|
description: The name of the specific action to execute
|
|
schema:
|
|
type: string
|
|
- name: containerId
|
|
in: path
|
|
required: true
|
|
description: The ID or name of the target container/service. Accepts a full or partial container ID, a container name, or the service name (matched case-insensitively).
|
|
schema:
|
|
type: string
|
|
get:
|
|
summary: Retrieve data or status from a service action
|
|
tags: ["actions"]
|
|
description: Forwards the request (method, headers, query string, and body) to the target container's /action/{actionName} endpoint. The target service is responsible for authentication; any client-supplied auth headers are forwarded as-is.
|
|
responses:
|
|
'200':
|
|
description: Request successful
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ActionResponse'
|
|
'400':
|
|
description: Bad Request - Invalid input parameters
|
|
'401':
|
|
description: Unauthorized - the target service rejected the request
|
|
'404':
|
|
description: Action or container not found
|
|
'500':
|
|
description: Failed to forward the request to the container
|
|
post:
|
|
summary: Execute a state-changing action in the service
|
|
tags: ["actions"]
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ActionRequest'
|
|
responses:
|
|
'200':
|
|
description: Action executed successfully
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ActionResponse'
|
|
'400':
|
|
description: Bad Request - Invalid input parameters
|
|
'401':
|
|
description: Unauthorized - the target service rejected the request
|
|
'404':
|
|
description: Action or container not found
|
|
'500':
|
|
description: Failed to forward the request to the container
|
|
put:
|
|
summary: Update configuration for a specific action
|
|
tags: ["actions"]
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ActionRequest'
|
|
responses:
|
|
'200':
|
|
description: Configuration updated
|
|
'400':
|
|
description: Bad Request - Invalid input parameters
|
|
'401':
|
|
description: Unauthorized - the target service rejected the request
|
|
'404':
|
|
description: Action or container not found
|
|
'500':
|
|
description: Failed to forward the request to the container
|
|
delete:
|
|
tags: ["actions"]
|
|
summary: Reset or remove an action's state
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/ActionRequest'
|
|
responses:
|
|
'200':
|
|
description: Action state reset
|
|
'400':
|
|
description: Bad Request - Invalid input parameters
|
|
'401':
|
|
description: Unauthorized - the target service rejected the request
|
|
'404':
|
|
description: Action or container not found
|
|
'500':
|
|
description: Failed to forward the request to the container
|
|
|
|
/api/containers:
|
|
get:
|
|
summary: List the services Maestro manages
|
|
tags: ["containers"]
|
|
responses:
|
|
'200':
|
|
description: List of services
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: array
|
|
items:
|
|
$ref: '#/components/schemas/Container'
|
|
'500':
|
|
description: Internal server error
|
|
|
|
/api/containers/install:
|
|
post:
|
|
summary: Install a service container (downloads the compose file and pulls the image)
|
|
tags: ["containers"]
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/RegistryContainer'
|
|
responses:
|
|
'200':
|
|
description: Container downloaded and image pulled successfully
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/SuccessResponse'
|
|
'400':
|
|
description: Invalid JSON payload or missing required fields (name, image)
|
|
'500':
|
|
description: Failed to install container
|
|
|
|
/api/containers/uninstall:
|
|
post:
|
|
summary: Uninstall a service container and remove it from the cache
|
|
tags: ["containers"]
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
required: [name]
|
|
properties:
|
|
name:
|
|
type: string
|
|
responses:
|
|
'200':
|
|
description: Container uninstalled successfully
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/SuccessResponse'
|
|
'400':
|
|
description: Invalid JSON payload or missing required field (name)
|
|
'500':
|
|
description: Failed to uninstall container
|
|
|
|
/api/containers/start:
|
|
post:
|
|
summary: Start a previously installed service container
|
|
tags: ["containers"]
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
required: [name]
|
|
properties:
|
|
name:
|
|
type: string
|
|
responses:
|
|
'200':
|
|
description: Container started successfully
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/SuccessResponse'
|
|
'400':
|
|
description: Invalid JSON payload or missing required field (name)
|
|
'500':
|
|
description: Failed to start container
|
|
|
|
/api/containers/stop:
|
|
post:
|
|
summary: Stop a running service container
|
|
tags: ["containers"]
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
required: [name]
|
|
properties:
|
|
name:
|
|
type: string
|
|
responses:
|
|
'200':
|
|
description: Container stopped successfully
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/SuccessResponse'
|
|
'400':
|
|
description: Invalid JSON payload or missing required field (name)
|
|
'500':
|
|
description: Failed to stop container
|
|
|
|
/api/registry:
|
|
get:
|
|
summary: List the services available in the Dcntrlized registry
|
|
tags: ["registry"]
|
|
responses:
|
|
'200':
|
|
description: List of registry services
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: array
|
|
items:
|
|
$ref: '#/components/schemas/RegistryContainer'
|
|
'500':
|
|
description: Internal server error
|
|
'502':
|
|
description: Failed to connect to upstream registry
|
|
|
|
/api/ws:
|
|
get:
|
|
summary: WebSocket endpoint for live container events
|
|
tags: ["containers"]
|
|
description: Establishes a WebSocket connection that streams container lifecycle events as JSON.
|
|
responses:
|
|
'101':
|
|
description: Switching Protocols - WebSocket upgrade
|
|
|
|
/api/peers:
|
|
get:
|
|
summary: List pending authorizations and the approved peer trust store
|
|
tags: ["security"]
|
|
description: >
|
|
Security theme M2 §3. Returns the shared auth service's first-contact
|
|
authorization requests (services whose certificate is valid but has
|
|
not been approved by the instance owner yet) and the approved keys.
|
|
Maestro is only the UI; the auth service owns the enforcement.
|
|
responses:
|
|
'200':
|
|
description: Combined pending + approved trust state
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
pending:
|
|
type: array
|
|
items:
|
|
$ref: '#/components/schemas/PendingAuthorization'
|
|
authorized:
|
|
type: array
|
|
items:
|
|
$ref: '#/components/schemas/AuthorizedKey'
|
|
'500':
|
|
description: Internal server error (auth management client unavailable)
|
|
'502':
|
|
description: Failed to reach the shared auth service
|
|
|
|
/api/peers/attestation:
|
|
get:
|
|
summary: Read the auth service's public signing key statement
|
|
tags: ["security"]
|
|
description: >
|
|
The auth service's RSA public key, its kid, and the sha256 fingerprint
|
|
peers should pin after first contact (TOFU, D3).
|
|
responses:
|
|
'200':
|
|
description: Auth public key attestation
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/Attestation'
|
|
'502':
|
|
description: Failed to reach the shared auth service
|
|
|
|
/api/peers/approve:
|
|
post:
|
|
summary: Approve a peer's fingerprint in the auth service trust store
|
|
tags: ["security"]
|
|
description: >
|
|
Forwards the instance owner's approval for a service's identity
|
|
fingerprint. After this call the service's requests to /auth/* proceed
|
|
normally (enforced by the auth service itself).
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/PeerAction'
|
|
responses:
|
|
'200':
|
|
description: Peer approved
|
|
'400':
|
|
description: Missing service or fingerprint
|
|
'502':
|
|
description: Failed to reach the shared auth service
|
|
|
|
/api/peers/revoke:
|
|
post:
|
|
summary: Revoke a peer's fingerprint (returns it to the no-op path)
|
|
tags: ["security"]
|
|
description: >
|
|
Forwards the instance owner's revocation. Calls from that fingerprint
|
|
become uniform 403 no-ops again immediately (the SSH authorized_keys
|
|
"delete the line" action).
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/PeerAction'
|
|
responses:
|
|
'200':
|
|
description: Peer revoked
|
|
'400':
|
|
description: Missing service or fingerprint
|
|
'502':
|
|
description: Failed to reach the shared auth service
|