Fresh boots opened the owner/token/session stores before EnsureDataRoot dropped to the host user, leaving a root-owned maestro/ state dir whose EACCES surfaced as a bogus ERR_INVALID_CREDENTIALS on /api/auth/setup. main() now runs the handoff first; EnsureDataRoot heals pre-existing root-owned state dirs while still root (healOwnerState). Storage failures from the owner store now answer 500 ERR_INTERNAL everywhere (status/setup/login/change-password/RequireSession/ GateWebSocket) instead of blaming credentials or re-advertising setup mode; SetupRequired returns the store error instead of swallowing it, and FileOwnerStore.Verify propagates read errors rather than masking them as unknown-user.
861 lines
26 KiB
YAML
861 lines
26 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. Every /api/* route except /api/auth/* and the deliberately public GET /api/instance (hub-app theme) requires an instance-owner session (cookies issued by the auth endpoints); in setup mode gated routes answer 428 ERR_SETUP_REQUIRED, otherwise 401 ERR_UNAUTHENTICATED (an unreadable owner store fails closed with 500 ERR_INTERNAL). Mutating requests from browser origins are additionally checked against the request host (CSRF).
|
|
|
|
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
|
|
|
|
ApiError:
|
|
type: object
|
|
description: The uniform API error shape (onboarding theme M1).
|
|
properties:
|
|
error:
|
|
type: object
|
|
properties:
|
|
code:
|
|
type: string
|
|
example: "ERR_UNAUTHENTICATED"
|
|
|
|
AuthStatus:
|
|
type: object
|
|
description: The instance-owner gate state; the SPA bootstraps from it.
|
|
properties:
|
|
setup_required:
|
|
type: boolean
|
|
description: True until the first owner account exists.
|
|
authenticated:
|
|
type: boolean
|
|
username:
|
|
type: string
|
|
|
|
Credentials:
|
|
type: object
|
|
required: [username, password]
|
|
properties:
|
|
username:
|
|
type: string
|
|
description: "3-64 chars: lowercase letters, digits and . _ - ; must start alphanumeric."
|
|
password:
|
|
type: string
|
|
minLength: 10
|
|
|
|
SessionResponse:
|
|
type: object
|
|
description: >-
|
|
Login/setup/rotation result. The access and refresh tokens ride as
|
|
HttpOnly SameSite=Lax cookies (maestro_access / maestro_refresh).
|
|
properties:
|
|
username:
|
|
type: string
|
|
role:
|
|
type: string
|
|
example: "admin"
|
|
access_ttl_s:
|
|
type: integer
|
|
example: 900
|
|
|
|
PasswordChange:
|
|
type: object
|
|
required: [current_password, new_password]
|
|
properties:
|
|
current_password:
|
|
type: string
|
|
new_password:
|
|
type: string
|
|
minLength: 10
|
|
|
|
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
|
|
|
|
InstanceIdentity:
|
|
type: object
|
|
description: The instance's public self-description (hub-app M1).
|
|
required: [name]
|
|
properties:
|
|
name:
|
|
type: string
|
|
example: "home-node"
|
|
title:
|
|
type: string
|
|
description:
|
|
type: string
|
|
fingerprint:
|
|
type: string
|
|
description: Instance CA public-key SHA256 (lib/pki); omitted when no CA is provisioned.
|
|
example: "sha256:…"
|
|
|
|
PublicService:
|
|
type: object
|
|
description: |
|
|
Deliberately reduced view of a running service — no container id,
|
|
docker name, private IP, or port mapping leaves the instance (hub-app D3).
|
|
required: [service_name, status, image]
|
|
properties:
|
|
service_name:
|
|
type: string
|
|
title:
|
|
type: string
|
|
description:
|
|
type: string
|
|
status:
|
|
type: string
|
|
version:
|
|
type: string
|
|
capabilities:
|
|
type: array
|
|
items:
|
|
type: string
|
|
has_auth:
|
|
type: boolean
|
|
image:
|
|
type: string
|
|
|
|
TrustedInstance:
|
|
type: object
|
|
description: A remote instance this instance trusts (federation store row).
|
|
required: [name, url, fingerprint, status]
|
|
properties:
|
|
name:
|
|
type: string
|
|
url:
|
|
type: string
|
|
fingerprint:
|
|
type: string
|
|
example: "sha256:…"
|
|
status:
|
|
type: string
|
|
example: "authorized"
|
|
scopes:
|
|
type: array
|
|
items:
|
|
type: string
|
|
|
|
InstancePublicProfile:
|
|
type: object
|
|
description: |
|
|
The unauthenticated GET /api/instance response (hub-app theme). With
|
|
DCNTRLIZED_PUBLIC_PROFILE=false only `instance` is returned. Absent
|
|
federation store → trusted_instances:null; present-but-empty → [].
|
|
required: [instance]
|
|
properties:
|
|
instance:
|
|
$ref: '#/components/schemas/InstanceIdentity'
|
|
services:
|
|
type: array
|
|
nullable: true
|
|
items:
|
|
$ref: '#/components/schemas/PublicService'
|
|
trusted_instances:
|
|
type: array
|
|
nullable: true
|
|
items:
|
|
$ref: '#/components/schemas/TrustedInstance'
|
|
|
|
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/instance:
|
|
get:
|
|
summary: Public instance profile (unauthenticated)
|
|
tags: ["instance"]
|
|
security: []
|
|
description: >-
|
|
The one read surface the owner gate deliberately allows through
|
|
(hub-app theme M1/D2): what this instance is, which non-hidden services
|
|
it runs (public fields only), and which remote instances it trusts.
|
|
DCNTRLIZED_PUBLIC_PROFILE=false omits services + trusted_instances.
|
|
responses:
|
|
'200':
|
|
description: The instance public profile
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/InstancePublicProfile'
|
|
|
|
/api/auth/status:
|
|
get:
|
|
summary: Instance-owner gate state (public)
|
|
tags: ["auth"]
|
|
description: >-
|
|
The SPA bootstraps from this endpoint. All other /api/* routes are
|
|
gated by the instance-owner session (onboarding theme M1): in setup
|
|
mode they answer 428 ERR_SETUP_REQUIRED, otherwise 401
|
|
ERR_UNAUTHENTICATED without a valid session; an unreadable owner store
|
|
fails closed with 500 ERR_INTERNAL everywhere.
|
|
responses:
|
|
'200':
|
|
description: Gate state
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/AuthStatus'
|
|
'500':
|
|
description: Internal server error
|
|
|
|
/api/auth/setup:
|
|
post:
|
|
summary: Create the first owner account (admin)
|
|
tags: ["auth"]
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/Credentials'
|
|
responses:
|
|
'200':
|
|
description: Owner created; session cookies issued
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/SessionResponse'
|
|
'400':
|
|
description: Invalid credentials (ERR_INVALID_REQUEST / ERR_INVALID_CREDENTIALS)
|
|
'409':
|
|
description: Setup already completed (ERR_SETUP_COMPLETED)
|
|
'429':
|
|
description: Rate limited (ERR_RATE_LIMITED)
|
|
'500':
|
|
description: Owner store unreadable/unwritable (ERR_INTERNAL)
|
|
|
|
/api/auth/login:
|
|
post:
|
|
summary: Log in as the instance owner
|
|
tags: ["auth"]
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/Credentials'
|
|
responses:
|
|
'200':
|
|
description: Session cookies issued
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/SessionResponse'
|
|
'400':
|
|
description: Invalid JSON payload
|
|
'401':
|
|
description: Unknown user or wrong password (ERR_INVALID_CREDENTIALS)
|
|
'429':
|
|
description: Rate limited (ERR_RATE_LIMITED) with Retry-After
|
|
'500':
|
|
description: Owner store unreadable (ERR_INTERNAL)
|
|
|
|
/api/auth/refresh:
|
|
post:
|
|
summary: Rotate the access/refresh pair
|
|
tags: ["auth"]
|
|
description: >-
|
|
Uses the maestro_refresh cookie. Reuse of an already-rotated jti is a
|
|
theft signal and revokes every session of the user
|
|
(ERR_SESSION_REVOKED).
|
|
responses:
|
|
'200':
|
|
description: Fresh pair issued
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/SessionResponse'
|
|
'401':
|
|
description: Missing/invalid/replayed refresh cookie
|
|
|
|
/api/auth/logout:
|
|
post:
|
|
summary: Revoke the current refresh token and clear cookies
|
|
tags: ["auth"]
|
|
responses:
|
|
'200':
|
|
description: Logged out (idempotent, bodyless-safe)
|
|
|
|
/api/auth/change-password:
|
|
post:
|
|
summary: Change the owner's password (session required)
|
|
tags: ["auth"]
|
|
description: Revokes every existing session of the user on success.
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/PasswordChange'
|
|
responses:
|
|
'200':
|
|
description: Password changed; all sessions revoked
|
|
'400':
|
|
description: Invalid payload or wrong current password
|
|
'401':
|
|
description: Not authenticated
|
|
'429':
|
|
description: Rate limited (ERR_RATE_LIMITED)
|
|
'500':
|
|
description: Owner store unreadable/unwritable (ERR_INTERNAL)
|
|
|
|
/api/ws:
|
|
get:
|
|
summary: WebSocket endpoint for live container events
|
|
tags: ["containers"]
|
|
description: >-
|
|
Establishes a WebSocket connection that streams container lifecycle
|
|
events as JSON. The upgrade is gated by the instance-owner session;
|
|
anonymous handshakes close with 401 (428 in setup mode).
|
|
responses:
|
|
'101':
|
|
description: Switching Protocols - WebSocket upgrade
|
|
'401':
|
|
description: Not authenticated
|
|
'428':
|
|
description: Setup required
|
|
|
|
/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
|
|
|
|
/api/peers/reject:
|
|
post:
|
|
summary: Reject a pending peer authorization request
|
|
tags: ["security"]
|
|
description: >
|
|
Denies a first-contact authorization request without authorizing it.
|
|
The auth service drops the request from the pending list and audits
|
|
the decision; the peer stays on the no-op path (a later contact simply
|
|
re-records a fresh pending request).
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/PeerAction'
|
|
responses:
|
|
'200':
|
|
description: Pending request rejected
|
|
'400':
|
|
description: Missing service or fingerprint
|
|
'502':
|
|
description: Failed to reach the shared auth service
|