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
1 change: 1 addition & 0 deletions docs/globals.rst
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ Currently, the following resources and properties are being supported:
OpenApiVersion:
Domain:
SecurityPolicy:
EndpointAccessMode:

HttpApi:
# Properties of AWS::Serverless::HttpApi
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
[
{
"LogicalResourceId": "MyApi",
"ResourceType": "AWS::ApiGateway::RestApi"
},
{
"LogicalResourceId": "MyApiDeployment",
"ResourceType": "AWS::ApiGateway::Deployment"
},
{
"LogicalResourceId": "MyApiProdStage",
"ResourceType": "AWS::ApiGateway::Stage"
},
{
"LogicalResourceId": "ApiGatewayDomainName",
"ResourceType": "AWS::ApiGateway::DomainName"
},
{
"LogicalResourceId": "MyApiBasePathMapping",
"ResourceType": "AWS::ApiGateway::BasePathMapping"
},
{
"LogicalResourceId": "RecordSetGroup",
"ResourceType": "AWS::Route53::RecordSetGroup"
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
[
{
"LogicalResourceId": "MyApi",
"ResourceType": "AWS::ApiGateway::RestApi"
},
{
"LogicalResourceId": "MyApiDeployment",
"ResourceType": "AWS::ApiGateway::Deployment"
},
{
"LogicalResourceId": "MyApiProdStage",
"ResourceType": "AWS::ApiGateway::Stage"
},
{
"LogicalResourceId": "ApiGatewayDomainName",
"ResourceType": "AWS::ApiGateway::DomainName"
},
{
"LogicalResourceId": "MyApiBasePathMapping",
"ResourceType": "AWS::ApiGateway::BasePathMapping"
},
{
"LogicalResourceId": "RecordSetGroup",
"ResourceType": "AWS::Route53::RecordSetGroup"
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[
{
"LogicalResourceId": "MyApi",
"ResourceType": "AWS::ApiGateway::RestApi"
},
{
"LogicalResourceId": "MyApiDeployment",
"ResourceType": "AWS::ApiGateway::Deployment"
},
{
"LogicalResourceId": "MyApiProdStage",
"ResourceType": "AWS::ApiGateway::Stage"
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
Parameters:
DomainName:
Type: String
CertificateArn:
Type: String
HostedZoneId:
Type: String

Resources:
MyApi:
Type: AWS::Serverless::Api
Properties:
StageName: Prod
DefinitionUri: ${definitionuri}
EndpointConfiguration:
Type: EDGE
Domain:
DomainName: !Ref DomainName
CertificateArn: !Ref CertificateArn
EndpointConfiguration: EDGE
SecurityPolicy: SecurityPolicy_TLS13_2025_EDGE
EndpointAccessMode: STRICT
Route53:
HostedZoneId: !Ref HostedZoneId

Metadata:
SamTransformTest: true
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
Parameters:
DomainName:
Type: String
CertificateArn:
Type: String
HostedZoneId:
Type: String

Resources:
MyApi:
Type: AWS::Serverless::Api
Properties:
StageName: Prod
DefinitionUri: ${definitionuri}
EndpointConfiguration:
Type: REGIONAL
Domain:
DomainName: !Ref DomainName
CertificateArn: !Ref CertificateArn
EndpointConfiguration: REGIONAL
SecurityPolicy: SecurityPolicy_TLS13_1_3_2025_09
EndpointAccessMode: STRICT
Route53:
HostedZoneId: !Ref HostedZoneId

Metadata:
SamTransformTest: true
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
Parameters:
SecurityPolicyValue:
Type: String
Default: SecurityPolicy_TLS13_1_3_2025_09
EndpointAccessModeValue:
Type: String
Default: STRICT

Resources:
MyApi:
Type: AWS::Serverless::Api
Properties:
StageName: Prod
DefinitionUri: ${definitionuri}
SecurityPolicy: !Ref SecurityPolicyValue
EndpointAccessMode: !Ref EndpointAccessModeValue
EndpointConfiguration:
Type: REGIONAL
Metadata:
SamTransformTest: true
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
from unittest.case import skipIf

from integration.config.service_names import CUSTOM_DOMAIN
from integration.helpers.base_internal_test import BaseInternalTest
from integration.helpers.base_test import nonblocking
from integration.helpers.resource import current_region_not_included


@skipIf(
current_region_not_included([CUSTOM_DOMAIN]),
"Custom domain is not supported in this testing region",
)
@nonblocking
class TestApiWithCustomDomainSecurityPolicy(BaseInternalTest):
"""
Test AWS::Serverless::Api with SecurityPolicy and EndpointAccessMode in Domain configuration
"""

def test_api_with_custom_domain_security_policy_regional(self):
self.create_and_verify_stack("single/api_with_custom_domain_security_policy_regional")

domain_name_id = self.get_physical_id_by_type("AWS::ApiGateway::DomainName")
result = self.client_provider.api_client.get_domain_name(domainName=domain_name_id)

end_point_config = result["endpointConfiguration"]
self.assertEqual(["REGIONAL"], end_point_config["types"])
self.assertEqual("SecurityPolicy_TLS13_1_3_2025_09", result["securityPolicy"])
self.assertEqual("STRICT", result["endpointAccessMode"])

def test_api_with_custom_domain_security_policy_edge(self):
self.create_and_verify_stack("single/api_with_custom_domain_security_policy_edge")

domain_name_id = self.get_physical_id_by_type("AWS::ApiGateway::DomainName")
result = self.client_provider.api_client.get_domain_name(domainName=domain_name_id)

end_point_config = result["endpointConfiguration"]
self.assertEqual(["EDGE"], end_point_config["types"])
self.assertEqual("SecurityPolicy_TLS13_2025_EDGE", result["securityPolicy"])
self.assertEqual("STRICT", result["endpointAccessMode"])
38 changes: 38 additions & 0 deletions integration/single/test_api_with_endpoint_access_mode.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
from unittest.case import skipIf

from integration.config.service_names import REST_API
from integration.helpers.base_test import BaseTest
from integration.helpers.resource import current_region_does_not_support


@skipIf(current_region_does_not_support([REST_API]), "Rest API is not supported in this testing region")
class TestApiWithEndpointAccessMode(BaseTest):
"""
Tests for AWS::Serverless::Api with EndpointAccessMode property
"""

def test_api_with_endpoint_access_mode(self):
# Create stack with STRICT
parameters = [
{"ParameterKey": "SecurityPolicyValue", "ParameterValue": "SecurityPolicy_TLS13_1_3_2025_09"},
{"ParameterKey": "EndpointAccessModeValue", "ParameterValue": "STRICT"},
]
self.create_and_verify_stack("single/api_with_endpoint_access_mode", parameters)

rest_api_id = self.get_physical_id_by_type("AWS::ApiGateway::RestApi")
rest_api = self.client_provider.api_client.get_rest_api(restApiId=rest_api_id)

self.assertEqual(rest_api["securityPolicy"], "SecurityPolicy_TLS13_1_3_2025_09")
self.assertEqual(rest_api["endpointAccessMode"], "STRICT")

# Update stack with BASIC
update_parameters = [
{"ParameterKey": "SecurityPolicyValue", "ParameterValue": "SecurityPolicy_TLS13_1_3_2025_09"},
{"ParameterKey": "EndpointAccessModeValue", "ParameterValue": "BASIC"},
]
self.update_stack(parameters=update_parameters)

rest_api = self.client_provider.api_client.get_rest_api(restApiId=rest_api_id)

self.assertEqual(rest_api["securityPolicy"], "SecurityPolicy_TLS13_1_3_2025_09")
self.assertEqual(rest_api["endpointAccessMode"], "BASIC")
15 changes: 15 additions & 0 deletions samtranslator/internal/schema_source/aws_serverless_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,11 @@ class Domain(BaseModel):
EndpointConfiguration: Optional[SamIntrinsicable[Literal["REGIONAL", "EDGE", "PRIVATE"]]] = domain(
"EndpointConfiguration"
)
EndpointAccessMode: Optional[PassThroughProp] = passthrough_prop(
DOMAIN_STEM,
"EndpointAccessMode",
["AWS::ApiGateway::DomainName", "Properties", "EndpointAccessMode"],
)
IpAddressType: Optional[PassThroughProp] # TODO: add documentation; currently unavailable
MutualTlsAuthentication: Optional[PassThroughProp] = passthrough_prop(
DOMAIN_STEM,
Expand Down Expand Up @@ -306,6 +311,11 @@ class Properties(BaseModel):
)
DisableExecuteApiEndpoint: Optional[PassThroughProp] = properties("DisableExecuteApiEndpoint")
Domain: Optional[Domain] = properties("Domain")
EndpointAccessMode: Optional[PassThroughProp] = passthrough_prop(
PROPERTIES_STEM,
"EndpointAccessMode",
["AWS::ApiGateway::RestApi", "Properties", "EndpointAccessMode"],
)
EndpointConfiguration: Optional[EndpointConfigurationType] = properties("EndpointConfiguration")
FailOnWarnings: Optional[PassThroughProp] = passthrough_prop(
PROPERTIES_STEM,
Expand Down Expand Up @@ -419,6 +429,11 @@ class Globals(BaseModel):
"SecurityPolicy",
["AWS::ApiGateway::RestApi", "Properties", "SecurityPolicy"],
)
EndpointAccessMode: Optional[PassThroughProp] = passthrough_prop(
PROPERTIES_STEM,
"EndpointAccessMode",
["AWS::ApiGateway::RestApi", "Properties", "EndpointAccessMode"],
)


class Resource(ResourceAttributes):
Expand Down
2 changes: 2 additions & 0 deletions samtranslator/internal/schema_source/sam-docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
"CertificateArn": "The Amazon Resource Name (https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/ARN.html) of an AWS managed certificate this domain name's endpoint. AWS Certificate Manager is the only supported source. \n*Type*: String \n*Required*: Yes \n*CloudFormation compatibility*: This property is similar to the [`CertificateArn`](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-domainname.html#cfn-apigateway-domainname-certificatearn) property of an `AWS::ApiGateway::DomainName` resource. If `EndpointConfiguration` is set to `REGIONAL` (the default value), `CertificateArn` maps to [RegionalCertificateArn](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-domainname.html#cfn-apigateway-domainname-regionalcertificatearn) in `AWS::ApiGateway::DomainName`. If the `EndpointConfiguration` is set to `EDGE`, `CertificateArn` maps to [CertificateArn](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-domainname.html#cfn-apigateway-domainname-certificatearn) in `AWS::ApiGateway::DomainName`. If `EndpointConfiguration` is set to `PRIVATE`, this property is passed to the [AWS::ApiGateway::DomainNameV2](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-domainnamev2) resource. \n*Additional notes*: For an `EDGE` endpoint, you must create the certificate in the `us-east-1` AWS Region.",
"DomainName": "The custom domain name for your API Gateway API. Uppercase letters are not supported. \nAWS SAM generates an [https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-domainname.html](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-domainname.html) resource when this property is set. For information about this scenario, see [DomainName property is specified](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-specification-generated-resources-api.html#sam-specification-generated-resources-api-domain-name). For information about generated CloudFormation resources, see [Generated CloudFormation resources for AWS SAM](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-specification-generated-resources.html). \n*Type*: String \n*Required*: Yes \n*CloudFormation compatibility*: This property is passed directly to the [`DomainName`](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-domainname.html#cfn-apigateway-domainname-domainname) property of an `AWS::ApiGateway::DomainName` resource, or to [https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-domainnamev2](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-domainnamev2) when EndpointConfiguration is set to `PRIVATE`.",
"EndpointConfiguration": "Defines the type of API Gateway endpoint to map to the custom domain. The value of this property determines how the `CertificateArn` property is mapped in CloudFormation. \n*Valid values*: `EDGE`, `REGIONAL`, or `PRIVATE` \n*Type*: String \n*Required*: No \n*Default*: `REGIONAL` \n*CloudFormation compatibility*: This property is unique to AWS SAM and doesn't have an CloudFormation equivalent.",
"EndpointAccessMode": "The endpoint access mode for the custom domain name. \n*Type*: String \n*Required*: No \n*CloudFormation compatibility*: This property is passed directly to the [`EndpointAccessMode`](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-domainname.html#cfn-apigateway-domainname-endpointaccessmode) property of an `AWS::ApiGateway::DomainName` resource, or to `AWS::ApiGateway::DomainNameV2` when `EndpointConfiguration` is set to `PRIVATE`.",
"MutualTlsAuthentication": "The mutual Transport Layer Security (https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/TLS.html) authentication configuration for a custom domain name. \n*Type*: [MutualTlsAuthentication](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-domainname.html#cfn-apigateway-domainname-mutualtlsauthentication) \n*Required*: No \n*CloudFormation compatibility*: This property is passed directly to the [`MutualTlsAuthentication`](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-domainname.html#cfn-apigateway-domainname-mutualtlsauthentication) property of an `AWS::ApiGateway::DomainName` resource.",
"NormalizeBasePath": "Indicates whether non-alphanumeric characters are allowed in basepaths defined by the `BasePath` property. When set to `True`, non-alphanumeric characters are removed from basepaths. \nUse `NormalizeBasePath` with the `BasePath` property. \n*Type*: Boolean \n*Required*: No \n*Default*: True \n*CloudFormation compatibility*: This property is unique to AWS SAM and doesn't have an CloudFormation equivalent.",
"OwnershipVerificationCertificateArn": "The ARN of the public certificate issued by ACM to validate ownership of your custom domain. Required only when you configure mutual TLS and you specify an ACM imported or private CA certificate ARN for the `CertificateArn`. \n*Type*: String \n*Required*: No \n*CloudFormation compatibility*: This property is passed directly to the [`OwnershipVerificationCertificateArn`](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-domainname.html#cfn-apigateway-domainname-ownershipverificationcertificatearn) property of an `AWS::ApiGateway::DomainName` resource.",
Expand Down Expand Up @@ -723,6 +724,7 @@
"Description": "A description of the Api resource. \n*Type*: String \n*Required*: No \n*CloudFormation compatibility*: This property is passed directly to the [`Description`](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-restapi.html#cfn-apigateway-restapi-description) property of an `AWS::ApiGateway::RestApi` resource.",
"DisableExecuteApiEndpoint": "Specifies whether clients can invoke your API by using the default `execute-api` endpoint. By default, clients can invoke your API with the default `https://{api_id}.execute-api.{region}.amazonaws.com`. To require that clients use a custom domain name to invoke your API, specify `True`. \n*Type*: Boolean \n*Required*: No \n*CloudFormation compatibility*: This property is similar to the `[ DisableExecuteApiEndpoint](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-restapi.html#cfn-apigateway-restapi-disableexecuteapiendpoint)` property of an `AWS::ApiGateway::RestApi` resource. It is passed directly to the `disableExecuteApiEndpoint` property of an `[ x-amazon-apigateway-endpoint-configuration](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-swagger-extensions-endpoint-configuration.html)` extension, which gets added to the ` [ Body](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-restapi.html#cfn-apigateway-restapi-body)` property of an `AWS::ApiGateway::RestApi` resource.",
"Domain": "Configures a custom domain for this API Gateway API. \n*Type*: [DomainConfiguration](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-property-api-domainconfiguration.html) \n*Required*: No \n*CloudFormation compatibility*: This property is unique to AWS SAM and doesn't have an CloudFormation equivalent.",
"EndpointAccessMode": "The endpoint access mode for the RestApi. \n*Type*: String \n*Required*: No \n*AWS CloudFormation compatibility*: This property is passed directly to the [`EndpointAccessMode`](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-restapi.html#cfn-apigateway-restapi-endpointaccessmode) property of an `AWS::ApiGateway::RestApi` resource.",
"EndpointConfiguration": "The endpoint type of a REST API. \n*Type*: [EndpointConfiguration](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-property-api-endpointconfiguration.html) \n*Required*: No \n*CloudFormation compatibility*: This property is similar to the [`EndpointConfiguration`](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-restapi.html#cfn-apigateway-restapi-endpointconfiguration) property of an `AWS::ApiGateway::RestApi` resource. The nested configuration properties are named differently.",
"FailOnWarnings": "Specifies whether to roll back the API creation (`true`) or not (`false`) when a warning is encountered. The default value is `false`. \n*Type*: Boolean \n*Required*: No \n*CloudFormation compatibility*: This property is passed directly to the [`FailOnWarnings`](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-restapi.html#cfn-apigateway-restapi-failonwarnings) property of an `AWS::ApiGateway::RestApi` resource.",
"GatewayResponses": "Configures Gateway Responses for an API. Gateway Responses are responses returned by API Gateway, either directly or through the use of Lambda Authorizers. For more information, see the documentation for the [Api Gateway OpenApi extension for Gateway Responses](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-swagger-extensions-gateway-responses.html). \n*Type*: Map \n*Required*: No \n*CloudFormation compatibility*: This property is unique to AWS SAM and doesn't have an CloudFormation equivalent.",
Expand Down
9 changes: 8 additions & 1 deletion samtranslator/model/api/api_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,7 @@ def __init__( # noqa: PLR0913
feature_toggle: Optional[FeatureToggle] = None,
policy: Optional[Union[Dict[str, Any], Intrinsicable[str]]] = None,
security_policy: Optional[Intrinsicable[str]] = None,
endpoint_access_mode: Optional[Intrinsicable[str]] = None,
):
"""Constructs an API Generator class that generates API Gateway resources

Expand Down Expand Up @@ -281,8 +282,9 @@ def __init__( # noqa: PLR0913
self.feature_toggle = feature_toggle
self.policy = policy
self.security_policy = security_policy
self.endpoint_access_mode = endpoint_access_mode

def _construct_rest_api(self) -> ApiGatewayRestApi:
def _construct_rest_api(self) -> ApiGatewayRestApi: # noqa: PLR0912
"""Constructs and returns the ApiGateway RestApi.

:returns: the RestApi to which this SAM Api corresponds
Expand Down Expand Up @@ -340,6 +342,9 @@ def _construct_rest_api(self) -> ApiGatewayRestApi:
if self.security_policy:
rest_api.SecurityPolicy = self.security_policy

if self.endpoint_access_mode:
rest_api.EndpointAccessMode = self.endpoint_access_mode

return rest_api

def _validate_properties(self) -> None:
Expand Down Expand Up @@ -739,6 +744,8 @@ def _set_optional_domain_properties(self, domain: Union[ApiGatewayDomainName, Ap
return
if self.domain.get("SecurityPolicy", None):
domain.SecurityPolicy = self.domain["SecurityPolicy"]
if self.domain.get("EndpointAccessMode", None):
domain.EndpointAccessMode = self.domain["EndpointAccessMode"]
if self.domain.get("Policy", None):
domain.Policy = self.domain["Policy"]
if self.domain.get("OwnershipVerificationCertificateArn", None):
Expand Down
Loading
Loading