import * as apigw from "aws-cdk-lib/aws-apigatewayv2"; import * as authorizers from "aws-cdk-lib/aws-apigatewayv2-authorizers"; import type { IUserPool } from "aws-cdk-lib/aws-cognito"; import type * as ec2 from "aws-cdk-lib/aws-ec2"; import type * as elb from "aws-cdk-lib/aws-elasticloadbalancingv2"; import type * as events from "aws-cdk-lib/aws-events"; import * as iam from "aws-cdk-lib/aws-iam"; import * as lambda from "aws-cdk-lib/aws-lambda"; import type * as logs from "aws-cdk-lib/aws-logs"; import type * as sqs from "aws-cdk-lib/aws-sqs"; import * as constructs from "constructs"; import { type ApiGatewayAccessLogsProps } from "./access-logs"; import { type ApiGatewayDnsProps } from "./domain"; /** * Props for the {@link ApiGateway} construct. * * @author Kristian Rekstad * @author Hermann Mørkrid */ export type ApiGatewayProps = { /** Settings for the external-facing part of the API-GW. */ dns: ApiGatewayDnsProps; /** * If no integration is specified for a route, this integration is used. * * See {@link IntegrationProps} for the available options. */ defaultIntegration?: IntegrationProps; /** * If no authorization is specified for a route, this authorization is used. * * See {@link AuthorizationProps} for the available options. */ defaultAuthorization?: AuthorizationProps; routes: ApiGatewayRoute[]; /** * The API-GW access logs for the `$default` stage are kept. * This has options for the logs. */ accessLogs?: ApiGatewayAccessLogsProps; /** * Set to false to disable route-level metrics. * This can increase CloudWatch costs when not disabled. * * See [AWS: Working with metrics for HTTP APIs](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-metrics.html?icmpid=apigateway_console_help#:~:text=and%20stage%20ID.-,ApiId%2C%20Stage%2C%20Route,-Filters%20API%20Gateway) * for more info. * * @default true */ detailedMetrics?: boolean; /** * Throttling of requests. * If not set, the AWS default of 5000 burst and 10000 rate is used. * * The default throttling is per-account, and counts all APIs in the account and region. * If you have another API in this region getting 10_000 request rate, it may impact this API as well. */ throttling?: { /** * Going over `5_000` may require you to contact AWS Support - they are account bound. */ burst?: number; /** * Going over `10_000` may require you to contact AWS Support - they are account bound. */ rate?: number; }; /** * Sets CORS headers on responses from the API Gateway to allow all origins, headers and methods. * Useful if your API should be accessed by any browser. */ corsAllowAll?: boolean; /** * If some settings in this construct do not work for you, this is an escape hatch mechanism to * override anything. */ propsOverride?: { /** * Override settings for the {@link apigw.HttpApi} (accessible in {@link ApiGateway.httpApi}). * * For example, if you have a frontend accessing this API, you might want to set * [CORS preflight](https://docs.aws.amazon.com/cdk/api/v1/docs/aws-apigatewayv2-readme.html#cross-origin-resource-sharing-cors) * settings. */ httpApi?: Partial; }; }; export type ApiGatewayRoute = { /** The path of the route to expose through the API Gateway. Use "/" for the root route. */ path: string; /** * By default, we only forward requests that match the route's path exactly. So for a route with * path `/api/users`, a request to `/api/users` will be forwarded, but a request to * `/api/users/admin` will not. If you want to forward requests to all sub-paths under the route's * path, you can set this to true. * * @default false */ includeSubpaths?: boolean; /** * The HTTP method to expose. `ANY` exposes all HTTP methods on the path. * * @default "ANY" */ method?: HttpMethod; /** * The integration that the route will forward to. See {@link IntegrationProps} for the available * options. * * If undefined, uses the {@link ApiGatewayProps.defaultIntegration}. */ integration?: IntegrationProps; /** * How requests on the route are authenticated. See {@link AuthorizationProps} for the available * options. * * If undefined, uses the {@link ApiGatewayProps.defaultAuthorization}. */ authorization?: AuthorizationProps; }; export type HttpMethod = "ANY" | "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "OPTIONS" | "HEAD"; export type IntegrationProps = /** Use this when connecting the route to an ALB (Application Load Balancer). */ ({ type: "ALB"; } & AlbIntegrationProps) /** Use this when connecting route to a Lambda. */ | ({ type: "Lambda"; } & LambdaIntegrationProps) /** Use this when connecting a route to send to an SQS queue. */ | ({ type: "SQS"; } & SqsIntegrationProps) /** Use this when connecting a route to send to an EventBridge event bus. */ | ({ type: "EventBridge"; } & EventBridgeIntegrationProps); /** * Props for the API-GW -> ALB (Application Load Balancer) integration. * * See the note on {@link ApiGateway} about the load balancer security group. */ export type AlbIntegrationProps = { /** * A listener on e.g. port 443 (HTTPS). * * See the note on {@link ApiGateway} about the load balancer security group. */ loadBalancerListener: elb.IApplicationListener; /** * The host name (domain name) of the backend service that we want to reach through the ALB. * * This is used to: * - Verify the HTTPS certificate of the backend service, so that the request forwarded from * API-GW can use TLS * - Set the `Host` header on the request when forwarding to the ALB, so that requests can be * routed to the correct Target Group * * Example value: `.staging.my-project.liflig.io` (not prefixed by `https://`). */ hostName: string; /** * The VPC used by the ALB. The API-GW integration will connect to the ALB using a VPC Link for * this VPC. * * See the note on {@link ApiGateway} about the load balancer security group. */ vpc: ec2.IVpc; /** * A security group (SG) that allows incoming traffic to the ALB. Will be used by the VPC link, so * the API-GW integration can connect. * * This is usually the same SG as the ALB uses, because they have a rule that allows traffic from * others in the same SG. * * See the note on {@link ApiGateway} about the load balancer security group. */ securityGroup: ec2.ISecurityGroup; /** * Map request parameters (add/overwrite path/headers/query params) before forwarding to the * backend. * * See {@link https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-parameter-mapping.html} * for more on this. Read the 'Reserved headers' section for which headers cannot be overridden. * In addition to the AWS-reserved headers, you should not override the 'Host' header either, as * that's used for routing the request to the correct service behind the load balancer. * * ### Example: * * Adding a header: * ``` * mapParameters: (parameters) => parameters.overwriteHeader( * "X-My-Custom-Header", * apigw.MappingValue.custom("my-custom-value"), * ) * ``` * * Overwriting the path (if, for example, you configure a `/users` route on the API Gateway that * you want to forward to `/api/users` on the backend): * ``` * mapParameters: (parameters) => parameters.overwritePath("/api/users") * ``` */ mapParameters?: (parameters: apigw.ParameterMapping) => void; }; export type LambdaIntegrationProps = { /** * The Lambda integration uses the V2 payload format: * {@link https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html} * * If writing the Lambda in TypeScript, this means you should use `APIGatewayProxyEventV2` as the * request type, and `APIGatewayProxyResultV2` as the response type. */ lambda: lambda.IFunction; }; export type SqsIntegrationProps = { queue: sqs.IQueue; /** * Message attributes to pass on to SQS. The keys in this object are the names of the attributes. * Each attribute has a DataType field, and either a StringValue or BinaryValue field depending on * its type. See AWS docs: * https://docs.aws.amazon.com/AWSSimpleQueueService/latest/APIReference/API_MessageAttributeValue.html * * In the StringValue field, you can do API Gateway parameter mapping: * https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-parameter-mapping.html * * Example: * ``` * messageAttributes: { * clientId: { * DataType: "String", * StringValue: "${context.authorizer.clientId}", * }, * }, * ``` */ messageAttributes?: { [attributeName: string]: { DataType: "String" | "Number"; StringValue: string; } | { DataType: "Binary"; /** Base64-encoded binary data object. */ BinaryValue: string; }; }; }; export type EventBridgeIntegrationProps = { /** * A role is needed in order to grant api gateway access to put events * on the desired event bus. * * If no role is provided, a new role will be created in order to grant apigw access to the event bus * * @default: creates role that can be assumed by api gw with access to putEvents on the but inputted */ role?: iam.IRole; /** * An EventBridge event bus. Request bodies sent to the route will be forwarded to this event bus, * in the `Detail` field (see * https://docs.aws.amazon.com/eventbridge/latest/APIReference/API_PutEventsRequestEntry.html). */ eventBus: events.IEventBus; /** * Sets the `DetailType` field (i.e., event type) on events published to the event bus. * * See AWS docs for more on this: * https://docs.aws.amazon.com/eventbridge/latest/APIReference/API_PutEventsRequestEntry.html */ detailType: string; }; export type AuthorizationProps = /** * No authentication, for when you want a fully public route (or handle authentication in the * backend integration). */ { type: "NONE"; } /** * AWS IAM authorization. * https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-access-control-iam.html */ | { type: "IAM"; } /** * Creates a custom Lambda authorizer which reads `Authorization: Bearer ` header * and verifies the token against a Cognito user pool. */ | ({ type: "COGNITO_USER_POOL"; } & CognitoUserPoolAuthorizerProps) /** * Creates a custom Lambda authorizer which reads `Authorization: Basic ` * header and verifies the credentials against a given secret. */ | ({ type: "BASIC_AUTH"; } & BasicAuthAuthorizerProps) /** * Creates a custom Lambda authorizer which allows both: * - `Authorization: Bearer ` header, for which the token is checked against the * given Cognito user pool * - `Authorization: Basic ` header, for which the credentials are * checked against the credentials from the given basic auth secret name * * If either of these are given and valid, the request is authenticated. */ | ({ type: "COGNITO_USER_POOL_OR_BASIC_AUTH"; } & CognitoUserPoolOrBasicAuthAuthorizerProps) /** * Creates a custom authorizer with the given Lambda function. Use this if you have custom * authorization logic, and the other authorizers from this construct don't meet your needs. * * https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-lambda-authorizer.html */ | ({ type: "CUSTOM_LAMBDA_AUTHORIZER"; } & CustomLambdaAuthorizerProps); export type CognitoUserPoolAuthorizerProps = { userPool: IUserPool; /** * Verifies that access token claims contain the given scope. * * When defined as part of a resource server, scopes are on the format: * `{resource server identifier}/{scope name}`, e.g. `external/view_users`. * * To get more type safety on this parameter, see the docs for the `AuthScopesT` type parameter on * {@link ApiGateway}. */ requiredScope?: AuthScopesT; /** * Name of secret in AWS Secrets Manager that stores basic auth credentials for the backend * service, to be forwarded to the backend if Cognito user pool authentication succeeded. * * The secret value must follow this format: * ```json * { "username": "", "password": "" } * ``` * * This prop solves the following use-case: * - You want to do Cognito user pool authentication in the API Gateway * - You want an additional auth check in the backend, but you don't want to deal with Cognito * there * - The backend uses basic auth * * This prop solves this by letting you specify credentials to pass to the backend after API-GW * authentication succeeds. You can pass the encoded credentials through * {@link AlbIntegrationProps.mapParameters}, using the `authorizer.internalAuthorizationHeader` * context variable, like so: * ``` * mapParameters: (parameters) => parameters.overwriteHeader( * // 'Authorization' header cannot be overridden, so we use a custom header * "X-Internal-Authorization", * apigw.MappingValue.contextVariable("authorizer.internalAuthorizationHeader"), * ) * ``` * The backend can then check the `X-Internal-Authorization` header. */ credentialsForInternalAuthorization?: string; }; export type BasicAuthAuthorizerProps = { /** * Name of secret in AWS Secrets Manager that stores basic auth credentials. * * The following formats are supported for the secret value: * - Single username and password: * ```json * { "username": "", "password": "" } * ``` * - Array of username + password objects: * ```json * { "credentials": "[{\"username\":\"\",\"password\":\"password-1\"},{\"username\":\"\",\"password\":\"\"}]" } * ``` * - The value of the `credentials` field is a string, with a stringified, escaped JSON array of * objects with `username` and `password` fields. * - The reason that this second format stores stringified JSON _inside_ JSON, is due to a * limitation in Liflig's `load-secrets` library, which only allows storing string values. * - Array of base64-encoded credentials: * ```json * { "credentials": "[\"\",\"\"]" } * ``` * - Each element is encoded from `:`. * - The array is stringified for the same reason as above. * * If the secret uses one of the array formats, the authorizer will match the request's * Authorization header against any one of the credentials. */ credentialsSecretName: string; }; export type CognitoUserPoolOrBasicAuthAuthorizerProps = { userPool: IUserPool; /** * Name of secret in AWS Secrets Manager that stores basic auth credentials. * * See {@link BasicAuthAuthorizerProps.credentialsSecretName} for the supported formats. */ basicAuthCredentialsSecretName?: string; /** * Verifies that access token claims contain the given scope. Only applicable for requests that * use `Authorization: Bearer ` (not applicable for basic auth). * * When defined as part of a resource server, scopes are on the format: * `{resource server identifier}/{scope name}`, e.g. `external/view_users`. * * To get more type safety on this parameter, see the docs for the `AuthScopesT` type parameter on * {@link ApiGateway}. */ requiredScope?: AuthScopesT; }; type CustomLambdaAuthorizerProps = { /** * The Lambda function that will be run whenever the API Gateway route is invoked, to authenticate * the request. See AWS docs for more on how to write the Lambda: * https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-lambda-authorizer.html * * The default response format used is `HttpLambdaResponseType.SIMPLE` (format 2.0). If you write * your Lambda in TypeScript, this means that your handler must return * `APIGatewaySimpleAuthorizerResult` (from the `aws-lambda` package). The request event will also * use format 2.0 (`APIGatewayRequestAuthorizerEventV2` in TypeScript). See the AWS docs linked * above for more details on these formats. * * You can override the response format type in * {@link CustomLambdaAuthorizerProps.authorizerProps}. * * See the Lambdas under the `authorizers` folder next to the `ApiGateway` construct in * `liflig-cdk` for examples. */ lambdaAuthorizer: lambda.IFunction; /** * Props for the `HttpLambdaAuthorizer` construct. We provide some different defaults: * - `responseTypes` defaults to `[HttpLambdaResponseType.SIMPLE]` * - `resultsCacheTtl` defaults to `Duration.hours(1)` */ authorizerProps?: Partial; }; /** * This construct tries to simplify the creation of an API Gateway for a service, by collecting most * of the common setup here. * * The approach followed in this construct is: * 1. One API-GW per service * 2. One subdomain per API-GW / service * 3. Use HTTP API, not REST * 4. Use a $default stage with autodeploy * 5. Support multiple routes (with possible `/{proxy+}` to let all sub-paths through) * 6. Allow custom integration/authorizer for each route, or defaults for the whole gateway * * The route integration is one of these: * - ALB private integration with VPC Link using HTTPS to the ALB * - Lambda integration * - SQS integration * * ### Load Balancer Security Group * * Note that the load balancer used in an {@link AlbIntegrationProps} must allow outbound HTTPS * traffic to its SecurityGroup. Otherwise, the VPC Link used by the API-GW can't get traffic from * the ALB. * * ``` * const loadBalancerSecurityGroup = new ec2.SecurityGroup(..., { * allowAllOutbound: false, * }) * * loadBalancerSecurityGroup.addEgressRule( * loadBalancerSecurityGroup, * ec2.Port.tcp(443), * "Outbound to self for ALB to API-GW VPC-Link", * ) * * const loadBalancer = new lifligLoadBalancer.LoadBalancer(..., * { * overrideLoadBalancerProps: { * securityGroup: loadBalancerSecurityGroup, * }, * }, * ) * ``` * * @template AuthScopesT This type parameter allows you to improve type safety on the * `requiredScope` field on {@link CognitoUserPoolOrBasicAuthAuthorizerProps}, by narrowing the type * to specific strings. You can then extend the `ApiGateway` with this type to enforce those scopes * across the application. Remember that auth scopes must be on the format * `{resource server identifier}/{scope name}`. * * Example: * ``` * type AuthScopes = "external/read_users" | "internal/create_users" * * export class MyProjectApiGateway extends ApiGateway {} * ``` * TypeScript will then enforce that `requiredScope` is one of `AuthScopes`, and provide * auto-complete. * * @author Kristian Rekstad * @author Hermann Mørkrid */ export declare class ApiGateway extends constructs.Construct { /** The API Gateway HTTP API. This is the main construct for API-GW. */ readonly httpApi: apigw.HttpApi; /** The routes which connect the {@link httpApi} to the backend integration(s). */ readonly routes: apigw.HttpRoute[]; /** The domain which consumers must use.*/ readonly domain: string; /** Access log group. */ readonly logGroup: logs.LogGroup; private readonly props; constructor(scope: constructs.Construct, id: string, props: ApiGatewayProps); /** @throws Error */ private static validateProps; /** * The authorizer only accepts requests from external users that are authorized. * Unauthorized users are stopped in the API-GW, and not forwarded to the integration. */ private createAuthorizer; private createIntegration; /** * Allows a grantable target (role, user etc.) permission to invoke the API. * Only works when using `IAM` as {@link ApiGatewayRoute.authorization}. * * @param target A grantable, like {@link iam.Role} */ grantInvoke(target: iam.IGrantable): void; } export {};