Skip to content

Update AWS STS tutorial for new authorization model#676

Open
jhrozek wants to merge 2 commits intomainfrom
aws_sts_update
Open

Update AWS STS tutorial for new authorization model#676
jhrozek wants to merge 2 commits intomainfrom
aws_sts_update

Conversation

@jhrozek
Copy link
Copy Markdown
Contributor

@jhrozek jhrozek commented Apr 8, 2026

Description

AWS MCP Server no longer uses aws-mcp:* IAM actions (effective
March 24, 2026). Authorization now happens at the AWS service level
using two new IAM condition keys: aws:ViaAWSMCPService and
aws:CalledViaAWSMCP.

Changes to docs/toolhive/integrations/aws-sts.mdx:

  • Replace the two-layer aws-mcp:* permission model explanation with
    the new single-layer model, documenting both condition keys with
    descriptions and when to use each
  • Update the default role policy example to use sts:GetCallerIdentity
    scoped to aws:ViaAWSMCPService, with a BoolIfExists deny guardrail
    that prevents the temporary credentials from being used outside of MCP
  • Update the S3 role policy example to use service-level actions
    (s3:GetObject, s3:ListBucket) with aws:CalledViaAWSMCP condition;
    remove aws-mcp:* statement entirely
  • Add a "Security best practices" section covering the deny guardrail
    pattern, BoolIfExists truth table, and guidance on choosing between
    the two condition keys
  • Update the troubleshooting entry to reflect service-level errors
    instead of aws-mcp:InvokeMcp
  • Add missing service: aws-mcp field to the MCPExternalAuthConfig
    example (required for correct SigV4 signing)
  • Add resourceUrl to the MCPRemoteProxy oidcConfig example
    (required for OAuth 2.0 protected resource discovery, enabling
    MCP clients to auto-discover the OIDC provider)
  • Fix verification test: tools/list requires a prior initialize
    handshake; updated to a two-step curl flow
  • Fix portproxyPort (the port field is deprecated per CRD spec)
  • Rename "What's next?" to "Next steps" per style guide

Depends on stacklok/toolhive#4670 (SigV4 proxy header fix required
for requests routed through Gateway API / ngrok).

Type of change

  • Documentation update

Related issues/PRs

Fixes #587
Depends on stacklok/toolhive#4670

Submitter checklist

Content and formatting

  • I have reviewed the content for technical accuracy
  • I have reviewed the content for spelling, grammar, and style

jhrozek and others added 2 commits April 8, 2026 12:00
AWS MCP Server no longer uses aws-mcp:* IAM actions. Authorization
now happens at the AWS service level using two new condition keys:
aws:ViaAWSMCPService and aws:CalledViaAWSMCP.

- Replace aws-mcp:* permission model explanation with new single-layer
  model and document both condition keys
- Update default role policy to use sts:GetCallerIdentity scoped to
  aws:ViaAWSMCPService with a BoolIfExists deny guardrail
- Update S3 role policy to use service-level actions with
  aws:CalledViaAWSMCP condition
- Add security best practices section covering the deny guardrail
  pattern, BoolIfExists truth table, and condition key guidance
- Update troubleshooting section to reflect service-level errors
- Add missing service: aws-mcp field to MCPExternalAuthConfig example
- Add resourceUrl to MCPRemoteProxy oidcConfig example for OAuth
  protected resource discovery

Depends on stacklok/toolhive#4670 (SigV4 proxy header fix).

Fixes #587

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Four issues found during docs review:

- proxyPort: the MCPRemoteProxy example used the deprecated `port`
  field. CRD spec marks it as deprecated in favor of `proxyPort`.

- Verification test: the curl test called tools/list directly, which
  requires a prior initialize handshake. Added the two-step flow:
  initialize (capturing Mcp-Session-Id), then tools/list with the
  session ID header.

- resourceUrl cross-reference: the oidcConfig example uses
  <YOUR_DOMAIN> before the reader has chosen a domain (Step 5 comes
  later). Added a comment making the dependency explicit.

- Section title: renamed "What's next?" to "Next steps" to match the
  project style guide.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings April 8, 2026 11:04
@vercel
Copy link
Copy Markdown

vercel bot commented Apr 8, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
docs-website Ready Ready Preview, Comment Apr 8, 2026 11:04am

Request Review

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Updates the ToolHive AWS STS integration documentation to reflect the AWS MCP Server authorization model change (service-level IAM permissions scoped via new IAM condition keys), and refreshes the examples to match current CRD fields and MCP client handshake behavior.

Changes:

  • Replaces the deprecated aws-mcp:* action model with service-level IAM examples using aws:ViaAWSMCPService / aws:CalledViaAWSMCP.
  • Updates IAM policy examples (default + S3 read-only) and adds a new “Security best practices” section describing the deny-guardrail approach.
  • Fixes/extends configuration and verification examples (adds awsSts.service, adds oidcConfig.resourceUrl, and updates the curl flow to include initialize before tools/list).

