Skip to content

token-exchange-aiac: put the refusal reason in the rejection, not only the message #1570

Closed

Description

@anatolykoyfman

Background

token-exchange-aiac answers two different refusals with the same 403 policy.forbidden:

  • audience_not_granted: Keycloak has a client for the destination, but the caller is not granted it.
  • target_disabled: the destination's client is disabled, for example a quarantined service.

The reason is recorded in the session event (pctx.Record(... Reason: reason ...)), but the rejection the caller sees carries no Details: deny() in core/plugins/tokenexchangeaiac/plugin.go returns pipeline.DenyStatus(status, code, msg). Only the human-readable message tells them apart ("the destination's client is disabled" vs "this workload is not granted the destination's audience").

rossoctl/aiac's system harness must tell them apart: its quarantine test (test_failed_tool_outbound_call_denied) counts a refusal as "blocked by the quarantine" only for target_disabled. It matches the message text, a copy of the cortex string, which breaks if the message is reworded. Raised in the review of rossoctl/aiac#242.

Task

  • Put the machine-stable reason in the rejection, for example Details: {"reason": "target_disabled"} (and audience_not_granted, exchange_failed, no_token, foreign_namespace), so it reaches the caller in the plain 403 body (details) and in error.data.details of the JSON-RPC frame.
  • Document the field in docs/token-exchange-aiac-plugin.md as part of the contract.

Acceptance criteria

  • A refusal of a disabled target carries details.reason == "target_disabled"; a refusal of an ungranted target carries details.reason == "audience_not_granted", in both rejection shapes.
  • The message text is no longer the only way to tell them apart.

Follow-up on the AIAC side: key target_disabled_refusal (k8s/authbridge-rejection.py) on the reason instead of the message.

Activity

  1. huang195 commented on Oct 29, 2026

    @huang195
    Member

    Closing for now: this is specific to the Kubernetes / envoy-sidecar / SPIRE deployment path. Current prioritization is the laptop install; K8s and envoy-sidecar remain supported targets but are not priority. Reopen when that work resumes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    aiacAIAC team workenhancementNew feature or request

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions