Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
219 changes: 219 additions & 0 deletions plugins/aws-serverless/skills/api-gateway/SKILL.md

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
# Authentication and Authorization

## Decision Tree

```
Is this a WebSocket API?
YES -> Lambda Authorizer (REQUEST type only on $connect; TOKEN type not
supported; cached policy applies for entire connection, must
cover all routes) or IAM (SigV4)
NO ->
Is the consumer an AWS service or resource?
YES -> IAM Authorization (SigV4)
NO -> Is the consumer a browser-based app?
YES -> Do you use Cognito?
YES -> REST API: Cognito User Pool Authorizer
HTTP API: JWT Authorizer (Cognito issuer)
NO -> Do you use another OIDC provider?
YES -> HTTP API: JWT Authorizer
REST API: Lambda Authorizer (validate JWT)
NO -> Lambda Authorizer (custom logic)
NO -> Is this machine-to-machine (M2M)?
YES -> Do you need certificate-based auth?
YES -> mTLS (Regional custom domain + S3 truststore)
NO -> OAuth 2.0 Client Credentials Grant (Cognito + JWT/Cognito authorizer)
NO -> Lambda Authorizer (most flexible)
```

## IAM Authorization (SigV4)

- Works for REST, HTTP, and WebSocket APIs (WebSocket: evaluated on `$connect` only)
- Caller signs requests with AWS Signature Version 4
- Best for: AWS-to-AWS service calls, Cognito identity pools, resources already integrated with IAM
- **Cross-account REST API**: Requires BOTH IAM policy (caller account) AND resource policy (API account)
- **Cross-account HTTP API**: No resource policies; use `sts:AssumeRole` to assume a role in the API account
- **Multi-region**: SigV4 signatures are region-specific: the signing region must match the region receiving the request. In multi-region deployments with Route 53 failover or latency-based routing, clients signing for one region will get auth failures if routed to another. SigV4a (multi-region signing) is not supported by API Gateway. Workarounds: use a region-agnostic auth mechanism (Lambda authorizer, JWT) for multi-region APIs, or implement client-side retry logic that re-signs for the correct region on auth failure

## Lambda Authorizers

### REST API

- **TOKEN type**: Receives a single header value (typically `Authorization`) as input. Returns IAM policy document. If the identity source header is missing, API Gateway returns 401 immediately **without invoking the Lambda**; the authorizer function never gets the chance to handle missing tokens
- **REQUEST type**: Receives headers, query strings, stage variables, and context variables as input. Returns IAM policy document. When caching is enabled and identity sources are specified, a request missing any identity source returns 401 without invoking the Lambda
- Both types must return `principalId` (string identifying the caller) alongside the policy document. Missing `principalId` causes 500 Internal Server Error
- **Response limits**: IAM policy document max ~8 KB. Exceeding this or returning a malformed response causes 500 Internal Server Error (not 401/403), a common debugging pitfall
- **Caching**: TTL default 300s, max 3600s. Cache key is the token value (TOKEN type) or identity sources (REQUEST type). When caching is enabled, the IAM policy returned by the first request is reused for subsequent requests with the same cache key. If that policy only covers specific resources (e.g., the path of the initial request), subsequent requests to other paths will be denied by the cached partial policy, causing hard-to-troubleshoot failures where clients intermittently cannot access parts of the API. Always generate IAM policies that cover the entire API when caching is enabled

### HTTP API

- **Simple response format**: Returns `{isAuthorized: true/false, context: {...}}`, much simpler than IAM policy
- **IAM policy format**: Also supported for more complex authorization. When using IAM policy format with caching, the same full-API policy guidance from REST API applies; see REST API caching note above
- **Identity sources**: `$request.header.X`, `$request.querystring.X`, `$context.X`, `$stageVariables.X`
- **Caching**: Disabled by default (TTL=0), unlike REST API (TTL=300s). Add `$context.routeKey` to identity sources to cache per-route when enabling caching
- **Timeout**: 10,000ms max

## JWT Authorizers (HTTP API Only)

