Skip to content

feat: add STS service with all 8 operations#15

Merged
tyrchen merged 8 commits intomasterfrom
feat/sts-service
Mar 21, 2026
Merged

feat: add STS service with all 8 operations#15
tyrchen merged 8 commits intomasterfrom
feat/sts-service

Conversation

@tyrchen
Copy link
Owner

@tyrchen tyrchen commented Mar 20, 2026

Summary

  • Add AWS Security Token Service (STS) with all 8 operations: GetCallerIdentity, AssumeRole, GetSessionToken, GetAccessKeyInfo, AssumeRoleWithSAML, AssumeRoleWithWebIdentity, DecodeAuthorizationMessage, GetFederationToken
  • Three-crate architecture: ruststack-sts-model (Smithy codegen), ruststack-sts-http (awsQuery protocol), ruststack-sts-core (business logic)
  • SigV4 credential scope routing (service=sts) to disambiguate from SNS (both use form-urlencoded POST)
  • Temporary credential generation (ASIA* access keys, session tokens), session tag propagation across chained AssumeRole calls
  • 50 unit tests covering credential generation, identity resolution, tag propagation, validation, routing, XML responses
  • 16 integration tests using aws-sdk-sts covering all 8 operations and edge cases

Test plan

  • Unit tests pass: cargo test -p ruststack-sts-model -p ruststack-sts-http -p ruststack-sts-core (50 tests)
  • Clippy clean: cargo clippy -p ruststack-sts-{model,http,core} -- -D warnings
  • Full server builds with all features
  • Pre-commit hooks pass (fmt, clippy, deny, typos, cargo test)
  • Integration tests: cargo test -p ruststack-integration -- sts --ignored (requires running server)
  • AWS CLI smoke test: aws sts get-caller-identity --endpoint-url http://localhost:4566

🤖 Generated with Claude Code

tyrchen and others added 8 commits March 20, 2026 08:11
Implement AWS Security Token Service (STS) for RustStack with full API
coverage across two phases:

Phase 0 - Core operations:
- GetCallerIdentity: returns account/ARN/user ID for caller
- AssumeRole: generates temporary credentials with session tags
- GetSessionToken: generates temporary credentials for current identity
- GetAccessKeyInfo: extracts account ID from access key

Phase 1 - Federation and advanced:
- AssumeRoleWithSAML: accepts SAML assertions (no crypto validation)
- AssumeRoleWithWebIdentity: accepts JWT tokens (no signature verification)
- DecodeAuthorizationMessage: returns static decoded message
- GetFederationToken: generates federated user credentials

Architecture (3-crate pattern):
- ruststack-sts-model: Smithy-codegen'd types, operations, errors
- ruststack-sts-http: awsQuery protocol (form-urlencoded/XML), SigV4 routing
- ruststack-sts-core: business logic, credential/session stores, tag propagation

Key features:
- SigV4 credential scope routing (service=sts) to disambiguate from SNS
- Temporary credential generation (ASIA* access keys, session tokens)
- Session tag propagation across chained AssumeRole calls
- Account ID encoding in access keys (base-36)
- Permissive identity resolution (unknown keys treated as root)
- 50 unit tests, 16 integration tests

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix mismatched request IDs: pass request_id from HTTP layer through
  handler to XML response (was generating two different UUIDs)
- Validate DurationSeconds parameter with proper range checking per AWS
  spec (AssumeRole: 900-43200, GetSessionToken: 900-129600, etc.)
- Use error.status_code field in error_to_response instead of
  re-computing from error code (respects per-instance overrides)
- Remove expect() in encode_base36 by building String from chars directly
- Remove dead SAML base64 decode that was immediately discarded
- Remove dead error_type() methods (JSON-oriented, unused in awsQuery)
- Fix module doc comment: STS uses XML/awsQuery, not JSON/__type

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add sts-test.yml: 13 AWS CLI test steps covering all 8 operations,
  chained AssumeRole tag propagation, error cases, and Rust SDK tests
- Update integration.yml to set STS_SKIP_SIGNATURE_VALIDATION=true

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
AssumeRoleWithWebIdentity and AssumeRoleWithSAML are federation
operations that don't require SigV4 signing (used for initial auth).
The AWS CLI sends these without an Authorization header.

Update STS router to match unsigned form-urlencoded POST requests
(no Authorization header) in addition to SigV4 service=sts requests.

Update SNS router to explicitly check service=sns in SigV4 instead
of catching all form-urlencoded requests, preventing routing conflicts.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ation

CloudWatch SDK v1.108+ switched from awsQuery to rpcv2Cbor protocol,
breaking our awsQuery-based CloudWatch router. Pin to ~1.60 (last
awsQuery version) until the CloudWatch service is migrated to support
the new protocol.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
AWS SDK CloudWatch v1.108+ switched from awsQuery to Smithy rpcv2Cbor
protocol. Update CloudWatch HTTP service to handle both protocols:

- Detect protocol from smithy-protocol header or content-type
- Route rpcv2Cbor requests via URL path (/service/.../operation/{Op})
- Deserialize CBOR request bodies using ciborium (serde-compatible)
- Serialize CBOR responses for all 31 CloudWatch operations
- Return CBOR-encoded errors with __type and message fields
- Keep full backward compatibility with awsQuery (form-urlencoded/XML)

Also:
- Unpin aws-sdk-cloudwatch to latest (was ~1.60, now "1")
- Add ciborium and http-body to workspace dependencies
- Fix inline http-body deps in STS and CloudWatch crates

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Smithy rpcv2Cbor encodes timestamps as CBOR tag 1 + epoch-seconds
floats, but our model types use chrono::DateTime with serde expecting
ISO 8601 strings. The AWS SDK also expects tag 1 epochs in responses.

Fix by converting timestamps at the CBOR/JSON boundary:
- Input: CBOR → Value → JSON (epoch→ISO transform) → deserialize
- Output: serialize → JSON → Value → CBOR (ISO→epoch tag 1 transform)

This handles all CloudWatch timestamp fields (StartTime, EndTime,
AlarmConfigurationUpdatedTimestamp, CreationDate, LastModified, etc.)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The Timestamps field (plural, Vec<DateTime>) was not being converted
because is_timestamp_field only matched singular names. Also, the
epoch_to_iso_string/iso_string_to_epoch functions only handled scalar
values, not arrays.

Fix: add array-aware convert_timestamp_value_to_json/cbor helpers and
extend is_timestamp_field to match plural forms (Times, Timestamps).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@tyrchen tyrchen merged commit 9614018 into master Mar 21, 2026
19 checks passed
@tyrchen tyrchen deleted the feat/sts-service branch March 21, 2026 05:48
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.

1 participant