125 lines
6.1 KiB
Markdown
125 lines
6.1 KiB
Markdown
# Policy Gateway
|
||
|
||
> **Delivery scope:** `StellaOps.Policy.Gateway` minimal 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-Tenant` header.
|
||
- Gateway client credentials (DPoP) for service automation or Offline Kit flows when no caller token is present.
|
||
|
||
## 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`, or `PolicyRevisionActivationDto` as described in the Policy Engine API doc (`/docs/api/policy.md`).
|
||
- Errors always return RFC 7807 `ProblemDetails` with deterministic fields (`title`, `detail`, `status`). Missing caller credentials now surface `401` with `"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`:
|
||
|
||
```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 with `outcome` (`activated`, `pending_second_approval`, `already_active`, `bad_request`, `not_found`, `unauthorized`, `forbidden`, `error`) and `source` (`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`:
|
||
|
||
```bash
|
||
# 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 proof` to mint sender-constrained proofs locally. The command accepts `--htu`, `--htm`, and `--token` arguments 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.sample` and the resolved runtime config in the Offline Kit’s `config/` tree.
|
||
- Place the DPoP private key under `secrets/policy-gateway-dpop.pem` with 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_total` metrics in the Prometheus snapshot.
|
||
|
||
## 7 · Change log
|
||
|
||
- **2025-10-27 – Sprint 18.5**: Initial gateway bootstrap + activation metrics + DPoP client credentials.
|