feat(policy): add credential grant authorization package
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Build and Publish Container Image / build-and-push (push) Successful in 37s

Assistant: codex
Assistant-Model: gpt-5.6-sol
Assistant-Session: 01a02e47-6aac-7ee1-914d-0584c75d3c81
This commit is contained in:
tegwick 2026-08-23 13:59:03 +02:00
parent df54c1b8f5
commit acbaa4a7c9
12 changed files with 476 additions and 3 deletions

View file

@ -103,6 +103,36 @@ func TestRunCheckOpsWarden(t *testing.T) {
}
}
func TestRunRailiancePlatformCredentialGrantContract(t *testing.T) {
var stdout, stderr bytes.Buffer
code := run([]string{"test-policy", "--file", railiancePlatformPath("policy_package.md")}, &stdout, &stderr)
if code != 0 || !strings.Contains(stdout.String(), `"valid": true`) {
t.Fatalf("test-policy code = %d, stderr = %s, stdout = %s", code, stderr.String(), stdout.String())
}
stdout.Reset()
stderr.Reset()
code = run([]string{
"check",
"--registry", railiancePlatformPath("registry_snapshot.json"),
"--policy", railiancePlatformPath("policy_package.md"),
"--request", railiancePlatformPath("check_request_allow.json"),
}, &stdout, &stderr)
if code != 0 {
t.Fatalf("check code = %d, stderr = %s", code, stderr.String())
}
var decision api.DecisionEnvelope
if err := json.Unmarshal(stdout.Bytes(), &decision); err != nil {
t.Fatal(err)
}
if decision.Effect != api.DecisionEffectAllow || decision.Reason != "credential_grant_allowed" {
t.Fatalf("decision = %s/%s; want allow/credential_grant_allowed", decision.Effect, decision.Reason)
}
if decision.Binding == nil || decision.Binding.Context["requested_ttl_seconds"] != float64(900) {
t.Fatalf("binding = %+v; want normalized numeric TTL", decision.Binding)
}
}
func TestServeOpsWardenCheckContract(t *testing.T) {
logPath := filepath.Join(t.TempDir(), "decisions.jsonl")
engine, err := buildEngine(context.Background(), opsPath("registry_snapshot.json"), opsPath("policy_package.md"), logPath)
@ -370,6 +400,10 @@ func opsPath(name string) string {
return filepath.Join("..", "..", "examples", "ops-warden", name)
}
func railiancePlatformPath(name string) string {
return filepath.Join("..", "..", "examples", "railiance-platform", name)
}
func opsWardenProductionSignRequest(subjectID, actor, actorType, principal string, ttlHours float64) api.CheckRequest {
return api.CheckRequest{
ID: "check:ops-warden-production-" + actor,

View file

@ -0,0 +1,16 @@
# Railiance Platform credential-grant action vocabulary
flex-auth uses one protected-system action for the credential broker:
| Action | Resource type | Meaning |
| --- | --- | --- |
| `issue` | `credential-grant` | Authorize issuance of one bounded credential lease from a registered grant. |
The request subject is the requesting actor. `context.bound_subject` is the
identity to which the resulting credential is bound. Grant id, credential
type, issuer, audience, TTL ceiling, permitted actor classes, purposes, and
delivery modes are registry-owned resource attributes, not caller assertions.
`context.requested_ttl_seconds` is numeric seconds. Parsing the broker's source
duration string happens once in the selected wire translator; the policy
rejects strings to prevent unit ambiguity.

View file

@ -0,0 +1,16 @@
# Railiance Platform credential-grant example
This package provides the flex-auth side of `FLEX-WP-0012`: registered grant
metadata, representative requester subjects, a default-deny `issue` policy,
and fixtures for TTL, actor type, purpose, delivery mode, and unknown grants.
The wire translation is intentionally outside the policy. Both candidate
integration shapes must emit `requested_ttl_seconds` as a positive number;
duration strings such as `15m` are rejected rather than guessed.
```bash
go run ./cmd/flex-auth load-registry --file examples/railiance-platform/registry_snapshot.json
go run ./cmd/flex-auth test-policy --file examples/railiance-platform/policy_package.md
go run ./cmd/flex-auth check --registry examples/railiance-platform/registry_snapshot.json --policy examples/railiance-platform/policy_package.md --request examples/railiance-platform/check_request_allow.json
go run ./cmd/flex-auth check --registry examples/railiance-platform/registry_snapshot.json --policy examples/railiance-platform/policy_package.md --request examples/railiance-platform/check_request_deny_unknown.json
```

View file

@ -0,0 +1,21 @@
{
"id": "check:credential-grant-allow-http-shape",
"tenant": "tenant:platform",
"subject": {
"id": "agent:codex/railiance-platform",
"type": "Agent"
},
"action": "issue",
"resource": {
"id": "credential-grant:rapp-postgres/audit-core-runtime",
"type": "credential-grant",
"system": "railiance-platform"
},
"context": {
"actor_type": "approved-agent",
"bound_subject": "agent:codex/railiance-platform",
"purpose": "audit-core-runtime",
"delivery_mode": "exec-env",
"requested_ttl_seconds": 900
}
}

View file

@ -0,0 +1,21 @@
{
"id": "check:credential-grant-unknown-http-shape",
"tenant": "tenant:platform",
"subject": {
"id": "agent:codex/railiance-platform",
"type": "Agent"
},
"action": "issue",
"resource": {
"id": "credential-grant:unknown/grant",
"type": "credential-grant",
"system": "railiance-platform"
},
"context": {
"actor_type": "approved-agent",
"bound_subject": "agent:codex/railiance-platform",
"purpose": "unknown",
"delivery_mode": "exec-env",
"requested_ttl_seconds": 900
}
}

View file

@ -0,0 +1,59 @@
- id: fixture:credential-grant-allow
request: &allow_request
id: check:credential-grant-allow
tenant: tenant:platform
subject: {id: agent:codex/railiance-platform, type: Agent}
action: issue
resource:
id: credential-grant:rapp-postgres/audit-core-runtime
type: credential-grant
system: railiance-platform
attributes:
grant_id: rapp-postgres/audit-core-runtime
credential_type: openbao-database-credential
issuer: openbao
audience: audit-core
max_ttl_seconds: 3600
allowed_actor_types: [human-operator, approved-agent, ci-runner]
allowed_purposes: [audit-core-runtime]
allowed_delivery_modes: [exec-env]
context: &allow_context
actor_type: approved-agent
bound_subject: agent:codex/railiance-platform
purpose: audit-core-runtime
delivery_mode: exec-env
requested_ttl_seconds: 900
expect: {effect: allow, reason: credential_grant_allowed}
- id: fixture:credential-grant-ttl-deny
request:
<<: *allow_request
id: check:credential-grant-ttl-deny
context: {<<: *allow_context, requested_ttl_seconds: 5400}
expect: {effect: deny, reason: ttl_out_of_bounds}
- id: fixture:credential-grant-actor-type-deny
request:
<<: *allow_request
id: check:credential-grant-actor-deny
context: {<<: *allow_context, actor_type: unapproved-agent}
expect: {effect: deny, reason: actor_type_not_allowed}
- id: fixture:credential-grant-purpose-deny
request:
<<: *allow_request
id: check:credential-grant-purpose-deny
context: {<<: *allow_context, purpose: unrelated-purpose}
expect: {effect: deny, reason: purpose_not_allowed}
- id: fixture:credential-grant-delivery-deny
request:
<<: *allow_request
id: check:credential-grant-delivery-deny
context: {<<: *allow_context, delivery_mode: chat}
expect: {effect: deny, reason: delivery_mode_not_allowed}
- id: fixture:credential-grant-unknown-deny
request:
<<: *allow_request
id: check:credential-grant-unknown-deny
resource:
id: credential-grant:unknown/grant
type: credential-grant
system: railiance-platform
expect: {effect: deny, reason: unknown_grant}

View file

@ -0,0 +1,158 @@
---
id: railiance-platform.credential-grant.issue
name: Railiance Platform credential-grant issuance
namespace: railiance-platform:credential-grant
version: v1
status: ready
package: flexauth.railiance_platform.credential_grant
actions: [issue]
owner: team:platform-security
fixtures: [policy_fixtures.yaml]
caring:
profile: caring-0.4.0-rc2
enforce: false
canonical_roles: [Operator]
organization_relations: [ServiceProvider]
scopes:
- level: Platform
id: platform:credential-broker
tenant: tenant:platform
planes: [Identity, Secret, Audit]
capabilities: [Use, Operate, Audit]
exposure_modes: [Metadata]
conditions: [PurposeBound, TimeLimited, Logged]
restrictions: [PrivilegeEscalationBlocked, SecretAccessBlocked]
activation: {mode: local}
metadata:
source: examples/railiance-platform/policy_package.md
ttl_unit: seconds
---
# Railiance Platform Credential-grant Issuance
The policy evaluates non-secret grant metadata only. Duration strings such as
`15m` must be parsed by the eventual wire translator into the numeric
`context.requested_ttl_seconds`; Rego never infers duration units.
## Rules
```rego
import future.keywords.if
import future.keywords.in
decision := {"effect": "allow", "reason": "credential_grant_allowed"} if {
allowed
} else := {"effect": "deny", "reason": first_denial} if {
true
}
allowed if {
input.action == "issue"
input.resource.system == "railiance-platform"
input.resource.type == "credential-grant"
input.tenant == "tenant:platform"
known_grant
actor_type_allowed
purpose_allowed
delivery_mode_allowed
ttl_allowed
has_bound_subject
}
default first_denial := "no_matching_rule"
first_denial := "wrong_action" if {
input.action != "issue"
} else := "wrong_system" if {
input.resource.system != "railiance-platform"
} else := "wrong_resource_type" if {
input.resource.type != "credential-grant"
} else := "wrong_tenant" if {
input.tenant != "tenant:platform"
} else := "unknown_grant" if {
not known_grant
} else := "actor_type_not_allowed" if {
not actor_type_allowed
} else := "purpose_not_allowed" if {
not purpose_allowed
} else := "delivery_mode_not_allowed" if {
not delivery_mode_allowed
} else := "ttl_out_of_bounds" if {
not ttl_allowed
} else := "missing_subject_binding" if {
not has_bound_subject
}
known_grant if {
is_string(input.resource.attributes.grant_id)
input.resource.id == sprintf("credential-grant:%s", [input.resource.attributes.grant_id])
is_number(input.resource.attributes.max_ttl_seconds)
}
actor_type_allowed if {
is_string(input.context.actor_type)
input.context.actor_type in input.resource.attributes.allowed_actor_types
}
purpose_allowed if {
is_string(input.context.purpose)
input.context.purpose != ""
input.context.purpose in input.resource.attributes.allowed_purposes
}
delivery_mode_allowed if {
is_string(input.context.delivery_mode)
input.context.delivery_mode in input.resource.attributes.allowed_delivery_modes
}
ttl_allowed if {
is_number(input.context.requested_ttl_seconds)
input.context.requested_ttl_seconds > 0
input.context.requested_ttl_seconds <= input.resource.attributes.max_ttl_seconds
}
has_bound_subject if {
is_string(input.context.bound_subject)
input.context.bound_subject != ""
}
```
## Tests
```rego test
package flexauth.railiance_platform.credential_grant_test
import future.keywords.if
import data.flexauth.railiance_platform.credential_grant
request := {
"tenant": "tenant:platform",
"subject": {"id": "agent:codex/railiance-platform", "type": "Agent"},
"action": "issue",
"resource": {
"id": "credential-grant:rapp-postgres/audit-core-runtime",
"type": "credential-grant",
"system": "railiance-platform",
"attributes": {
"grant_id": "rapp-postgres/audit-core-runtime",
"max_ttl_seconds": 3600,
"allowed_actor_types": ["human-operator", "approved-agent", "ci-runner"],
"allowed_purposes": ["audit-core-runtime"],
"allowed_delivery_modes": ["exec-env"]
}
},
"context": {"actor_type": "approved-agent", "bound_subject": "agent:codex/railiance-platform", "purpose": "audit-core-runtime", "delivery_mode": "exec-env", "requested_ttl_seconds": 900}
}
test_allow if {
credential_grant.decision.effect == "allow" with input as request
}
test_ttl_string_denied if {
credential_grant.decision.reason == "ttl_out_of_bounds" with input as object.union(request, {"context": object.union(request.context, {"requested_ttl_seconds": "15m"})})
}
test_unknown_grant_denied if {
credential_grant.decision.reason == "unknown_grant" with input as object.union(request, {"resource": {"id": "credential-grant:missing", "type": "credential-grant", "system": "railiance-platform", "attributes": {}}})
}
```

View file

@ -0,0 +1,19 @@
id: railiance-platform
name: Railiance Platform Credential Broker
resource_types:
- name: credential-grant
scope_level: Resource
planes: [Identity, Secret, Audit]
metadata:
description: Non-secret authorization metadata for a bounded credential lease.
actions:
- name: issue
capabilities: [Use, Operate, Audit]
planes: [Identity, Secret, Audit]
exposure_modes: [Metadata]
metadata:
required_context: [actor_type, bound_subject, purpose, delivery_mode, requested_ttl_seconds]
caring_profiles: [caring-0.4.0-rc2]
metadata:
flex_auth_contract: protected-system-v0
tenant: tenant:platform

View file

@ -0,0 +1,45 @@
{
"systems": [
{
"id": "railiance-platform",
"name": "Railiance Platform Credential Broker",
"resource_types": [
{
"name": "credential-grant",
"scope_level": "Resource",
"planes": ["Identity", "Secret", "Audit"]
}
],
"actions": [
{
"name": "issue",
"capabilities": ["Use", "Operate", "Audit"],
"planes": ["Identity", "Secret", "Audit"],
"exposure_modes": ["Metadata"]
}
],
"caring_profiles": ["caring-0.4.0-rc2"],
"metadata": {"tenant": "tenant:platform"}
}
],
"resource_manifests": [
{
"id": "railiance-platform-credential-grants",
"system": "railiance-platform",
"resources": [
{"id": "credential-grant:rapp-postgres/audit-core-runtime", "type": "credential-grant", "owner": "team:platform-security", "attributes": {"grant_id": "rapp-postgres/audit-core-runtime", "credential_type": "openbao-database-credential", "issuer": "openbao", "audience": "audit-core", "max_ttl_seconds": 3600, "allowed_actor_types": ["human-operator", "approved-agent", "ci-runner"], "allowed_purposes": ["audit-core-runtime"], "allowed_delivery_modes": ["exec-env"]}},
{"id": "credential-grant:rapp-postgres/audit-core-migration", "type": "credential-grant", "owner": "team:platform-security", "attributes": {"grant_id": "rapp-postgres/audit-core-migration", "credential_type": "openbao-database-credential", "issuer": "openbao", "audience": "audit-core", "max_ttl_seconds": 1800, "allowed_actor_types": ["human-operator", "approved-agent", "ci-runner"], "allowed_purposes": ["audit-core-migration"], "allowed_delivery_modes": ["exec-env"]}},
{"id": "credential-grant:ops-warden/warden-sign", "type": "credential-grant", "owner": "team:platform-security", "attributes": {"grant_id": "ops-warden/warden-sign", "credential_type": "openbao-token", "issuer": "openbao", "audience": "ops-warden", "max_ttl_seconds": 3600, "allowed_actor_types": ["human-operator", "approved-agent", "ci-runner"], "allowed_purposes": ["flex-auth-openbao-smoke", "ops-warden-production-sign-smoke"], "allowed_delivery_modes": ["exec-env", "response-wrap", "local-token-file", "kubernetes-auth"]}}
],
"actions": ["issue"],
"caring_profile": "caring-0.4.0-rc2",
"metadata": {"tenant": "tenant:platform"}
}
],
"tenants": [{"id": "tenant:platform", "name": "Platform Tenant"}],
"subjects": [
{"id": "operator:platform", "type": "Human", "roles": ["Operator"], "tenant": "tenant:platform"},
{"id": "agent:codex/railiance-platform", "type": "Agent", "roles": ["Operator"], "tenant": "tenant:platform"},
{"id": "ci:railiance-platform", "type": "Automation", "roles": ["Operator"], "tenant": "tenant:platform"}
]
}

View file

@ -0,0 +1,44 @@
id: railiance-platform-credential-grants
system: railiance-platform
resources:
- id: credential-grant:rapp-postgres/audit-core-runtime
type: credential-grant
owner: team:platform-security
attributes:
grant_id: rapp-postgres/audit-core-runtime
credential_type: openbao-database-credential
issuer: openbao
audience: audit-core
max_ttl_seconds: 3600
allowed_actor_types: [human-operator, approved-agent, ci-runner]
allowed_purposes: [audit-core-runtime]
allowed_delivery_modes: [exec-env]
- id: credential-grant:rapp-postgres/audit-core-migration
type: credential-grant
owner: team:platform-security
attributes:
grant_id: rapp-postgres/audit-core-migration
credential_type: openbao-database-credential
issuer: openbao
audience: audit-core
max_ttl_seconds: 1800
allowed_actor_types: [human-operator, approved-agent, ci-runner]
allowed_purposes: [audit-core-migration]
allowed_delivery_modes: [exec-env]
- id: credential-grant:ops-warden/warden-sign
type: credential-grant
owner: team:platform-security
attributes:
grant_id: ops-warden/warden-sign
credential_type: openbao-token
issuer: openbao
audience: ops-warden
max_ttl_seconds: 3600
allowed_actor_types: [human-operator, approved-agent, ci-runner]
allowed_purposes: [flex-auth-openbao-smoke, ops-warden-production-sign-smoke]
allowed_delivery_modes: [exec-env, response-wrap, local-token-file, kubernetes-auth]
actions: [issue]
caring_profile: caring-0.4.0-rc2
metadata:
flex_auth_contract: resource-registration-v0
tenant: tenant:platform

View file

@ -0,0 +1,23 @@
id: subjects:railiance-platform
tenants:
- id: tenant:platform
name: Platform Tenant
subjects:
- id: operator:platform
type: Human
display_name: Platform Operator
organization_relation: ServiceProvider
roles: [Operator]
tenant: tenant:platform
- id: agent:codex/railiance-platform
type: Agent
display_name: Railiance Platform Agent
organization_relation: ServiceProvider
roles: [Operator]
tenant: tenant:platform
- id: ci:railiance-platform
type: Automation
display_name: Railiance Platform CI
organization_relation: ServiceProvider
roles: [Operator]
tenant: tenant:platform

View file

@ -135,7 +135,7 @@ place duration normalization explicitly.
```task
id: FLEX-WP-0012-T02
status: todo
status: done
priority: medium
state_hub_task_id: "7e9c4e59-e59c-4617-a87f-99142952fe78"
```
@ -168,11 +168,20 @@ id. Verify against the real binary: `test-policy`, `load-registry`, and
Done when all Rego tests and fixtures pass and default-deny is demonstrated
for an unregistered grant.
Completed 2026-08-23. `examples/railiance-platform/` registers all three
current catalog grants and representative requester subjects. The policy uses
numeric `requested_ttl_seconds`; duration parsing belongs to the selected wire
translator, and strings are explicitly denied. Three embedded Rego tests and
six external fixtures pass, covering allow, TTL, actor type, purpose, delivery
mode, and unknown-grant default deny. Real `load-registry` and `check` commands
returned `credential_grant_allowed` for the registered runtime grant and
`unknown_grant` for an unregistered id.
## T03 - Implement the decided integration and prove it end to end
```task
id: FLEX-WP-0012-T03
status: todo
status: wait
priority: medium
state_hub_task_id: "3335b2b7-cf1a-411d-95b3-03c4b4c35659"
```
@ -199,6 +208,10 @@ request metadata only. Nothing in this workplan should read, log, or persist a
credential value; if a task appears to require one, that is a signal the
design has drifted, not a reason to handle secrets here.
Waiting on T01's wire-translation decision. The policy-side normalized request
contract is implemented; either integration option must produce its numeric
TTL field without changing policy semantics.
Done when allow and all four denials are demonstrated against the real helper
over real HTTP, `go test ./...` is green, and `gofmt`/`go vet` are clean.
@ -206,7 +219,7 @@ over real HTTP, `go test ./...` is green, and `gofmt`/`go vet` are clean.
```task
id: FLEX-WP-0012-T04
status: todo
status: wait
priority: low
state_hub_task_id: "40015a87-040c-4d48-b360-fd5566dbc552"
```
@ -225,3 +238,7 @@ actually meant before promising an endpoint.
Update `docs/` with a credential-grant action vocabulary, matching the
existing per-consumer vocabulary docs.
The source-side action vocabulary is now present at
`docs/railiance-platform-action-vocabulary.md`; final consumer/deployment
handoff waits on T01 and T03.