- **Validates**: `iss`, `aud`/`client_id`, `exp`, `nbf` (must be before current time), `iat` (must be before current time), `scope`/`scp` (against route-configured scopes). Uses `kid` for JWKS key lookup. Request is denied if any validation fails
- Only RSA-based algorithms supported (RS256, RS384, RS512). ECDSA (ES256, ES384, ES512) is not supported. If your IdP signs tokens with ECDSA, use a Lambda authorizer instead
- Public key cached for 2 hours; account for this in key rotation
- Token validation runs on every request (no result caching); only the JWKS public keys are cached (2 hours). This differs from REST API Cognito authorizer which caches the validation result
- JWKS endpoint timeout: 1,500ms
- Max audiences per authorizer: 50. Max scopes per route: 10
- Use access tokens with scopes for authorization. ID tokens also work when no scopes are configured on the route, but access tokens are preferred for API authorization
- Only supports self-contained JWTs; opaque access tokens are not supported. If your IdP issues opaque tokens by default, use a Lambda authorizer instead
- Works natively with Cognito, Auth0, Okta, and any OIDC-compliant provider

## Cognito User Pools (REST API)

- Native authorizer type for REST APIs
- When no OAuth scopes configured on the method: use **ID token**
- When scopes configured: use **access token**
- Set up: Create user pool, app client, configure scopes on resource server
- **Token revocation not enforced**: The Cognito authorizer validates tokens locally (signature + claims) and does not check revocation status with Cognito. Revoked tokens (`GlobalSignOut`, `AdminUserGlobalSignOut`) are accepted until the token's `exp` time, as revocation is invisible to local validation regardless of caching. Separately, caching (default TTL 300s) means expired tokens may be accepted for up to the TTL duration after `exp`. For immediate revocation, use a Lambda authorizer with token introspection instead
- **M2M auth**: OAuth 2.0 Client Credentials Grant (confidential app client with client ID + secret, custom resource server scopes). Also works with HTTP API JWT authorizer using Cognito as issuer

## Resource Policies (REST API Only)

Four key use cases:

1. **Cross-account access**: Allow specific AWS accounts by specifying the account principal in the `Principal` field
2. **IP filtering**: Allow/deny CIDR ranges via `aws:SourceIp` (public) or `aws:VpcSourceIp` (private/VPC)
3. **VPC restriction**: Restrict to specific VPCs via `aws:SourceVpc`
4. **VPC endpoint restriction**: Restrict to specific VPC endpoints via `aws:SourceVpce`

### Policy Evaluation

Evaluation depends on which auth type is combined with the resource policy:

- **Same account + IAM or Lambda authorizer**: OR logic. If the auth mechanism allows, access is granted even if the resource policy has no matching statement (silent). An explicit Deny in the resource policy still wins
- **Same account + Cognito**: AND logic. Both the Cognito authorizer and the resource policy must allow
- **Resource policy alone** (no other auth): Must explicitly allow, otherwise request is denied
- **Cross-account**: AND logic. BOTH resource policy AND caller auth must explicitly allow. A silent resource policy results in implicit deny. This applies regardless of auth type (IAM, Cognito, Lambda authorizer)
- An explicit Deny always wins regardless of combination
- **Always redeploy the API after changing the resource policy**

## Mutual TLS (mTLS)

- Truststore in S3 (PEM-encoded, max 1,000 certs, max 1 MB). Certificate chain max 4 levels deep; minimum SHA-256 signature, RSA-2048 or ECDSA-256 key strength
- S3 bucket must be in the same region as API Gateway; enable versioning for rollback
- Works with **Regional** custom domain names for REST and HTTP APIs. Edge-optimized custom domains do not support mTLS
- WebSocket APIs do not support native mTLS; use CloudFront viewer mTLS instead (see `references/security.md`)
- ACM certificate required for the API Gateway domain (ACM-issued or imported) for server-side TLS. Truststore accepts CA certificates from any source (ACM Private CA, commercial CA, self-signed root); just needs PEM format
- **Private APIs do not natively support mTLS**. Use ALB as a reverse proxy in front: Client → ALB (mTLS verify with trust store) → VPC endpoint → Private API Gateway → backend. The ALB terminates the mTLS handshake, validates the client certificate, and forwards the request to the private API via the execute-api VPC endpoint
- **Disable default endpoint**: Always set `disableExecuteApiEndpoint: true` when using mTLS; otherwise clients can bypass mTLS entirely by calling the default `execute-api` URL directly
- **CRL checks**: API Gateway does not check Certificate Revocation Lists. Implement via Lambda authorizer checking against CRL in DynamoDB/S3
- **Certificate propagation to backend**: Use Lambda authorizer to extract subject, return in context, inject as custom header via `RequestParameters`

