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
4 changes: 3 additions & 1 deletion DEVELOPMENT_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,9 @@ Running Tests

### Unit testing with one Python version

If you're trying to do a quick run, it's ok to use the current python version. Run `make pr`.
If you're trying to do a quick run, it's ok to use the current python version.
Run `make test` or `make test-fast`. Once all tests pass make sure to run
`make pr` before sending out your PR.

### Unit testing with multiple Python versions

Expand Down
5 changes: 4 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
target:
$(info ${HELP_MESSAGE})
@exit 0

init:
pip install -e '.[dev]'

test:
pytest --cov samtranslator --cov-report term-missing --cov-fail-under 95 -n auto tests/*

test-fast:
pytest -x --cov samtranslator --cov-report term-missing --cov-fail-under 95 -n auto tests/*

test-cov-report:
pytest --cov samtranslator --cov-report term-missing --cov-report html --cov-fail-under 95 tests/*

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
[
{
"LogicalResourceId": "MyDefaultIamAuthHttpApi",
"ResourceType": "AWS::ApiGatewayV2::Api"
},
{
"LogicalResourceId": "MyDefaultIamAuthHttpApiApiGatewayDefaultStage",
"ResourceType": "AWS::ApiGatewayV2::Stage"
},
{
"LogicalResourceId": "MyIamAuthEnabledHttpApi",
"ResourceType": "AWS::ApiGatewayV2::Api"
},
{
"LogicalResourceId": "MyIamAuthEnabledHttpApiApiGatewayDefaultStage",
"ResourceType": "AWS::ApiGatewayV2::Stage"
},
{
"LogicalResourceId": "MyLambdaFunction",
"ResourceType": "AWS::Lambda::Function"
},
{
"LogicalResourceId": "MyLambdaFunctionImplicitApiDefaultAuthEventPermission",
"ResourceType": "AWS::Lambda::Permission"
},
{
"LogicalResourceId": "MyLambdaFunctionImplicitApiIamAuthEventPermission",
"ResourceType": "AWS::Lambda::Permission"
},
{
"LogicalResourceId": "MyLambdaFunctionMyDefaultIamAuthHttpApiDefaultAuthEventPermission",
"ResourceType": "AWS::Lambda::Permission"
},
{
"LogicalResourceId": "MyLambdaFunctionMyDefaultIamAuthHttpApiIamAuthEventPermission",
"ResourceType": "AWS::Lambda::Permission"
},
{
"LogicalResourceId": "MyLambdaFunctionMyDefaultIamAuthHttpApiNoAuthEventPermission",
"ResourceType": "AWS::Lambda::Permission"
},
{
"LogicalResourceId": "MyLambdaFunctionMyIamAuthEnabledHttpApiDefaultAuthEventPermission",
"ResourceType": "AWS::Lambda::Permission"
},
{
"LogicalResourceId": "MyLambdaFunctionMyIamAuthEnabledHttpApiIamAuthEventPermission",
"ResourceType": "AWS::Lambda::Permission"
},
{
"LogicalResourceId": "MyLambdaFunctionRole",
"ResourceType": "AWS::IAM::Role"
},
{
"LogicalResourceId": "ServerlessHttpApi",
"ResourceType": "AWS::ApiGatewayV2::Api"
},
{
"LogicalResourceId": "ServerlessHttpApiApiGatewayDefaultStage",
"ResourceType": "AWS::ApiGatewayV2::Stage"
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
Globals:
HttpApi:
Auth:
EnableIamAuthorizer: true
Resources:
#######
# Serverless function that use the implicit AWS::Serverless::HttpApi called "ServerlessHttpApi".
# IAM Authorizer of the implicit AWS::Serverless::HttpApi is enabled using the global above.
#######
MyLambdaFunction:
Type: AWS::Serverless::Function
Properties:
Handler: index.handler
Runtime: nodejs12.x
CodeUri: ${codeuri}
Events:
# The following events use the implicit AWS::Serverless::HttpApi called "ServerlessHttpApi".
# The Iam Authorizer of the implicit AWS::Serverless::HttpApi is enabled using the global above.
# Should not have any auth enabled because there is no one set as the default.
ImplicitApiDefaultAuthEvent:
Type: HttpApi
Properties:
Path: /default-auth
Method: GET
# Should have Iam auth as it is set here.
ImplicitApiIamAuthEvent:
Type: HttpApi
Properties:
Auth:
Authorizer: AWS_IAM
Path: /iam-auth
Method: GET

# The following events use the defined AWS::Serverless::HttpApi further down.
# Should not have any auth enabled.
MyDefaultIamAuthHttpApiNoAuthEvent:
Type: HttpApi
Properties:
ApiId:
Ref: MyDefaultIamAuthHttpApi
Auth:
Authorizer: NONE
Path: /no-auth
Method: GET
# Should have Iam auth as it is set as the default for the Api.
MyDefaultIamAuthHttpApiDefaultAuthEvent:
Type: HttpApi
Properties:
ApiId:
Ref: MyDefaultIamAuthHttpApi
Path: /default-auth
Method: GET
# Should have Iam auth as it is set here.
MyDefaultIamAuthHttpApiIamAuthEvent:
Type: HttpApi
Properties:
ApiId:
Ref: MyDefaultIamAuthHttpApi
Auth:
Authorizer: AWS_IAM
Path: /iam-auth
Method: GET
# The following events use the defined AWS::Serverless::HttpApi further down.
# Should not have any auth enabled because there is no one set as the default.
MyIamAuthEnabledHttpApiDefaultAuthEvent:
Type: HttpApi
Properties:
ApiId:
Ref: MyIamAuthEnabledHttpApi
Path: /default-auth
Method: GET
# Should have Iam auth as it is set here.
MyIamAuthEnabledHttpApiIamAuthEvent:
Type: HttpApi
Properties:
ApiId:
Ref: MyIamAuthEnabledHttpApi
Auth:
Authorizer: AWS_IAM
Path: /iam-auth
Method: GET

# HTTP API resource with the Iam authorizer enabled and set to the default.
MyDefaultIamAuthHttpApi:
Type: AWS::Serverless::HttpApi
Properties:
Auth:
EnableIamAuthorizer: true
DefaultAuthorizer: AWS_IAM

# HTTP API resource with the Iam authorizer enabled and NOT set to the default.
MyIamAuthEnabledHttpApi:
Type: AWS::Serverless::HttpApi
Properties:
Auth:
EnableIamAuthorizer: true
29 changes: 29 additions & 0 deletions integration/single/test_function_with_http_api_and_auth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import requests
from parameterized import parameterized
from integration.helpers.base_test import BaseTest


class TestFunctionWithHttpApiAndAuth(BaseTest):
"""
AWS::Lambda::Function tests with http api events and auth
"""

def test_function_with_http_api_and_auth(self):
# If the request is not signed, which none of the below are, IAM will respond with a "Forbidden" message.
# We are not testing that IAM auth works here, we are simply testing if it was applied.
IAM_AUTH_OUTPUT = '{"message":"Forbidden"}'

self.create_and_verify_stack("function_with_http_api_events_and_auth")

implicitEndpoint = self.get_api_v2_endpoint("ServerlessHttpApi")
self.assertEqual(requests.get(implicitEndpoint + "/default-auth").text, self.FUNCTION_OUTPUT)
self.assertEqual(requests.get(implicitEndpoint + "/iam-auth").text, IAM_AUTH_OUTPUT)

defaultIamEndpoint = self.get_api_v2_endpoint("MyDefaultIamAuthHttpApi")
self.assertEqual(requests.get(defaultIamEndpoint + "/no-auth").text, self.FUNCTION_OUTPUT)
self.assertEqual(requests.get(defaultIamEndpoint + "/default-auth").text, IAM_AUTH_OUTPUT)
self.assertEqual(requests.get(defaultIamEndpoint + "/iam-auth").text, IAM_AUTH_OUTPUT)

iamEnabledEndpoint = self.get_api_v2_endpoint("MyIamAuthEnabledHttpApi")
self.assertEqual(requests.get(iamEnabledEndpoint + "/default-auth").text, self.FUNCTION_OUTPUT)
self.assertEqual(requests.get(iamEnabledEndpoint + "/iam-auth").text, IAM_AUTH_OUTPUT)
17 changes: 12 additions & 5 deletions samtranslator/model/api/http_api_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@
)
CorsProperties.__new__.__defaults__ = (None, None, None, None, None, False)

AuthProperties = namedtuple("_AuthProperties", ["Authorizers", "DefaultAuthorizer"])
AuthProperties.__new__.__defaults__ = (None, None)
AuthProperties = namedtuple("_AuthProperties", ["Authorizers", "DefaultAuthorizer", "EnableIamAuthorizer"])
AuthProperties.__new__.__defaults__ = (None, None, False)
DefaultStageName = "$default"
HttpApiTagName = "httpapi:createdBy"

Expand Down Expand Up @@ -422,7 +422,7 @@ def _add_auth(self):
)
open_api_editor = OpenApiEditor(self.definition_body)
auth_properties = AuthProperties(**self.auth)
authorizers = self._get_authorizers(auth_properties.Authorizers, auth_properties.DefaultAuthorizer)
authorizers = self._get_authorizers(auth_properties.Authorizers, auth_properties.EnableIamAuthorizer)

# authorizers is guaranteed to return a value or raise an exception
open_api_editor.add_authorizers_security_definitions(authorizers)
Expand Down Expand Up @@ -494,14 +494,21 @@ def _set_default_authorizer(self, open_api_editor, authorizers, default_authoriz
path, default_authorizer, authorizers=authorizers, api_authorizers=api_authorizers
)

def _get_authorizers(self, authorizers_config, default_authorizer=None):
def _get_authorizers(self, authorizers_config, enable_iam_authorizer=False):
"""
Returns all authorizers for an API as an ApiGatewayV2Authorizer object
:param authorizers_config: authorizer configuration from the API Auth section
:param default_authorizer: name of the default authorizer
:param enable_iam_authorizer: if True add an "AWS_IAM" authorizer
"""
authorizers = {}

if enable_iam_authorizer is True:
authorizers["AWS_IAM"] = ApiGatewayV2Authorizer(is_aws_iam_authorizer=True)

# If all the customer wants to do is enable the IAM authorizer the authorizers_config will be None.
if not authorizers_config:
return authorizers

if not isinstance(authorizers_config, dict):
raise InvalidResourceException(self.logical_id, "Authorizers must be a dictionary.")

Expand Down
12 changes: 12 additions & 0 deletions samtranslator/model/apigatewayv2.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ def __init__(
identity=None,
authorizer_payload_format_version=None,
enable_simple_responses=None,
is_aws_iam_authorizer=False,
):
"""
Creates an authorizer for use in V2 Http Apis
Expand All @@ -87,6 +88,7 @@ def __init__(
self.identity = identity
self.authorizer_payload_format_version = authorizer_payload_format_version
self.enable_simple_responses = enable_simple_responses
self.is_aws_iam_authorizer = is_aws_iam_authorizer

self._validate_input_parameters()

Expand All @@ -100,6 +102,8 @@ def __init__(
self._validate_lambda_authorizer()

def _get_auth_type(self):
if self.is_aws_iam_authorizer:
return "AWS_IAM"
if self.jwt_configuration:
return "JWT"
return "REQUEST"
Expand Down Expand Up @@ -174,6 +178,14 @@ def generate_openapi(self):
"""
authorizer_type = self._get_auth_type()

if authorizer_type == "AWS_IAM":
openapi = {
"type": "apiKey",
"name": "Authorization",
"in": "header",
"x-amazon-apigateway-authtype": "awsSigv4",
}

if authorizer_type == "JWT":
openapi = {"type": "oauth2"}
openapi[APIGATEWAY_AUTHORIZER_KEY] = {
Expand Down
Loading