Repository navigation
feat(aws): assume customer roles with Maple's own OIDC tokens - #1715
Conversation
Customer roles now trust an IAM OIDC provider for {api}/oidc/aws instead of a
deploy-minted IAM user with an external ID. Maple signs a five-minute RS256
token per connection (sub aws-connection:<id>) and exchanges it with STS
AssumeRoleWithWebIdentity, unsigned. api serves the discovery document and
JWKS; the signing key is an alchemy KeyPair per stage, so no AWS credential
exists on Maple's side and previews need no AWS resources.
The setup script registers the provider when the account lacks it, the
template creates it only on request and retains it, and the cleanup removes
it with the last Maple role. external_id is dropped (no prd connections).
…red provider A provider without Maple's audience is the customer's setup (the script adds the client id), a JWKS fetch failure is transient, and Terraform looks the account's provider up instead of owning it, so destroying one connection cannot disconnect the others. Tokens are Redacted and allow a minute of skew.
Maple review🟡 Confidence 6/10 · needs attention Replaces the customer-visible IAM user and external id with Maple-signed OIDC tokens: new issuer routes, a per-stage signing key, rewritten setup and cleanup scripts, and
Before merge
Findings🟠 Warning · F1 · A wrong OIDC thumbprint is reported as Maple's fault, not the customer'scorrectness ·
🔵 Note · F2 · STS assume call has no
|
| Issue | Service | Occurrences | File |
|---|---|---|---|
| @maple/backend/integrations/RailwayApiError | alerting | 8 | packages/backend/src/services/integrations/aws/api-impl.ts |
| @maple/backend/integrations/RailwayApiError | alerting | 6 | packages/backend/src/services/integrations/aws/api-impl.ts |
| @maple/backend/integrations/AwsApiError | maple-api | 4 | packages/backend/src/services/integrations/aws/api-impl.ts |
| @maple/backend/integrations/RailwayApiError | alerting | 4 | packages/backend/src/services/integrations/aws/api-impl.ts |
After this merges, Maple checks whether they stop.
Production traffic of the changed files (last 7 days)
| File | Calls/day | Busiest operations |
|---|---|---|
packages/backend/src/services/integrations/aws/setup.ts |
56k | maple 56k/day, 82.6% err |
apps/api/src/worker.ts |
12k | POST /mcp 5.7k/day, 0.0% errPOST /mcp 5.7k/day, 0.1% errGET /mcp 305/day, 0.0% err |
apps/api/src/routes/aws-oidc.http.ts |
897 | GET 758/day, 1.9% errGET 139/day, 0.0% err |
packages/backend/src/services/integrations/AwsConnectionService.ts |
8 | AwsConnectionService.checkDue 8/day, 0.0% err |
Telemetry this change adds and removes (1)
- ➕ span name
makeAwsOidcSigner·packages/backend/src/services/integrations/aws/oidc.ts:49
What was checked
- Trust policy pins both
${issuer}:audand${issuer}:sub(packages/domain/src/aws.ts:216), so another connection's token cannot assume the role - api and alerting both derive the issuer from the same
MAPLE_API_BASE_URL(packages/infra/src/env.ts:125), so minted tokens match the served JWKS - JWKS publishes only
kty/n/e/kid/alg/use;aws-oidc.http.test.ts:65asserts no privatedis served
Observability coverage: 2 of 3 changes observable
| Change | Kind | Observable | Evidence |
|---|---|---|---|
GET /oidc/aws/.well-known/openid-configuration and /oidc/aws/jwks.json (apps/api) |
inbound HTTP route | yes | registered in apps/api/src/runtime/http-graph.ts:245 alongside DiscoveryRouter, same raw-route stack |
AssumeRoleWithWebIdentity call to sts.amazonaws.com in checkRole |
outbound HTTP call | no | bare httpClient.execute; only maple.aws.role_arn is annotated, no peer.service |
AwsConnectionService.checkDue sweep (alerting cron) |
background work | yes | existing AwsConnectionService.checkDue.connection span per connection, AwsConnectionService.ts:698 |
fd083cd · Updated on every push. Reply "won't fix" to dismiss a finding, or mention @maple-review-bot to ask about one. Check ids refer to Maple's instrumentation audit.
|
Warning Review limit reachedYou've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Next included review available in 19 seconds. View limit details
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
🍁 Maple PR previewNote Preview resources were removed when this pull request closed. Final commit |
Maple review🟢 Confidence 7/10 · likely safe to merge Customer roles now trust Maple's OIDC issuer: apps/api publishes discovery and JWKS,
Before merge
Still open from earlier reviews
Production impactOpen errors in the changed files
After this merges, Maple checks whether they stop. Production traffic of the changed files (last 7 days)
Telemetry this change adds and removes (1)
What was checked
Observability coverage: 1 of 2 changes observable
|
Preview test: real AWS, end to end (pr-1715, commit c564d83)Issuer from the outside: Lifecycle in account 465760687006 (the setup and cleanup scripts the preview served, byte-identical, sha256 checked, to the ones rendered from this branch):
Traces ( With the JWKS cached, a whole check is ~143 ms (trace Not verified live: the hourly sweep from alerting (same signer and env binding as api; covered by the sweep tests). The metric stream path is unchanged by this PR (verified on #1680). |
Maple review🟢 Confidence 8/10 · likely safe to merge The last commits make a stale OIDC thumbprint read as the customer's setup, which fixes F1; the STS call itself is unchanged and still uninstrumented (F2 open). None of the five open error issues in this file is addressed:
Before merge
Findings🔵 Note · F3 · New
|
| Issue | Service | Occurrences | File |
|---|---|---|---|
| @maple/backend/integrations/RailwayApiError | alerting | 8 | packages/backend/src/services/integrations/aws/api-impl.ts |
| @maple/backend/integrations/RailwayApiError | alerting | 6 | packages/backend/src/services/integrations/aws/api-impl.ts |
| @maple/backend/integrations/AwsApiError | maple-api | 4 | packages/backend/src/services/integrations/aws/api-impl.ts |
| @maple/backend/integrations/RailwayApiError | alerting | 4 | packages/backend/src/services/integrations/aws/api-impl.ts |
| @maple/backend/integrations/AwsApiError | maple-api | 3 | packages/backend/src/services/integrations/aws/api-impl.ts |
After this merges, Maple checks whether they stop.
Production traffic of the changed files (last 7 days)
| File | Calls/day | Busiest operations |
|---|---|---|
packages/backend/src/services/integrations/aws/setup.ts |
56k | maple 56k/day, 82.6% err |
apps/api/src/worker.ts |
12k | POST /mcp 5.7k/day, 0.0% errPOST /mcp 5.6k/day, 0.1% errGET /mcp 303/day, 0.0% err |
apps/api/src/routes/aws-oidc.http.ts |
896 | GET 757/day, 1.9% errGET 139/day, 0.0% err |
packages/backend/src/services/integrations/AwsConnectionService.ts |
8 | AwsConnectionService.checkDue 8/day, 0.0% err |
Telemetry this change adds and removes (1)
- ➕ span name
makeAwsOidcSigner·packages/backend/src/services/integrations/aws/oidc.ts:49
What was checked
- Thu
stsErrororder: throttle,assume_denied, thenInvalidIdentityTokenasmaple_identity(api-impl.ts:73-81) - The audience case still matches the regex through
Incorrect token audience(fake-aws.ts:222) checkRolereads credentials throughStsAnswer, so a changed STS shape fails asupstream
Observability coverage: 0 of 1 changes observable
| Change | Kind | Observable | Evidence |
|---|---|---|---|
STS AssumeRoleWithWebIdentity call over raw HttpClient |
outbound call | no | api-impl.ts:127-137 executes on FetchHttpClient.layer with no OTel instrumentation; no Client span or peer.service |
ed14019 · Updated on every push. Reply "won't fix" to dismiss a finding, or mention @maple-review-bot to ask about one.
|
Merged with 5 steps from "Before merge" still open. If they are done, tick them on the review comment; if not, now is the time.
|
Maple: after this shippedDeployed to New errors since the deploy:
Errors in the changed files:
Maple looks once, an hour after the first deploy that carries the merge. |
Customer AWS roles now trust Maple as an OIDC identity provider instead of a deploy-minted IAM user with a static access key and an external ID. Maple holds no AWS credential at all; previews and dev no longer create anything in AWS for the integration.
How it works
{MAPLE_API_BASE_URL}/oidc/aws(api.maple.dev/oidc/aws,api.eu.maple.dev/oidc/aws,api-pr-<n>.maple.dev/oidc/aws). apps/api serves/oidc/aws/.well-known/openid-configurationand/oidc/aws/jwks.json, built from configuration, never request headers. The issuer is permanent once an account trusts it.KeyPair(RSA 2048) per stage, bound to api and alerting asMAPLE_AWS_OIDC_SIGNING_KEY.kidis the RFC 7638 thumbprint.sub = aws-connection:<id>,aud = sts.amazonaws.com) and calls STSAssumeRoleWithWebIdentityunsigned overHttpClient(JSON answers); the IAM self-reads use the session as before.Federated: arn:aws:iam::<acct>:oidc-provider/<issuer>,StringEqualson<issuer>:audand<issuer>:sub. A token for another connection can't assume the role.CreateOidcProvider=false.DeletionPolicy: Retain.datasource, so a destroy can't take it from other connections.MapleIntegration-*role is left.AccessDenied= setup not done (pending, or disconnected after a first success); a token STS can't verify = Maple's side; JWKS fetch failures and throttles = transient.Removed
packages/infra/src/aws/integration-principal.ts(IAM user + access key),MAPLE_AWS_PRINCIPAL_ARN/_ACCESS_KEY_ID/_SECRET_ACCESS_KEY, theawsIntegrationPrincipalprofile flag, and theaws_connections.external_idcolumn (migration20261022223850_aws_oidc). Public v2external_idis replaced byoidc_issuer+oidc_subject. Production has no AWS connections (noPOST /v2/integrations/aws/connectionsin prd since #1635 shipped), so nothing to migrate.AWS_TEMPLATE_VERSIONis now 2.Verification
aud/subconditions, the template'sFn::If/Refbranch. Covered: a token for another connection, a key that isn't Maple's, an account without the provider, a provider without Maple's audience.kidrecomputed from RFC 7638.awsCLI: provider created only when missing and before the stack, client id added to an existing one, provider kept while another Maple role exists and deleted with the last.Host/X-Forwarded-Host, public key only, 404 without a key.bun typecheckfor domain, backend, infra, db, api, alerting, web; backend integrations 783 tests, api routes, db parity, infra profile, web scenes, domain v2.🤖 Generated with Claude Code
Need help on this PR? Tag
@codesmith-botwith what you need.