125 lines
		
	
	
		
			6.2 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
			
		
		
	
	
			125 lines
		
	
	
		
			6.2 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.
 |