Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add EnableFunctionDefaultPermissions property to HTTP Api #3001

Merged
merged 17 commits into from
Mar 13, 2023
Merged
Show file tree
Hide file tree
Changes from 14 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
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ class LambdaAuthorizer(BaseModel):
EnableSimpleResponses: Optional[bool] = lambdaauthorizer("EnableSimpleResponses")
FunctionArn: SamIntrinsicable[str] = lambdaauthorizer("FunctionArn")
FunctionInvokeRole: Optional[SamIntrinsicable[str]] = lambdaauthorizer("FunctionInvokeRole")
EnableFunctionDefaultPermissions: Optional[bool] # TODO: add docs
Identity: Optional[LambdaAuthorizerIdentity] = lambdaauthorizer("Identity")


Expand Down
63 changes: 61 additions & 2 deletions samtranslator/model/api/http_api_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@
ApiGatewayV2Stage,
)
from samtranslator.model.exceptions import InvalidResourceException
from samtranslator.model.intrinsics import fnGetAtt, is_intrinsic, is_intrinsic_no_value, ref
from samtranslator.model.intrinsics import fnGetAtt, fnSub, is_intrinsic, is_intrinsic_no_value, ref
from samtranslator.model.lambda_ import LambdaPermission
from samtranslator.model.route53 import Route53RecordSetGroup
from samtranslator.model.s3_utils.uri_parser import parse_s3_uri
from samtranslator.open_api.open_api import OpenApiEditor
from samtranslator.translator.arn_generator import ArnGenerator
from samtranslator.translator.logical_id_generator import LogicalIdGenerator
from samtranslator.utils.types import Intrinsicable
from samtranslator.utils.utils import InvalidValueType, dict_deep_get
Expand Down Expand Up @@ -511,6 +513,60 @@ def _add_tags(self) -> None:
open_api_editor.add_tags(self.tags)
self.definition_body = open_api_editor.openapi

def _get_permission(
self, authorizer_name: str, authorizer_lambda_function_arn: str, api_arn: str
) -> LambdaPermission:
"""Constructs and returns the Lambda Permission resource allowing the Authorizer to invoke the function.

:returns: the permission resource
:rtype: model.lambda_.LambdaPermission
"""

resource = "${__ApiId__}/authorizers/*"
hoffa marked this conversation as resolved.
Show resolved Hide resolved
source_arn = fnSub(
ArnGenerator.generate_arn(partition="${AWS::Partition}", service="execute-api", resource=resource),
{"__ApiId__": api_arn},
)

lambda_permission = LambdaPermission(
self.logical_id + authorizer_name + "AuthorizerPermission", attributes=self.passthrough_resource_attributes
)
lambda_permission.Action = "lambda:InvokeFunction"
lambda_permission.FunctionName = authorizer_lambda_function_arn
lambda_permission.Principal = "apigateway.amazonaws.com"
lambda_permission.SourceArn = source_arn

return lambda_permission

def _construct_authorizer_lambda_permission(self, http_api: ApiGatewayV2HttpApi) -> List[LambdaPermission]:
if not self.auth:
return []

auth_properties = AuthProperties(**self.auth)
authorizers = self._get_authorizers(auth_properties.Authorizers, auth_properties.EnableIamAuthorizer)

if not authorizers:
return []

permissions: List[LambdaPermission] = []

for authorizer_name, authorizer in authorizers.items():
# Construct permissions for Lambda Authorizers only
# Http Api shouldn't create the permissions by default (when its none)
if (
not authorizer.function_arn
or authorizer.enable_function_default_permissions is None
or not authorizer.enable_function_default_permissions
):
continue

permission = self._get_permission(
authorizer_name, authorizer.function_arn, http_api.get_runtime_attr("http_api_id")
)
permissions.append(permission)

return permissions