Comment on lines +570 to +583
`BoolIfExists` is important here. Without the `IfExists` suffix, the deny would
apply even when the condition key is absent — which would block legitimate STS
operations like `AssumeRoleWithWebIdentity` itself. With `IfExists`, the
condition only evaluates when the key is present:

| `aws:ViaAWSMCPService` present? | Value | Deny applies? |
| ------------------------------- | ------- | ------------- |
| No (key absent) | — | No |
| Yes | `true` | No |
| Yes | `false` | Yes |

This means credentials can only be used when the request flows through an AWS
MCP server. If the temporary credentials are ever extracted and used directly
against the AWS API, the deny fires and access is blocked.
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The explanation of BoolIfExists here appears inaccurate for IAM condition evaluation. ...IfExists operators generally match when the key is missing, and the role’s identity policy is not evaluated for the initial sts:AssumeRoleWithWebIdentity call (only the trust policy is). Please re-check and adjust the narrative + truth table so it reflects how the deny actually behaves when aws:ViaAWSMCPService is absent vs present, and why that matters for STS exchange vs subsequent API calls made with the temporary credentials.

Suggested change
`BoolIfExists` is important here. Without the `IfExists` suffix, the deny would
apply even when the condition key is absent — which would block legitimate STS
operations like `AssumeRoleWithWebIdentity` itself. With `IfExists`, the
condition only evaluates when the key is present:
| `aws:ViaAWSMCPService` present? | Value | Deny applies? |
| ------------------------------- | ------- | ------------- |
| No (key absent) || No |
| Yes | `true` | No |
| Yes | `false` | Yes |
This means credentials can only be used when the request flows through an AWS
MCP server. If the temporary credentials are ever extracted and used directly
against the AWS API, the deny fires and access is blocked.
`BoolIfExists` is important here, but its behavior is easy to misread. For IAM
condition operators with the `IfExists` suffix, the condition matches when the
key is absent, and otherwise evaluates the operator normally. For this deny
statement, that means the deny applies when `aws:ViaAWSMCPService` is missing or
explicitly `false`, and does not apply only when the key is present and `true`:
| `aws:ViaAWSMCPService` present? | Value | Deny applies? |
| ------------------------------- | ------- | ------------- |
| No (key absent) || Yes |
| Yes | `true` | No |
| Yes | `false` | Yes |
That does **not** block the initial
[`AssumeRoleWithWebIdentity`](https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRoleWithWebIdentity.html)
exchange. When AWS evaluates that request, it uses the role's **trust policy**;
the role's identity policy is not evaluated yet. This deny statement matters
for **subsequent AWS API calls made with the temporary credentials**. If those
credentials are used directly against AWS APIs, `aws:ViaAWSMCPService` is
typically absent and the deny blocks the request. When the request is routed
through an AWS MCP server, the key is present and `true`, so this deny does not
apply.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 2d67adf — corrected the truth table (key absent → deny applies: Yes) and rewrote the prose to clarify that BoolIfExists matches when the key is absent, and that this deny applies to API calls made with the temporary credentials, not to the AssumeRoleWithWebIdentity exchange itself (which is governed by the trust policy).

Also went further in e0e7bb9 after verifying with AWS docs: sts:GetCallerIdentity is immune to all IAM evaluation (it always succeeds and cannot be denied), so the previous AllowMinimalAccessViaMCP statement was inert and the deny guardrail had no effect on it. Replaced the default policy with a deny-only statement — the role is now correctly documented as a no-access fallback whose only purpose is letting the OIDC token exchange succeed and blocking misuse of the resulting credentials.

type: inline
# Public URL of this proxy's MCP endpoint, advertised to clients
# via WWW-Authenticate so they can discover your OIDC provider.
# Must match the hostname you configure in Step 5.
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The inline comment says resourceUrl must match the hostname configured in Step 5, but resourceUrl is intended to be the full client-facing resource URL (scheme + host + path) used for protected resource/OIDC discovery. Consider rewording to avoid readers setting only the hostname and omitting the /mcp path (see similar guidance in docs/toolhive/guides-k8s/connect-clients.mdx:234-245).

Suggested change
# Must match the hostname you configure in Step 5.
# Must match the full public MCP endpoint URL you configure in Step 5,
# including the scheme, host, and /mcp path.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 2d67adf — updated the comment to say "full public MCP endpoint URL you configure in Step 5, including the scheme, host, and /mcp path" to make clear the entire URL is required, not just the hostname.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Update AWS STS tutorial for MCP Server authorization model change (March 24, 2026)

2 participants