feat(security): trusted-services surface + auth mgmt client (M2 §3)

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.
This commit is contained in:
2026-08-15 23:27:21 +01:00
parent dc79629c7e
commit 950b6e5827
16 changed files with 839 additions and 28 deletions
+147
View File
@@ -29,6 +29,62 @@ components:
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]
@@ -399,3 +455,94 @@ paths:
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