6.2 KiB
Policy Gateway
Delivery scope:
StellaOps.Policy.Gatewayminimal API service fronting Policy Engine pack CRUD + activation endpoints for UI/CLI clients. Sender-constrained with DPoP and tenant headers, suitable for online and Offline Kit deployments.
1 · Responsibilities
- Proxy policy pack CRUD and activation requests to Policy Engine while enforcing scope policies (
policy:read,policy:author,policy:review,policy:operate,policy:activate). - Normalise responses (DTO +
ProblemDetails) so Console, CLI, and automation receive consistent payloads. - Guard activation actions with structured logging and metrics so approvals are auditable.
- Support dual auth modes:
- Forwarded caller tokens (Console/CLI) with DPoP proofs +
X-Stella-Tenantheader. - Gateway client credentials (DPoP) for service automation or Offline Kit flows when no caller token is present.
- Forwarded caller tokens (Console/CLI) with DPoP proofs +
2 · Endpoints
| Route | Method | Description | Required scope(s) |
|---|---|---|---|
/api/policy/packs |
GET |
List policy packs and revisions for the active tenant. | policy:read |
/api/policy/packs |
POST |
Create a policy pack shell or upsert display metadata. | policy:author |
/api/policy/packs/{packId}/revisions |
POST |
Create or update a policy revision (draft/approved). | policy:author |
/api/policy/packs/{packId}/revisions/{version}:activate |
POST |
Activate a revision, enforcing single/two-person approvals. | policy:operate, policy:activate |
Response shapes
- Successful responses return camel-case DTOs matching
PolicyPackDto,PolicyRevisionDto, orPolicyRevisionActivationDtoas described in the Policy Engine API doc (/docs/api/policy.md). - Errors always return RFC 7807
ProblemDetailswith deterministic fields (title,detail,status). Missing caller credentials now surface401with"Upstream authorization missing"detail.
3 · Authentication & headers
| Header | Source | Notes |
|---|---|---|
Authorization |
Forwarded caller token or gateway client credentials. | Caller tokens must include tenant scope; gateway tokens default to DPoP scheme. |
DPoP |
Caller or gateway. | Required when Authority mandates proof-of-possession (default). Generated per request; gateway keeps ES256/ES384 key material under etc/policy-gateway-dpop.pem. |
X-Stella-Tenant |
Caller | Tenant isolation header. Forwarded unchanged; gateway automation omits it. |
Gateway client credentials are configured in policy-gateway.yaml:
policyEngine:
baseAddress: "https://policy-engine.internal"
audience: "api://policy-engine"
clientCredentials:
enabled: true
clientId: "policy-gateway"
clientSecret: "<secret>"
scopes:
- policy:read
- policy:author
- policy:review
- policy:operate
- policy:activate
dpop:
enabled: true
keyPath: "../etc/policy-gateway-dpop.pem"
algorithm: "ES256"
🔐 DPoP key – store the private key alongside Offline Kit secrets; rotate it whenever the gateway identity or Authority configuration changes.
4 · Metrics & logging
All activation calls emit:
policy_gateway_activation_requests_total{outcome,source}– counter labelled withoutcome(activated,pending_second_approval,already_active,bad_request,not_found,unauthorized,forbidden,error) andsource(caller,service).policy_gateway_activation_latency_ms{outcome,source}– histogram measuring proxy latency.
Structured logs (category StellaOps.Policy.Gateway.Activation) include PackId, Version, Outcome, Source, and upstream status code for audit trails.
5 · Sample curl workflows
Assuming you already obtained a DPoP-bound access token ($TOKEN) for tenant acme:
# Generate a DPoP proof for GET via the CLI helper
DPoP_PROOF=$(stella auth dpop proof \
--htu https://gateway.example.com/api/policy/packs \
--htm GET \
--token "$TOKEN")
curl -sS https://gateway.example.com/api/policy/packs \
-H "Authorization: DPoP $TOKEN" \
-H "DPoP: $DPoP_PROOF" \
-H "X-Stella-Tenant: acme"
# Draft a new revision
DPoP_PROOF=$(stella auth dpop proof \
--htu https://gateway.example.com/api/policy/packs/policy.core/revisions \
--htm POST \
--token "$TOKEN")
curl -sS https://gateway.example.com/api/policy/packs/policy.core/revisions \
-H "Authorization: DPoP $TOKEN" \
-H "DPoP: $DPoP_PROOF" \
-H "X-Stella-Tenant: acme" \
-H "Content-Type: application/json" \
-d '{"version":5,"requiresTwoPersonApproval":true,"initialStatus":"Draft"}'
# Activate revision 5 (returns 202 when awaiting the second approver)
DPoP_PROOF=$(stella auth dpop proof \
--htu https://gateway.example.com/api/policy/packs/policy.core/revisions/5:activate \
--htm POST \
--token "$TOKEN")
curl -sS https://gateway.example.com/api/policy/packs/policy.core/revisions/5:activate \
-H "Authorization: DPoP $TOKEN" \
-H "DPoP: $DPoP_PROOF" \
-H "X-Stella-Tenant: acme" \
-H "Content-Type: application/json" \
-d '{"comment":"Rollout baseline"}'
For air-gapped environments, bundle policy-gateway.yaml and the DPoP key in the Offline Kit (see /docs/24_OFFLINE_KIT.md §5.7).
DPoP proof helper: Use
stella auth dpop proofto mint sender-constrained proofs locally. The command accepts--htu,--htm, and--tokenarguments and emits a ready-to-use header value. Teams maintaining alternate tooling (for example,scripts/make-dpop.sh) can substitute it as long as the inputs and output match the CLI behaviour.
6 · Offline Kit guidance
- Include
policy-gateway.yaml.sampleand the resolved runtime config in the Offline Kit’sconfig/tree. - Place the DPoP private key under
secrets/policy-gateway-dpop.pemwith restricted permissions; document rotation steps in the manifest. - When building verification scripts, use the gateway endpoints above instead of hitting Policy Engine directly. The Offline Kit validator now expects
policy_gateway_activation_requests_totalmetrics in the Prometheus snapshot.
7 · Change log
- 2025-10-27 – Sprint 18.5: Initial gateway bootstrap + activation metrics + DPoP client credentials.