def _set_default_authorizer(
self,
open_api_editor: OpenApiEditor,
Expand Down Expand Up @@ -582,6 +638,7 @@ def _get_authorizers(
identity=authorizer.get("Identity"),
authorizer_payload_format_version=authorizer.get("AuthorizerPayloadFormatVersion"),
enable_simple_responses=authorizer.get("EnableSimpleResponses"),
enable_function_default_permissions=authorizer.get("EnableFunctionDefaultPermissions"),
)
return authorizers

Expand Down Expand Up @@ -719,6 +776,7 @@ def to_cloudformation(
Optional[ApiGatewayV2DomainName],
Optional[List[ApiGatewayV2ApiMapping]],
Optional[Route53RecordSetGroup],
Optional[List[LambdaPermission]],
]:
"""Generates CloudFormation resources from a SAM HTTP API resource

Expand All @@ -727,6 +785,7 @@ def to_cloudformation(
"""
http_api = self._construct_http_api()
domain, basepath_mapping, route53 = self._construct_api_domain(http_api, route53_record_set_groups)
permissions = self._construct_authorizer_lambda_permission(http_api)
stage = self._construct_stage()

return http_api, stage, domain, basepath_mapping, route53
return http_api, stage, domain, basepath_mapping, route53, permissions
14 changes: 14 additions & 0 deletions samtranslator/model/apigatewayv2.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ def __init__( # type: ignore[no-untyped-def] # noqa: too-many-arguments
authorizer_payload_format_version=None,
enable_simple_responses=None,
is_aws_iam_authorizer=False,
enable_function_default_permissions=None,
):
"""
Creates an authorizer for use in V2 Http Apis
Expand All @@ -103,6 +104,7 @@ def __init__( # type: ignore[no-untyped-def] # noqa: too-many-arguments
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.enable_function_default_permissions = enable_function_default_permissions

self._validate_input_parameters()

Expand All @@ -115,6 +117,13 @@ def __init__( # type: ignore[no-untyped-def] # noqa: too-many-arguments
if authorizer_type == "REQUEST":
self._validate_lambda_authorizer()

if enable_function_default_permissions is not None:
sam_expect(
enable_function_default_permissions,
api_logical_id,
f"Authorizers.{name}.EnableFunctionDefaultPermissions",
).to_be_a_bool()
aaythapa marked this conversation as resolved.
Show resolved Hide resolved

def _get_auth_type(self) -> str:
if self.is_aws_iam_authorizer:
return "AWS_IAM"
Expand Down Expand Up @@ -166,6 +175,11 @@ def _validate_input_parameters(self) -> None:
self.api_logical_id, "EnableSimpleResponses must be defined only for Lambda Authorizer."
)

if self.enable_function_default_permissions is not None and authorizer_type != "REQUEST":
raise InvalidResourceException(
self.api_logical_id, "EnableFunctionDefaultPermissions must be defined only for Lambda Authorizer."
)

def _validate_jwt_authorizer(self) -> None:
if not self.jwt_configuration:
raise InvalidResourceException(
Expand Down
3 changes: 3 additions & 0 deletions samtranslator/model/sam_resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -1394,9 +1394,12 @@ def to_cloudformation(self, **kwargs): # type: ignore[no-untyped-def]
domain,
basepath_mapping,
route53,
permissions,
) = api_generator.to_cloudformation(kwargs.get("route53_record_set_groups", {}))

resources.append(http_api)
if permissions:
resources.extend(permissions)
if domain:
resources.append(domain)
if basepath_mapping:
Expand Down
4 changes: 4 additions & 0 deletions samtranslator/schema/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -195725,6 +195725,10 @@
"markdownDescription": "Specifies the format of the payload sent to an HTTP API Lambda authorizer\\. Required for HTTP API Lambda authorizers\\. \nThis is passed through to the `authorizerPayloadFormatVersion` section of an `x-amazon-apigateway-authorizer` in the `securitySchemes` section of an OpenAPI definition\\. \n*Valid values*: `1.0` or `2.0` \n*Type*: String \n*Required*: Yes \n*AWS CloudFormation compatibility*: This property is unique to AWS SAM and doesn't have an AWS CloudFormation equivalent\\.",
"title": "AuthorizerPayloadFormatVersion"
},
"EnableFunctionDefaultPermissions": {
"title": "Enablefunctiondefaultpermissions",
"type": "boolean"
},
"EnableSimpleResponses": {
"description": "Specifies whether a Lambda authorizer returns a response in a simple format\\. By default, a Lambda authorizer must return an AWS Identity and Access Management \\(IAM\\) policy\\. If enabled, the Lambda authorizer can return a boolean value instead of an IAM policy\\. \nThis is passed through to the `enableSimpleResponses` section of an `x-amazon-apigateway-authorizer` in the `securitySchemes` section of an OpenAPI definition\\. \n*Type*: Boolean \n*Required*: No \n*AWS CloudFormation compatibility*: This property is unique to AWS SAM and doesn't have an AWS CloudFormation equivalent\\.",
"markdownDescription": "Specifies whether a Lambda authorizer returns a response in a simple format\\. By default, a Lambda authorizer must return an AWS Identity and Access Management \\(IAM\\) policy\\. If enabled, the Lambda authorizer can return a boolean value instead of an IAM policy\\. \nThis is passed through to the `enableSimpleResponses` section of an `x-amazon-apigateway-authorizer` in the `securitySchemes` section of an OpenAPI definition\\. \n*Type*: Boolean \n*Required*: No \n*AWS CloudFormation compatibility*: This property is unique to AWS SAM and doesn't have an AWS CloudFormation equivalent\\.",
Expand Down
4 changes: 4 additions & 0 deletions schema_source/sam.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -1561,6 +1561,10 @@
"markdownDescription": "Specifies the format of the payload sent to an HTTP API Lambda authorizer\\. Required for HTTP API Lambda authorizers\\. \nThis is passed through to the `authorizerPayloadFormatVersion` section of an `x-amazon-apigateway-authorizer` in the `securitySchemes` section of an OpenAPI definition\\. \n*Valid values*: `1.0` or `2.0` \n*Type*: String \n*Required*: Yes \n*AWS CloudFormation compatibility*: This property is unique to AWS SAM and doesn't have an AWS CloudFormation equivalent\\.",
"title": "AuthorizerPayloadFormatVersion"
},
"EnableFunctionDefaultPermissions": {
"title": "Enablefunctiondefaultpermissions",
"type": "boolean"
},
"EnableSimpleResponses": {
"description": "Specifies whether a Lambda authorizer returns a response in a simple format\\. By default, a Lambda authorizer must return an AWS Identity and Access Management \\(IAM\\) policy\\. If enabled, the Lambda authorizer can return a boolean value instead of an IAM policy\\. \nThis is passed through to the `enableSimpleResponses` section of an `x-amazon-apigateway-authorizer` in the `securitySchemes` section of an OpenAPI definition\\. \n*Type*: Boolean \n*Required*: No \n*AWS CloudFormation compatibility*: This property is unique to AWS SAM and doesn't have an AWS CloudFormation equivalent\\.",
"markdownDescription": "Specifies whether a Lambda authorizer returns a response in a simple format\\. By default, a Lambda authorizer must return an AWS Identity and Access Management \\(IAM\\) policy\\. If enabled, the Lambda authorizer can return a boolean value instead of an IAM policy\\. \nThis is passed through to the `enableSimpleResponses` section of an `x-amazon-apigateway-authorizer` in the `securitySchemes` section of an OpenAPI definition\\. \n*Type*: Boolean \n*Required*: No \n*AWS CloudFormation compatibility*: This property is unique to AWS SAM and doesn't have an AWS CloudFormation equivalent\\.",
Expand Down
15 changes: 15 additions & 0 deletions tests/model/test_api_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,21 @@ def test_create_authorizer_fails_with_enable_simple_responses_non_lambda(self):
+ "EnableSimpleResponses must be defined only for Lambda Authorizer.",
)

def test_create_authorizer_fails_with_enable_function_default_permissions_non_lambda(self):
with pytest.raises(InvalidResourceException) as e:
ApiGatewayV2Authorizer(
api_logical_id="logicalId",
name="authName",
jwt_configuration={"config": "value"},
authorization_scopes=["scope1", "scope2"],
enable_function_default_permissions=True,
)
self.assertEqual(
e.value.message,
"Resource with id [logicalId] is invalid. "
+ "EnableFunctionDefaultPermissions must be defined only for Lambda Authorizer.",
aaythapa marked this conversation as resolved.
Show resolved Hide resolved
)

@mock.patch(
"samtranslator.model.apigatewayv2.ApiGatewayV2Authorizer._get_auth_type", mock.MagicMock(return_value="JWT")
)
Expand Down
11 changes: 11 additions & 0 deletions tests/translator/input/error_http_api_invalid_lambda_auth.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,14 @@ Resources:
AuthorizerPayloadFormatVersion: 2.0
EnableSimpleResponses: true
DefaultAuthorizer: LambdaAuth

MyApi3:
Type: AWS::Serverless::HttpApi
Properties:
Auth:
Authorizers:
LambdaAuth:
FunctionArn: !GetAtt MyAuthFn.Arn
EnableFunctionDefaultPermissions: foo
AuthorizerPayloadFormatVersion: 2.0
DefaultAuthorizer: LambdaAuth
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
Resources:
HttpApiFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: s3://sam-demo-bucket/todo_list.zip
Handler: index.restapi
Runtime: python3.7
Events:
Basic:
Type: HttpApi
Properties:
Path: /basic
Method: GET
ApiId: !Ref MyApi

MyAuthFn:
Type: AWS::Serverless::Function
Properties:
CodeUri: s3://bucket/key
Handler: index.handler
Runtime: nodejs12.x

MyApi:
Type: AWS::Serverless::HttpApi
Properties:
Tags:
Tag1: value1
Tag2: value2
Auth:
Authorizers:
LambdaAuthWithEnablePropertyTrue:
# should create permissions resource for this auth
FunctionArn: !GetAtt MyAuthFn.Arn
EnableFunctionDefaultPermissions: true
AuthorizerPayloadFormatVersion: 1.0
LambdaAuthNoEnableProperty:
# should not create permissions resource for this auth as http api doesn't create the resource by default
FunctionArn: !GetAtt MyAuthFn.Arn
AuthorizerPayloadFormatVersion: 1.0
LambdaAuthWithEnablePropertySetFalse:
# should not create permissions resource for this auth
FunctionArn: !GetAtt MyAuthFn.Arn
AuthorizerPayloadFormatVersion: 1.0
EnableFunctionDefaultPermissions: false
LambdaAuthFull:
# should create permissions resource for this auth
FunctionArn: !GetAtt MyAuthFn.Arn
FunctionInvokeRole: !GetAtt MyAuthFnRole.Arn
EnableFunctionDefaultPermissions: true
Identity:
Context:
- contextVar
Headers:
- Authorization
QueryStrings:
- petId
StageVariables:
- stageVar
ReauthorizeEvery: 60
AuthorizerPayloadFormatVersion: 2.0
EnableSimpleResponses: true
DefaultAuthorizer: LambdaAuthWithEnablePropertyTrue
Loading