diff --git a/docs/events/apigateway.md b/docs/events/apigateway.md index d84ed239c..7657f3e55 100644 --- a/docs/events/apigateway.md +++ b/docs/events/apigateway.md @@ -739,6 +739,43 @@ provider: - vpce-456 ``` +### Security Policy + +You can configure the TLS version for your API Gateway REST API by setting the `securityPolicy` property under `apiGateway` in the `provider` block. This maps directly to the [SecurityPolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/TemplateReference/aws-resource-apigateway-restapi.html#cfn-apigateway-restapi-securitypolicy) property of the `AWS::ApiGateway::RestApi` CloudFormation resource. +Specific explanation about Security Policy types and structure can be found [here](https://aws.amazon.com/blogs/compute/enhancing-api-security-with-amazon-api-gateway-tls-security-policies/) + +```yml +service: my-service +provider: + name: aws + apiGateway: + securityPolicy: TLS_1_2 +functions: + hello: + events: + - http: + path: user/create + method: get +``` + +### Endpoint Access Mode + +You can control how clients access your API Gateway endpoint by setting the `endpointAccessMode` property under `apiGateway` in the `provider` block. Valid values are `STRICT` and `BASIC`. This maps directly to the [EndpointAccessMode](https://docs.aws.amazon.com/AWSCloudFormation/latest/TemplateReference/aws-resource-apigateway-restapi.html#cfn-apigateway-restapi-endpointaccessmode) property of the `AWS::ApiGateway::RestApi` CloudFormation resource. According to AWS documentation, if a security policy is configured with a legacy template (that doesn't have the `SecurityPolicy_` prefix) access Mode should be empty) + +```yml +service: my-service +provider: + name: aws + apiGateway: + endpointAccessMode: STRICT +functions: + hello: + events: + - http: + path: user/create + method: get +``` + ### Request Parameters To pass optional and required parameters to your functions, so you can use them in API Gateway tests and SDK generation, marking them as `true` will make them required, `false` will make them optional. diff --git a/lib/plugins/aws/custom-resources/resources/api-gateway-cloud-watch-role/handler.js b/lib/plugins/aws/custom-resources/resources/api-gateway-cloud-watch-role/handler.js index 972471766..d2d253392 100644 --- a/lib/plugins/aws/custom-resources/resources/api-gateway-cloud-watch-role/handler.js +++ b/lib/plugins/aws/custom-resources/resources/api-gateway-cloud-watch-role/handler.js @@ -58,10 +58,7 @@ async function create(event, context) { return (await iam.send(new ListAttachedRolePoliciesCommand({ RoleName: roleName }))) .AttachedPolicies; } catch (error) { - if ( - error.code === 'NoSuchEntity' || - error.message.includes('cannot be found') - ) { + if (error.code === 'NoSuchEntity' || error.message.includes('cannot be found')) { // Role doesn't exist yet, create; await iam.send( new CreateRoleCommand({ diff --git a/lib/plugins/aws/package/compile/events/api-gateway/lib/rest-api.js b/lib/plugins/aws/package/compile/events/api-gateway/lib/rest-api.js index 894fca97e..95086b74e 100644 --- a/lib/plugins/aws/package/compile/events/api-gateway/lib/rest-api.js +++ b/lib/plugins/aws/package/compile/events/api-gateway/lib/rest-api.js @@ -17,10 +17,20 @@ module.exports = { let endpointType = 'EDGE'; let vpcEndpointIds; let BinaryMediaTypes; + let SecurityPolicy; + let EndpointAccessMode; if (apiGateway.binaryMediaTypes) { BinaryMediaTypes = apiGateway.binaryMediaTypes; } + if (apiGateway.securityPolicy) { + SecurityPolicy = apiGateway.securityPolicy; + } + + if (apiGateway.endpointAccessMode) { + EndpointAccessMode = apiGateway.endpointAccessMode.toUpperCase(); + } + if (this.serverless.service.provider.endpointType) { endpointType = this.serverless.service.provider.endpointType.toUpperCase(); @@ -52,6 +62,8 @@ module.exports = { BinaryMediaTypes, DisableExecuteApiEndpoint, EndpointConfiguration, + SecurityPolicy, + EndpointAccessMode, }; // Tags diff --git a/lib/plugins/aws/provider.js b/lib/plugins/aws/provider.js index 2bb29e701..e2ba5bf89 100644 --- a/lib/plugins/aws/provider.js +++ b/lib/plugins/aws/provider.js @@ -848,6 +848,12 @@ class AwsProvider { type: 'array', items: { type: 'string', pattern: '^\\S+\\/\\S+$' }, }, + securityPolicy: { + type: 'string', + }, + endpointAccessMode: { + anyOf: ['strict', 'basic', ''].map(caseInsensitive), + }, description: { type: 'string' }, disableDefaultEndpoint: { type: 'boolean' }, metrics: { type: 'boolean' }, diff --git a/test/unit/lib/plugins/aws/package/compile/events/api-gateway/lib/rest-api.test.js b/test/unit/lib/plugins/aws/package/compile/events/api-gateway/lib/rest-api.test.js index c8b0fe418..79f7177cd 100644 --- a/test/unit/lib/plugins/aws/package/compile/events/api-gateway/lib/rest-api.test.js +++ b/test/unit/lib/plugins/aws/package/compile/events/api-gateway/lib/rest-api.test.js @@ -51,6 +51,56 @@ describe('#compileRestApi()', () => { EndpointConfiguration: { Types: ['EDGE'], }, + SecurityPolicy: undefined, + EndpointAccessMode: undefined, + Policy: '', + }, + }); + }); + + it('should create a REST API resource with security policy', () => { + awsCompileApigEvents.serverless.service.provider.apiGateway = { + securityPolicy: 'SecurityPolicy_TLS13_1_3_2025_09', + }; + awsCompileApigEvents.compileRestApi(); + const resources = + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources; + + expect(resources.ApiGatewayRestApi).to.deep.equal({ + Type: 'AWS::ApiGateway::RestApi', + Properties: { + BinaryMediaTypes: undefined, + DisableExecuteApiEndpoint: undefined, + Name: 'dev-new-service', + EndpointConfiguration: { + Types: ['EDGE'], + }, + SecurityPolicy: 'SecurityPolicy_TLS13_1_3_2025_09', + EndpointAccessMode: undefined, + Policy: '', + }, + }); + }); + + it('should create a REST API resource with endpoint access mode', () => { + awsCompileApigEvents.serverless.service.provider.apiGateway = { + endpointAccessMode: 'STRICT', + }; + awsCompileApigEvents.compileRestApi(); + const resources = + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources; + + expect(resources.ApiGatewayRestApi).to.deep.equal({ + Type: 'AWS::ApiGateway::RestApi', + Properties: { + BinaryMediaTypes: undefined, + DisableExecuteApiEndpoint: undefined, + Name: 'dev-new-service', + EndpointConfiguration: { + Types: ['EDGE'], + }, + SecurityPolicy: undefined, + EndpointAccessMode: 'STRICT', Policy: '', }, }); @@ -75,6 +125,8 @@ describe('#compileRestApi()', () => { EndpointConfiguration: { Types: ['EDGE'], }, + SecurityPolicy: undefined, + EndpointAccessMode: undefined, Policy: '', Tags: [ { Key: 'tagKey1', Value: 'tagValue1' }, @@ -113,6 +165,8 @@ describe('#compileRestApi()', () => { EndpointConfiguration: { Types: ['EDGE'], }, + SecurityPolicy: undefined, + EndpointAccessMode: undefined, Policy: { Version: '2012-10-17', Statement: [ @@ -148,6 +202,8 @@ describe('#compileRestApi()', () => { Types: ['EDGE'], }, Policy: '', + SecurityPolicy: undefined, + EndpointAccessMode: undefined, }, }); }); @@ -181,6 +237,8 @@ describe('#compileRestApi()', () => { }, Name: 'dev-new-service', Policy: '', + SecurityPolicy: undefined, + EndpointAccessMode: undefined, }, }); });