Principal-scoped policies
The policies in this section decide from who is calling. They match on the principal — a JWT identity or an IAM caller — and leave the action and input either unconstrained or only loosely constrained.
Use these patterns to express who a gateway serves: which roles may reach which class of tool, which account is excluded, and which agent gets which subset of the inventory. For rules that turn on what is being called or what values it carries, see Action and resource scoped policies.
Every example authorizes the gateway described in Resource configuration for examples. These policies use only constructs that are valid in both Dogwood and Cedar.
Note
The resource you write depends on whether you name specific actions. A policy that names one or more
specific actions must use a specific gateway ARN with resource ==. A policy that leaves the action
unconstrained must use a type test, resource is AgentCore::Gateway. A policy that leaves the resource
entirely unconstrained is rejected when you create it. For details, see
Resource specificity requirements.
OAuth principals
These patterns apply when your gateway uses a JWT authorizer, so the principal is an
AgentCore::OAuthUser. The entity ID is the token’s sub claim, and the token’s other claims are
available as tags.
Important
Every JWT claim becomes a string tag on the principal, whatever its type in the token. A numeric claim
such as exp becomes the string "1735689600", and a boolean becomes "true". An array or object claim
becomes its JSON text, so an array-valued scope claim becomes the string
["insurance:claim","insurance:view"]. Compare tags with string operators, and match multi-valued claims
with like rather than .contains(). A claim whose value is null produces no tag at all, so hasTag
returns false for it.
Require an OAuth scope
Intent: Allow filing a claim only for principals whose scope claim includes insurance:claim.
Test with hasTag before reading a tag with getTag; reading a tag that does not exist is an error that
denies the request. The like operator matches the scope wherever it appears in the claim, which is what you
want because the
scope claim holds several values. That holds whether the token delivers them space-separated or as a JSON
array.
permit( principal is AgentCore::OAuthUser, action == AgentCore::Action::"InsuranceAPI___file_claim", resource == AgentCore::Gateway::"arn:aws:bedrock-agentcore:us-west-2:123456789012:gateway/insurance-gw-a1b2c3d4e5" ) when { principal.hasTag("scope") && principal.getTag("scope") like "*insurance:claim*" };
Because like matches substrings, choose the pattern carefully: *insurance:claim* also matches
insurance:claim:admin. Match the delimiters as well if you need an exact value.
Permit a specific user
Intent: Allow the user clare to update coverage.
If the identity you want to match is the token subject, match the entity directly in the policy scope with
principal == AgentCore::OAuthUser::"<sub>" instead. That needs no condition at all, and is cheaper to
evaluate.
permit( principal is AgentCore::OAuthUser, action == AgentCore::Action::"InsuranceAPI___update_coverage", resource == AgentCore::Gateway::"arn:aws:bedrock-agentcore:us-west-2:123456789012:gateway/insurance-gw-a1b2c3d4e5" ) when { principal.hasTag("username") && principal.getTag("username") == "clare" };
Permit a set of roles
Intent: Allow only administrators and managers to delete a claim.
permit( principal is AgentCore::OAuthUser, action == AgentCore::Action::"InsuranceAPI___delete_claim", resource == AgentCore::Gateway::"arn:aws:bedrock-agentcore:us-west-2:123456789012:gateway/insurance-gw-a1b2c3d4e5" ) when { principal.hasTag("role") && (principal.getTag("role") == "admin" || principal.getTag("role") == "manager") };
Forbid everyone except a set of roles
Intent: Block coverage updates unless the principal is a senior adjuster or a manager.
This reaches a similar outcome to the previous example by the opposite route, and the difference matters.
A permit grants access that another permit could also grant independently. A forbid … unless
denies access that no permit can restore, because forbid always wins. Use forbid … unless for a
restriction you want to hold regardless of what other policies the engine contains.
forbid( principal is AgentCore::OAuthUser, action == AgentCore::Action::"InsuranceAPI___update_coverage", resource == AgentCore::Gateway::"arn:aws:bedrock-agentcore:us-west-2:123456789012:gateway/insurance-gw-a1b2c3d4e5" ) unless { principal.hasTag("role") && (principal.getTag("role") == "senior-adjuster" || principal.getTag("role") == "manager") };
Block a user everywhere on the gateway
Intent: Revoke all access for a suspended account immediately.
Because the action is unconstrained, the resource is written as the type test
resource is AgentCore::Gateway rather than a specific ARN. This policy applies to every gateway the
policy engine is attached to, and to every target type on them — MCP tools, runtime invocations, and
inference calls alike.
forbid( principal is AgentCore::OAuthUser, action, resource is AgentCore::Gateway ) when { principal.hasTag("username") && principal.getTag("username") == "suspended-user" };
IAM principals
These patterns apply when your gateway uses AWS_IAM, so the principal is an AgentCore::IamEntity.
The entity ID is the caller’s ARN with the session name removed, which makes an assumed role appear as
arn:aws:sts::<account>:assumed-role/<role-name> and lets you match it reliably across sessions.
Permit any IAM caller
Intent: Allow any IAM-authenticated caller to read a contract.
Use this only when authenticating as any IAM identity is a sufficient authorization decision. The gateway’s resource policy and its execution role already limit who can reach it.
permit( principal is AgentCore::IamEntity, action == AgentCore::Action::"InsuranceAPI___get_contract", resource == AgentCore::Gateway::"arn:aws:bedrock-agentcore:us-west-2:123456789012:gateway/insurance-gw-a1b2c3d4e5" );
Permit one specific IAM role
Intent: Allow only the claims-processing service to disburse payments.
Matching with principal == in the policy scope is the most precise form and needs no condition. Write
the ARN without a session name; the service removes the session name before evaluation.
permit( principal == AgentCore::IamEntity::"arn:aws:sts::123456789012:assumed-role/ClaimsProcessorRole", action == AgentCore::Action::"InsuranceAPI___disburse_payment", resource == AgentCore::Gateway::"arn:aws:bedrock-agentcore:us-west-2:123456789012:gateway/insurance-gw-a1b2c3d4e5" );
Match a family of IAM roles
Intent: Allow any claims-processing role, in any account, to read claim status.
Use principal.id like when one policy must cover several roles. Two other useful patterns:
-
principal.id like "arn:aws:sts::123456789012:assumed-role/*"matches any role in one account. -
principal.id like "*:123456789012:*"matches any IAM ARN from one account, whatever its form.
permit( principal is AgentCore::IamEntity, action == AgentCore::Action::"InsuranceAPI___get_claim_status", resource == AgentCore::Gateway::"arn:aws:bedrock-agentcore:us-west-2:123456789012:gateway/insurance-gw-a1b2c3d4e5" ) when { principal.id like "arn:aws:sts::*:assumed-role/ClaimsProcessor*" };
A like pattern with no wildcard is equivalent to principal == but costs a condition, so prefer
principal == for a single role.
Forbid an account
Intent: Block a vendor account from every action on the gateway.
The pattern *:444455556666:* matches any ARN containing that account ID, in any of the forms an
IAM caller can take. Because forbid wins over every permit, this holds no matter what else the engine
allows.
forbid( principal is AgentCore::IamEntity, action, resource is AgentCore::Gateway ) when { principal.id like "*:444455556666:*" };
Forbid a read-only role from write operations
Intent: Prevent a role intended for reads from reaching any tool that changes state.
A forbid that lists the write actions fails safe in one direction only. Adding a new read tool needs no
policy change. Adding a new write tool requires you to extend the list, or the new tool goes unprotected. If
you prefer the opposite bias, write a permit that lists the read actions instead, so a
newly added tool is denied until you deliberately allow it.
forbid( principal == AgentCore::IamEntity::"arn:aws:sts::123456789012:assumed-role/ReadOnlyAgentRole", action in [ AgentCore::Action::"InsuranceAPI___file_claim", AgentCore::Action::"InsuranceAPI___update_coverage", AgentCore::Action::"InsuranceAPI___approve_claim", AgentCore::Action::"InsuranceAPI___disburse_payment", AgentCore::Action::"InsuranceAPI___delete_claim" ], resource == AgentCore::Gateway::"arn:aws:bedrock-agentcore:us-west-2:123456789012:gateway/insurance-gw-a1b2c3d4e5" );
Give each agent its own tool set
Intent: Let a triage agent read claims while a processing agent both reads and disburses.
Several agents can share one gateway and one policy engine while seeing different tools. Each agent’s
tools/list response contains only the tools its principal is authorized to call, so an agent is not
told about tools it cannot use.
// Triage agent: read only permit( principal == AgentCore::IamEntity::"arn:aws:sts::123456789012:assumed-role/TriageAgentRole", action in [ AgentCore::Action::"InsuranceAPI___get_contract", AgentCore::Action::"InsuranceAPI___get_claim_status" ], resource == AgentCore::Gateway::"arn:aws:bedrock-agentcore:us-west-2:123456789012:gateway/insurance-gw-a1b2c3d4e5" ); // Processing agent: read, approve, and disburse permit( principal == AgentCore::IamEntity::"arn:aws:sts::123456789012:assumed-role/ProcessingAgentRole", action in [ AgentCore::Action::"InsuranceAPI___get_contract", AgentCore::Action::"InsuranceAPI___get_claim_status", AgentCore::Action::"InsuranceAPI___approve_claim", AgentCore::Action::"InsuranceAPI___disburse_payment" ], resource == AgentCore::Gateway::"arn:aws:bedrock-agentcore:us-west-2:123456789012:gateway/insurance-gw-a1b2c3d4e5" );