From f84b0520838dba9f2ab70670611890a712546005 Mon Sep 17 00:00:00 2001 From: uid10804 Date: Fri, 19 Jan 2024 21:42:33 +0100 Subject: [PATCH] feat(internal-apigateway): add all restapi gateway properties with partial security restrictions --- API.md | 750 ++++++++++++++++++++++++++++++++-- src/internal-apigateway.ts | 112 ++--- test/apigateway.test.ts | 113 ++++- test/internal-website.test.ts | 8 +- test/security.test.ts | 104 ++++- 5 files changed, 974 insertions(+), 113 deletions(-) diff --git a/API.md b/API.md index a5e7b25..a8c7b6e 100644 --- a/API.md +++ b/API.md @@ -496,7 +496,7 @@ The tree node. ### InternalApiGatewayProps -Properties for ApiGateway. +Properties for ApiGateway Includes all properties of RestApiProps except: endpointConfiguration, policy This is done by intention to prevent the user from overriding the required security settings. #### Initializer @@ -510,62 +510,287 @@ const internalApiGatewayProps: InternalApiGatewayProps = { ... } | **Name** | **Type** | **Description** | | --- | --- | --- | +| defaultCorsPreflightOptions | aws-cdk-lib.aws_apigateway.CorsOptions | Adds a CORS preflight OPTIONS method to this resource and all child resources. | +| defaultIntegration | aws-cdk-lib.aws_apigateway.Integration | An integration to use as a default for all methods created within this API unless an integration is specified. | +| defaultMethodOptions | aws-cdk-lib.aws_apigateway.MethodOptions | Method options to use as a default for all methods created within this API unless custom options are specified. | +| cloudWatchRole | boolean | Automatically configure an AWS CloudWatch role for API Gateway. | +| deploy | boolean | Indicates if a Deployment should be automatically created for this API, and recreated when the API model (resources, methods) changes. | +| deployOptions | aws-cdk-lib.aws_apigateway.StageOptions | Options for the API Gateway stage that will always point to the latest deployment when `deploy` is enabled. | +| description | string | A description of the RestApi construct. | +| disableExecuteApiEndpoint | boolean | Specifies whether clients can invoke the API using the default execute-api endpoint. | +| domainName | aws-cdk-lib.aws_apigateway.DomainNameOptions | Configure a custom domain name and map it to this API. | +| endpointExportName | string | Export name for the CfnOutput containing the API endpoint. | +| endpointTypes | aws-cdk-lib.aws_apigateway.EndpointType[] | A list of the endpoint types of the API. | +| failOnWarnings | boolean | Indicates whether to roll back the resource if a warning occurs while API Gateway is creating the RestApi resource. | +| parameters | {[ key: string ]: string} | Custom header parameters for the request. | +| policy | aws-cdk-lib.aws_iam.PolicyDocument | A policy document that contains the permissions for this RestApi. | +| restApiName | string | A name for the API Gateway RestApi resource. | +| retainDeployments | boolean | Retains old deployment resources when the API changes. | +| apiKeySourceType | aws-cdk-lib.aws_apigateway.ApiKeySourceType | The source of the API key for metering requests according to a usage plan. | +| binaryMediaTypes | string[] | The list of binary media mime-types that are supported by the RestApi resource, such as "image/png" or "application/octet-stream". | +| cloneFrom | aws-cdk-lib.aws_apigateway.IRestApi | The ID of the API Gateway RestApi resource that you want to clone. | +| endpointConfiguration | aws-cdk-lib.aws_apigateway.EndpointConfiguration | The EndpointConfiguration property type specifies the endpoint types of a REST API. | +| minCompressionSize | aws-cdk-lib.Size | A Size(in bytes, kibibytes, mebibytes etc) that is used to enable compression (with non-negative between 0 and 10485760 (10M) bytes, inclusive) or disable compression (when undefined) on an API. | +| minimumCompressionSize | number | A nullable integer that is used to enable compression (with non-negative between 0 and 10485760 (10M) bytes, inclusive) or disable compression (when undefined) on an API. | | domains | aws-cdk-lib.aws_apigateway.IDomainName[] | List of custom domains names to be used for the API Gateway. | -| stage | string | Stage name used for all cloudformation resource names and internal aws resource names. | | vpcEndpoint | aws-cdk-lib.aws_ec2.IInterfaceVpcEndpoint | VPC endpoint id of execute-api vpc endpoint. | | apiBasePathMappingPath | string | Path for custom domain base path mapping that will be attached to the api gateway. | -| binaryMediaTypes | string[] | Binary media types for the internal api gateway. | -| minimumCompressionSize | number | minimum compression size for the internal api gateway in kilobytes. | +| stage | string | Stage name used for all cloudformation resource names and internal aws resource names. | --- -##### `domains`Required +##### `defaultCorsPreflightOptions`Optional ```typescript -public readonly domains: IDomainName[]; +public readonly defaultCorsPreflightOptions: CorsOptions; ``` -- *Type:* aws-cdk-lib.aws_apigateway.IDomainName[] +- *Type:* aws-cdk-lib.aws_apigateway.CorsOptions +- *Default:* CORS is disabled -List of custom domains names to be used for the API Gateway. +Adds a CORS preflight OPTIONS method to this resource and all child resources. + +You can add CORS at the resource-level using `addCorsPreflight`. --- -##### `stage`Required +##### `defaultIntegration`Optional ```typescript -public readonly stage: string; +public readonly defaultIntegration: Integration; +``` + +- *Type:* aws-cdk-lib.aws_apigateway.Integration +- *Default:* Inherited from parent. + +An integration to use as a default for all methods created within this API unless an integration is specified. + +--- + +##### `defaultMethodOptions`Optional + +```typescript +public readonly defaultMethodOptions: MethodOptions; +``` + +- *Type:* aws-cdk-lib.aws_apigateway.MethodOptions +- *Default:* Inherited from parent. + +Method options to use as a default for all methods created within this API unless custom options are specified. + +--- + +##### `cloudWatchRole`Optional + +```typescript +public readonly cloudWatchRole: boolean; +``` + +- *Type:* boolean +- *Default:* false if `@aws-cdk/aws-apigateway:disableCloudWatchRole` is enabled, true otherwise + +Automatically configure an AWS CloudWatch role for API Gateway. + +--- + +##### `deploy`Optional + +```typescript +public readonly deploy: boolean; +``` + +- *Type:* boolean +- *Default:* true + +Indicates if a Deployment should be automatically created for this API, and recreated when the API model (resources, methods) changes. + +Since API Gateway deployments are immutable, When this option is enabled +(by default), an AWS::ApiGateway::Deployment resource will automatically +created with a logical ID that hashes the API model (methods, resources +and options). This means that when the model changes, the logical ID of +this CloudFormation resource will change, and a new deployment will be +created. + +If this is set, `latestDeployment` will refer to the `Deployment` object +and `deploymentStage` will refer to a `Stage` that points to this +deployment. To customize the stage options, use the `deployOptions` +property. + +A CloudFormation Output will also be defined with the root URL endpoint +of this REST API. + +--- + +##### `deployOptions`Optional + +```typescript +public readonly deployOptions: StageOptions; +``` + +- *Type:* aws-cdk-lib.aws_apigateway.StageOptions +- *Default:* Based on defaults of `StageOptions`. + +Options for the API Gateway stage that will always point to the latest deployment when `deploy` is enabled. + +If `deploy` is disabled, +this value cannot be set. + +--- + +##### `description`Optional + +```typescript +public readonly description: string; ``` - *Type:* string +- *Default:* 'Automatically created by the RestApi construct' -Stage name used for all cloudformation resource names and internal aws resource names. +A description of the RestApi construct. --- -##### `vpcEndpoint`Required +##### `disableExecuteApiEndpoint`Optional ```typescript -public readonly vpcEndpoint: IInterfaceVpcEndpoint; +public readonly disableExecuteApiEndpoint: boolean; ``` -- *Type:* aws-cdk-lib.aws_ec2.IInterfaceVpcEndpoint +- *Type:* boolean +- *Default:* false -VPC endpoint id of execute-api vpc endpoint. +Specifies whether clients can invoke the API using the default execute-api endpoint. -This endpoint will be used to forward requests from the load balancer`s target group to the api gateway. +To require that clients use a custom domain name to invoke the +API, disable the default endpoint. + +> [https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-restapi.html](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-restapi.html) --- -##### `apiBasePathMappingPath`Optional +##### `domainName`Optional ```typescript -public readonly apiBasePathMappingPath: string; +public readonly domainName: DomainNameOptions; +``` + +- *Type:* aws-cdk-lib.aws_apigateway.DomainNameOptions +- *Default:* no domain name is defined, use `addDomainName` or directly define a `DomainName`. + +Configure a custom domain name and map it to this API. + +--- + +##### `endpointExportName`Optional + +```typescript +public readonly endpointExportName: string; ``` - *Type:* string +- *Default:* when no export name is given, output will be created without export -Path for custom domain base path mapping that will be attached to the api gateway. +Export name for the CfnOutput containing the API endpoint. + +--- + +##### `endpointTypes`Optional + +```typescript +public readonly endpointTypes: EndpointType[]; +``` + +- *Type:* aws-cdk-lib.aws_apigateway.EndpointType[] +- *Default:* EndpointType.EDGE + +A list of the endpoint types of the API. + +Use this property when creating +an API. + +--- + +##### `failOnWarnings`Optional + +```typescript +public readonly failOnWarnings: boolean; +``` + +- *Type:* boolean +- *Default:* false + +Indicates whether to roll back the resource if a warning occurs while API Gateway is creating the RestApi resource. + +--- + +##### `parameters`Optional + +```typescript +public readonly parameters: {[ key: string ]: string}; +``` + +- *Type:* {[ key: string ]: string} +- *Default:* No parameters. + +Custom header parameters for the request. + +> [https://docs.aws.amazon.com/cli/latest/reference/apigateway/import-rest-api.html](https://docs.aws.amazon.com/cli/latest/reference/apigateway/import-rest-api.html) + +--- + +##### `policy`Optional + +```typescript +public readonly policy: PolicyDocument; +``` + +- *Type:* aws-cdk-lib.aws_iam.PolicyDocument +- *Default:* No policy. + +A policy document that contains the permissions for this RestApi. + +--- + +##### `restApiName`Optional + +```typescript +public readonly restApiName: string; +``` + +- *Type:* string +- *Default:* ID of the RestApi construct. + +A name for the API Gateway RestApi resource. + +--- + +##### `retainDeployments`Optional + +```typescript +public readonly retainDeployments: boolean; +``` + +- *Type:* boolean +- *Default:* false + +Retains old deployment resources when the API changes. + +This allows +manually reverting stages to point to old deployments via the AWS +Console. + +--- + +##### `apiKeySourceType`Optional + +```typescript +public readonly apiKeySourceType: ApiKeySourceType; +``` + +- *Type:* aws-cdk-lib.aws_apigateway.ApiKeySourceType +- *Default:* Metering is disabled. + +The source of the API key for metering requests according to a usage plan. --- @@ -576,20 +801,127 @@ public readonly binaryMediaTypes: string[]; ``` - *Type:* string[] +- *Default:* RestApi supports only UTF-8-encoded text payloads. + +The list of binary media mime-types that are supported by the RestApi resource, such as "image/png" or "application/octet-stream". + +--- + +##### `cloneFrom`Optional + +```typescript +public readonly cloneFrom: IRestApi; +``` -Binary media types for the internal api gateway. +- *Type:* aws-cdk-lib.aws_apigateway.IRestApi +- *Default:* None. + +The ID of the API Gateway RestApi resource that you want to clone. --- -##### `minimumCompressionSize`Optional +##### `endpointConfiguration`Optional + +```typescript +public readonly endpointConfiguration: EndpointConfiguration; +``` + +- *Type:* aws-cdk-lib.aws_apigateway.EndpointConfiguration +- *Default:* EndpointType.EDGE + +The EndpointConfiguration property type specifies the endpoint types of a REST API. + +> [https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-restapi-endpointconfiguration.html](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-restapi-endpointconfiguration.html) + +--- + +##### `minCompressionSize`Optional + +```typescript +public readonly minCompressionSize: Size; +``` + +- *Type:* aws-cdk-lib.Size +- *Default:* Compression is disabled. + +A Size(in bytes, kibibytes, mebibytes etc) that is used to enable compression (with non-negative between 0 and 10485760 (10M) bytes, inclusive) or disable compression (when undefined) on an API. + +When compression is enabled, compression or +decompression is not applied on the payload if the payload size is +smaller than this value. Setting it to zero allows compression for any +payload size. + +--- + +##### ~~`minimumCompressionSize`~~Optional + +- *Deprecated:* - superseded by `minCompressionSize` ```typescript public readonly minimumCompressionSize: number; ``` - *Type:* number +- *Default:* Compression is disabled. + +A nullable integer that is used to enable compression (with non-negative between 0 and 10485760 (10M) bytes, inclusive) or disable compression (when undefined) on an API. + +When compression is enabled, compression or +decompression is not applied on the payload if the payload size is +smaller than this value. Setting it to zero allows compression for any +payload size. + +--- + +##### `domains`Required + +```typescript +public readonly domains: IDomainName[]; +``` + +- *Type:* aws-cdk-lib.aws_apigateway.IDomainName[] -minimum compression size for the internal api gateway in kilobytes. +List of custom domains names to be used for the API Gateway. + +--- + +##### `vpcEndpoint`Required + +```typescript +public readonly vpcEndpoint: IInterfaceVpcEndpoint; +``` + +- *Type:* aws-cdk-lib.aws_ec2.IInterfaceVpcEndpoint + +VPC endpoint id of execute-api vpc endpoint. + +This endpoint will be used to forward requests from the load balancer`s target group to the api gateway. + +--- + +##### `apiBasePathMappingPath`Optional + +```typescript +public readonly apiBasePathMappingPath: string; +``` + +- *Type:* string + +Path for custom domain base path mapping that will be attached to the api gateway. + +--- + +##### ~~`stage`~~Optional + +- *Deprecated:* use deployOptions.stageName instead + +```typescript +public readonly stage: string; +``` + +- *Type:* string + +Stage name used for all cloudformation resource names and internal aws resource names. --- @@ -779,12 +1111,32 @@ const internalWebsiteProps: InternalWebsiteProps = { ... } | **Name** | **Type** | **Description** | | --- | --- | --- | +| defaultCorsPreflightOptions | aws-cdk-lib.aws_apigateway.CorsOptions | Adds a CORS preflight OPTIONS method to this resource and all child resources. | +| defaultIntegration | aws-cdk-lib.aws_apigateway.Integration | An integration to use as a default for all methods created within this API unless an integration is specified. | +| defaultMethodOptions | aws-cdk-lib.aws_apigateway.MethodOptions | Method options to use as a default for all methods created within this API unless custom options are specified. | +| cloudWatchRole | boolean | Automatically configure an AWS CloudWatch role for API Gateway. | +| deploy | boolean | Indicates if a Deployment should be automatically created for this API, and recreated when the API model (resources, methods) changes. | +| deployOptions | aws-cdk-lib.aws_apigateway.StageOptions | Options for the API Gateway stage that will always point to the latest deployment when `deploy` is enabled. | +| description | string | A description of the RestApi construct. | +| disableExecuteApiEndpoint | boolean | Specifies whether clients can invoke the API using the default execute-api endpoint. | +| domainName | aws-cdk-lib.aws_apigateway.DomainNameOptions | Configure a custom domain name and map it to this API. | +| endpointExportName | string | Export name for the CfnOutput containing the API endpoint. | +| endpointTypes | aws-cdk-lib.aws_apigateway.EndpointType[] | A list of the endpoint types of the API. | +| failOnWarnings | boolean | Indicates whether to roll back the resource if a warning occurs while API Gateway is creating the RestApi resource. | +| parameters | {[ key: string ]: string} | Custom header parameters for the request. | +| policy | aws-cdk-lib.aws_iam.PolicyDocument | A policy document that contains the permissions for this RestApi. | +| restApiName | string | A name for the API Gateway RestApi resource. | +| retainDeployments | boolean | Retains old deployment resources when the API changes. | +| apiKeySourceType | aws-cdk-lib.aws_apigateway.ApiKeySourceType | The source of the API key for metering requests according to a usage plan. | +| binaryMediaTypes | string[] | The list of binary media mime-types that are supported by the RestApi resource, such as "image/png" or "application/octet-stream". | +| cloneFrom | aws-cdk-lib.aws_apigateway.IRestApi | The ID of the API Gateway RestApi resource that you want to clone. | +| endpointConfiguration | aws-cdk-lib.aws_apigateway.EndpointConfiguration | The EndpointConfiguration property type specifies the endpoint types of a REST API. | +| minCompressionSize | aws-cdk-lib.Size | A Size(in bytes, kibibytes, mebibytes etc) that is used to enable compression (with non-negative between 0 and 10485760 (10M) bytes, inclusive) or disable compression (when undefined) on an API. | +| minimumCompressionSize | number | A nullable integer that is used to enable compression (with non-negative between 0 and 10485760 (10M) bytes, inclusive) or disable compression (when undefined) on an API. | | domains | aws-cdk-lib.aws_apigateway.IDomainName[] | List of custom domains names to be used for the API Gateway. | -| stage | string | Stage name used for all cloudformation resource names and internal aws resource names. | | vpcEndpoint | aws-cdk-lib.aws_ec2.IInterfaceVpcEndpoint | VPC endpoint id of execute-api vpc endpoint. | | apiBasePathMappingPath | string | Path for custom domain base path mapping that will be attached to the api gateway. | -| binaryMediaTypes | string[] | Binary media types for the internal api gateway. | -| minimumCompressionSize | number | minimum compression size for the internal api gateway in kilobytes. | +| stage | string | Stage name used for all cloudformation resource names and internal aws resource names. | | sourcePath | string | Path of website folder containing the website`s sources. | | bucketName | string | Name of s3 bucket to use for the website deployment. | | enableSourceDeployment | boolean | Enable/disable automatic sync of the website`s sources to the S3bucket. | @@ -792,53 +1144,258 @@ const internalWebsiteProps: InternalWebsiteProps = { ... } --- -##### `domains`Required +##### `defaultCorsPreflightOptions`Optional ```typescript -public readonly domains: IDomainName[]; +public readonly defaultCorsPreflightOptions: CorsOptions; ``` -- *Type:* aws-cdk-lib.aws_apigateway.IDomainName[] +- *Type:* aws-cdk-lib.aws_apigateway.CorsOptions +- *Default:* CORS is disabled -List of custom domains names to be used for the API Gateway. +Adds a CORS preflight OPTIONS method to this resource and all child resources. + +You can add CORS at the resource-level using `addCorsPreflight`. --- -##### `stage`Required +##### `defaultIntegration`Optional ```typescript -public readonly stage: string; +public readonly defaultIntegration: Integration; +``` + +- *Type:* aws-cdk-lib.aws_apigateway.Integration +- *Default:* Inherited from parent. + +An integration to use as a default for all methods created within this API unless an integration is specified. + +--- + +##### `defaultMethodOptions`Optional + +```typescript +public readonly defaultMethodOptions: MethodOptions; +``` + +- *Type:* aws-cdk-lib.aws_apigateway.MethodOptions +- *Default:* Inherited from parent. + +Method options to use as a default for all methods created within this API unless custom options are specified. + +--- + +##### `cloudWatchRole`Optional + +```typescript +public readonly cloudWatchRole: boolean; +``` + +- *Type:* boolean +- *Default:* false if `@aws-cdk/aws-apigateway:disableCloudWatchRole` is enabled, true otherwise + +Automatically configure an AWS CloudWatch role for API Gateway. + +--- + +##### `deploy`Optional + +```typescript +public readonly deploy: boolean; +``` + +- *Type:* boolean +- *Default:* true + +Indicates if a Deployment should be automatically created for this API, and recreated when the API model (resources, methods) changes. + +Since API Gateway deployments are immutable, When this option is enabled +(by default), an AWS::ApiGateway::Deployment resource will automatically +created with a logical ID that hashes the API model (methods, resources +and options). This means that when the model changes, the logical ID of +this CloudFormation resource will change, and a new deployment will be +created. + +If this is set, `latestDeployment` will refer to the `Deployment` object +and `deploymentStage` will refer to a `Stage` that points to this +deployment. To customize the stage options, use the `deployOptions` +property. + +A CloudFormation Output will also be defined with the root URL endpoint +of this REST API. + +--- + +##### `deployOptions`Optional + +```typescript +public readonly deployOptions: StageOptions; +``` + +- *Type:* aws-cdk-lib.aws_apigateway.StageOptions +- *Default:* Based on defaults of `StageOptions`. + +Options for the API Gateway stage that will always point to the latest deployment when `deploy` is enabled. + +If `deploy` is disabled, +this value cannot be set. + +--- + +##### `description`Optional + +```typescript +public readonly description: string; ``` - *Type:* string +- *Default:* 'Automatically created by the RestApi construct' -Stage name used for all cloudformation resource names and internal aws resource names. +A description of the RestApi construct. --- -##### `vpcEndpoint`Required +##### `disableExecuteApiEndpoint`Optional ```typescript -public readonly vpcEndpoint: IInterfaceVpcEndpoint; +public readonly disableExecuteApiEndpoint: boolean; ``` -- *Type:* aws-cdk-lib.aws_ec2.IInterfaceVpcEndpoint +- *Type:* boolean +- *Default:* false -VPC endpoint id of execute-api vpc endpoint. +Specifies whether clients can invoke the API using the default execute-api endpoint. -This endpoint will be used to forward requests from the load balancer`s target group to the api gateway. +To require that clients use a custom domain name to invoke the +API, disable the default endpoint. + +> [https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-restapi.html](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-restapi.html) --- -##### `apiBasePathMappingPath`Optional +##### `domainName`Optional ```typescript -public readonly apiBasePathMappingPath: string; +public readonly domainName: DomainNameOptions; +``` + +- *Type:* aws-cdk-lib.aws_apigateway.DomainNameOptions +- *Default:* no domain name is defined, use `addDomainName` or directly define a `DomainName`. + +Configure a custom domain name and map it to this API. + +--- + +##### `endpointExportName`Optional + +```typescript +public readonly endpointExportName: string; ``` - *Type:* string +- *Default:* when no export name is given, output will be created without export -Path for custom domain base path mapping that will be attached to the api gateway. +Export name for the CfnOutput containing the API endpoint. + +--- + +##### `endpointTypes`Optional + +```typescript +public readonly endpointTypes: EndpointType[]; +``` + +- *Type:* aws-cdk-lib.aws_apigateway.EndpointType[] +- *Default:* EndpointType.EDGE + +A list of the endpoint types of the API. + +Use this property when creating +an API. + +--- + +##### `failOnWarnings`Optional + +```typescript +public readonly failOnWarnings: boolean; +``` + +- *Type:* boolean +- *Default:* false + +Indicates whether to roll back the resource if a warning occurs while API Gateway is creating the RestApi resource. + +--- + +##### `parameters`Optional + +```typescript +public readonly parameters: {[ key: string ]: string}; +``` + +- *Type:* {[ key: string ]: string} +- *Default:* No parameters. + +Custom header parameters for the request. + +> [https://docs.aws.amazon.com/cli/latest/reference/apigateway/import-rest-api.html](https://docs.aws.amazon.com/cli/latest/reference/apigateway/import-rest-api.html) + +--- + +##### `policy`Optional + +```typescript +public readonly policy: PolicyDocument; +``` + +- *Type:* aws-cdk-lib.aws_iam.PolicyDocument +- *Default:* No policy. + +A policy document that contains the permissions for this RestApi. + +--- + +##### `restApiName`Optional + +```typescript +public readonly restApiName: string; +``` + +- *Type:* string +- *Default:* ID of the RestApi construct. + +A name for the API Gateway RestApi resource. + +--- + +##### `retainDeployments`Optional + +```typescript +public readonly retainDeployments: boolean; +``` + +- *Type:* boolean +- *Default:* false + +Retains old deployment resources when the API changes. + +This allows +manually reverting stages to point to old deployments via the AWS +Console. + +--- + +##### `apiKeySourceType`Optional + +```typescript +public readonly apiKeySourceType: ApiKeySourceType; +``` + +- *Type:* aws-cdk-lib.aws_apigateway.ApiKeySourceType +- *Default:* Metering is disabled. + +The source of the API key for metering requests according to a usage plan. --- @@ -849,20 +1406,127 @@ public readonly binaryMediaTypes: string[]; ``` - *Type:* string[] +- *Default:* RestApi supports only UTF-8-encoded text payloads. + +The list of binary media mime-types that are supported by the RestApi resource, such as "image/png" or "application/octet-stream". + +--- + +##### `cloneFrom`Optional + +```typescript +public readonly cloneFrom: IRestApi; +``` -Binary media types for the internal api gateway. +- *Type:* aws-cdk-lib.aws_apigateway.IRestApi +- *Default:* None. + +The ID of the API Gateway RestApi resource that you want to clone. --- -##### `minimumCompressionSize`Optional +##### `endpointConfiguration`Optional + +```typescript +public readonly endpointConfiguration: EndpointConfiguration; +``` + +- *Type:* aws-cdk-lib.aws_apigateway.EndpointConfiguration +- *Default:* EndpointType.EDGE + +The EndpointConfiguration property type specifies the endpoint types of a REST API. + +> [https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-restapi-endpointconfiguration.html](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apigateway-restapi-endpointconfiguration.html) + +--- + +##### `minCompressionSize`Optional + +```typescript +public readonly minCompressionSize: Size; +``` + +- *Type:* aws-cdk-lib.Size +- *Default:* Compression is disabled. + +A Size(in bytes, kibibytes, mebibytes etc) that is used to enable compression (with non-negative between 0 and 10485760 (10M) bytes, inclusive) or disable compression (when undefined) on an API. + +When compression is enabled, compression or +decompression is not applied on the payload if the payload size is +smaller than this value. Setting it to zero allows compression for any +payload size. + +--- + +##### ~~`minimumCompressionSize`~~Optional + +- *Deprecated:* - superseded by `minCompressionSize` ```typescript public readonly minimumCompressionSize: number; ``` - *Type:* number +- *Default:* Compression is disabled. + +A nullable integer that is used to enable compression (with non-negative between 0 and 10485760 (10M) bytes, inclusive) or disable compression (when undefined) on an API. + +When compression is enabled, compression or +decompression is not applied on the payload if the payload size is +smaller than this value. Setting it to zero allows compression for any +payload size. + +--- + +##### `domains`Required + +```typescript +public readonly domains: IDomainName[]; +``` + +- *Type:* aws-cdk-lib.aws_apigateway.IDomainName[] -minimum compression size for the internal api gateway in kilobytes. +List of custom domains names to be used for the API Gateway. + +--- + +##### `vpcEndpoint`Required + +```typescript +public readonly vpcEndpoint: IInterfaceVpcEndpoint; +``` + +- *Type:* aws-cdk-lib.aws_ec2.IInterfaceVpcEndpoint + +VPC endpoint id of execute-api vpc endpoint. + +This endpoint will be used to forward requests from the load balancer`s target group to the api gateway. + +--- + +##### `apiBasePathMappingPath`Optional + +```typescript +public readonly apiBasePathMappingPath: string; +``` + +- *Type:* string + +Path for custom domain base path mapping that will be attached to the api gateway. + +--- + +##### ~~`stage`~~Optional + +- *Deprecated:* use deployOptions.stageName instead + +```typescript +public readonly stage: string; +``` + +- *Type:* string + +Stage name used for all cloudformation resource names and internal aws resource names. --- diff --git a/src/internal-apigateway.ts b/src/internal-apigateway.ts index 7809d11..e4d2e44 100644 --- a/src/internal-apigateway.ts +++ b/src/internal-apigateway.ts @@ -1,19 +1,18 @@ -import { - Size, - aws_apigateway as apigateway, - aws_iam as iam, -} from "aws-cdk-lib"; +import { aws_apigateway as apigateway, aws_iam as iam } from "aws-cdk-lib"; import { IInterfaceVpcEndpoint } from "aws-cdk-lib/aws-ec2"; import { Construct } from "constructs"; /** * Properties for ApiGateway + * Includes all properties of RestApiProps except: endpointConfiguration, policy + * This is done by intention to prevent the user from overriding the required security settings. */ -export interface InternalApiGatewayProps { +export interface InternalApiGatewayProps extends apigateway.RestApiProps { /** * Stage name used for all cloudformation resource names and internal aws resource names. + * @deprecated use deployOptions.stageName instead */ - readonly stage: string; + readonly stage?: string; /** * List of custom domains names to be used for the API Gateway. @@ -29,16 +28,6 @@ export interface InternalApiGatewayProps { * Path for custom domain base path mapping that will be attached to the api gateway */ readonly apiBasePathMappingPath?: string; - - /** - * Binary media types for the internal api gateway - */ - readonly binaryMediaTypes?: string[] | undefined; - - /** - * minimum compression size for the internal api gateway in kilobytes - */ - readonly minimumCompressionSize?: number | undefined; } export abstract class InternalApiGateway extends Construct { @@ -52,33 +41,57 @@ export abstract class InternalApiGateway extends Construct { protected readonly apiGateway: apigateway.LambdaRestApi; constructor(scope: Construct, id: string, props: InternalApiGatewayProps) { super(scope, id); - - const apiResourcePolicy = new iam.PolicyDocument({ - statements: [ - new iam.PolicyStatement({ - effect: iam.Effect.DENY, - principals: [new iam.AnyPrincipal()], - actions: ["execute-api:Invoke"], - resources: ["execute-api:/*/*/*"], - conditions: { - StringNotEquals: { - "aws:sourceVpce": props.vpcEndpoint.vpcEndpointId, + let apiResourcePolicy = props.policy; + if (!apiResourcePolicy) { + apiResourcePolicy = new iam.PolicyDocument({ + statements: [ + new iam.PolicyStatement({ + effect: iam.Effect.DENY, + principals: [new iam.AnyPrincipal()], + actions: ["execute-api:Invoke"], + resources: ["execute-api:/*/*/*"], + conditions: { + StringNotEquals: { + "aws:sourceVpce": props.vpcEndpoint.vpcEndpointId, + }, }, - }, - }), - new iam.PolicyStatement({ - effect: iam.Effect.ALLOW, - principals: [new iam.AnyPrincipal()], - actions: ["execute-api:Invoke"], - resources: ["execute-api:/*/*/*"], - conditions: { - StringEquals: { - "aws:sourceVpce": props.vpcEndpoint.vpcEndpointId, + }), + new iam.PolicyStatement({ + effect: iam.Effect.ALLOW, + principals: [new iam.AnyPrincipal()], + actions: ["execute-api:Invoke"], + resources: ["execute-api:/*/*/*"], + conditions: { + StringEquals: { + "aws:sourceVpce": props.vpcEndpoint.vpcEndpointId, + }, }, - }, - }), - ], - }); + }), + ], + }); + } + + if ( + props.endpointConfiguration && + props.endpointConfiguration!.types && + !props.endpointConfiguration!.types!.includes( + apigateway.EndpointType.PRIVATE + ) + ) { + throw new Error( + "InternalApiGateway: endpointConfiguration.types must be EndpointType.PRIVATE" + ); + } + + if ( + props.endpointConfiguration && + props.endpointConfiguration!.vpcEndpoints && + props.endpointConfiguration!.vpcEndpoints!.length === 0 + ) { + throw new Error( + "InternalApiGateway: endpointConfiguration.vpcEndpoints must be set" + ); + } this.apiGateway = new apigateway.RestApi(this, `Gateway-${id}`, { description: "This service serves an internal api gateway", @@ -86,14 +99,15 @@ export abstract class InternalApiGateway extends Construct { types: [apigateway.EndpointType.PRIVATE], vpcEndpoints: [props.vpcEndpoint], }, - policy: apiResourcePolicy, - deployOptions: { - stageName: props.stage, - }, - binaryMediaTypes: props.binaryMediaTypes, - minCompressionSize: props.minimumCompressionSize - ? Size.kibibytes(props.minimumCompressionSize!) + deployOptions: props.deployOptions + ? props.deployOptions + : props.stage + ? { + stageName: props.stage, + } : undefined, + policy: apiResourcePolicy, + ...props, }); for (const domainItem of props.domains) { diff --git a/test/apigateway.test.ts b/test/apigateway.test.ts index 4054b5f..b3bed2b 100644 --- a/test/apigateway.test.ts +++ b/test/apigateway.test.ts @@ -1,8 +1,10 @@ import { App, + Size, + Stack, + aws_apigateway as apigateway, aws_ec2 as ec2, aws_route53 as route53, - Stack, } from "aws-cdk-lib"; import { Match, Template } from "aws-cdk-lib/assertions"; import { Construct } from "constructs"; @@ -67,7 +69,6 @@ beforeEach(() => { test("Api Gateway Stack provider - set default values", () => { new ApiGatewayStackTest(stack, "apiGatewayStack", { - stage: "dev", domains: internalServiceStack.domains, vpcEndpoint: vpcEndpointId, }); @@ -144,7 +145,7 @@ test("Api Gateway Stack provider - set default values", () => { }, "/", Object { - "Ref": "apiGatewayStackGatewayapiGatewayStackDeploymentStagedevF51461BA", + "Ref": "apiGatewayStackGatewayapiGatewayStackDeploymentStageprodEB2E72B0", }, "/", ], @@ -327,7 +328,7 @@ test("Api Gateway Stack provider - set default values", () => { }, "Type": "AWS::ApiGateway::Deployment", }, - "apiGatewayStackGatewayapiGatewayStackDeploymentStagedevF51461BA": Object { + "apiGatewayStackGatewayapiGatewayStackDeploymentStageprodEB2E72B0": Object { "DependsOn": Array [ "apiGatewayStackGatewayapiGatewayStackAccount7F19EDD7", ], @@ -338,7 +339,7 @@ test("Api Gateway Stack provider - set default values", () => { "RestApiId": Object { "Ref": "apiGatewayStackGatewayapiGatewayStackC685BA6E", }, - "StageName": "dev", + "StageName": "prod", }, "Type": "AWS::ApiGateway::Stage", }, @@ -371,7 +372,7 @@ test("Api Gateway Stack provider - set default values", () => { "Ref": "apiGatewayStackGatewayapiGatewayStackC685BA6E", }, "Stage": Object { - "Ref": "apiGatewayStackGatewayapiGatewayStackDeploymentStagedevF51461BA", + "Ref": "apiGatewayStackGatewayapiGatewayStackDeploymentStageprodEB2E72B0", }, }, "Type": "AWS::ApiGateway::BasePathMapping", @@ -386,7 +387,7 @@ test("Api Gateway Stack provider - set default values", () => { "Ref": "apiGatewayStackGatewayapiGatewayStackC685BA6E", }, "Stage": Object { - "Ref": "apiGatewayStackGatewayapiGatewayStackDeploymentStagedevF51461BA", + "Ref": "apiGatewayStackGatewayapiGatewayStackDeploymentStageprodEB2E72B0", }, }, "Type": "AWS::ApiGateway::BasePathMapping", @@ -401,7 +402,7 @@ test("Api Gateway Stack provider - set default values", () => { "Ref": "apiGatewayStackGatewayapiGatewayStackC685BA6E", }, "Stage": Object { - "Ref": "apiGatewayStackGatewayapiGatewayStackDeploymentStagedevF51461BA", + "Ref": "apiGatewayStackGatewayapiGatewayStackDeploymentStageprodEB2E72B0", }, }, "Type": "AWS::ApiGateway::BasePathMapping", @@ -898,10 +899,9 @@ test("Api Gateway Stack provider - set default values", () => { test("Api Gateway Stack provider - set optional parameters", () => { new ApiGatewayStackTest(stack, "apiGatewayStackOptionalParameters", { - stage: "dev", domains: internalServiceStack.domains, vpcEndpoint: vpcEndpointId, - minimumCompressionSize: 1000, + minCompressionSize: Size.kibibytes(1000), binaryMediaTypes: ["application/octet-stream"], apiBasePathMappingPath: "test", }); @@ -928,3 +928,96 @@ test("Api Gateway Stack provider - set optional parameters", () => { }) ); }); + +test("Api Gateway Stack provider - set deprecated stage parameter", () => { + new ApiGatewayStackTest(stack, "apiGatewayStackOptionalParameters", { + domains: internalServiceStack.domains, + vpcEndpoint: vpcEndpointId, + stage: "dev", + }); + + const template = Template.fromStack(stack); + template.hasResourceProperties( + "AWS::ApiGateway::Stage", + Match.objectLike({ + StageName: "dev", + RestApiId: { + Ref: "apiGatewayStackOptionalParametersGatewayapiGatewayStackOptionalParameters47F8C933", + }, + }) + ); +}); + +test("Api Gateway Stack provider - set no stage parameter", () => { + new ApiGatewayStackTest(stack, "apiGatewayStackOptionalParameters", { + domains: internalServiceStack.domains, + vpcEndpoint: vpcEndpointId, + }); + + const template = Template.fromStack(stack); + template.hasResourceProperties( + "AWS::ApiGateway::Stage", + Match.objectLike({ + StageName: "prod", + RestApiId: { + Ref: "apiGatewayStackOptionalParametersGatewayapiGatewayStackOptionalParameters47F8C933", + }, + }) + ); +}); + +test("Api Gateway Stack provider - set stage parameter with deployOptions", () => { + new ApiGatewayStackTest(stack, "apiGatewayStackOptionalParameters", { + domains: internalServiceStack.domains, + vpcEndpoint: vpcEndpointId, + deployOptions: { + stageName: "test", + }, + }); + + const template = Template.fromStack(stack); + template.hasResourceProperties( + "AWS::ApiGateway::Stage", + Match.objectLike({ + StageName: "test", + RestApiId: { + Ref: "apiGatewayStackOptionalParametersGatewayapiGatewayStackOptionalParameters47F8C933", + }, + }) + ); +}); + +test("Api Gateway Stack EndpointConfiguration - Exception Wrong EndpointType", () => { + expect(() => { + new ApiGatewayStackTest(stack, "apiGatewayStackExceptionWrongType", { + domains: internalServiceStack.domains, + vpcEndpoint: vpcEndpointId, + deployOptions: { + stageName: "test", + }, + endpointConfiguration: { + types: [apigateway.EndpointType.EDGE], + }, + }); + }).toThrowError( + "InternalApiGateway: endpointConfiguration.types must be EndpointType.PRIVATE" + ); +}); + +test("Api Gateway Stack EndpointConfiguration - Exception No VPC Endpoint provided", () => { + expect(() => { + new ApiGatewayStackTest(stack, "apiGatewayStackExceptionNoVpcEndpoint", { + domains: internalServiceStack.domains, + vpcEndpoint: vpcEndpointId, + deployOptions: { + stageName: "test", + }, + endpointConfiguration: { + vpcEndpoints: [], + types: [apigateway.EndpointType.PRIVATE], + }, + }); + }).toThrowError( + "InternalApiGateway: endpointConfiguration.vpcEndpoints must be set" + ); +}); diff --git a/test/internal-website.test.ts b/test/internal-website.test.ts index 78d3b3d..e362df8 100644 --- a/test/internal-website.test.ts +++ b/test/internal-website.test.ts @@ -56,7 +56,9 @@ beforeEach(() => { test("Internal Website provider - set default values", () => { new InternalWebsite(stack, "internalGatewayStack", { - stage: "dev", + deployOptions: { + stageName: "dev", + }, domains: internalServiceStack.domains, vpcEndpoint: vpcEndpointId, sourcePath: "./test/website-sample", @@ -1331,7 +1333,9 @@ test("Internal Website provider - set default values", () => { test("Api Gateway Stack provider - set optional parameters", () => { new InternalWebsite(stack, "internalWebsiteStackOptionalParameters", { - stage: "dev", + deployOptions: { + stageName: "dev", + }, domains: internalServiceStack.domains, vpcEndpoint: vpcEndpointId, sourcePath: "./test/website-sample", diff --git a/test/security.test.ts b/test/security.test.ts index ff7e821..534c6e7 100644 --- a/test/security.test.ts +++ b/test/security.test.ts @@ -1,15 +1,29 @@ import { App, Aspects, + RemovalPolicy, Stack, + aws_apigateway as apigateway, aws_ec2 as ec2, aws_route53 as route53, } from "aws-cdk-lib"; import { Annotations, Match } from "aws-cdk-lib/assertions"; // eslint-disable-next-line import/no-extraneous-dependencies +import { LogGroup } from "aws-cdk-lib/aws-logs"; import { AwsSolutionsChecks, NagSuppressions } from "cdk-nag"; -import { InternalService } from "../src"; +import { Construct } from "constructs"; +import { + InternalApiGateway, + InternalApiGatewayProps, + InternalService, +} from "../src"; +export class ApiGatewayStackTest extends InternalApiGateway { + constructor(scope: Construct, id: string, props: InternalApiGatewayProps) { + super(scope, id, props); + this.apiGateway.root.addMethod("GET", undefined); + } +} describe("cdk-nag AwsSolutions Pack", () => { let stack: Stack; let app: App; @@ -43,13 +57,46 @@ describe("cdk-nag AwsSolutions Pack", () => { ), }; - new InternalService(stack, "internal-service", { - subnetSelection: vpcPublicSubnets, - subDomain: "vt-runner", - vpcEndpointIPAddresses: ["192.168.2.1", "192.168.2.2"], - subjectAlternativeNames: ["test.example.io"], - hostedZone, - vpc, + const internalServiceStack = new InternalService( + stack, + "internal-service", + { + subnetSelection: vpcPublicSubnets, + subDomain: "vt-runner", + vpcEndpointIPAddresses: ["192.168.2.1", "192.168.2.2"], + subjectAlternativeNames: ["test.example.io"], + hostedZone, + vpc, + } + ); + + let vpcEndpointId = + ec2.InterfaceVpcEndpoint.fromInterfaceVpcEndpointAttributes( + stack, + "vpcEndpoint", + { + port: 443, + vpcEndpointId: "vpce-1234567890", + } + ); + const logGroup = new LogGroup(stack, "LogGroup", { + logGroupName: "/aws/apigateway/my-api", + removalPolicy: RemovalPolicy.DESTROY, + }); + const accessLogDestination = new apigateway.LogGroupLogDestination( + logGroup + ); + const accessLogFormat = apigateway.AccessLogFormat.jsonWithStandardFields(); + + new ApiGatewayStackTest(stack, "apiGatewayStack", { + domains: internalServiceStack.domains, + vpcEndpoint: vpcEndpointId, + deployOptions: { + stageName: "dev", + accessLogDestination, + accessLogFormat, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + }, }); Aspects.of(app).add(new AwsSolutionsChecks({ verbose: true })); @@ -57,6 +104,13 @@ describe("cdk-nag AwsSolutions Pack", () => { // THEN test("No unsuppressed Warnings", () => { + NagSuppressions.addStackSuppressions(stack, [ + { + id: "AwsSolutions-APIG3", + reason: "AWS WAFv2 is not required for internal projects", + }, + ]); + const githubRunnerStackWarnings = Annotations.fromStack(stack).findWarning( "*", Match.stringLikeRegexp("AwsSolutions-.*") @@ -77,7 +131,39 @@ describe("cdk-nag AwsSolutions Pack", () => { NagSuppressions.addStackSuppressions(stack, [ { id: "AwsSolutions-S1", - reason: "no access logs needed for a access logs bucket", + reason: + "No additional access logs needed for the load balancer access logs bucket", + }, + ]); + + NagSuppressions.addStackSuppressions(stack, [ + { + id: "AwsSolutions-APIG4", + reason: + "The API does not implement authorization. Depends on solution requested.", + }, + ]); + + NagSuppressions.addStackSuppressions(stack, [ + { + id: "AwsSolutions-APIG2", + reason: "Input validation depends on solution requested.", + }, + ]); + + NagSuppressions.addStackSuppressions(stack, [ + { + id: "AwsSolutions-COG4", + reason: + "The API GW method does not use a Cognito user pool authorizer. Depends on solution requested.", + }, + ]); + + NagSuppressions.addStackSuppressions(stack, [ + { + id: "AwsSolutions-IAM4", + reason: + "AmazonAPIGatewayPushToCloudWatchLogs is a default role set by aws cdk.", }, ]);