Skip to content
Merged
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
61 changes: 61 additions & 0 deletions docs/events/apigateway.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ Summary:
- [Catching Exceptions In Your Lambda Function](#catching-exceptions-in-your-lambda-function)
- [Setting API keys for your Rest API](#setting-api-keys-for-your-rest-api)
- [Configuring endpoint types](#configuring-endpoint-types)
- [Security Policy](#security-policy)
- [Request Parameters](#request-parameters)
- [Request Schema Validators](#request-schema-validators)
- [Setting source of API key for metering requests](#setting-source-of-api-key-for-metering-requests)
Expand Down Expand Up @@ -752,6 +753,66 @@ provider:
- vpce-456
```

### Security Policy

You can configure the TLS security policy for the generated API Gateway REST API by setting `provider.apiGateway.endpoint.securityPolicy`. This maps 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.

This applies only to REST APIs generated from `http` events. It does not apply to HTTP APIs generated from `httpApi` events, and it does not apply when you import an external REST API with `provider.apiGateway.restApiId`.

For the default edge-optimized REST API endpoint, use an edge-compatible policy:

```yml
provider:
name: aws
apiGateway:
endpoint:
securityPolicy: SecurityPolicy_TLS13_2025_EDGE
accessMode: strict
functions:
hello:
events:
- http:
path: user/create
method: get
```

For Regional or private REST APIs, set `provider.endpointType` and use a policy supported by that endpoint type:

```yml
provider:
name: aws
endpointType: REGIONAL
apiGateway:
endpoint:
securityPolicy: SecurityPolicy_TLS13_1_3_2025_09
accessMode: basic
functions:
hello:
events:
- http:
path: user/create
method: get
```

AWS treats policies that start with `SecurityPolicy_` as enhanced security policies. When using an enhanced policy, you must also set `provider.apiGateway.endpoint.accessMode` to `basic` or `strict`.

`strict` adds additional host and endpoint-type checks. AWS recommends migrating by first using `basic`, validating traffic, and then switching to `strict`.

When changing an API from an enhanced policy back to a legacy policy, AWS requires endpoint access mode to be unset with an empty string:

```yml
provider:
name: aws
apiGateway:
endpoint:
securityPolicy: TLS_1_0
accessMode: ''
```

For normal legacy policy usage, omit `accessMode`.

Supported security policies differ by endpoint type. See the [AWS supported security policies documentation](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-security-policies-list.html) for the current policy list.

### 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.
Expand Down
7 changes: 7 additions & 0 deletions docs/guides/serverless.yml.md
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,13 @@ provider:
websocketApiId: xxxx
# Disable the default 'execute-api' HTTP endpoint (default: false)
disableDefaultEndpoint: true
# Optional REST API endpoint security settings
endpoint:
# TLS security policy for the generated REST API
securityPolicy: SecurityPolicy_TLS13_2025_EDGE
# Endpoint access mode for enhanced security policies: basic or strict
# Use "" only to unset access mode when reverting from enhanced to legacy policies
accessMode: strict
# Source of API key for usage plan: HEADER or AUTHORIZER
apiKeySourceType: HEADER
# List of API keys for the REST API
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const ServerlessError = require('../../../../../../../serverless-error');
module.exports = {
compileRestApi() {
const apiGateway = this.serverless.service.provider.apiGateway || {};
const endpoint = apiGateway.endpoint || {};

// immediately return if we're using an external REST API id
if (apiGateway.restApiId) {
Expand Down Expand Up @@ -53,6 +54,14 @@ module.exports = {
EndpointConfiguration,
};

if (endpoint.securityPolicy) {
properties.SecurityPolicy = endpoint.securityPolicy;
}

if (endpoint.accessMode != null) {
properties.EndpointAccessMode = endpoint.accessMode.toUpperCase();
}

// Tags
if (this.serverless.service.provider.tags) {
properties.Tags = Object.entries(this.serverless.service.provider.tags).map(
Expand Down
10 changes: 10 additions & 0 deletions lib/plugins/aws/provider.js
Original file line number Diff line number Diff line change
Expand Up @@ -841,6 +841,16 @@ class AwsProvider {
},
description: { type: 'string' },
disableDefaultEndpoint: { type: 'boolean' },
endpoint: {
type: 'object',
properties: {
securityPolicy: { type: 'string' },
accessMode: {
anyOf: [...['BASIC', 'STRICT'].map(caseInsensitive), { const: '' }],
},
},
additionalProperties: false,
},
metrics: { type: 'boolean' },
minimumCompressionSize: { type: 'integer', minimum: 0, maximum: 10485760 },
resourcePolicy: { $ref: '#/definitions/awsResourcePolicyStatements' },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,71 @@ describe('#compileRestApi()', () => {
});
});

it('should support `provider.apiGateway.endpoint.securityPolicy`', () => {
awsCompileApigEvents.serverless.service.provider.apiGateway = {
endpoint: {
securityPolicy: 'TLS_1_0',
},
};

awsCompileApigEvents.compileRestApi();
const resource =
awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources
.ApiGatewayRestApi;

expect(resource.Properties.SecurityPolicy).to.equal('TLS_1_0');
expect(resource.Properties.EndpointAccessMode).to.equal(undefined);
});

it('should support `provider.apiGateway.endpoint.accessMode`', () => {
awsCompileApigEvents.serverless.service.provider.apiGateway = {
endpoint: {
securityPolicy: 'SecurityPolicy_TLS13_2025_EDGE',
accessMode: 'basic',
},
};

awsCompileApigEvents.compileRestApi();
const resource =
awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources
.ApiGatewayRestApi;

expect(resource.Properties.SecurityPolicy).to.equal('SecurityPolicy_TLS13_2025_EDGE');
expect(resource.Properties.EndpointAccessMode).to.equal('BASIC');
});

it('should support strict endpoint access mode', () => {
awsCompileApigEvents.serverless.service.provider.apiGateway = {
endpoint: {
securityPolicy: 'SecurityPolicy_TLS13_2025_EDGE',
accessMode: 'STRICT',
},
};

awsCompileApigEvents.compileRestApi();
const resource =
awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources
.ApiGatewayRestApi;

expect(resource.Properties.EndpointAccessMode).to.equal('STRICT');
});

it('should preserve empty `provider.apiGateway.endpoint.accessMode`', () => {
awsCompileApigEvents.serverless.service.provider.apiGateway = {
endpoint: {
securityPolicy: 'TLS_1_0',
accessMode: '',
},
};

awsCompileApigEvents.compileRestApi();
const resource =
awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate.Resources
.ApiGatewayRestApi;

expect(resource.Properties).to.have.property('EndpointAccessMode', '');
});

it('should throw error if endpointType property is not PRIVATE and vpcEndpointIds property is [id1]', () => {
awsCompileApigEvents.serverless.service.provider.endpointType = 'Testing';
awsCompileApigEvents.serverless.service.provider.vpcEndpointIds = ['id1'];
Expand Down Expand Up @@ -219,6 +284,94 @@ describe('lib/plugins/aws/package/compile/events/apiGateway/lib/restApi.test.js'
expect(resource.Properties.DisableExecuteApiEndpoint).to.equal(true);
});

it('should support `provider.apiGateway.endpoint.securityPolicy` and `accessMode`', async () => {
const { cfTemplate } = await runServerless({
fixture: 'api-gateway',
command: 'package',
configExt: {
provider: {
apiGateway: {
endpoint: {
securityPolicy: 'SecurityPolicy_TLS13_2025_EDGE',
accessMode: 'strict',
},
},
},
},
});
const resource = cfTemplate.Resources.ApiGatewayRestApi;

expect(resource.Properties.SecurityPolicy).to.equal('SecurityPolicy_TLS13_2025_EDGE');
expect(resource.Properties.EndpointAccessMode).to.equal('STRICT');
});

it('should support empty `provider.apiGateway.endpoint.accessMode`', async () => {
const { cfTemplate } = await runServerless({
fixture: 'api-gateway',
command: 'package',
configExt: {
provider: {
apiGateway: {
endpoint: {
securityPolicy: 'TLS_1_0',
accessMode: '',
},
},
},
},
});
const resource = cfTemplate.Resources.ApiGatewayRestApi;

expect(resource.Properties.SecurityPolicy).to.equal('TLS_1_0');
expect(resource.Properties).to.have.property('EndpointAccessMode', '');
});

it('should reject invalid `provider.apiGateway.endpoint.accessMode`', async () => {
let error;
try {
await runServerless({
fixture: 'api-gateway',
command: 'package',
configExt: {
provider: {
apiGateway: {
endpoint: {
accessMode: 'invalid',
},
},
},
},
});
} catch (caughtError) {
error = caughtError;
}

expect(error).to.have.property('code', 'INVALID_NON_SCHEMA_COMPLIANT_CONFIGURATION');
});

it('should reject unknown `provider.apiGateway.endpoint` properties', async () => {
let error;
try {
await runServerless({
fixture: 'api-gateway',
command: 'package',
configExt: {
provider: {
apiGateway: {
endpoint: {
unknownProperty: true,
},
},
},
},
});
} catch (caughtError) {
error = caughtError;
}

expect(error).to.have.property('code', 'INVALID_NON_SCHEMA_COMPLIANT_CONFIGURATION');
});

it('should support `provider.apiGateway.resourcePolicy[].Principal.AWS with Fn::If`', async () => {
const { cfTemplate } = await runServerless({
fixture: 'api-gateway',
Expand Down
4 changes: 4 additions & 0 deletions types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -821,6 +821,10 @@ export interface AWS {
binaryMediaTypes?: string[];
description?: string;
disableDefaultEndpoint?: boolean;
endpoint?: {
securityPolicy?: string;
accessMode?: string;
};
metrics?: boolean;
minimumCompressionSize?: number;
resourcePolicy?: AwsResourcePolicyStatements;
Expand Down