Skip to content

Translation-authorisation rules

A translation rule answers exactly one question:

May this principal substitute this placeholder when calling this provider at this route, as this artifact type?

Identity (who is asking) is not enough on its own. Translation rules are the layer that turns identity into authority.

Warning

Default is deny. When no rule matches a request, the proxy refuses the substitution. This applies to every principal kind — there is no "default allow" mode, no implicit grant for an authenticated workload.


Where in the UI

Policy → Translation Auth.

Translation authorisation rules

The page has three regions:

Region What it does
Rules Inventory: ID, Principal, Provider, Route, Placeholder, Artifact, Action, Remove
Create rule Raw JSON editor + Create button
Evaluate Paste a candidate request as JSON, click Dry-run or Evaluate to see which rule matches and what action it produced

Rule listing and evaluate/dry-run are operator-level actions. Creating and deleting rules is admin-only.


Anatomy of a rule

A rule is a JSON object. Every field except action and id is optional. Omitted fields mean "match any value of this dimension."

{
  "id":                    "payments-stripe-prod",
  "description":           "Payments service may redeem the live Stripe key",
  "principal_kind":        "workload",
  "principal_id":          "wkl-89e881e9706d38fc",
  "namespace":             "prod/payments",
  "providers":             ["stripe"],
  "route_families":        ["stripe"],
  "operations":            ["placeholder_substitution"],
  "method":                "POST",
  "path_prefix":           "/v1/charges",
  "allowed_placeholders":  ["XCALIBUR_STRIPE_SECRET_KEY"],
  "artifact_types":        ["bearer_token"],
  "action":                "allow",
  "enabled":               true
}

Match dimensions

Field Meaning
principal_kind user_session · service · workload · developer_device
principal_id Exact principal ID (most-specific match)
namespace Hierarchical namespace; matches the principal's namespace exactly
providers Provider IDs (github, stripe, aws, bank, …)
route_families Route family tag (usually equal to provider ID)
operations placeholder_substitution · surrogate_restoration · adapter_restore
method HTTP method
path_prefix URL path prefix
allowed_placeholders Names the rule scopes itself to (omit = any)
artifact_types bearer_token, refresh_token, cookie, api_key, …
action allow · deny

Determinism

When multiple rules match, candidates are sorted by rule ID (lexicographic ascending) and the first match wins. Audit logs always quote the rule ID that produced the decision, so two identical requests will always cite the same rule.


Walkthrough — author & dry-run a rule

This five-step walkthrough creates a rule, validates it with a dry-run, and verifies it against a real request.

1. Draft the rule

In the Create rule card, paste:

{
  "id":                   "github-app-prod",
  "principal_kind":       "workload",
  "providers":            ["github"],
  "route_families":       ["github"],
  "operations":           ["placeholder_substitution"],
  "allowed_placeholders": ["XCALIBUR_GITHUB_TOKEN"],
  "artifact_types":       ["bearer_token"],
  "action":               "allow"
}

2. Dry-run it before saving

In the Evaluate card, paste a candidate request:

{
  "principal_kind":  "workload",
  "principal_id":    "wkl-89e881e9706d38fc",
  "provider":        "github",
  "placeholder":     "XCALIBUR_GITHUB_TOKEN",
  "artifact":        "bearer_token",
  "route":           "/repos"
}

Click Dry-run. The result panel shows whether any rule matches, which rule ID, and the resulting action — without writing anything to the live policy.

3. Create

Click Create in the Create-rule card. The rule appears immediately in the Rules table. The next request that matches it will be allowed; the audit event will quote rule_id=github-app-prod.

4. Verify against a real request

From the workload:

curl -H "Authorization: Bearer XCALIBUR_GITHUB_TOKEN" \
     https://api.github.com/repos/acme/excalibur-demo/pulls

On Audit, search for type=translation_allowed and you will see the event quoting your rule ID. Search for type=translation_denied to confirm there are no unintended denies.

5. Or do all of this from the CLI

# Create
curl -X POST http://localhost:8443/api/policy/translation \
  -H "Content-Type: application/json" \
  -d @rule.json

# List
curl http://localhost:8443/api/policy/translation | jq

# Dry-run
curl -X POST http://localhost:8443/api/policy/translation/dry-run \
  -H "Content-Type: application/json" \
  -d @candidate.json

# Evaluate (records the decision)
curl -X POST http://localhost:8443/api/policy/translation/evaluate \
  -H "Content-Type: application/json" \
  -d @candidate.json

# Delete
curl -X DELETE http://localhost:8443/api/policy/translation/github-app-prod

Common patterns

These patterns cover the most frequent rule shapes in production.

Per-principal (most common in production)

{
  "id":             "payments-svc-stripe",
  "principal_kind": "workload",
  "principal_id":   "wkl-89e881e9706d38fc",
  "providers":      ["stripe"],
  "operations":     ["placeholder_substitution"],
  "action":         "allow"
}

Only wkl-89e881e9706d38fc may substitute Stripe placeholders. Everyone else is denied.

Per-namespace

{
  "id":             "ns-prod-payments",
  "principal_kind": "workload",
  "namespace":      "prod/payments",
  "providers":      ["stripe","bank"],
  "operations":     ["placeholder_substitution"],
  "action":         "allow"
}

Any workload in prod/payments may redeem Stripe and bank placeholders. Combine with a namespace egress policy to also restrict where they call out to.

Per-route narrowing

{
  "id":             "github-readonly",
  "principal_kind": "workload",
  "principal_id":   "wkl-readonly-bot",
  "providers":      ["github"],
  "method":         "GET",
  "path_prefix":    "/repos",
  "operations":     ["placeholder_substitution"],
  "action":         "allow"
}

Allowed: GET /repos/.... Denied: any POST to /repos/..., any path outside /repos. Useful when the same identity needs to read GitHub but not push.

Explicit deny for emergencies

{
  "id":             "z-zz-quarantine-suspect",
  "principal_kind": "workload",
  "principal_id":   "wkl-suspect",
  "action":         "deny"
}

The z-zz- prefix sorts last, so it only fires if no earlier rule matched. Useful as a "cancel everything" override after an incident, before you finish revoking the underlying credentials.


What gets recorded

Every translation decision writes one event:

  • translation_allowed with rule_id, principal, provider, placeholder, artifact.
  • translation_denied with the same fields plus reason (no_matching_rule · explicit_deny · principal_unresolvable · …).

Both event types are visible on the Audit page. Filter with type=translation_* to scope to authorisation events.


Failure modes & how to read them

reason Meaning
no_matching_rule Default-deny fired — write a rule.
principal_unresolvable The caller has no usable identity (e.g. missing Proxy-Authorization).
explicit_deny A rule with "action":"deny" matched.
placeholder_not_in_allowed The rule matched the principal/provider but excluded this placeholder.
artifact_type_not_in_allowed Same as above for artifact types.