## API Keys

- **Not a primary authorization mechanism** (easily shared/exposed)
- Use with usage plans for throttling/quota enforcement only
- Max 10,000 API keys per region (adjustable). Imported key values must be 20-128 characters
- Key source: `HEADER` (default, `x-api-key`) or `AUTHORIZER` (Lambda returns key in `usageIdentifierKey`)
- REST API only. HTTP API does not support API keys or usage plans
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
# Custom Domains and Routing

## Custom Domain Names

### Setup by Endpoint Type

- **Edge-optimized**: ACM certificate must be in `us-east-1`. Creates an internal, AWS-managed CloudFront distribution (not visible in your CloudFront console, not configurable). Does **NOT** cache at the edge. For actual edge caching, use a separate CloudFront distribution with a Regional API. DNS CNAME/alias to CloudFront domain
- **Regional**: ACM certificate must be in same region as API. DNS CNAME/alias to regional domain name (`d-xxx.execute-api.region.amazonaws.com`)
- **Private**: REST API only. Dualstack only (`AWS::ApiGateway::DomainNameV2`). Domain name access associations link the domain to VPC endpoints. Route 53 alias in private hosted zone pointing to VPC endpoint regional DNS. Cross-account sharing via AWS RAM domain name access associations. ACM certificate in the same region

**Certificate requirements:**

- **Edge-optimized**: ACM-issued public certificate or certificate imported into ACM. Must be in us-east-1. Imported certificates must be [manually rotated before expiration](https://docs.aws.amazon.com/apigateway/latest/developerguide/how-to-edge-optimized-custom-domain-name.html)
- **Regional and Private**: ACM-issued public certificate or certificate imported into ACM. Private CA certificates (ACM Private CA) are only for mTLS truststores, not for the domain itself

### Limits

- Public custom domains: 120/region
- Private custom domains: 50/region
- API mappings per domain: 200
- Base path max length: 300 characters

### Common Issues

- **CNAMEAlreadyExists** (edge-optimized only): CNAME already associated with another CloudFront distribution. Delete or update existing CNAME first, or use Regional endpoint type to avoid this
- **Wrong certificate returned**: DNS record points to stage URL instead of API Gateway domain name target
- **Deletion quota**: 1 per 30 seconds. Use exponential backoff
- **403 "Missing Authentication Token"**: Stage name included in URL when using custom domain. Remove stage name from path

## Base Path Mappings

### Multi-Segment Paths

- Paths can contain forward slashes: `/sales/reporting`, `/sales/reporting/v2`, `/corp/admin`
- Each routes to a different API endpoint
- Use `AWS::ApiGatewayV2::DomainName` and `AWS::ApiGatewayV2::ApiMapping` with `ApiMappingKey`
- Works with both REST (v1) and HTTP (v2) APIs
- Domain and APIs must be in same account and Region
- Each sub-application deployed independently

### Multi-Tenant White-Label

White-label domain support allows SaaS providers to serve multiple external customers through customer-specific subdomains (e.g., `customer1.example.com`, `customer2.example.com`) while routing all traffic through a single API Gateway API. Based on the pattern described in [Using API Gateway as a Single Entry Point for Web Applications and API Microservices](https://aws.amazon.com/blogs/architecture/using-api-gateway-as-a-single-entry-point-for-web-applications-and-api-microservices/) (AWS Architecture Blog).

**Setup:**

1. Register a domain (e.g., `example.com`) and create CNAME records for each customer subdomain (`customer1.example.com`, `customer2.example.com`) via Route 53 or your DNS provider
2. Create an ACM wildcard certificate (`*.example.com`), which covers one subdomain level only (`tenant1.example.com` matches, `a.tenant1.example.com` does not)
3. Create a custom domain in API Gateway for each customer subdomain using the wildcard certificate. Each subdomain can have its own base path mappings or routing rules, or use a shared mapping with backend routing based on the forwarded Host header
4. Point each subdomain's CNAME record to the API Gateway domain name target
5. Forward the original `Host` header as a custom header to the backend so it can identify the customer:
- REST API: map `method.request.header.host` to `integration.request.header.Customer` via `RequestParameters`
- HTTP API: use parameter mapping: `overwrite` on `integration.request.header.Customer` from `$request.header.host`

**Key considerations:**

- The wildcard certificate applied to API Gateway allows multiple subdomains to be served by a single API endpoint
- Each customer subdomain is created as a separate custom domain in API Gateway, enabling per-customer base path mappings or routing rules
- Backend microservices use the forwarded customer header to apply customer-specific business logic
- API Gateway's request/response transformation can insert or modify headers per customer
- The 120 public custom domains per region quota limits the number of customer subdomains (request increase if needed)

## Routing Rules (Preferred for New Domains)

**Routing rules are the recommended approach** over base path mappings for new custom domains, offering header-based routing, priority-based evaluation, and simpler management. Supports public and private REST APIs only. HTTP API and WebSocket API do not support routing rules; use base path mappings instead.

### Rule Structure

- **Conditions**: Up to 2 `MatchHeaders` + 1 `MatchBasePaths` (AND logic)
- **Actions**: Invoke any stage of any REST API in the same account and region
- **Priority**: 1-1,000,000 (lower = higher precedence, no duplicates). Leave gaps between priorities (100, 200, 300) to allow inserting new rules later. Creating a rule with a duplicate priority fails with `ConflictException`
- Header matching supports wildcards: `*latest` (matches values ending with "latest"), `alpha*` (matches values starting with "alpha"), `*v2*` (contains). Header names are case-insensitive; header values are case-sensitive

### Routing Modes

1. **API mappings only** (default): Traditional base path mapping behavior. Use if not adopting routing rules
2. **Routing rules then API mappings**: Routing rules take precedence; unmatched requests fall back to base path mappings. Use for zero-downtime migration from base path mappings to routing rules
3. **Routing rules only**: **Recommended mode** for new custom domains or after completing migration from base path mappings. Requests that match no routing rule receive a 404 response

### Migration from Base Path Mappings

1. Set routing mode to "Routing rules then API mappings" — existing base path mappings continue as fallback
2. Progressively create routing rules (e.g., start with a test header rule for controlled traffic). Include a catch-all rule (no conditions) at the lowest priority as a safety net; without this, unmatched requests will receive a 404 after switching modes in step 4
3. Monitor with `$context.customDomain.routingRuleIdMatched` in access logs to verify routing behavior and confirm all expected traffic paths are covered by rules
4. Once all traffic is covered by rules, switch to "Routing rules only" mode

### Implementation

- CloudFormation: `AWS::ApiGatewayV2::RoutingRule`
- Observability: `$context.customDomain.routingRuleIdMatched` in access logs
- No additional charges for routing rules; standard API Gateway request pricing applies
- A rule with no conditions serves as a catch-all matching all requests

### Use Cases

- **API versioning**: Route by `Accept` or `X-API-Version` header to different API implementations
- **Gradual rollouts**: Route a percentage of users to new version by adding a header in application code, then gradually increase
- **A/B testing**: Route specific user cohorts by custom header (e.g., `x-test-group: beta-testers`)
- **Cell-based architecture**: Route by tenant ID or hostname header to different cell backends
- **Dynamic backend selection**: Route by cookie value, media type, or any custom header

## Header-Based API Versioning

Route API requests to different backend implementations based on a version header (REST APIs only).

- Create a routing rule per version with `MatchHeaders` on the version header (e.g., `X-API-Version: v1`, `X-API-Version: v2`)
- Each rule invokes the corresponding API/stage
- Add a catch-all rule at the lowest priority to route unversioned requests to the default (latest stable) version
- Monitor with `$context.customDomain.routingRuleIdMatched` in access logs to track version adoption
- No additional infrastructure, no Lambda@Edge, no DynamoDB. Purely declarative

## Host Header Forwarding

- API Gateway overwrites Host header with integration endpoint hostname
- Cannot forward original Host header directly
- **REST API workaround**: Create custom header in Method Request, map in Integration Request: `method.request.header.host` -> `integration.request.header.my_host`
- **HTTP API workaround**: Use parameter mapping to forward the host header: `overwrite` on `integration.request.header.X-Original-Host` from `$request.header.host`
Loading
Loading