From cd28c89f70534465da137d2514741bbcf23782b8 Mon Sep 17 00:00:00 2001 From: Brett Andrews Date: Thu, 9 Aug 2018 08:51:49 -0700 Subject: [PATCH 01/19] feat(auth): add support for API Gateway Authorizers This is a WIP. Remaining: 1. Tests 1. Documentation 1. Examples 1. Additional end-to-end manual verification 1. Fixing some known edge cases (e.g. remove hanging NONE authorizers when no DefaultAuthorizer set) --- DEVELOPMENT_GUIDE.rst | 19 +++ bin/sam-translate.py | 2 +- .../2016-10-31/api_cognito_auth/src/index.js | 3 + .../2016-10-31/api_cognito_auth/template.yaml | 135 +++++++++++++++++ samtranslator/model/api/api_generator.py | 78 +++++++++- samtranslator/model/apigateway.py | 142 +++++++++++++++++- samtranslator/model/eventsources/push.py | 25 ++- samtranslator/model/sam_resources.py | 6 +- .../plugins/api/implicit_api_plugin.py | 5 +- samtranslator/swagger/swagger.py | 106 +++++++++++++ 10 files changed, 511 insertions(+), 10 deletions(-) create mode 100644 examples/2016-10-31/api_cognito_auth/src/index.js create mode 100644 examples/2016-10-31/api_cognito_auth/template.yaml diff --git a/DEVELOPMENT_GUIDE.rst b/DEVELOPMENT_GUIDE.rst index 034acc25b7..2c6913d597 100755 --- a/DEVELOPMENT_GUIDE.rst +++ b/DEVELOPMENT_GUIDE.rst @@ -81,4 +81,23 @@ Install snakeviz `pip install snakeviz` ``` python -m cProfile -o sam_profile_results bin/sam-translate.py translate --input-file=tests/translator/input/alexa_skill.yaml --output-file=cfn-template.json snakeviz sam_profile_results +``` + +Verifying transforms +-------------------- + +If you make changes to the transformer and want to verify the resulting CloudFormation template works as expected, you can transform your SAM template into a CloudFormation template using the following process: + +```shell +# Optional: You only need to run the package command in certain cases; e.g. when your CodeUri specifies a local path +# Replace MY_TEMPLATE_PATH with the path to your template and MY_S3_BUCKET with an existing S3 bucket +aws cloudformation package --template-file MY_TEMPLATE_PATH/template.yaml --output-template-file output-template.yaml --s3-bucket MY_S3_BUCKET + +# Transform your SAM template into a CloudFormation template +# Replace "output-template.yaml" if you didn't run the package command above or specified a different path for --output-template-file +bin/sam-translate.py --input-file=output-template.yaml + +# Deploy your transformed CloudFormation template +# Replace MY_STACK_NAME with a unique name each time you deploy +aws cloudformation deploy --template-file cfn-template.json --capabilities CAPABILITY_NAMED_IAM --stack-name MY_STACK_NAME ``` \ No newline at end of file diff --git a/bin/sam-translate.py b/bin/sam-translate.py index 5c67b0552b..4b0df09739 100755 --- a/bin/sam-translate.py +++ b/bin/sam-translate.py @@ -57,7 +57,7 @@ def main(): except InvalidDocumentException as e: errorMessage = reduce(lambda message, error: message + ' ' + error.message, e.causes, e.message) print(errorMessage) - errors = map(lambda cause: {'errorMessage': cause.message}, e.causes) + errors = map(lambda cause: cause.message, e.causes) print(errors) diff --git a/examples/2016-10-31/api_cognito_auth/src/index.js b/examples/2016-10-31/api_cognito_auth/src/index.js new file mode 100644 index 0000000000..fd69bd0267 --- /dev/null +++ b/examples/2016-10-31/api_cognito_auth/src/index.js @@ -0,0 +1,3 @@ +module.exports = async (event) => { + return event +} \ No newline at end of file diff --git a/examples/2016-10-31/api_cognito_auth/template.yaml b/examples/2016-10-31/api_cognito_auth/template.yaml new file mode 100644 index 0000000000..8ebf70cbbf --- /dev/null +++ b/examples/2016-10-31/api_cognito_auth/template.yaml @@ -0,0 +1,135 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 +Description: API Gateway + Cognito User Pools Auth +Resources: + MyApi: + Type: AWS::Serverless::Api + Properties: + StageName: TestStage + Auth: + DefaultAuthorizer: MyLambdaAuthorizer + Authorizers: + MyCognitoAuthorizer: + UserPoolArn: !GetAtt MyCognitoUserPool.Arn + + MyLambdaAuthorizer: + FunctionArn: !GetAtt MyAuthFunction.Arn + FunctionInvokeRole: NONE + Identity: # Optional + Header: Authn # Optional; Default: Authorization + ValidationExpression: myexpresso # Optional + ReauthorizeEvery: 33 + + MyLambdaRequestAuthorizer: + FunctionPayloadType: REQUEST + FunctionArn: !GetAtt MyAuthFunction.Arn + FunctionInvokeRole: !Sub arn:aws:iam::${AWS::AccountId}:role/admin + Identity: + Headers: + - Authorization1 + QueryStrings: + - Authorization2 + StageVariables: + - Authorization3 + Context: + - Authorization4 + ReauthorizeEvery: 100 + + MyFunction: + Type: AWS::Serverless::Function + Properties: + CodeUri: ./src + Handler: index.handler + Runtime: nodejs8.10 + Events: + WithNoAuthorizer: + Type: Api + Properties: + RestApiId: !Ref MyApi + Path: / + Method: get + Auth: + Authorizer: NONE + WithLambdaTokenAuthorizer: + Type: Api + Properties: + RestApiId: !Ref MyApi + Path: /users + Method: get + Auth: + Authorizer: MyLambdaAuthorizer + WithCognitoAuthorizer: + Type: Api + Properties: + RestApiId: !Ref MyApi + Path: /users + Method: post + Auth: + Authorizer: MyCognitoAuthorizer + WithLambdaRequestAuthorizer: + Type: Api + Properties: + RestApiId: !Ref MyApi + Path: /users + Method: delete + Auth: + Authorizer: MyLambdaRequestAuthorizer + WithDefaultAuthorizer: + Type: Api + Properties: + RestApiId: !Ref MyApi + Path: /users + Method: put + + MyAuthFunction: + Type: AWS::Serverless::Function + Properties: + CodeUri: ./src + Handler: index.handler + Runtime: nodejs8.10 + + MyCognitoUserPool: + Type: AWS::Cognito::UserPool + Properties: + UserPoolName: MyUserPool + LambdaConfig: + PreSignUp: !GetAtt PreSignupLambdaFunction.Arn + Policies: + PasswordPolicy: + MinimumLength: 8 + Schema: + - AttributeDataType: String + Name: email + Required: false + + PreSignupLambdaFunction: + Type: AWS::Serverless::Function + Properties: + InlineCode: | + exports.handler = async (event, context, callback) => { + event.response = { autoConfirmUser: true } + return event + } + Handler: index.handler + MemorySize: 128 + Runtime: nodejs8.10 + Timeout: 3 + # TODO: + # Events: + # CognitoUserPoolPreSignup: + # Type: CognitoUserPool + # Properties: + # UserPool: !Ref MyCognitoUserPool + + LambdaCognitoUserPoolExecutionPermission: + Type: AWS::Lambda::Permission + Properties: + Action: lambda:InvokeFunction + FunctionName: !GetAtt PreSignupLambdaFunction.Arn + Principal: cognito-idp.amazonaws.com + SourceArn: !Sub 'arn:aws:cognito-idp:${AWS::Region}:${AWS::AccountId}:userpool/${MyCognitoUserPool}' + +Outputs: + ApiURL: + Description: "API endpoint URL for Prod environment" + Value: !Sub 'https://${MyApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/' \ No newline at end of file diff --git a/samtranslator/model/api/api_generator.py b/samtranslator/model/api/api_generator.py index 99cc72d47d..4f8c562ac6 100644 --- a/samtranslator/model/api/api_generator.py +++ b/samtranslator/model/api/api_generator.py @@ -3,7 +3,7 @@ from samtranslator.model.intrinsics import ref from samtranslator.model.apigateway import (ApiGatewayDeployment, ApiGatewayRestApi, - ApiGatewayStage) + ApiGatewayStage, ApiGatewayAuthorizer) from samtranslator.model.exceptions import InvalidResourceException from samtranslator.model.s3_utils.uri_parser import parse_s3_uri from samtranslator.region_configuration import RegionConfiguration @@ -15,9 +15,13 @@ # Default the Cors Properties to '*' wildcard. Other properties are actually Optional CorsProperties.__new__.__defaults__ = (None, None, _CORS_WILDCARD, None) +AuthProperties = namedtuple("_AuthProperties", ["Authorizers", "DefaultAuthorizer"]) +AuthProperties.__new__.__defaults__ = (None, None) + + class ApiGenerator(object): - def __init__(self, logical_id, cache_cluster_enabled, cache_cluster_size, variables, depends_on, definition_body, definition_uri, name, stage_name, endpoint_configuration=None, method_settings=None, binary_media=None, cors=None): + def __init__(self, logical_id, cache_cluster_enabled, cache_cluster_size, variables, depends_on, definition_body, definition_uri, name, stage_name, endpoint_configuration=None, method_settings=None, binary_media=None, cors=None, auth=None): """Constructs an API Generator class that generates API Gateway resources :param logical_id: Logical id of the SAM API Resource @@ -43,6 +47,7 @@ def __init__(self, logical_id, cache_cluster_enabled, cache_cluster_size, variab self.method_settings = method_settings self.binary_media = binary_media self.cors = cors + self.auth = auth def _construct_rest_api(self): """Constructs and returns the ApiGateway RestApi. @@ -61,12 +66,12 @@ def _construct_rest_api(self): # to Regional which is the only supported config. self._set_endpoint_configuration(rest_api, "REGIONAL") - if self.definition_uri and self.definition_body: raise InvalidResourceException(self.logical_id, "Specify either 'DefinitionUri' or 'DefinitionBody' property and not both") self._add_cors() + self._add_auth() if self.definition_uri: rest_api.BodyS3Location = self._construct_body_s3_dict() @@ -208,6 +213,73 @@ def _add_cors(self): # Assign the Swagger back to template self.definition_body = editor.swagger + def _add_auth(self): + """ + Add Auth configuration to the Swagger file, if necessary + """ + + if not self.auth: + return + + INVALID_ERROR = "Invalid value for 'Auth' property" + + if not isinstance(self.auth, dict): + raise InvalidResourceException(self.logical_id, + "Auth must be a dictionary") + + if self.auth and not self.definition_body: + raise InvalidResourceException(self.logical_id, + "Auth works only with inline Swagger specified in " + "'DefinitionBody' property") + + # Make sure keys in the dict are recognized + if not all(key in AuthProperties._fields for key in self.auth.keys()): + raise InvalidResourceException(self.logical_id, INVALID_ERROR) + + if not SwaggerEditor.is_valid(self.definition_body): + raise InvalidResourceException(self.logical_id, "Unable to add Auth configuration because " + "'DefinitionBody' does not contain a valid Swagger") + swagger_editor = SwaggerEditor(self.definition_body) + auth_properties = AuthProperties(**self.auth) + authorizers = self._get_authorizers(auth_properties.Authorizers) + + if authorizers: + swagger_editor.add_authorizers(authorizers) + self._set_default_authorizer(swagger_editor, authorizers, auth_properties.DefaultAuthorizer) + + # Assign the Swagger back to template + self.definition_body = swagger_editor.swagger + + def _get_authorizers(self, authorizers_config): + if not authorizers_config: + return None + + authorizers = {} + + for authorizerName, authorizer in authorizers_config.items(): + authorizers[authorizerName] = ApiGatewayAuthorizer( + api_logical_id=self.logical_id, + name=authorizer.get('Name'), + user_pool_arn=authorizer.get('UserPoolArn'), + function_arn=authorizer.get('FunctionArn'), + identity=authorizer.get('Identity'), + function_payload_type=authorizer.get('FunctionPayloadType'), + function_invoke_role=authorizer.get('FunctionInvokeRole') + ) + + return authorizers + + def _set_default_authorizer(self, swagger_editor, authorizers, default_authorizer): + if not default_authorizer: + return + + if not authorizers.get(default_authorizer): + raise InvalidResourceException(self.logical_id, "Unable to set DefaultAuthorizer because '" + + default_authorizer + "' was not defined in 'Authorizers'") + + for path in swagger_editor.iter_on_path(): + swagger_editor.set_path_default_authorizer(path, default_authorizer, authorizers=authorizers) + def _set_endpoint_configuration(self, rest_api, value): """ Sets endpoint configuration property of AWS::ApiGateway::RestApi resource diff --git a/samtranslator/model/apigateway.py b/samtranslator/model/apigateway.py index 8c2ff8b30b..5c4af00aed 100644 --- a/samtranslator/model/apigateway.py +++ b/samtranslator/model/apigateway.py @@ -1,7 +1,8 @@ from samtranslator.model import PropertyType, Resource from samtranslator.model.types import is_type, one_of, is_str -from samtranslator.model.intrinsics import ref +from samtranslator.model.intrinsics import ref, fnSub from samtranslator.translator import logical_id_generator +from samtranslator.translator.arn_generator import ArnGenerator class ApiGatewayRestApi(Resource): @@ -85,3 +86,142 @@ def make_auto_deployable(self, stage, swagger=None): hash = generator.get_hash(length=40) # Get the full hash self.Description = "RestApi deployment id: {}".format(hash) stage.update_deployment_ref(self.logical_id) + + +class ApiGatewayAuthorizer(object): + def __init__(self, api_logical_id=None, name=None, user_pool_arn=None, function_arn=None, identity=None, + function_payload_type=None, function_invoke_role=None): + self.name = name + self.user_pool_arn = user_pool_arn + self.function_arn = function_arn + self.identity = identity + self.function_payload_type = function_payload_type + self.function_invoke_role = self._construct_role(function_invoke_role) + + def generate_swagger(self): + authorizer_type = self._get_type() + APIGATEWAY_AUTHORIZER_KEY = 'x-amazon-apigateway-authorizer' + swagger = { + "type": "apiKey", + "name": self._get_swagger_header_name(), + "in": "header", + "x-amazon-apigateway-authtype": self._get_swagger_authtype(), + "x-amazon-apigateway-authorizer": { + "type": self._get_swagger_authorizer_type() + } + } + + if authorizer_type == 'COGNITO_USER_POOLS': + swagger[APIGATEWAY_AUTHORIZER_KEY]['providerARNs'] = self._get_user_pool_arn_array() + + elif authorizer_type == 'LAMBDA': + partition = ArnGenerator.get_partition_name() + authorizer_uri = fnSub('arn:{0}:apigateway:${{AWS::Region}}:lambda:path/2015-03-31/functions/' + '${{__FunctionArn__}}/invocations'.format(partition), + {'__FunctionArn__': self.function_arn}) + + swagger[APIGATEWAY_AUTHORIZER_KEY]['authorizerUri'] = authorizer_uri + reauthorize_every = self._get_reauthorize_every() + function_invoke_role = self._get_function_invoke_role() + + if reauthorize_every: + swagger[APIGATEWAY_AUTHORIZER_KEY]['authorizerResultTtlInSeconds'] = reauthorize_every + + if function_invoke_role: + swagger[APIGATEWAY_AUTHORIZER_KEY]['authorizerCredentials'] = function_invoke_role + + if self._get_function_payload_type() == 'REQUEST': + swagger[APIGATEWAY_AUTHORIZER_KEY]['identitySource'] = self._get_identity_source() + + if authorizer_type == 'COGNITO_USER_POOLS' or (authorizer_type == 'LAMBDA' and self._get_function_payload_type() == 'TOKEN'): + identity_validation_expression = self._get_identity_validation_expression() + + if identity_validation_expression: + swagger[APIGATEWAY_AUTHORIZER_KEY]['identityValidationExpression'] = identity_validation_expression + + return swagger + + def _construct_role(self, function_invoke_role): + return function_invoke_role if function_invoke_role != 'NONE' else None + + def _get_identity_validation_expression(self): + return self.identity and self.identity.get('ValidationExpression') + + def _get_identity_source(self): + if not self.identity: + return None + + if self.identity.get('Headers'): + identity_source_headers = map(lambda h: 'method.request.header.' + h, self.identity.get('Headers')) + + if self.identity.get('QueryStrings'): + identity_source_query_strings = map(lambda qs: 'method.request.querystring.' + qs, self.identity.get('QueryStrings')) + + if self.identity.get('StageVariables'): + identity_source_stage_variables = map(lambda sv: 'stageVariables.' + sv, self.identity.get('StageVariables')) + + if self.identity.get('Context'): + identity_source_context = map(lambda c: 'context.' + c, self.identity.get('Context')) + + identity_source_array = identity_source_headers + identity_source_query_strings + identity_source_stage_variables + identity_source_context + identity_source = ', '.join(identity_source_array) + + return identity_source + + def _get_user_pool_arn_array(self): + return self.user_pool_arn if isinstance(self.user_pool_arn, list) else [self.user_pool_arn] + + def _get_swagger_header_name(self): + authorizer_type = self._get_type() + payload_type = self._get_function_payload_type() + + if authorizer_type == 'LAMBDA' and payload_type == 'REQUEST': + return 'Unused' + + return self._get_identity_header() + + def _get_type(self): + if self.user_pool_arn: + return 'COGNITO_USER_POOLS' + + return 'LAMBDA' + + def _get_identity_header(self): + if not self.identity or not self.identity.get('Header'): + return 'Authorization' + + return self.identity.get('Header') + + def _get_reauthorize_every(self): + if not self.identity or not self.identity.get('ReauthorizeEvery'): + return None + + return self.identity.get('ReauthorizeEvery') + + def _get_function_invoke_role(self): + if not self.function_invoke_role or self.function_invoke_role == 'NONE': + return None + + return self.function_invoke_role + + def _get_swagger_authtype(self): + authorizer_type = self._get_type() + + return 'cognito_user_pools' if authorizer_type == 'COGNITO_USER_POOLS' else 'custom' + + def _get_function_payload_type(self): + return 'TOKEN' if not self.function_payload_type else self.function_payload_type + + def _get_swagger_authorizer_type(self): + authorizer_type = self._get_type() + + if authorizer_type == 'COGNITO_USER_POOLS': + return 'cognito_user_pools' + + payload_type = self._get_function_payload_type() + + if payload_type == 'REQUEST': + return 'request' + + if payload_type == 'TOKEN': + return 'token' diff --git a/samtranslator/model/eventsources/push.py b/samtranslator/model/eventsources/push.py index 7320d82ba6..af091fcf46 100644 --- a/samtranslator/model/eventsources/push.py +++ b/samtranslator/model/eventsources/push.py @@ -334,7 +334,8 @@ class Api(PushEventSource): 'Method': PropertyType(True, is_str()), # Api Event sources must "always" be paired with a Serverless::Api - 'RestApiId': PropertyType(True, is_str()) + 'RestApiId': PropertyType(True, is_str()), + 'Auth': PropertyType(False, is_type(dict)) } def resources_to_link(self, resources): @@ -470,6 +471,28 @@ def _add_swagger_integration(self, api, function): method=self.Method, path=self.Path)) editor.add_lambda_integration(self.Path, self.Method, uri) + + if self.Auth: + method_authorizer = self.Auth.get('Authorizer') + + if method_authorizer: + api_auth = api.get('Auth') + api_authorizers = api_auth and api_auth.get('Authorizers') + + if not api_authorizers: + raise InvalidEventException( + self.relative_id, + 'Unable to set Authorizer "{authorizer}" on API method "{method}" for path "{path}" because the related API does not define any Authorizers.'.format( + authorizer=method_authorizer, method=self.Method, path=self.Path)) + + if method_authorizer != 'NONE' and not api_authorizers.get(method_authorizer): + raise InvalidEventException( + self.relative_id, + 'Unable to set Authorizer "{authorizer}" on API method "{method}" for path "{path}" because it was not defined in the API\'s Authorizers.'.format( + authorizer=method_authorizer, method=self.Method, path=self.Path)) + + editor.add_auth_to_method(api=api, path=self.Path, method_name=self.Method, auth=self.Auth) + api["DefinitionBody"] = editor.swagger diff --git a/samtranslator/model/sam_resources.py b/samtranslator/model/sam_resources.py index c9f4c2c3d9..ce63dd940b 100644 --- a/samtranslator/model/sam_resources.py +++ b/samtranslator/model/sam_resources.py @@ -477,7 +477,8 @@ class SamApi(SamResourceMacro): 'EndpointConfiguration': PropertyType(False, is_str()), 'MethodSettings': PropertyType(False, is_type(list)), 'BinaryMediaTypes': PropertyType(False, is_type(list)), - 'Cors': PropertyType(False, one_of(is_str(), is_type(dict))) + 'Cors': PropertyType(False, one_of(is_str(), is_type(dict))), + 'Auth': PropertyType(False, is_type(dict)) } referable_properties = { @@ -507,7 +508,8 @@ def to_cloudformation(self, **kwargs): endpoint_configuration=self.EndpointConfiguration, method_settings=self.MethodSettings, binary_media=self.BinaryMediaTypes, - cors=self.Cors) + cors=self.Cors, + auth=self.Auth) rest_api, deployment, stage = api_generator.to_cloudformation() diff --git a/samtranslator/plugins/api/implicit_api_plugin.py b/samtranslator/plugins/api/implicit_api_plugin.py index 807bc867bc..21de86aa91 100644 --- a/samtranslator/plugins/api/implicit_api_plugin.py +++ b/samtranslator/plugins/api/implicit_api_plugin.py @@ -87,8 +87,8 @@ def _get_api_events(self, function): :param SamResource function: Function Resource object :return dict: Dictionary of API events along with any other configuration passed to it. Example: { - FooEvent: {Path: "/foo", Method: "post", RestApiId: blah, MethodSettings: {}, Cors: {}}, - BarEvent: {Path: "/bar", Method: "any", MethodSettings: {}, Cors: {}}" + FooEvent: {Path: "/foo", Method: "post", RestApiId: blah, MethodSettings: {}, Cors: {}, Auth: {}}, + BarEvent: {Path: "/bar", Method: "any", MethodSettings: {}, Cors: {}, Auth: {}}" } """ @@ -232,6 +232,7 @@ def __init__(self): "StageName": "Prod", "DefinitionBody": swagger, + # "Auth": None, # Internal property that means Event source code can add Events. Used only for implicit APIs, to # prevent back compatibility issues for explicit APIs "__MANAGE_SWAGGER": True diff --git a/samtranslator/swagger/swagger.py b/samtranslator/swagger/swagger.py index bce74c55a9..3f5581022f 100644 --- a/samtranslator/swagger/swagger.py +++ b/samtranslator/swagger/swagger.py @@ -30,6 +30,7 @@ def __init__(self, doc): self._doc = copy.deepcopy(doc) self.paths = self._doc["paths"] + self.security_definitions = self._doc.get("securityDefinitions", {}) def has_path(self, path, method=None): """ @@ -285,6 +286,107 @@ def _make_cors_allowed_methods_for_path(self, path): # Allow-Methods is comma separated string return ','.join(allow_methods) + def add_authorizers(self, authorizers): + """ + Add Authorizer definitions to the securityDefinitions part of Swagger. + + :param list authorizers: List of Authorizer configurations which get translated to securityDefinitions. + """ + self.security_definitions = self.security_definitions or {} + + for authorizerName, authorizer in authorizers.items(): + self.security_definitions[authorizerName] = authorizer.generate_swagger() + + def set_path_default_authorizer(self, path, default_authorizer, authorizers): + """ + Sets the DefaultAuthorizer for each method on this path. The DefaultAuthorizer won't be set if an Authorizer + was defined at the Function/Path/Method level + + :param string path: Path name + :param string default_authorizer: Name of the authorizer to use as the default. Must be a key in the authorizers param. + :param list authorizers: List of Authorizer configurations defined on the related Api. + """ + for method_name, method in self.paths[path].items(): + self.set_method_authorizer(path, method_name, default_authorizer, authorizers, default_authorizer=default_authorizer, is_default=True) + + def add_auth_to_method(self, path, method_name, auth, api): + """ + Adds auth settings for this path/method. Auth settings currently consist solely of Authorization + (aka Custom Authorizers) but this method will eventually include setting other auth settings such as API Key, + Resource Policy, etc. + + :param string path: Path name + :param string method_name: Method name + :param dict auth: Auth configuration such as Authorizers, ApiKey, ResourcePolicy (only Authorizers supported + currently) + :param dict api: Reference to the related Api's properties as defined in the template. + """ + method_authorizer = auth and auth.get('Authorizer') + if method_authorizer: + api_auth = api.get('Auth') + api_authorizers = api_auth and api_auth.get('Authorizers') + default_authorizer = api_auth and api_auth.get('DefaultAuthorizer') + + self.set_method_authorizer(path, method_name, method_authorizer, api_authorizers, default_authorizer) + + def set_method_authorizer(self, path, method_name, authorizer_name, authorizers, default_authorizer, + is_default=False): + existing_security = self.paths[path][method_name].get('security', []) # TEST: [{'sigv4': []}, {'api_key': []}]) + authorizer_names = set(authorizers.keys()) + existing_non_authorizer_security = [] + existing_authorizer_security = [] + + # Split existing security into Authorizers and everything else + # (e.g. sigv4 (AWS_IAM), api_key (API Key/Usage Plans), NONE (marker for ignoring default)) + # We want to ensure only a single Authorizer security entry exists while keeping everything else + for security in existing_security: + if authorizer_names.isdisjoint(security.keys()): + existing_non_authorizer_security.append(security) + else: + existing_authorizer_security.append(security) + + none_idx = -1 + authorizer_security = [] + + # If this is the Api-level DefaultAuthorizer we need to check for an + # existing Authorizer before applying the default. It would be simpler + # if instead we applied the DefaultAuthorizer first and then simply + # overwrote it if necessary, however, the order in which things get + # applied (Function Api Events first; then Api Resource) complicates it. + if is_default: + # Check if Function/Path/Method specified 'NONE' for Authorizer + for idx, security in enumerate(existing_non_authorizer_security): + is_none = any(key == 'NONE' for key in security.keys()) + + if is_none: + none_idx = idx + break + + # NONE was found; remove it and don't add the DefaultAuthorizer + if none_idx > -1: + del existing_non_authorizer_security[none_idx] + + # Existing Authorizer found (defined at Function/Path/Method); use that instead of default + elif existing_authorizer_security: + authorizer_security = existing_authorizer_security + + # No existing Authorizer found; use default + else: + security_dict = {} + security_dict[authorizer_name] = [] + authorizer_security = [security_dict] + + # This is a Function/Path/Method level Authorizer; simply set it + else: + security_dict = {} + security_dict[authorizer_name] = [] + authorizer_security = [security_dict] + + security = existing_non_authorizer_security + authorizer_security + + if security: + self.paths[path][method_name]['security'] = security + @property def swagger(self): """ @@ -295,6 +397,10 @@ def swagger(self): # Make sure any changes to the paths are reflected back in output self._doc["paths"] = self.paths + + if self.security_definitions: + self._doc["securityDefinitions"] = self.security_definitions + return copy.deepcopy(self._doc) @staticmethod From 8cb9f6361097422bc9ba0f68ad6ac88a8f0621d1 Mon Sep 17 00:00:00 2001 From: Brett Andrews Date: Thu, 9 Aug 2018 14:54:44 -0700 Subject: [PATCH 02/19] addressed my own feedback in GitHub comments --- samtranslator/model/api/api_generator.py | 6 ++--- samtranslator/model/apigateway.py | 23 +++++++++++-------- samtranslator/model/eventsources/push.py | 13 +++++++++-- .../plugins/api/implicit_api_plugin.py | 1 - 4 files changed, 28 insertions(+), 15 deletions(-) diff --git a/samtranslator/model/api/api_generator.py b/samtranslator/model/api/api_generator.py index 4f8c562ac6..755396bcbe 100644 --- a/samtranslator/model/api/api_generator.py +++ b/samtranslator/model/api/api_generator.py @@ -256,10 +256,10 @@ def _get_authorizers(self, authorizers_config): authorizers = {} - for authorizerName, authorizer in authorizers_config.items(): - authorizers[authorizerName] = ApiGatewayAuthorizer( + for authorizer_name, authorizer in authorizers_config.items(): + authorizers[authorizer_name] = ApiGatewayAuthorizer( api_logical_id=self.logical_id, - name=authorizer.get('Name'), + name=authorizer_name, user_pool_arn=authorizer.get('UserPoolArn'), function_arn=authorizer.get('FunctionArn'), identity=authorizer.get('Identity'), diff --git a/samtranslator/model/apigateway.py b/samtranslator/model/apigateway.py index 5c4af00aed..afbd2b6217 100644 --- a/samtranslator/model/apigateway.py +++ b/samtranslator/model/apigateway.py @@ -1,4 +1,5 @@ from samtranslator.model import PropertyType, Resource +from samtranslator.model.exceptions import InvalidResourceException from samtranslator.model.types import is_type, one_of, is_str from samtranslator.model.intrinsics import ref, fnSub from samtranslator.translator import logical_id_generator @@ -13,7 +14,6 @@ class ApiGatewayRestApi(Resource): 'CloneFrom': PropertyType(False, is_str()), 'Description': PropertyType(False, is_str()), 'FailOnWarnings': PropertyType(False, is_type(bool)), - 'Name': PropertyType(False, is_str()), 'Parameters': PropertyType(False, is_type(dict)), 'EndpointConfiguration': PropertyType(False, is_type(dict)), "BinaryMediaTypes": PropertyType(False, is_type(list)) @@ -89,14 +89,21 @@ def make_auto_deployable(self, stage, swagger=None): class ApiGatewayAuthorizer(object): + _VALID_FUNCTION_PAYLOAD_TYPES = [None, 'TOKEN', 'REQUEST'] + def __init__(self, api_logical_id=None, name=None, user_pool_arn=None, function_arn=None, identity=None, function_payload_type=None, function_invoke_role=None): + if function_payload_type not in ApiGatewayAuthorizer._VALID_FUNCTION_PAYLOAD_TYPES: + raise InvalidResourceException(api_logical_id, name + " Authorizer has invalid " + "'FunctionPayloadType': " + function_payload_type) + + self.api_logical_id = api_logical_id self.name = name self.user_pool_arn = user_pool_arn self.function_arn = function_arn self.identity = identity self.function_payload_type = function_payload_type - self.function_invoke_role = self._construct_role(function_invoke_role) + self.function_invoke_role = function_invoke_role def generate_swagger(self): authorizer_type = self._get_type() @@ -124,8 +131,7 @@ def generate_swagger(self): reauthorize_every = self._get_reauthorize_every() function_invoke_role = self._get_function_invoke_role() - if reauthorize_every: - swagger[APIGATEWAY_AUTHORIZER_KEY]['authorizerResultTtlInSeconds'] = reauthorize_every + swagger[APIGATEWAY_AUTHORIZER_KEY]['authorizerResultTtlInSeconds'] = reauthorize_every if function_invoke_role: swagger[APIGATEWAY_AUTHORIZER_KEY]['authorizerCredentials'] = function_invoke_role @@ -133,7 +139,9 @@ def generate_swagger(self): if self._get_function_payload_type() == 'REQUEST': swagger[APIGATEWAY_AUTHORIZER_KEY]['identitySource'] = self._get_identity_source() - if authorizer_type == 'COGNITO_USER_POOLS' or (authorizer_type == 'LAMBDA' and self._get_function_payload_type() == 'TOKEN'): + is_lambda_token_authorizer = authorizer_type == 'LAMBDA' and self._get_function_payload_type() == 'TOKEN' + + if authorizer_type == 'COGNITO_USER_POOLS' or is_lambda_token_authorizer: identity_validation_expression = self._get_identity_validation_expression() if identity_validation_expression: @@ -141,9 +149,6 @@ def generate_swagger(self): return swagger - def _construct_role(self, function_invoke_role): - return function_invoke_role if function_invoke_role != 'NONE' else None - def _get_identity_validation_expression(self): return self.identity and self.identity.get('ValidationExpression') @@ -193,7 +198,7 @@ def _get_identity_header(self): return self.identity.get('Header') def _get_reauthorize_every(self): - if not self.identity or not self.identity.get('ReauthorizeEvery'): + if not self.identity: return None return self.identity.get('ReauthorizeEvery') diff --git a/samtranslator/model/eventsources/push.py b/samtranslator/model/eventsources/push.py index af091fcf46..c99c312053 100644 --- a/samtranslator/model/eventsources/push.py +++ b/samtranslator/model/eventsources/push.py @@ -482,15 +482,24 @@ def _add_swagger_integration(self, api, function): if not api_authorizers: raise InvalidEventException( self.relative_id, - 'Unable to set Authorizer "{authorizer}" on API method "{method}" for path "{path}" because the related API does not define any Authorizers.'.format( + 'Unable to set Authorizer "{authorizer}" on API method "{method}" for path "{path}" because the ' + 'related API does not define any Authorizers.'.format( authorizer=method_authorizer, method=self.Method, path=self.Path)) if method_authorizer != 'NONE' and not api_authorizers.get(method_authorizer): raise InvalidEventException( self.relative_id, - 'Unable to set Authorizer "{authorizer}" on API method "{method}" for path "{path}" because it was not defined in the API\'s Authorizers.'.format( + 'Unable to set Authorizer "{authorizer}" on API method "{method}" for path "{path}" because it ' + 'wasn\'t defined in the API\'s Authorizers.'.format( authorizer=method_authorizer, method=self.Method, path=self.Path)) + if method_authorizer == 'NONE' and not api_auth.get('DefaultAuthorizer'): + raise InvalidEventException( + self.relative_id, + 'Unable to set Authorizer on API method "{method}" for path "{path}" because "NONE" ' + 'is only a valid value when a DefaultAuthorizer on the API is specified.'.format( + method=self.Method, path=self.Path)) + editor.add_auth_to_method(api=api, path=self.Path, method_name=self.Method, auth=self.Auth) api["DefinitionBody"] = editor.swagger diff --git a/samtranslator/plugins/api/implicit_api_plugin.py b/samtranslator/plugins/api/implicit_api_plugin.py index 21de86aa91..c4a1dc528c 100644 --- a/samtranslator/plugins/api/implicit_api_plugin.py +++ b/samtranslator/plugins/api/implicit_api_plugin.py @@ -232,7 +232,6 @@ def __init__(self): "StageName": "Prod", "DefinitionBody": swagger, - # "Auth": None, # Internal property that means Event source code can add Events. Used only for implicit APIs, to # prevent back compatibility issues for explicit APIs "__MANAGE_SWAGGER": True From 915760ce29b1fb1f7064516b3758054418c324fa Mon Sep 17 00:00:00 2001 From: Brett Andrews Date: Fri, 10 Aug 2018 08:56:35 -0700 Subject: [PATCH 03/19] improvement: remove check for Auth dict since it's verified by property check --- samtranslator/model/api/api_generator.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/samtranslator/model/api/api_generator.py b/samtranslator/model/api/api_generator.py index 755396bcbe..3b14191c44 100644 --- a/samtranslator/model/api/api_generator.py +++ b/samtranslator/model/api/api_generator.py @@ -223,10 +223,6 @@ def _add_auth(self): INVALID_ERROR = "Invalid value for 'Auth' property" - if not isinstance(self.auth, dict): - raise InvalidResourceException(self.logical_id, - "Auth must be a dictionary") - if self.auth and not self.definition_body: raise InvalidResourceException(self.logical_id, "Auth works only with inline Swagger specified in " @@ -254,6 +250,9 @@ def _get_authorizers(self, authorizers_config): if not authorizers_config: return None + if not isinstance(authorizers_config, dict): + raise InvalidResourceException(self.logical_id, + "Authorizers must be a dictionary") authorizers = {} for authorizer_name, authorizer in authorizers_config.items(): From 0ea0ce16ec7dd8fe441d1e888a2f1bee9a075ad5 Mon Sep 17 00:00:00 2001 From: Brett Andrews Date: Fri, 10 Aug 2018 13:09:40 -0700 Subject: [PATCH 04/19] test: add error tests for authorizers --- samtranslator/model/apigateway.py | 1 + samtranslator/model/eventsources/push.py | 8 +- .../input/error_api_invalid_auth.yaml | 257 ++++++++++++++++++ .../output/error_api_invalid_auth.json | 3 + tests/translator/test_translator.py | 3 +- 5 files changed, 267 insertions(+), 5 deletions(-) create mode 100644 tests/translator/input/error_api_invalid_auth.yaml create mode 100644 tests/translator/output/error_api_invalid_auth.json diff --git a/samtranslator/model/apigateway.py b/samtranslator/model/apigateway.py index afbd2b6217..0b70056bd5 100644 --- a/samtranslator/model/apigateway.py +++ b/samtranslator/model/apigateway.py @@ -14,6 +14,7 @@ class ApiGatewayRestApi(Resource): 'CloneFrom': PropertyType(False, is_str()), 'Description': PropertyType(False, is_str()), 'FailOnWarnings': PropertyType(False, is_type(bool)), + 'Name': PropertyType(False, is_str()), 'Parameters': PropertyType(False, is_type(dict)), 'EndpointConfiguration': PropertyType(False, is_type(dict)), "BinaryMediaTypes": PropertyType(False, is_type(list)) diff --git a/samtranslator/model/eventsources/push.py b/samtranslator/model/eventsources/push.py index c99c312053..2f3d816632 100644 --- a/samtranslator/model/eventsources/push.py +++ b/samtranslator/model/eventsources/push.py @@ -482,21 +482,21 @@ def _add_swagger_integration(self, api, function): if not api_authorizers: raise InvalidEventException( self.relative_id, - 'Unable to set Authorizer "{authorizer}" on API method "{method}" for path "{path}" because the ' - 'related API does not define any Authorizers.'.format( + 'Unable to set Authorizer [{authorizer}] on API method [{method}] for path [{path}] because ' + 'the related API does not define any Authorizers.'.format( authorizer=method_authorizer, method=self.Method, path=self.Path)) if method_authorizer != 'NONE' and not api_authorizers.get(method_authorizer): raise InvalidEventException( self.relative_id, - 'Unable to set Authorizer "{authorizer}" on API method "{method}" for path "{path}" because it ' + 'Unable to set Authorizer [{authorizer}] on API method [{method}] for path [{path}] because it ' 'wasn\'t defined in the API\'s Authorizers.'.format( authorizer=method_authorizer, method=self.Method, path=self.Path)) if method_authorizer == 'NONE' and not api_auth.get('DefaultAuthorizer'): raise InvalidEventException( self.relative_id, - 'Unable to set Authorizer on API method "{method}" for path "{path}" because "NONE" ' + 'Unable to set Authorizer on API method [{method}] for path [{path}] because \'NONE\' ' 'is only a valid value when a DefaultAuthorizer on the API is specified.'.format( method=self.Method, path=self.Path)) diff --git a/tests/translator/input/error_api_invalid_auth.yaml b/tests/translator/input/error_api_invalid_auth.yaml new file mode 100644 index 0000000000..17876f31ae --- /dev/null +++ b/tests/translator/input/error_api_invalid_auth.yaml @@ -0,0 +1,257 @@ +Resources: + NoAuthApi: + Type: AWS::Serverless::Api + Properties: + StageName: Prod + + NoAuthFn: + Type: AWS::Serverless::Function + Properties: + CodeUri: s3://bucket/key + Handler: index.handler + Runtime: nodejs8.10 + Events: + GetRoot: + Type: Api + Properties: + RestApiId: !Ref NoAuthApi + Path: / + Method: get + Auth: + Authorizer: MyAuth + + NoAuthorizersApi: + Type: AWS::Serverless::Api + Properties: + StageName: Prod + Auth: + DefaultAuthorizer: Foo + + NoAuthorizersFn: + Type: AWS::Serverless::Function + Properties: + CodeUri: s3://bucket/key + Handler: index.handler + Runtime: nodejs8.10 + Events: + GetRoot: + Type: Api + Properties: + RestApiId: !Ref NoAuthorizersApi + Path: / + Method: get + Auth: + Authorizer: MyAuth + + MissingAuthorizerApi: + Type: AWS::Serverless::Api + Properties: + StageName: Prod + Auth: + Authorizers: + MyCognitoAuthorizer: + UserPoolArn: 'arn:aws' + + MissingAuthorizerFn: + Type: AWS::Serverless::Function + Properties: + CodeUri: s3://bucket/key + Handler: index.handler + Runtime: nodejs8.10 + Events: + GetRoot: + Type: Api + Properties: + RestApiId: !Ref MissingAuthorizerApi + Path: / + Method: get + Auth: + Authorizer: UnspecifiedAuthorizer + + NoDefaultAuthorizerWithNoneApi: + Type: AWS::Serverless::Api + Properties: + StageName: Prod + Auth: + Authorizers: + MyCognitoAuthorizer: + UserPoolArn: 'arn:aws' + + NoDefaultAuthorizerWithNoneFn: + Type: AWS::Serverless::Function + Properties: + CodeUri: s3://bucket/key + Handler: index.handler + Runtime: nodejs8.10 + Events: + GetRoot: + Type: Api + Properties: + RestApiId: !Ref NoDefaultAuthorizerWithNoneApi + Path: / + Method: get + Auth: + Authorizer: NONE + + AuthNotDictApi: + Type: AWS::Serverless::Api + Properties: + StageName: Prod + Auth: notadict + + AuthorizersNotDictApi: + Type: AWS::Serverless::Api + Properties: + StageName: Prod + Auth: + Authorizers: notadict + + AuthWithDefinitionUriApi: + Type: AWS::Serverless::Api + Properties: + StageName: Prod + DefinitionUri: s3://bucket/key + Auth: + DefaultAuthorizer: Foo + + AuthWithAdditionalPropertyApi: + Type: AWS::Serverless::Api + Properties: + StageName: Prod + Auth: + MyBad: Foo + + AuthWithInvalidDefinitionBodyApi: + Type: AWS::Serverless::Api + Properties: + StageName: Prod + DefinitionBody: { invalid: true } + Auth: + DefaultAuthorizer: Foo + + AuthWithMissingDefaultAuthorizerApi: + Type: AWS::Serverless::Api + Properties: + StageName: Prod + DefaultAuthorizer: NotThere + Auth: + DefaultAuthorizer: Foo + + NoApiAuthorizerFn: + Type: AWS::Serverless::Function + Properties: + CodeUri: s3://bucket/key + Handler: index.handler + Runtime: nodejs8.10 + Events: + GetRoot: + Type: Api + Properties: + Path: / + Method: get + Auth: + Authorizer: MyAuth + + + InvalidFunctionPayloadTypeApi: + Type: AWS::Serverless::Api + Properties: + StageName: Prod + Auth: + Authorizers: + MyLambdaAuthorizer: + FunctionArn: 'arn:aws' + FunctionPayloadType: INVALID + + # InvalidFunctionPayloadTypeFn: + # Type: AWS::Serverless::Function + # Properties: + # CodeUri: s3://bucket/key + # Handler: index.handler + # Runtime: nodejs8.10 + # Events: + # GetRoot: + # Type: Api + # Properties: + # RestApiId: !Ref InvalidFunctionPayloadTypeApi + # Path: / + # Method: get + # Auth: + # Authorizer: NONE +# invalid FunctionPayloadType + + # StageName: TestStage + # Auth: + # DefaultAuthorizer: MyLambdaAuthorizer + # Authorizers: + # MyCognitoAuthorizer: + # UserPoolArn: !GetAtt MyCognitoUserPool.Arn + + # MyLambdaAuthorizer: + # FunctionArn: !GetAtt MyAuthFunction.Arn + # FunctionInvokeRole: NONE + # Identity: # Optional + # Header: Authn # Optional; Default: Authorization + # ValidationExpression: myexpresso # Optional + # ReauthorizeEvery: 33 + + # MyLambdaRequestAuthorizer: + # FunctionPayloadType: REQUEST + # FunctionArn: !GetAtt MyAuthFunction.Arn + # FunctionInvokeRole: !Sub arn:aws:iam::${AWS::AccountId}:role/admin + # Identity: + # Headers: + # - Authorization1 + # QueryStrings: + # - Authorization2 + # StageVariables: + # - Authorization3 + # Context: + # - Authorization4 + # ReauthorizeEvery: 100 + + # MyFunction: + # Type: AWS::Serverless::Function + # Properties: + # CodeUri: s3://bucket/key + # Handler: index.handler + # Runtime: nodejs8.10 + # Events: + # WithNoAuthorizer: + # Type: Api + # Properties: + # RestApiId: !Ref MyApi + # Path: / + # Method: get + # Auth: + # Authorizer: NONE + # WithLambdaTokenAuthorizer: + # Type: Api + # Properties: + # RestApiId: !Ref MyApi + # Path: /users + # Method: get + # Auth: + # Authorizer: MyLambdaAuthorizer + # WithCognitoAuthorizer: + # Type: Api + # Properties: + # RestApiId: !Ref MyApi + # Path: /users + # Method: post + # Auth: + # Authorizer: MyCognitoAuthorizer + # WithLambdaRequestAuthorizer: + # Type: Api + # Properties: + # RestApiId: !Ref MyApi + # Path: /users + # Method: delete + # Auth: + # Authorizer: MyLambdaRequestAuthorizer + # WithDefaultAuthorizer: + # Type: Api + # Properties: + # RestApiId: !Ref MyApi + # Path: /users + # Method: put \ No newline at end of file diff --git a/tests/translator/output/error_api_invalid_auth.json b/tests/translator/output/error_api_invalid_auth.json new file mode 100644 index 0000000000..4e32417d80 --- /dev/null +++ b/tests/translator/output/error_api_invalid_auth.json @@ -0,0 +1,3 @@ +{ + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 12. Resource with id [AuthNotDictApi] is invalid. Type of property 'Auth' is invalid. Resource with id [AuthWithAdditionalPropertyApi] is invalid. Invalid value for 'Auth' property Resource with id [AuthWithDefinitionUriApi] is invalid. Auth works only with inline Swagger specified in 'DefinitionBody' property Resource with id [AuthWithInvalidDefinitionBodyApi] is invalid. Unable to add Auth configuration because 'DefinitionBody' does not contain a valid Swagger Resource with id [AuthWithMissingDefaultAuthorizerApi] is invalid. property DefaultAuthorizer not defined for resource of type AWS::Serverless::Api Resource with id [AuthorizersNotDictApi] is invalid. Authorizers must be a dictionary Resource with id [InvalidFunctionPayloadTypeApi] is invalid. MyLambdaAuthorizer Authorizer has invalid 'FunctionPayloadType': INVALID Resource with id [MissingAuthorizerFn] is invalid. Event with id [GetRoot] is invalid. Unable to set Authorizer [UnspecifiedAuthorizer] on API method [get] for path [/] because it wasn't defined in the API's Authorizers. Resource with id [NoApiAuthorizerFn] is invalid. Event with id [GetRoot] is invalid. Unable to set Authorizer [MyAuth] on API method [get] for path [/] because the related API does not define any Authorizers. Resource with id [NoAuthFn] is invalid. Event with id [GetRoot] is invalid. Unable to set Authorizer [MyAuth] on API method [get] for path [/] because the related API does not define any Authorizers. Resource with id [NoAuthorizersFn] is invalid. Event with id [GetRoot] is invalid. Unable to set Authorizer [MyAuth] on API method [get] for path [/] because the related API does not define any Authorizers. Resource with id [NoDefaultAuthorizerWithNoneFn] is invalid. Event with id [GetRoot] is invalid. Unable to set Authorizer on API method [get] for path [/] because 'NONE' is only a valid value when a DefaultAuthorizer on the API is specified." +} \ No newline at end of file diff --git a/tests/translator/test_translator.py b/tests/translator/test_translator.py index ca56197412..5bf0561047 100644 --- a/tests/translator/test_translator.py +++ b/tests/translator/test_translator.py @@ -272,6 +272,7 @@ def _generate_new_deployment_hash(self, logical_id, dict_to_hash, rest_api_to_sw @pytest.mark.parametrize('testcase', [ 'error_api_duplicate_methods_same_path', + 'error_api_invalid_auth', 'error_api_invalid_definitionuri', 'error_api_invalid_definitionbody', 'error_api_invalid_restapiid', @@ -306,7 +307,7 @@ def _generate_new_deployment_hash(self, logical_id, dict_to_hash, rest_api_to_sw 'error_function_with_invalid_policy_statement' ]) def test_transform_invalid_document(testcase): - manifest = yaml.load(open(os.path.join(input_folder, testcase + '.yaml'), 'r')) + manifest = yaml_parse(open(os.path.join(input_folder, testcase + '.yaml'), 'r')) expected = json.load(open(os.path.join(output_folder, testcase + '.json'), 'r')) mock_policy_loader = MagicMock() From f4ecc380f2ec849249875fa1598375d73d9d0535 Mon Sep 17 00:00:00 2001 From: Brett Andrews Date: Fri, 10 Aug 2018 15:46:45 -0700 Subject: [PATCH 05/19] test: fix missing DefaultAuthorizer in Authorizers test --- samtranslator/model/apigateway.py | 5 + .../input/error_api_invalid_auth.yaml | 101 +----------------- .../output/error_api_invalid_auth.json | 2 +- 3 files changed, 11 insertions(+), 97 deletions(-) diff --git a/samtranslator/model/apigateway.py b/samtranslator/model/apigateway.py index 0b70056bd5..795745bf86 100644 --- a/samtranslator/model/apigateway.py +++ b/samtranslator/model/apigateway.py @@ -157,6 +157,11 @@ def _get_identity_source(self): if not self.identity: return None + identity_source_headers = [] + identity_source_query_strings = [] + identity_source_stage_variables = [] + identity_source_context = [] + if self.identity.get('Headers'): identity_source_headers = map(lambda h: 'method.request.header.' + h, self.identity.get('Headers')) diff --git a/tests/translator/input/error_api_invalid_auth.yaml b/tests/translator/input/error_api_invalid_auth.yaml index 17876f31ae..7b102caf0b 100644 --- a/tests/translator/input/error_api_invalid_auth.yaml +++ b/tests/translator/input/error_api_invalid_auth.yaml @@ -133,9 +133,11 @@ Resources: Type: AWS::Serverless::Api Properties: StageName: Prod - DefaultAuthorizer: NotThere Auth: - DefaultAuthorizer: Foo + DefaultAuthorizer: NotThere + Authorizers: + MyCognitoAuthorizer: + UserPoolArn: 'arn:aws' NoApiAuthorizerFn: Type: AWS::Serverless::Function @@ -161,97 +163,4 @@ Resources: Authorizers: MyLambdaAuthorizer: FunctionArn: 'arn:aws' - FunctionPayloadType: INVALID - - # InvalidFunctionPayloadTypeFn: - # Type: AWS::Serverless::Function - # Properties: - # CodeUri: s3://bucket/key - # Handler: index.handler - # Runtime: nodejs8.10 - # Events: - # GetRoot: - # Type: Api - # Properties: - # RestApiId: !Ref InvalidFunctionPayloadTypeApi - # Path: / - # Method: get - # Auth: - # Authorizer: NONE -# invalid FunctionPayloadType - - # StageName: TestStage - # Auth: - # DefaultAuthorizer: MyLambdaAuthorizer - # Authorizers: - # MyCognitoAuthorizer: - # UserPoolArn: !GetAtt MyCognitoUserPool.Arn - - # MyLambdaAuthorizer: - # FunctionArn: !GetAtt MyAuthFunction.Arn - # FunctionInvokeRole: NONE - # Identity: # Optional - # Header: Authn # Optional; Default: Authorization - # ValidationExpression: myexpresso # Optional - # ReauthorizeEvery: 33 - - # MyLambdaRequestAuthorizer: - # FunctionPayloadType: REQUEST - # FunctionArn: !GetAtt MyAuthFunction.Arn - # FunctionInvokeRole: !Sub arn:aws:iam::${AWS::AccountId}:role/admin - # Identity: - # Headers: - # - Authorization1 - # QueryStrings: - # - Authorization2 - # StageVariables: - # - Authorization3 - # Context: - # - Authorization4 - # ReauthorizeEvery: 100 - - # MyFunction: - # Type: AWS::Serverless::Function - # Properties: - # CodeUri: s3://bucket/key - # Handler: index.handler - # Runtime: nodejs8.10 - # Events: - # WithNoAuthorizer: - # Type: Api - # Properties: - # RestApiId: !Ref MyApi - # Path: / - # Method: get - # Auth: - # Authorizer: NONE - # WithLambdaTokenAuthorizer: - # Type: Api - # Properties: - # RestApiId: !Ref MyApi - # Path: /users - # Method: get - # Auth: - # Authorizer: MyLambdaAuthorizer - # WithCognitoAuthorizer: - # Type: Api - # Properties: - # RestApiId: !Ref MyApi - # Path: /users - # Method: post - # Auth: - # Authorizer: MyCognitoAuthorizer - # WithLambdaRequestAuthorizer: - # Type: Api - # Properties: - # RestApiId: !Ref MyApi - # Path: /users - # Method: delete - # Auth: - # Authorizer: MyLambdaRequestAuthorizer - # WithDefaultAuthorizer: - # Type: Api - # Properties: - # RestApiId: !Ref MyApi - # Path: /users - # Method: put \ No newline at end of file + FunctionPayloadType: INVALID \ No newline at end of file diff --git a/tests/translator/output/error_api_invalid_auth.json b/tests/translator/output/error_api_invalid_auth.json index 4e32417d80..16d5ca4a75 100644 --- a/tests/translator/output/error_api_invalid_auth.json +++ b/tests/translator/output/error_api_invalid_auth.json @@ -1,3 +1,3 @@ { - "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 12. Resource with id [AuthNotDictApi] is invalid. Type of property 'Auth' is invalid. Resource with id [AuthWithAdditionalPropertyApi] is invalid. Invalid value for 'Auth' property Resource with id [AuthWithDefinitionUriApi] is invalid. Auth works only with inline Swagger specified in 'DefinitionBody' property Resource with id [AuthWithInvalidDefinitionBodyApi] is invalid. Unable to add Auth configuration because 'DefinitionBody' does not contain a valid Swagger Resource with id [AuthWithMissingDefaultAuthorizerApi] is invalid. property DefaultAuthorizer not defined for resource of type AWS::Serverless::Api Resource with id [AuthorizersNotDictApi] is invalid. Authorizers must be a dictionary Resource with id [InvalidFunctionPayloadTypeApi] is invalid. MyLambdaAuthorizer Authorizer has invalid 'FunctionPayloadType': INVALID Resource with id [MissingAuthorizerFn] is invalid. Event with id [GetRoot] is invalid. Unable to set Authorizer [UnspecifiedAuthorizer] on API method [get] for path [/] because it wasn't defined in the API's Authorizers. Resource with id [NoApiAuthorizerFn] is invalid. Event with id [GetRoot] is invalid. Unable to set Authorizer [MyAuth] on API method [get] for path [/] because the related API does not define any Authorizers. Resource with id [NoAuthFn] is invalid. Event with id [GetRoot] is invalid. Unable to set Authorizer [MyAuth] on API method [get] for path [/] because the related API does not define any Authorizers. Resource with id [NoAuthorizersFn] is invalid. Event with id [GetRoot] is invalid. Unable to set Authorizer [MyAuth] on API method [get] for path [/] because the related API does not define any Authorizers. Resource with id [NoDefaultAuthorizerWithNoneFn] is invalid. Event with id [GetRoot] is invalid. Unable to set Authorizer on API method [get] for path [/] because 'NONE' is only a valid value when a DefaultAuthorizer on the API is specified." + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 12. Resource with id [AuthNotDictApi] is invalid. Type of property 'Auth' is invalid. Resource with id [AuthWithAdditionalPropertyApi] is invalid. Invalid value for 'Auth' property Resource with id [AuthWithDefinitionUriApi] is invalid. Auth works only with inline Swagger specified in 'DefinitionBody' property Resource with id [AuthWithInvalidDefinitionBodyApi] is invalid. Unable to add Auth configuration because 'DefinitionBody' does not contain a valid Swagger Resource with id [AuthWithMissingDefaultAuthorizerApi] is invalid. Unable to set DefaultAuthorizer because 'NotThere' was not defined in 'Authorizers' Resource with id [AuthorizersNotDictApi] is invalid. Authorizers must be a dictionary Resource with id [InvalidFunctionPayloadTypeApi] is invalid. MyLambdaAuthorizer Authorizer has invalid 'FunctionPayloadType': INVALID Resource with id [MissingAuthorizerFn] is invalid. Event with id [GetRoot] is invalid. Unable to set Authorizer [UnspecifiedAuthorizer] on API method [get] for path [/] because it wasn't defined in the API's Authorizers. Resource with id [NoApiAuthorizerFn] is invalid. Event with id [GetRoot] is invalid. Unable to set Authorizer [MyAuth] on API method [get] for path [/] because the related API does not define any Authorizers. Resource with id [NoAuthFn] is invalid. Event with id [GetRoot] is invalid. Unable to set Authorizer [MyAuth] on API method [get] for path [/] because the related API does not define any Authorizers. Resource with id [NoAuthorizersFn] is invalid. Event with id [GetRoot] is invalid. Unable to set Authorizer [MyAuth] on API method [get] for path [/] because the related API does not define any Authorizers. Resource with id [NoDefaultAuthorizerWithNoneFn] is invalid. Event with id [GetRoot] is invalid. Unable to set Authorizer on API method [get] for path [/] because 'NONE' is only a valid value when a DefaultAuthorizer on the API is specified." } \ No newline at end of file From e45173950ce6239bb674f4134899969f73a7235a Mon Sep 17 00:00:00 2001 From: Brett Andrews Date: Wed, 15 Aug 2018 13:54:42 -0700 Subject: [PATCH 06/19] fix: add permissions for API to invoke Authorizer Lambda Function --- .../api_lambda_token_auth/README.md | 39 +++++++++++++ .../api_lambda_token_auth/src/authorizer.js | 57 +++++++++++++++++++ .../api_lambda_token_auth/src/index.js | 7 +++ .../api_lambda_token_auth/template.yaml | 54 ++++++++++++++++++ samtranslator/model/api/api_generator.py | 48 +++++++++++++++- samtranslator/model/apigateway.py | 8 ++- samtranslator/model/sam_resources.py | 3 +- 7 files changed, 210 insertions(+), 6 deletions(-) create mode 100644 examples/2016-10-31/api_lambda_token_auth/README.md create mode 100644 examples/2016-10-31/api_lambda_token_auth/src/authorizer.js create mode 100644 examples/2016-10-31/api_lambda_token_auth/src/index.js create mode 100644 examples/2016-10-31/api_lambda_token_auth/template.yaml diff --git a/examples/2016-10-31/api_lambda_token_auth/README.md b/examples/2016-10-31/api_lambda_token_auth/README.md new file mode 100644 index 0000000000..ce0051da69 --- /dev/null +++ b/examples/2016-10-31/api_lambda_token_auth/README.md @@ -0,0 +1,39 @@ +# API Gateway + Lambda TOKEN Authorizer Example + +This example shows you how to create an API Gateway API with a Lambda TOKEN Authorizer using SAM. + +The Authorizer Lambda Function in this example simply accepts an `Authorization` header with valid values `"allow"` and `"deny"`. See [Introducing custom authorizers in Amazon API Gateway](https://aws.amazon.com/blogs/compute/introducing-custom-authorizers-in-amazon-api-gateway/) for a more detailed example using JWT. + +## Running the example + +Deploy the example into your account: + +```bash +# Replace YOUR_S3_ARTIFACTS_BUCKET +aws cloudformation package --template-file template.yaml --output-template-file template.packaged.yaml --s3-bucket YOUR_S3_ARTIFACTS_BUCKET + +aws cloudformation deploy --template-file ./template.packaged.yaml --stack-name sam-example-api-lambda-token-auth --capabilities CAPABILITY_IAM +``` + +Invoke the API's root endpoint `/` without an `Authorization` header to see the API respond with a 200. In the SAM template, we explicitly state `Authorizer: NONE` to make this a public/open endpoint (the Authorizer Lambda Function is not invoke). + +```bash +curl "$(aws cloudformation describe-stacks --stack-name sam-example-api-lambda-token-auth --query 'Stacks[].Outputs[?OutputKey==`ApiURL`].OutputValue' --output text)" +``` + +Invoke the API's `/users` endpoint without an `Authorization` header to see a `401 {"message":"Unauthorized"}` response. Since we didn't specify an `Authorization` header, API Gateway didn't even attempt to invoke our Authorizer Lambda Function. + +```bash +curl "$(aws cloudformation describe-stacks --stack-name sam-example-api-lambda-token-auth --query 'Stacks[].Outputs[?OutputKey==`ApiURL`].OutputValue' --output text)users" +``` + +Invoke the API's `/users` endpoint with an `Authorization: allow` header to see a `200` response. Try changing the header value to `"deny"` to see a `403` response and `"gibberish"` to see a `500` response. + +```bash +curl "$(aws cloudformation describe-stacks --stack-name sam-example-api-lambda-token-auth --query 'Stacks[].Outputs[?OutputKey==`ApiURL`].OutputValue' --output text)users" -H 'Authorization: allow' +``` + +## Additional resources + +- https://aws.amazon.com/blogs/compute/introducing-custom-authorizers-in-amazon-api-gateway/ +- https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-use-lambda-authorizer.html diff --git a/examples/2016-10-31/api_lambda_token_auth/src/authorizer.js b/examples/2016-10-31/api_lambda_token_auth/src/authorizer.js new file mode 100644 index 0000000000..c5b0df87e4 --- /dev/null +++ b/examples/2016-10-31/api_lambda_token_auth/src/authorizer.js @@ -0,0 +1,57 @@ +/* +** Adapted from https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-use-lambda-authorizer.html#api-gateway-lambda-authorizer-token-lambda-function-create +** A simple TOKEN authorizer example to demonstrate how to use an authorization token +** to allow or deny a request.In this example, the caller named 'user'is allowed to invoke +** a request if the client - supplied token value is 'allow'.The caller is not allowed to +** invoke the request if the token value is 'deny'.If the token value is 'Unauthorized', +** the function returns the 'Unauthorized' error with an HTTP status code of 401. For any +** other token value, the authorizer returns 'Error: Invalid token' as a 500 error. +*/ + +exports.handler = async function (event) { + const token = event.authorizationToken.toLowerCase() + const methodArn = event.methodArn + + switch (token) { + case 'allow': + return generateAuthResponse('user', 'Allow', methodArn) + case 'deny': + return generateAuthResponse('user', 'Deny', methodArn) + case 'unauthorized': + return Promise.reject('Unauthorized.') // Returns 401 Unauthorized + default: + return Promise.reject('Error: Invalid token') // Returns 500 Internal Server Error + } +} + +function generateAuthResponse(principalId, effect, methodArn) { + // If you need to provide additional information to your integration + // endpoint (e.g. your Lambda Function), you can add it to `context` + const context = { + 'stringKey': 'stringval', + 'numberKey': 123, + 'booleanKey': true + } + const policyDocument = generatePolicyDocument(effect, methodArn) + + return { + principalId, + context, + policyDocument + } +} + +function generatePolicyDocument(effect, methodArn) { + if (!effect || !methodArn) return null + + const policyDocument = { + Version: '2012-10-17', + Statement: [{ + Action: 'execute-api:Invoke', + Effect: effect, + Resource: methodArn + }] + } + + return policyDocument +} \ No newline at end of file diff --git a/examples/2016-10-31/api_lambda_token_auth/src/index.js b/examples/2016-10-31/api_lambda_token_auth/src/index.js new file mode 100644 index 0000000000..d33f8abad6 --- /dev/null +++ b/examples/2016-10-31/api_lambda_token_auth/src/index.js @@ -0,0 +1,7 @@ +exports.handler = async (event) => { + return { + statusCode: 200, + body: JSON.stringify(event), + headers: {} + } +} \ No newline at end of file diff --git a/examples/2016-10-31/api_lambda_token_auth/template.yaml b/examples/2016-10-31/api_lambda_token_auth/template.yaml new file mode 100644 index 0000000000..e4bd94e37a --- /dev/null +++ b/examples/2016-10-31/api_lambda_token_auth/template.yaml @@ -0,0 +1,54 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 +Description: API Gateway with Lambda Token Authorizer +Resources: + MyApi: + Type: AWS::Serverless::Api + Properties: + StageName: Prod + Auth: + DefaultAuthorizer: MyLambdaTokenAuthorizer + Authorizers: + MyLambdaTokenAuthorizer: + FunctionArn: !GetAtt MyAuthFunction.Arn + # FunctionInvokeRole: !Ref MyRole + # Identity: + # Header: Auth + # ValidationExpression: Bearer.* + # ReauthorizeEvery: 30 # seconds + + MyFunction: + Type: AWS::Serverless::Function + Properties: + CodeUri: ./src + Handler: index.handler + Runtime: nodejs8.10 + Events: + GetRoot: + Type: Api + Properties: + RestApiId: !Ref MyApi + Path: / + Method: get + Auth: + Authorizer: NONE + GetUsers: + Type: Api + Properties: + RestApiId: !Ref MyApi + Path: /users + Method: get + # Auth: + # Authorizer: MyOtherAuthorizer + + MyAuthFunction: + Type: AWS::Serverless::Function + Properties: + CodeUri: ./src + Handler: authorizer.handler + Runtime: nodejs8.10 + +Outputs: + ApiURL: + Description: "API URL" + Value: !Sub 'https://${MyApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/' \ No newline at end of file diff --git a/samtranslator/model/api/api_generator.py b/samtranslator/model/api/api_generator.py index 3b14191c44..19463d7ce2 100644 --- a/samtranslator/model/api/api_generator.py +++ b/samtranslator/model/api/api_generator.py @@ -8,7 +8,9 @@ from samtranslator.model.s3_utils.uri_parser import parse_s3_uri from samtranslator.region_configuration import RegionConfiguration from samtranslator.swagger.swagger import SwaggerEditor -from samtranslator.model.intrinsics import is_instrinsic +from samtranslator.model.intrinsics import is_instrinsic, fnSub +from samtranslator.model.lambda_ import LambdaPermission +from samtranslator.translator.arn_generator import ArnGenerator _CORS_WILDCARD = "'*'" CorsProperties = namedtuple("_CorsProperties", ["AllowMethods", "AllowHeaders", "AllowOrigin", "MaxAge"]) @@ -169,8 +171,9 @@ def to_cloudformation(self): swagger = rest_api.BodyS3Location stage = self._construct_stage(deployment, swagger) + permissions = self._construct_authorizer_lambda_permission() - return rest_api, deployment, stage + return rest_api, deployment, stage, permissions def _add_cors(self): """ @@ -268,6 +271,47 @@ def _get_authorizers(self, authorizers_config): return authorizers + def _get_permission(self, authorizer_name, authorizer_lambda_function_arn): + """Constructs and returns the Lambda Permission resource allowing the Authorizer to invoke the function. + + :returns: the permission resource + :rtype: model.lambda_.LambdaPermission + """ + rest_api = ApiGatewayRestApi(self.logical_id, depends_on=self.depends_on) + api_id = rest_api.get_runtime_attr('rest_api_id') + + partition = ArnGenerator.get_partition_name() + resource = '${__ApiId__}/authorizers/*' + source_arn = fnSub(ArnGenerator.generate_arn(partition=partition, service='execute-api', resource=resource), + {"__ApiId__": api_id}) + + lambda_permission = LambdaPermission(self.logical_id + authorizer_name + 'AuthorizerPermission') + 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): + auth_properties = AuthProperties(**self.auth) + authorizers = self._get_authorizers(auth_properties.Authorizers) + + if not authorizers: + return [] + + permissions = [] + + for authorizer_name, authorizer in authorizers.items(): + # Construct permissions for Lambda Authorizers only + if not authorizer.function_arn: + continue + + permission = self._get_permission(authorizer_name, authorizer.function_arn) + permissions.append(permission) + + return permissions + def _set_default_authorizer(self, swagger_editor, authorizers, default_authorizer): if not default_authorizer: return diff --git a/samtranslator/model/apigateway.py b/samtranslator/model/apigateway.py index 795745bf86..4cb46711ca 100644 --- a/samtranslator/model/apigateway.py +++ b/samtranslator/model/apigateway.py @@ -124,15 +124,17 @@ def generate_swagger(self): elif authorizer_type == 'LAMBDA': partition = ArnGenerator.get_partition_name() - authorizer_uri = fnSub('arn:{0}:apigateway:${{AWS::Region}}:lambda:path/2015-03-31/functions/' - '${{__FunctionArn__}}/invocations'.format(partition), + resource = 'lambda:path/2015-03-31/functions/${__FunctionArn__}/invocations' + authorizer_uri = fnSub(ArnGenerator.generate_arn(partition=partition, service='apigateway', + resource=resource, include_account_id=False), {'__FunctionArn__': self.function_arn}) swagger[APIGATEWAY_AUTHORIZER_KEY]['authorizerUri'] = authorizer_uri reauthorize_every = self._get_reauthorize_every() function_invoke_role = self._get_function_invoke_role() - swagger[APIGATEWAY_AUTHORIZER_KEY]['authorizerResultTtlInSeconds'] = reauthorize_every + if reauthorize_every is not None: + swagger[APIGATEWAY_AUTHORIZER_KEY]['authorizerResultTtlInSeconds'] = reauthorize_every if function_invoke_role: swagger[APIGATEWAY_AUTHORIZER_KEY]['authorizerCredentials'] = function_invoke_role diff --git a/samtranslator/model/sam_resources.py b/samtranslator/model/sam_resources.py index ce63dd940b..7e4588cdae 100644 --- a/samtranslator/model/sam_resources.py +++ b/samtranslator/model/sam_resources.py @@ -511,9 +511,10 @@ def to_cloudformation(self, **kwargs): cors=self.Cors, auth=self.Auth) - rest_api, deployment, stage = api_generator.to_cloudformation() + rest_api, deployment, stage, permissions = api_generator.to_cloudformation() resources.extend([rest_api, deployment, stage]) + resources.extend(permissions) return resources From 18ecefca8c58332c93352972abac559e54a365fa Mon Sep 17 00:00:00 2001 From: Brett Andrews Date: Wed, 15 Aug 2018 16:30:25 -0700 Subject: [PATCH 07/19] fix: fix error when no Auth defined --- examples/2016-10-31/api_lambda_token_auth/README.md | 4 ++-- samtranslator/model/api/api_generator.py | 3 +++ samtranslator/translator/arn_generator.py | 10 ++++++++-- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/examples/2016-10-31/api_lambda_token_auth/README.md b/examples/2016-10-31/api_lambda_token_auth/README.md index ce0051da69..eb2923edb6 100644 --- a/examples/2016-10-31/api_lambda_token_auth/README.md +++ b/examples/2016-10-31/api_lambda_token_auth/README.md @@ -15,13 +15,13 @@ aws cloudformation package --template-file template.yaml --output-template-file aws cloudformation deploy --template-file ./template.packaged.yaml --stack-name sam-example-api-lambda-token-auth --capabilities CAPABILITY_IAM ``` -Invoke the API's root endpoint `/` without an `Authorization` header to see the API respond with a 200. In the SAM template, we explicitly state `Authorizer: NONE` to make this a public/open endpoint (the Authorizer Lambda Function is not invoke). +Invoke the API's root endpoint `/` without an `Authorization` header to see the API respond with a 200. In the SAM template, we explicitly state `Authorizer: NONE` to make this a public/open endpoint (the Authorizer Lambda Function is not invoked). ```bash curl "$(aws cloudformation describe-stacks --stack-name sam-example-api-lambda-token-auth --query 'Stacks[].Outputs[?OutputKey==`ApiURL`].OutputValue' --output text)" ``` -Invoke the API's `/users` endpoint without an `Authorization` header to see a `401 {"message":"Unauthorized"}` response. Since we didn't specify an `Authorization` header, API Gateway didn't even attempt to invoke our Authorizer Lambda Function. +Invoke the API's `/users` endpoint without an `Authorization` header to see a `401 {"message":"Unauthorized"}` response. Since we didn't specify an `Authorization` header, API Gateway didn't even attempt to invoke our Authorizer Lambda Function. If you specify a `ValidationExpression` on the Authorizer, it will also not invoke the Authorizer Lambda Function if the header was provided but does not match the pattern. ```bash curl "$(aws cloudformation describe-stacks --stack-name sam-example-api-lambda-token-auth --query 'Stacks[].Outputs[?OutputKey==`ApiURL`].OutputValue' --output text)users" diff --git a/samtranslator/model/api/api_generator.py b/samtranslator/model/api/api_generator.py index 19463d7ce2..3f1388e02f 100644 --- a/samtranslator/model/api/api_generator.py +++ b/samtranslator/model/api/api_generator.py @@ -294,6 +294,9 @@ def _get_permission(self, authorizer_name, authorizer_lambda_function_arn): return lambda_permission def _construct_authorizer_lambda_permission(self): + if not self.auth: + return [] + auth_properties = AuthProperties(**self.auth) authorizers = self._get_authorizers(auth_properties.Authorizers) diff --git a/samtranslator/translator/arn_generator.py b/samtranslator/translator/arn_generator.py index 032ade3172..0769520d4a 100644 --- a/samtranslator/translator/arn_generator.py +++ b/samtranslator/translator/arn_generator.py @@ -3,12 +3,18 @@ class ArnGenerator(object): @classmethod - def generate_arn(cls, partition, service, resource): + def generate_arn(cls, partition, service, resource, include_account_id=True): if not service or not resource: raise RuntimeError("Could not construct ARN for resource.") - return 'arn:{0}:{1}:${{AWS::Region}}:${{AWS::AccountId}}:{2}'.format(partition, service, resource) + arn = 'arn:{0}:{1}:${{AWS::Region}}:' + if include_account_id: + arn += '${{AWS::AccountId}}:' + + arn += '{2}' + + return arn.format(partition, service, resource) @classmethod def generate_aws_managed_policy_arn(cls, policy_name): From ea7efc8ff618f61be515d6e26fdc895dfab2bbab Mon Sep 17 00:00:00 2001 From: Brett Andrews Date: Thu, 16 Aug 2018 16:43:24 -0700 Subject: [PATCH 08/19] docs: add Lambda REQUEST Authorizer example --- examples/.eslintrc.yml | 3 + .../api_lambda_request_auth/README.md | 39 ++++++++++++ .../api_lambda_request_auth/src/authorizer.js | 59 ++++++++++++++++++ .../api_lambda_request_auth/src/index.js | 7 +++ .../api_lambda_request_auth/template.yaml | 61 +++++++++++++++++++ .../api_lambda_token_auth/src/authorizer.js | 8 +-- examples/package.json | 19 ++++++ 7 files changed, 192 insertions(+), 4 deletions(-) create mode 100644 examples/.eslintrc.yml create mode 100644 examples/2016-10-31/api_lambda_request_auth/README.md create mode 100644 examples/2016-10-31/api_lambda_request_auth/src/authorizer.js create mode 100644 examples/2016-10-31/api_lambda_request_auth/src/index.js create mode 100644 examples/2016-10-31/api_lambda_request_auth/template.yaml create mode 100644 examples/package.json diff --git a/examples/.eslintrc.yml b/examples/.eslintrc.yml new file mode 100644 index 0000000000..f13cc3238a --- /dev/null +++ b/examples/.eslintrc.yml @@ -0,0 +1,3 @@ +extends: standard +rules: + prefer-promise-reject-errors: off # API Gateway expects string response from Lamdba (when using async + Promise.reject) diff --git a/examples/2016-10-31/api_lambda_request_auth/README.md b/examples/2016-10-31/api_lambda_request_auth/README.md new file mode 100644 index 0000000000..b8a141f44e --- /dev/null +++ b/examples/2016-10-31/api_lambda_request_auth/README.md @@ -0,0 +1,39 @@ +# API Gateway + Lambda REQUEST Authorizer Example + +This example shows you how to create an API Gateway API with a Lambda REQUEST Authorizer using SAM. + +The Authorizer Lambda Function in this example simply accepts an `Authorization` header with valid values `"allow"` and `"deny"`. See [Introducing custom authorizers in Amazon API Gateway](https://aws.amazon.com/blogs/compute/introducing-custom-authorizers-in-amazon-api-gateway/) for a more detailed example using JWT. + +## Running the example + +Deploy the example into your account: + +```bash +# Replace YOUR_S3_ARTIFACTS_BUCKET +aws cloudformation package --template-file template.yaml --output-template-file template.packaged.yaml --s3-bucket YOUR_S3_ARTIFACTS_BUCKET + +aws cloudformation deploy --template-file ./template.packaged.yaml --stack-name sam-example-api-lambda-request-auth --capabilities CAPABILITY_IAM +``` + +Invoke the API's root endpoint `/` without an `Authorization` header to see the API respond with a 200. In the SAM template, we explicitly state `Authorizer: NONE` to make this a public/open endpoint (the Authorizer Lambda Function is not invoked). + +```bash +curl "$(aws cloudformation describe-stacks --stack-name sam-example-api-lambda-request-auth --query 'Stacks[].Outputs[?OutputKey==`ApiURL`].OutputValue' --output text)" +``` + +Invoke the API's `/users` endpoint without an `Authorization` header to see a `401 {"message":"Unauthorized"}` response. Since we didn't specify an `Authorization` header, API Gateway didn't even attempt to invoke our Authorizer Lambda Function. + +```bash +curl "$(aws cloudformation describe-stacks --stack-name sam-example-api-lambda-request-auth --query 'Stacks[].Outputs[?OutputKey==`ApiURL`].OutputValue' --output text)users" +``` + +Invoke the API's `/users` endpoint with an `Authorization: allow` header to see a `200` response. Try changing the header value to `"deny"` to see a `403` response and `"gibberish"` to see a `500` response. + +```bash +curl "$(aws cloudformation describe-stacks --stack-name sam-example-api-lambda-request-auth --query 'Stacks[].Outputs[?OutputKey==`ApiURL`].OutputValue' --output text)users" -H 'Authorization: allow' +``` + +## Additional resources + +- https://aws.amazon.com/blogs/compute/introducing-custom-authorizers-in-amazon-api-gateway/ +- https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-use-lambda-authorizer.html diff --git a/examples/2016-10-31/api_lambda_request_auth/src/authorizer.js b/examples/2016-10-31/api_lambda_request_auth/src/authorizer.js new file mode 100644 index 0000000000..31b072c5e6 --- /dev/null +++ b/examples/2016-10-31/api_lambda_request_auth/src/authorizer.js @@ -0,0 +1,59 @@ +/* +** Adapted from https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-use-lambda-authorizer.html#api-gateway-lambda-authorizer-request-lambda-function-create +** A simple REQUEST authorizer example to demonstrate how to use request parameters to allow or deny a request. +** In this example, a request is authorized if the client-supplied Authorization header specifies 'allow'. +*/ +exports.handler = async function (event) { + const token = event.headers.Authorization.toLowerCase() + const methodArn = event.methodArn + + /* + ** queryStringParameters, stageVariables, requestContext and more are available on the `event` object + ** event.queryStringParameters: A map/object containing supplied in the URL's query string + ** event.stageVariables: A map/object containing variables defined on the API Gateway Stage + ** event.requestContext: A map/object containing additional request context + */ + + switch (token) { + case 'allow': + return generateAuthResponse('user', 'Allow', methodArn) + case 'deny': + return generateAuthResponse('user', 'Deny', methodArn) + case 'unauthorized': + return Promise.reject('Unauthorized.') // Returns 401 Unauthorized + default: + return Promise.reject('Error: Invalid token') // Returns 500 Internal Server Error + } +} + +function generateAuthResponse (principalId, effect, methodArn) { + // If you need to provide additional information to your integration + // endpoint (e.g. your Lambda Function), you can add it to `context` + const context = { + 'stringKey': 'stringval', + 'numberKey': 123, + 'booleanKey': true + } + const policyDocument = generatePolicyDocument(effect, methodArn) + + return { + principalId, + context, + policyDocument + } +} + +function generatePolicyDocument (effect, methodArn) { + if (!effect || !methodArn) return null + + const policyDocument = { + Version: '2012-10-17', + Statement: [{ + Action: 'execute-api:Invoke', + Effect: effect, + Resource: methodArn + }] + } + + return policyDocument +} diff --git a/examples/2016-10-31/api_lambda_request_auth/src/index.js b/examples/2016-10-31/api_lambda_request_auth/src/index.js new file mode 100644 index 0000000000..472369c6c6 --- /dev/null +++ b/examples/2016-10-31/api_lambda_request_auth/src/index.js @@ -0,0 +1,7 @@ +exports.handler = async (event) => { + return { + statusCode: 200, + body: JSON.stringify(event), + headers: {} + } +} diff --git a/examples/2016-10-31/api_lambda_request_auth/template.yaml b/examples/2016-10-31/api_lambda_request_auth/template.yaml new file mode 100644 index 0000000000..4f96d24fe7 --- /dev/null +++ b/examples/2016-10-31/api_lambda_request_auth/template.yaml @@ -0,0 +1,61 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 +Description: API Gateway with Lambda Token Authorizer +Resources: + MyApi: + Type: AWS::Serverless::Api + Properties: + StageName: Prod + Auth: + DefaultAuthorizer: MyLambdaRequestAuthorizer + Authorizers: + MyLambdaRequestAuthorizer: + FunctionPayloadType: REQUEST + FunctionArn: !GetAtt MyAuthFunction.Arn + # FunctionInvokeRole: !Ref MyRole + Identity: + Headers: + - Authorization + # QueryStrings: + # - authorization + # StageVariables: + # - AUTHORIZATION + # Context: + # - auth + # ReauthorizeEvery: 100 # seconds + + MyFunction: + Type: AWS::Serverless::Function + Properties: + CodeUri: ./src + Handler: index.handler + Runtime: nodejs8.10 + Events: + GetRoot: + Type: Api + Properties: + RestApiId: !Ref MyApi + Path: / + Method: get + Auth: + Authorizer: NONE + GetUsers: + Type: Api + Properties: + RestApiId: !Ref MyApi + Path: /users + Method: get + # Auth: + # Authorizer: MyOtherAuthorizer + + MyAuthFunction: + Type: AWS::Serverless::Function + Properties: + CodeUri: ./src + Handler: authorizer.handler + Runtime: nodejs8.10 + +Outputs: + ApiURL: + Description: "API URL" + Value: !Sub 'https://${MyApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/' \ No newline at end of file diff --git a/examples/2016-10-31/api_lambda_token_auth/src/authorizer.js b/examples/2016-10-31/api_lambda_token_auth/src/authorizer.js index c5b0df87e4..fd21c6b5e5 100644 --- a/examples/2016-10-31/api_lambda_token_auth/src/authorizer.js +++ b/examples/2016-10-31/api_lambda_token_auth/src/authorizer.js @@ -24,7 +24,7 @@ exports.handler = async function (event) { } } -function generateAuthResponse(principalId, effect, methodArn) { +function generateAuthResponse (principalId, effect, methodArn) { // If you need to provide additional information to your integration // endpoint (e.g. your Lambda Function), you can add it to `context` const context = { @@ -33,7 +33,7 @@ function generateAuthResponse(principalId, effect, methodArn) { 'booleanKey': true } const policyDocument = generatePolicyDocument(effect, methodArn) - + return { principalId, context, @@ -41,7 +41,7 @@ function generateAuthResponse(principalId, effect, methodArn) { } } -function generatePolicyDocument(effect, methodArn) { +function generatePolicyDocument (effect, methodArn) { if (!effect || !methodArn) return null const policyDocument = { @@ -54,4 +54,4 @@ function generatePolicyDocument(effect, methodArn) { } return policyDocument -} \ No newline at end of file +} diff --git a/examples/package.json b/examples/package.json new file mode 100644 index 0000000000..dc8bd46939 --- /dev/null +++ b/examples/package.json @@ -0,0 +1,19 @@ +{ + "name": "examples", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "Apache 2.0", + "devDependencies": { + "eslint": "^5.3.0", + "eslint-config-standard": "^11.0.0", + "eslint-plugin-import": "^2.14.0", + "eslint-plugin-node": "^7.0.1", + "eslint-plugin-promise": "^3.8.0", + "eslint-plugin-standard": "^3.1.0" + } +} From e0b412f5abda34b93f66a86b6034a1d94cb7b4b3 Mon Sep 17 00:00:00 2001 From: Brett Andrews Date: Fri, 17 Aug 2018 12:32:42 -0700 Subject: [PATCH 09/19] fix: add error handling when no identity source provided for Lambda REQUEST authorizer --- .../api_lambda_request_auth/README.md | 12 +++++----- .../api_lambda_request_auth/src/authorizer.js | 11 ++++----- .../api_lambda_request_auth/template.yaml | 10 ++++---- .../api_lambda_token_auth/README.md | 2 +- .../api_lambda_token_auth/src/authorizer.js | 2 -- samtranslator/model/apigateway.py | 22 +++++++++++++++++ .../input/error_api_invalid_auth.yaml | 24 ++++++++++++++++++- .../output/error_api_invalid_auth.json | 2 +- 8 files changed, 63 insertions(+), 22 deletions(-) diff --git a/examples/2016-10-31/api_lambda_request_auth/README.md b/examples/2016-10-31/api_lambda_request_auth/README.md index b8a141f44e..f2832953bc 100644 --- a/examples/2016-10-31/api_lambda_request_auth/README.md +++ b/examples/2016-10-31/api_lambda_request_auth/README.md @@ -2,35 +2,35 @@ This example shows you how to create an API Gateway API with a Lambda REQUEST Authorizer using SAM. -The Authorizer Lambda Function in this example simply accepts an `Authorization` header with valid values `"allow"` and `"deny"`. See [Introducing custom authorizers in Amazon API Gateway](https://aws.amazon.com/blogs/compute/introducing-custom-authorizers-in-amazon-api-gateway/) for a more detailed example using JWT. +The Authorizer Lambda Function in this example simply accepts an `auth` query string with valid values `"allow"` and `"deny"`. See [Introducing custom authorizers in Amazon API Gateway](https://aws.amazon.com/blogs/compute/introducing-custom-authorizers-in-amazon-api-gateway/) for a more detailed example using JWT. ## Running the example Deploy the example into your account: ```bash -# Replace YOUR_S3_ARTIFACTS_BUCKET +# Replace YOUR_S3_ARTIFACTS_BUCKET with the name of a bucket which already exists in your account aws cloudformation package --template-file template.yaml --output-template-file template.packaged.yaml --s3-bucket YOUR_S3_ARTIFACTS_BUCKET aws cloudformation deploy --template-file ./template.packaged.yaml --stack-name sam-example-api-lambda-request-auth --capabilities CAPABILITY_IAM ``` -Invoke the API's root endpoint `/` without an `Authorization` header to see the API respond with a 200. In the SAM template, we explicitly state `Authorizer: NONE` to make this a public/open endpoint (the Authorizer Lambda Function is not invoked). +Invoke the API's root endpoint `/` without an `auth` query string to see the API respond with a 200. In the SAM template, we explicitly state `Authorizer: NONE` to make this a public/open endpoint (the Authorizer Lambda Function is not invoked). ```bash curl "$(aws cloudformation describe-stacks --stack-name sam-example-api-lambda-request-auth --query 'Stacks[].Outputs[?OutputKey==`ApiURL`].OutputValue' --output text)" ``` -Invoke the API's `/users` endpoint without an `Authorization` header to see a `401 {"message":"Unauthorized"}` response. Since we didn't specify an `Authorization` header, API Gateway didn't even attempt to invoke our Authorizer Lambda Function. +Invoke the API's `/users` endpoint without an `auth` query string to see a `401 {"message":"Unauthorized"}` response. Since we didn't specify an `auth` query string, API Gateway didn't even attempt to invoke our Authorizer Lambda Function. ```bash curl "$(aws cloudformation describe-stacks --stack-name sam-example-api-lambda-request-auth --query 'Stacks[].Outputs[?OutputKey==`ApiURL`].OutputValue' --output text)users" ``` -Invoke the API's `/users` endpoint with an `Authorization: allow` header to see a `200` response. Try changing the header value to `"deny"` to see a `403` response and `"gibberish"` to see a `500` response. +Invoke the API's `/users` endpoint with an `auth=allow` query string to see a `200` response. Try changing the query string value to `"deny"` to see a `403` response and `"gibberish"` to see a `500` response. ```bash -curl "$(aws cloudformation describe-stacks --stack-name sam-example-api-lambda-request-auth --query 'Stacks[].Outputs[?OutputKey==`ApiURL`].OutputValue' --output text)users" -H 'Authorization: allow' +curl "$(aws cloudformation describe-stacks --stack-name sam-example-api-lambda-request-auth --query 'Stacks[].Outputs[?OutputKey==`ApiURL`].OutputValue' --output text)users?auth=allow" ``` ## Additional resources diff --git a/examples/2016-10-31/api_lambda_request_auth/src/authorizer.js b/examples/2016-10-31/api_lambda_request_auth/src/authorizer.js index 31b072c5e6..8661771921 100644 --- a/examples/2016-10-31/api_lambda_request_auth/src/authorizer.js +++ b/examples/2016-10-31/api_lambda_request_auth/src/authorizer.js @@ -1,15 +1,16 @@ /* ** Adapted from https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-use-lambda-authorizer.html#api-gateway-lambda-authorizer-request-lambda-function-create ** A simple REQUEST authorizer example to demonstrate how to use request parameters to allow or deny a request. -** In this example, a request is authorized if the client-supplied Authorization header specifies 'allow'. +** In this example, a request is authorized if the client provided an `auth=allow` in the query string. */ exports.handler = async function (event) { - const token = event.headers.Authorization.toLowerCase() + const token = event.queryStringParameters.auth.toLowerCase() const methodArn = event.methodArn /* - ** queryStringParameters, stageVariables, requestContext and more are available on the `event` object - ** event.queryStringParameters: A map/object containing supplied in the URL's query string + ** headers, queryStringParameters, stageVariables, requestContext and more are available on the `event` object + ** event.headers: A map/object containing HTTP request headers + ** event.queryStringParameters: A map/object containing query strings supplied in the URL ** event.stageVariables: A map/object containing variables defined on the API Gateway Stage ** event.requestContext: A map/object containing additional request context */ @@ -19,8 +20,6 @@ exports.handler = async function (event) { return generateAuthResponse('user', 'Allow', methodArn) case 'deny': return generateAuthResponse('user', 'Deny', methodArn) - case 'unauthorized': - return Promise.reject('Unauthorized.') // Returns 401 Unauthorized default: return Promise.reject('Error: Invalid token') // Returns 500 Internal Server Error } diff --git a/examples/2016-10-31/api_lambda_request_auth/template.yaml b/examples/2016-10-31/api_lambda_request_auth/template.yaml index 4f96d24fe7..9a5d7e3e3d 100644 --- a/examples/2016-10-31/api_lambda_request_auth/template.yaml +++ b/examples/2016-10-31/api_lambda_request_auth/template.yaml @@ -14,14 +14,14 @@ Resources: FunctionArn: !GetAtt MyAuthFunction.Arn # FunctionInvokeRole: !Ref MyRole Identity: - Headers: - - Authorization - # QueryStrings: - # - authorization + QueryStrings: + - auth + # Headers: + # - Authorization # StageVariables: # - AUTHORIZATION # Context: - # - auth + # - authorization # ReauthorizeEvery: 100 # seconds MyFunction: diff --git a/examples/2016-10-31/api_lambda_token_auth/README.md b/examples/2016-10-31/api_lambda_token_auth/README.md index eb2923edb6..c693a2a6b5 100644 --- a/examples/2016-10-31/api_lambda_token_auth/README.md +++ b/examples/2016-10-31/api_lambda_token_auth/README.md @@ -9,7 +9,7 @@ The Authorizer Lambda Function in this example simply accepts an `Authorization` Deploy the example into your account: ```bash -# Replace YOUR_S3_ARTIFACTS_BUCKET +# Replace YOUR_S3_ARTIFACTS_BUCKET with the name of a bucket which already exists in your account aws cloudformation package --template-file template.yaml --output-template-file template.packaged.yaml --s3-bucket YOUR_S3_ARTIFACTS_BUCKET aws cloudformation deploy --template-file ./template.packaged.yaml --stack-name sam-example-api-lambda-token-auth --capabilities CAPABILITY_IAM diff --git a/examples/2016-10-31/api_lambda_token_auth/src/authorizer.js b/examples/2016-10-31/api_lambda_token_auth/src/authorizer.js index fd21c6b5e5..b5bceb1be8 100644 --- a/examples/2016-10-31/api_lambda_token_auth/src/authorizer.js +++ b/examples/2016-10-31/api_lambda_token_auth/src/authorizer.js @@ -17,8 +17,6 @@ exports.handler = async function (event) { return generateAuthResponse('user', 'Allow', methodArn) case 'deny': return generateAuthResponse('user', 'Deny', methodArn) - case 'unauthorized': - return Promise.reject('Unauthorized.') // Returns 401 Unauthorized default: return Promise.reject('Error: Invalid token') // Returns 500 Internal Server Error } diff --git a/samtranslator/model/apigateway.py b/samtranslator/model/apigateway.py index 4cb46711ca..24650fe554 100644 --- a/samtranslator/model/apigateway.py +++ b/samtranslator/model/apigateway.py @@ -98,6 +98,14 @@ def __init__(self, api_logical_id=None, name=None, user_pool_arn=None, function_ raise InvalidResourceException(api_logical_id, name + " Authorizer has invalid " "'FunctionPayloadType': " + function_payload_type) + if function_payload_type == 'REQUEST' and self._is_missing_identity_source(identity): + raise InvalidResourceException(api_logical_id, name + " Authorizer must specify Identity with at least one " + "of Headers, QueryStrings, StageVariables, or Context.") + + if function_payload_type == 'REQUEST' and not identity.get('Headers') and not identity.get('QueryStrings') and not identity.get('StageVariables') and not identity.get('Context'): + raise InvalidResourceException(api_logical_id, name + " Authorizer must specify Identity with at least one " + "of Headers, QueryStrings, StageVariables, or Context.") + self.api_logical_id = api_logical_id self.name = name self.user_pool_arn = user_pool_arn @@ -106,6 +114,20 @@ def __init__(self, api_logical_id=None, name=None, user_pool_arn=None, function_ self.function_payload_type = function_payload_type self.function_invoke_role = function_invoke_role + def _is_missing_identity_source(self, identity): + if not identity: + return True + + headers = identity.get('Headers') + query_strings = identity.get('QueryStrings') + stage_variables = identity.get('StageVariables') + context = identity.get('Context') + + if not headers and not query_strings and not stage_variables and not context: + return True + + return False + def generate_swagger(self): authorizer_type = self._get_type() APIGATEWAY_AUTHORIZER_KEY = 'x-amazon-apigateway-authorizer' diff --git a/tests/translator/input/error_api_invalid_auth.yaml b/tests/translator/input/error_api_invalid_auth.yaml index 7b102caf0b..0d8e7f3b29 100644 --- a/tests/translator/input/error_api_invalid_auth.yaml +++ b/tests/translator/input/error_api_invalid_auth.yaml @@ -163,4 +163,26 @@ Resources: Authorizers: MyLambdaAuthorizer: FunctionArn: 'arn:aws' - FunctionPayloadType: INVALID \ No newline at end of file + FunctionPayloadType: INVALID + + NoIdentityOnRequestAuthorizer: + Type: AWS::Serverless::Api + Properties: + StageName: Prod + Auth: + Authorizers: + MyLambdaRequestAuthorizer: + FunctionArn: 'arn:aws' + FunctionPayloadType: REQUEST + + NoIdentitySourceOnRequestAuthorizer: + Type: AWS::Serverless::Api + Properties: + StageName: Prod + Auth: + Authorizers: + MyLambdaRequestAuthorizer: + FunctionArn: 'arn:aws' + FunctionPayloadType: REQUEST + Identity: + ReauthorizeEvery: 10 \ No newline at end of file diff --git a/tests/translator/output/error_api_invalid_auth.json b/tests/translator/output/error_api_invalid_auth.json index 16d5ca4a75..bf11bdb6e9 100644 --- a/tests/translator/output/error_api_invalid_auth.json +++ b/tests/translator/output/error_api_invalid_auth.json @@ -1,3 +1,3 @@ { - "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 12. Resource with id [AuthNotDictApi] is invalid. Type of property 'Auth' is invalid. Resource with id [AuthWithAdditionalPropertyApi] is invalid. Invalid value for 'Auth' property Resource with id [AuthWithDefinitionUriApi] is invalid. Auth works only with inline Swagger specified in 'DefinitionBody' property Resource with id [AuthWithInvalidDefinitionBodyApi] is invalid. Unable to add Auth configuration because 'DefinitionBody' does not contain a valid Swagger Resource with id [AuthWithMissingDefaultAuthorizerApi] is invalid. Unable to set DefaultAuthorizer because 'NotThere' was not defined in 'Authorizers' Resource with id [AuthorizersNotDictApi] is invalid. Authorizers must be a dictionary Resource with id [InvalidFunctionPayloadTypeApi] is invalid. MyLambdaAuthorizer Authorizer has invalid 'FunctionPayloadType': INVALID Resource with id [MissingAuthorizerFn] is invalid. Event with id [GetRoot] is invalid. Unable to set Authorizer [UnspecifiedAuthorizer] on API method [get] for path [/] because it wasn't defined in the API's Authorizers. Resource with id [NoApiAuthorizerFn] is invalid. Event with id [GetRoot] is invalid. Unable to set Authorizer [MyAuth] on API method [get] for path [/] because the related API does not define any Authorizers. Resource with id [NoAuthFn] is invalid. Event with id [GetRoot] is invalid. Unable to set Authorizer [MyAuth] on API method [get] for path [/] because the related API does not define any Authorizers. Resource with id [NoAuthorizersFn] is invalid. Event with id [GetRoot] is invalid. Unable to set Authorizer [MyAuth] on API method [get] for path [/] because the related API does not define any Authorizers. Resource with id [NoDefaultAuthorizerWithNoneFn] is invalid. Event with id [GetRoot] is invalid. Unable to set Authorizer on API method [get] for path [/] because 'NONE' is only a valid value when a DefaultAuthorizer on the API is specified." + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 14. Resource with id [AuthNotDictApi] is invalid. Type of property 'Auth' is invalid. Resource with id [AuthWithAdditionalPropertyApi] is invalid. Invalid value for 'Auth' property Resource with id [AuthWithDefinitionUriApi] is invalid. Auth works only with inline Swagger specified in 'DefinitionBody' property Resource with id [AuthWithInvalidDefinitionBodyApi] is invalid. Unable to add Auth configuration because 'DefinitionBody' does not contain a valid Swagger Resource with id [AuthWithMissingDefaultAuthorizerApi] is invalid. Unable to set DefaultAuthorizer because 'NotThere' was not defined in 'Authorizers' Resource with id [AuthorizersNotDictApi] is invalid. Authorizers must be a dictionary Resource with id [InvalidFunctionPayloadTypeApi] is invalid. MyLambdaAuthorizer Authorizer has invalid 'FunctionPayloadType': INVALID Resource with id [MissingAuthorizerFn] is invalid. Event with id [GetRoot] is invalid. Unable to set Authorizer [UnspecifiedAuthorizer] on API method [get] for path [/] because it wasn't defined in the API's Authorizers. Resource with id [NoApiAuthorizerFn] is invalid. Event with id [GetRoot] is invalid. Unable to set Authorizer [MyAuth] on API method [get] for path [/] because the related API does not define any Authorizers. Resource with id [NoAuthFn] is invalid. Event with id [GetRoot] is invalid. Unable to set Authorizer [MyAuth] on API method [get] for path [/] because the related API does not define any Authorizers. Resource with id [NoAuthorizersFn] is invalid. Event with id [GetRoot] is invalid. Unable to set Authorizer [MyAuth] on API method [get] for path [/] because the related API does not define any Authorizers. Resource with id [NoDefaultAuthorizerWithNoneFn] is invalid. Event with id [GetRoot] is invalid. Unable to set Authorizer on API method [get] for path [/] because 'NONE' is only a valid value when a DefaultAuthorizer on the API is specified. Resource with id [NoIdentityOnRequestAuthorizer] is invalid. MyLambdaRequestAuthorizer Authorizer must specify Identity with at least one of Headers, QueryStrings, StageVariables, or Context. Resource with id [NoIdentitySourceOnRequestAuthorizer] is invalid. MyLambdaRequestAuthorizer Authorizer must specify Identity with at least one of Headers, QueryStrings, StageVariables, or Context." } \ No newline at end of file From da1867e9d63c90a550e61e41dbcdc750b8f65991 Mon Sep 17 00:00:00 2001 From: Brett Andrews Date: Fri, 24 Aug 2018 15:18:47 -0700 Subject: [PATCH 10/19] docs(examples): improve README commands for api_lambda_request_auth --- examples/2016-10-31/api_lambda_request_auth/README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/examples/2016-10-31/api_lambda_request_auth/README.md b/examples/2016-10-31/api_lambda_request_auth/README.md index f2832953bc..61b36b5ce1 100644 --- a/examples/2016-10-31/api_lambda_request_auth/README.md +++ b/examples/2016-10-31/api_lambda_request_auth/README.md @@ -18,19 +18,20 @@ aws cloudformation deploy --template-file ./template.packaged.yaml --stack-name Invoke the API's root endpoint `/` without an `auth` query string to see the API respond with a 200. In the SAM template, we explicitly state `Authorizer: NONE` to make this a public/open endpoint (the Authorizer Lambda Function is not invoked). ```bash -curl "$(aws cloudformation describe-stacks --stack-name sam-example-api-lambda-request-auth --query 'Stacks[].Outputs[?OutputKey==`ApiURL`].OutputValue' --output text)" +api_url=$(aws cloudformation describe-stacks --stack-name sam-example-api-lambda-request-auth --query 'Stacks[].Outputs[?OutputKey==`ApiURL`].OutputValue' --output text) +curl $api_url ``` Invoke the API's `/users` endpoint without an `auth` query string to see a `401 {"message":"Unauthorized"}` response. Since we didn't specify an `auth` query string, API Gateway didn't even attempt to invoke our Authorizer Lambda Function. ```bash -curl "$(aws cloudformation describe-stacks --stack-name sam-example-api-lambda-request-auth --query 'Stacks[].Outputs[?OutputKey==`ApiURL`].OutputValue' --output text)users" +curl $api_url"users" ``` Invoke the API's `/users` endpoint with an `auth=allow` query string to see a `200` response. Try changing the query string value to `"deny"` to see a `403` response and `"gibberish"` to see a `500` response. ```bash -curl "$(aws cloudformation describe-stacks --stack-name sam-example-api-lambda-request-auth --query 'Stacks[].Outputs[?OutputKey==`ApiURL`].OutputValue' --output text)users?auth=allow" +curl $api_url"users?auth=allow" ``` ## Additional resources From 3998447bbd37c4603cfd90d265cbb5cef9e2160c Mon Sep 17 00:00:00 2001 From: Brett Andrews Date: Fri, 7 Sep 2018 13:56:50 -0700 Subject: [PATCH 11/19] add API Gateway + Cognito Authorizer example This commit also includes a fix for Authorizers when using the ANY method as well as a fix for InlineCode (which this example uses) when used in combination with aws cloudformation package --- .../2016-10-31/api_cognito_auth/README.md | 93 ++++++++++++++ .../2016-10-31/api_cognito_auth/src/app.js | 77 ++++++++++++ .../2016-10-31/api_cognito_auth/src/index.js | 3 - .../2016-10-31/api_cognito_auth/src/lambda.js | 7 ++ .../api_cognito_auth/src/package.json | 14 +++ .../api_cognito_auth/src/views/index.pug | 114 ++++++++++++++++++ .../2016-10-31/api_cognito_auth/template.yaml | 96 +++++++-------- samtranslator/model/sam_resources.py | 7 +- samtranslator/swagger/swagger.py | 5 +- 9 files changed, 357 insertions(+), 59 deletions(-) create mode 100644 examples/2016-10-31/api_cognito_auth/README.md create mode 100644 examples/2016-10-31/api_cognito_auth/src/app.js delete mode 100644 examples/2016-10-31/api_cognito_auth/src/index.js create mode 100644 examples/2016-10-31/api_cognito_auth/src/lambda.js create mode 100644 examples/2016-10-31/api_cognito_auth/src/package.json create mode 100644 examples/2016-10-31/api_cognito_auth/src/views/index.pug diff --git a/examples/2016-10-31/api_cognito_auth/README.md b/examples/2016-10-31/api_cognito_auth/README.md new file mode 100644 index 0000000000..628942f545 --- /dev/null +++ b/examples/2016-10-31/api_cognito_auth/README.md @@ -0,0 +1,93 @@ +# API Gateway + Cognito Auth + Cognito Hosted Auth Example + +This example shows you how to create an API Gateway API with a Cognito Authorizer using SAM. + +## Running the example + +Install the Node.js/NPM dependencies for your API's Lambda logic. This is necessary so that the dependencies get packaged up along with your Lambda function. + +```bash +cd src && npm install && cd .. +``` + +Deploy the example into your account: + +```bash +STACK_NAME=sam-example-api-cognito-auth +S3_BUCKET_NAME=YOUR_S3_ARTIFACTS_BUCKET + +aws cloudformation package \ +--template-file template.yaml \ +--output-template-file template.packaged.yaml \ +--s3-bucket $S3_BUCKET_NAME + +aws cloudformation deploy \ +--template-file ./template.packaged.yaml \ +--stack-name $STACK_NAME \ +--capabilities CAPABILITY_IAM +``` + +# TODO: remove +# bin/sam-translate.py deploy --template-file=examples/2016-10-31/api_cognito_auth/template.yaml --s3-bucket $S3_BUCKET_NAME --capabilities CAPABILITY_NAMED_IAM --stack-name $STACK_NAME + +Get the Cognito User Pool Id and Api Url from Stack Outputs. + +```bash +COGNITO_USER_POOL_ID=$(aws cloudformation describe-stacks \ +--stack-name sam-example-api-cognito-auth \ +--query 'Stacks[].Outputs[?OutputKey==`CognitoUserPoolId`].OutputValue' \ +--output text) + +API_URL=$(aws cloudformation describe-stacks \ +--stack-name sam-example-api-cognito-auth \ +--query 'Stacks[].Outputs[?OutputKey==`ApiUrl`].OutputValue' \ +--output text) +``` + +Cognito User Pools doesn't currently have CloudFormation support for configuring their Hosted Register/Signin UI. For now we will create these via the AWS CLI. + +```bash +COGNITO_USER_POOL_CLIENT_NAME="ui-client" +COGNITO_USER_POOL_CALLBACK_URL=$API_URL +COGNITO_USER_POOL_LOGOUT_URL=$API_URL + +COGNITO_USER_POOL_CLIENT_ID=$(aws cognito-idp create-user-pool-client \ +--user-pool-id $COGNITO_USER_POOL_ID \ +--client-name $COGNITO_USER_POOL_CLIENT_NAME \ +--supported-identity-providers COGNITO \ +--callback-urls "[\"$COGNITO_USER_POOL_CALLBACK_URL\"]" \ +--logout-urls "[\"$COGNITO_USER_POOL_LOGOUT_URL\"]" \ +--allowed-o-auth-flows code implicit \ +--allowed-o-auth-scopes openid email \ +--allowed-o-auth-flows-user-pool-client \ +--query 'UserPoolClient.ClientId' \ +--output text) + +COGNITO_USER_POOL_TIMESTAMP_SUFFIX=$(date +%s) +COGNITO_USER_POOL_DOMAIN_PREFIX="my-company-$COGNITO_USER_POOL_TIMESTAMP_SUFFIX" + +aws cognito-idp create-user-pool-domain \ +--domain $COGNITO_USER_POOL_DOMAIN_PREFIX \ +--user-pool-id $COGNITO_USER_POOL_ID +``` + +Finally, let's open the registration page created and hosted for you by Cognito in your browser. After the page loads, enter a Username and Password and click the Sign Up button. + +```bash +open https://$COGNITO_USER_POOL_DOMAIN_PREFIX.auth.us-east-1.amazoncognito.com/signup?response_type=token&client_id=$COGNITO_USER_POOL_CLIENT_ID&redirect_uri=$COGNITO_USER_POOL_CALLBACK_URL + +# The login page can be accessed here +# open https://$COGNITO_USER_POOL_DOMAIN_PREFIX.auth.us-east-1.amazoncognito.com/login?response_type=token&client_id=$COGNITO_USER_POOL_CLIENT_ID&redirect_uri=$COGNITO_USER_POOL_CALLBACK_URL +``` + +After clicking Sign Up, you will be redirected to the UI client for your API. + +To access the API UI directly as an unauthorized user (who has access to `GET /users` and `GET /users/{userId}`) you can run `open $API_URL`. + +## Additional resources + +- https://aws.amazon.com/blogs/aws/launch-amazon-cognito-user-pools-general-availability-app-integration-and-federation/ +- https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-integrate-with-cognito.html +- https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-app-idp-settings.html +- https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-invoke-api-integrated-with-cognito-user-pool.html +- https://docs.aws.amazon.com/cognito/latest/developerguide/token-endpoint.html \ No newline at end of file diff --git a/examples/2016-10-31/api_cognito_auth/src/app.js b/examples/2016-10-31/api_cognito_auth/src/app.js new file mode 100644 index 0000000000..875dabb062 --- /dev/null +++ b/examples/2016-10-31/api_cognito_auth/src/app.js @@ -0,0 +1,77 @@ +'use strict' +const express = require('express') +const bodyParser = require('body-parser') +const cors = require('cors') +const awsServerlessExpressMiddleware = require('aws-serverless-express/middleware') +const app = express() +const router = express.Router() + +app.set('view engine', 'pug') + +router.use(cors()) +router.use(bodyParser.json()) +router.use(bodyParser.urlencoded({ extended: true })) +router.use(awsServerlessExpressMiddleware.eventContext()) + +router.get('/', (req, res) => { + res.render('index', { + apiUrl: req.apiGateway ? `https://${req.apiGateway.event.headers.Host}/${req.apiGateway.event.requestContext.stage}` : 'http://localhost:3000' + }) +}) + +router.get('/users', (req, res) => { + res.json(users) +}) + +router.get('/users/:userId', (req, res) => { + const user = getUser(req.params.userId) + + if (!user) return res.status(404).json({}) + + return res.json(user) +}) + +router.post('/users', (req, res) => { + const user = { + id: ++userIdCounter, + name: req.body.name + } + users.push(user) + res.status(201).json(user) +}) + +router.put('/users/:userId', (req, res) => { + const user = getUser(req.params.userId) + + if (!user) return res.status(404).json({}) + + user.name = req.body.name + res.json(user) +}) + +router.delete('/users/:userId', (req, res) => { + const userIndex = getUserIndex(req.params.userId) + + if (userIndex === -1) return res.status(404).json({}) + + users.splice(userIndex, 1) + res.json(users) +}) + +const getUser = (userId) => users.find(u => u.id === parseInt(userId)) +const getUserIndex = (userId) => users.findIndex(u => u.id === parseInt(userId)) + +// Ephemeral in-memory data store +const users = [{ + id: 1, + name: 'Joe' +}, { + id: 2, + name: 'Jane' +}] +let userIdCounter = users.length + +app.use('/', router) + +// Export your express server so you can import it in the lambda function. +module.exports = app diff --git a/examples/2016-10-31/api_cognito_auth/src/index.js b/examples/2016-10-31/api_cognito_auth/src/index.js deleted file mode 100644 index fd69bd0267..0000000000 --- a/examples/2016-10-31/api_cognito_auth/src/index.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = async (event) => { - return event -} \ No newline at end of file diff --git a/examples/2016-10-31/api_cognito_auth/src/lambda.js b/examples/2016-10-31/api_cognito_auth/src/lambda.js new file mode 100644 index 0000000000..4f82597e70 --- /dev/null +++ b/examples/2016-10-31/api_cognito_auth/src/lambda.js @@ -0,0 +1,7 @@ +'use strict' +const awsServerlessExpress = require('aws-serverless-express') +const app = require('./app') + +const server = awsServerlessExpress.createServer(app) + +exports.handler = (event, context) => awsServerlessExpress.proxy(server, event, context) diff --git a/examples/2016-10-31/api_cognito_auth/src/package.json b/examples/2016-10-31/api_cognito_auth/src/package.json new file mode 100644 index 0000000000..2286492244 --- /dev/null +++ b/examples/2016-10-31/api_cognito_auth/src/package.json @@ -0,0 +1,14 @@ +{ + "name": "api_cognito_auth", + "version": "1.0.0", + "description": "Example using API Gateway with Cognito Authorizer.", + "main": "lambda.js", + "license": "Apache-2.0", + "dependencies": { + "aws-serverless-express": "^3.3.3", + "body-parser": "^1.17.1", + "cors": "^2.8.3", + "express": "^4.15.2", + "pug": "^2.0.0-rc.1" + } +} diff --git a/examples/2016-10-31/api_cognito_auth/src/views/index.pug b/examples/2016-10-31/api_cognito_auth/src/views/index.pug new file mode 100644 index 0000000000..7e7de5e773 --- /dev/null +++ b/examples/2016-10-31/api_cognito_auth/src/views/index.pug @@ -0,0 +1,114 @@ +doctype html +html + head + title My Serverless Application + style. + body { + width: 650px; + margin: auto; + } + h1 { + text-align: center; + } + .response > code { + display: block; + background-color: #eff0f1; + color: #393318; + padding: 5px; + font-family: Consolas,Menlo,Monaco,Lucida Console,Liberation Mono,DejaVu Sans Mono,Bitstream Vera Sans Mono,Courier New,monospace,sans-serif; + white-space: pre; + overflow-x: auto; + } + form { + margin-bottom: 1rem; + } + .form-group { + padding-bottom: 1rem; + } + label { + display: block; + } + body + h1 My Serverless Application + p + | Public endpoints: GET /, GET /users, GET /users/{userId} + p + | Authorized endpoints: POST /users, PUT /users/{userId}, DELETE /users/{userId} + + section.form + h2 Invoke API + p Experiment with the `users` resource with the form below. + form + div.form-group + label(for='methodField') Method + select(name='method' id='methodField') + option(value='GET') GET + option(value='POST') POST + option(value='PUT') PUT + option(value='DELETE') DELETE + div.form-group + label(for='idField') user id + input(type='text' name='id' id='idField') + div.form-group + label(for='nameField') name + input(type='text' name='name' id='nameField') + input(type='submit') + + section + h2 Response + p.request + span.request__method GET + span   + spand.request__endpoint /users + section.response + code + + + script. + const cognitoIdentityTokenMatch = document.location.hash.match(/id_token=(.*)&access_token=/) + const cognitoIdentityToken = cognitoIdentityTokenMatch ? cognitoIdentityTokenMatch[1] : null + const form = document.querySelector('form') + + form.addEventListener('submit', function(event) { + event.preventDefault() + const method = document.getElementById('methodField').value + const id = document.getElementById('idField').value + const name = document.getElementById('nameField').value + const endpoint = id ? 'users/' + id : 'users' + const body = ['POST', 'PUT'].includes(method) ? JSON.stringify({ name: name }) : undefined + const headers = { + 'content-type': 'application/json', + 'Authorization': cognitoIdentityToken + } + + document.querySelector('.request__method').innerText = method + document.querySelector('.request__endpoint').innerText = `/${endpoint}` + + return fetch(endpoint, { + method, + headers, + body + }) + .then(setResponseText) + .catch(setResponseText) + }) + + function setResponseText(response) { + if (response.headers.get('content-type').contains('application/json')) { + return response.json().then(json => { + document.querySelector('code').innerText = JSON.stringify(json, null, 4) + }) + } + + return response.text().then(text => { + document.querySelector('code').innerText = text + }) + } + + if (!window.fetch) { + alert('Your browser does not have fetch support, which this demo uses. Try again in a modern browser (https://caniuse.com/fetch) or modify the example to use an alternate approach (e.g. XMLHttpRequest or your library of choice.)') + } + + fetch('users') + .then(setResponseText) + .catch(setResponseText) diff --git a/examples/2016-10-31/api_cognito_auth/template.yaml b/examples/2016-10-31/api_cognito_auth/template.yaml index 8ebf70cbbf..801355a3d8 100644 --- a/examples/2016-10-31/api_cognito_auth/template.yaml +++ b/examples/2016-10-31/api_cognito_auth/template.yaml @@ -1,97 +1,87 @@ AWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 Description: API Gateway + Cognito User Pools Auth + +Parameters: + UserPoolName: + Type: String + Default: MyUserPool + Resources: MyApi: Type: AWS::Serverless::Api Properties: - StageName: TestStage + StageName: Prod + Cors: "'*'" Auth: - DefaultAuthorizer: MyLambdaAuthorizer + DefaultAuthorizer: MyCognitoAuthorizer Authorizers: MyCognitoAuthorizer: UserPoolArn: !GetAtt MyCognitoUserPool.Arn - MyLambdaAuthorizer: - FunctionArn: !GetAtt MyAuthFunction.Arn - FunctionInvokeRole: NONE - Identity: # Optional - Header: Authn # Optional; Default: Authorization - ValidationExpression: myexpresso # Optional - ReauthorizeEvery: 33 - - MyLambdaRequestAuthorizer: - FunctionPayloadType: REQUEST - FunctionArn: !GetAtt MyAuthFunction.Arn - FunctionInvokeRole: !Sub arn:aws:iam::${AWS::AccountId}:role/admin - Identity: - Headers: - - Authorization1 - QueryStrings: - - Authorization2 - StageVariables: - - Authorization3 - Context: - - Authorization4 - ReauthorizeEvery: 100 - MyFunction: Type: AWS::Serverless::Function Properties: CodeUri: ./src - Handler: index.handler + Handler: lambda.handler + MemorySize: 1024 Runtime: nodejs8.10 Events: - WithNoAuthorizer: + Root: Type: Api Properties: RestApiId: !Ref MyApi Path: / - Method: get + Method: GET Auth: Authorizer: NONE - WithLambdaTokenAuthorizer: + ProxyAny: Type: Api Properties: RestApiId: !Ref MyApi - Path: /users - Method: get + Path: /{proxy+} + Method: ANY Auth: - Authorizer: MyLambdaAuthorizer - WithCognitoAuthorizer: + Authorizer: NONE + GetUsers: Type: Api Properties: RestApiId: !Ref MyApi Path: /users - Method: post + Method: GET Auth: - Authorizer: MyCognitoAuthorizer - WithLambdaRequestAuthorizer: + Authorizer: NONE + GetUser: Type: Api Properties: RestApiId: !Ref MyApi - Path: /users - Method: delete + Path: /users/{userId} + Method: GET Auth: - Authorizer: MyLambdaRequestAuthorizer - WithDefaultAuthorizer: + Authorizer: NONE + CreateUser: Type: Api Properties: RestApiId: !Ref MyApi Path: /users - Method: put - - MyAuthFunction: - Type: AWS::Serverless::Function - Properties: - CodeUri: ./src - Handler: index.handler - Runtime: nodejs8.10 + Method: POST + DeleteUser: + Type: Api + Properties: + RestApiId: !Ref MyApi + Path: /users/{userId} + Method: DELETE + UpdateUser: + Type: Api + Properties: + RestApiId: !Ref MyApi + Path: /users/{userId} + Method: PUT MyCognitoUserPool: Type: AWS::Cognito::UserPool Properties: - UserPoolName: MyUserPool + UserPoolName: !Ref UserPoolName LambdaConfig: PreSignUp: !GetAtt PreSignupLambdaFunction.Arn Policies: @@ -130,6 +120,10 @@ Resources: SourceArn: !Sub 'arn:aws:cognito-idp:${AWS::Region}:${AWS::AccountId}:userpool/${MyCognitoUserPool}' Outputs: - ApiURL: + ApiUrl: Description: "API endpoint URL for Prod environment" - Value: !Sub 'https://${MyApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/' \ No newline at end of file + Value: !Sub 'https://${MyApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/' + + CognitoUserPoolId: + Description: "Cognito User Pool Id" + Value: !Ref MyCognitoUserPool \ No newline at end of file diff --git a/samtranslator/model/sam_resources.py b/samtranslator/model/sam_resources.py index 7e4588cdae..1eb631aa7d 100644 --- a/samtranslator/model/sam_resources.py +++ b/samtranslator/model/sam_resources.py @@ -315,12 +315,12 @@ def _generate_event_resources(self, lambda_function, execution_role, event_resou return resources def _construct_code_dict(self): - if self.CodeUri: - return self._construct_code_dict_code_uri() - elif self.InlineCode: + if self.InlineCode: return { "ZipFile": self.InlineCode } + elif self.CodeUri: + return self._construct_code_dict_code_uri() else: raise InvalidResourceException(self.logical_id, "Either 'InlineCode' or 'CodeUri' must be set") @@ -346,6 +346,7 @@ def _construct_code_dict_code_uri(self): s3_pointer = parse_s3_uri(self.CodeUri) if s3_pointer is None: + print('s3_pointer here', s3_pointer) raise InvalidResourceException(self.logical_id, '\'CodeUri\' is not a valid S3 Uri of the form ' '"s3://bucket/key" with optional versionId query parameter.') diff --git a/samtranslator/swagger/swagger.py b/samtranslator/swagger/swagger.py index 3f5581022f..17c97b3252 100644 --- a/samtranslator/swagger/swagger.py +++ b/samtranslator/swagger/swagger.py @@ -331,7 +331,8 @@ def add_auth_to_method(self, path, method_name, auth, api): def set_method_authorizer(self, path, method_name, authorizer_name, authorizers, default_authorizer, is_default=False): - existing_security = self.paths[path][method_name].get('security', []) # TEST: [{'sigv4': []}, {'api_key': []}]) + normalized_method_name = self._normalize_method_name(method_name) + existing_security = self.paths[path][normalized_method_name].get('security', []) # TEST: [{'sigv4': []}, {'api_key': []}]) authorizer_names = set(authorizers.keys()) existing_non_authorizer_security = [] existing_authorizer_security = [] @@ -385,7 +386,7 @@ def set_method_authorizer(self, path, method_name, authorizer_name, authorizers, security = existing_non_authorizer_security + authorizer_security if security: - self.paths[path][method_name]['security'] = security + self.paths[path][normalized_method_name]['security'] = security @property def swagger(self): From 60671ccde9ba8e0e5d37f73c87d1ff84856b17e3 Mon Sep 17 00:00:00 2001 From: Brett Andrews Date: Tue, 11 Sep 2018 15:16:47 -0700 Subject: [PATCH 12/19] simplify setup by using npm scripts --- .../2016-10-31/api_cognito_auth/README.md | 72 ++++--------------- .../2016-10-31/api_cognito_auth/package.json | 31 ++++++++ .../2016-10-31/api_cognito_auth/src/app.js | 1 + .../api_cognito_auth/src/package.json | 14 ---- .../api_cognito_auth/src/views/index.pug | 6 +- .../2016-10-31/api_cognito_auth/template.yaml | 8 ++- 6 files changed, 54 insertions(+), 78 deletions(-) create mode 100644 examples/2016-10-31/api_cognito_auth/package.json delete mode 100644 examples/2016-10-31/api_cognito_auth/src/package.json diff --git a/examples/2016-10-31/api_cognito_auth/README.md b/examples/2016-10-31/api_cognito_auth/README.md index 628942f545..5eae27aa22 100644 --- a/examples/2016-10-31/api_cognito_auth/README.md +++ b/examples/2016-10-31/api_cognito_auth/README.md @@ -4,85 +4,37 @@ This example shows you how to create an API Gateway API with a Cognito Authorize ## Running the example -Install the Node.js/NPM dependencies for your API's Lambda logic. This is necessary so that the dependencies get packaged up along with your Lambda function. +Install the Node.js/NPM dependencies for your API's Lambda logic into the `src/` directory. This is necessary so that the dependencies get packaged up along with your Lambda function. ```bash -cd src && npm install && cd .. +npm install . --prefix ./src ``` -Deploy the example into your account: +Deploy the example into your account (replace `YOUR_S3_ARTIFACTS_BUCKET` with an existing S3 bucket to store your app assets): ```bash -STACK_NAME=sam-example-api-cognito-auth -S3_BUCKET_NAME=YOUR_S3_ARTIFACTS_BUCKET - -aws cloudformation package \ ---template-file template.yaml \ ---output-template-file template.packaged.yaml \ ---s3-bucket $S3_BUCKET_NAME - -aws cloudformation deploy \ ---template-file ./template.packaged.yaml \ ---stack-name $STACK_NAME \ ---capabilities CAPABILITY_IAM +# The following default values are also allowed: STACK_NAME, COGNITO_USER_POOL_CLIENT_NAME, COGNITO_USER_POOL_DOMAIN_PREFIX +S3_BUCKET_NAME=YOUR_S3_ARTIFACTS_BUCKET \ +npm run package-deploy ``` -# TODO: remove -# bin/sam-translate.py deploy --template-file=examples/2016-10-31/api_cognito_auth/template.yaml --s3-bucket $S3_BUCKET_NAME --capabilities CAPABILITY_NAMED_IAM --stack-name $STACK_NAME - -Get the Cognito User Pool Id and Api Url from Stack Outputs. +Cognito User Pools doesn't currently have CloudFormation support for configuring their Hosted Register/Signin UI. For now we will create these via the AWS CLI: ```bash -COGNITO_USER_POOL_ID=$(aws cloudformation describe-stacks \ ---stack-name sam-example-api-cognito-auth \ ---query 'Stacks[].Outputs[?OutputKey==`CognitoUserPoolId`].OutputValue' \ ---output text) - -API_URL=$(aws cloudformation describe-stacks \ ---stack-name sam-example-api-cognito-auth \ ---query 'Stacks[].Outputs[?OutputKey==`ApiUrl`].OutputValue' \ ---output text) -``` - -Cognito User Pools doesn't currently have CloudFormation support for configuring their Hosted Register/Signin UI. For now we will create these via the AWS CLI. - -```bash -COGNITO_USER_POOL_CLIENT_NAME="ui-client" -COGNITO_USER_POOL_CALLBACK_URL=$API_URL -COGNITO_USER_POOL_LOGOUT_URL=$API_URL - -COGNITO_USER_POOL_CLIENT_ID=$(aws cognito-idp create-user-pool-client \ ---user-pool-id $COGNITO_USER_POOL_ID \ ---client-name $COGNITO_USER_POOL_CLIENT_NAME \ ---supported-identity-providers COGNITO \ ---callback-urls "[\"$COGNITO_USER_POOL_CALLBACK_URL\"]" \ ---logout-urls "[\"$COGNITO_USER_POOL_LOGOUT_URL\"]" \ ---allowed-o-auth-flows code implicit \ ---allowed-o-auth-scopes openid email \ ---allowed-o-auth-flows-user-pool-client \ ---query 'UserPoolClient.ClientId' \ ---output text) - -COGNITO_USER_POOL_TIMESTAMP_SUFFIX=$(date +%s) -COGNITO_USER_POOL_DOMAIN_PREFIX="my-company-$COGNITO_USER_POOL_TIMESTAMP_SUFFIX" - -aws cognito-idp create-user-pool-domain \ ---domain $COGNITO_USER_POOL_DOMAIN_PREFIX \ ---user-pool-id $COGNITO_USER_POOL_ID +npm run configure-cognito-user-pool ``` -Finally, let's open the registration page created and hosted for you by Cognito in your browser. After the page loads, enter a Username and Password and click the Sign Up button. +Open the registration page created and hosted for you by Cognito in your browser. After the page loads, enter a Username and Password and click the Sign Up button. ```bash -open https://$COGNITO_USER_POOL_DOMAIN_PREFIX.auth.us-east-1.amazoncognito.com/signup?response_type=token&client_id=$COGNITO_USER_POOL_CLIENT_ID&redirect_uri=$COGNITO_USER_POOL_CALLBACK_URL +npm run open-signup-page -# The login page can be accessed here -# open https://$COGNITO_USER_POOL_DOMAIN_PREFIX.auth.us-east-1.amazoncognito.com/login?response_type=token&client_id=$COGNITO_USER_POOL_CLIENT_ID&redirect_uri=$COGNITO_USER_POOL_CALLBACK_URL +# Alternatively, you can open the login page by running `npm run open-login-page` ``` After clicking Sign Up, you will be redirected to the UI client for your API. -To access the API UI directly as an unauthorized user (who has access to `GET /users` and `GET /users/{userId}`) you can run `open $API_URL`. +To access the API UI directly as an unauthorized user (who has access to `GET /users` and `GET /users/{userId}`) you can run `npm run open-api-ui`. ## Additional resources diff --git a/examples/2016-10-31/api_cognito_auth/package.json b/examples/2016-10-31/api_cognito_auth/package.json new file mode 100644 index 0000000000..74d4d8a32a --- /dev/null +++ b/examples/2016-10-31/api_cognito_auth/package.json @@ -0,0 +1,31 @@ +{ + "name": "api_cognito_auth", + "version": "1.0.0", + "description": "Example using API Gateway with Cognito Authorizer.", + "main": "lambda.js", + "license": "Apache-2.0", + "dependencies": { + "aws-serverless-express": "^3.3.3", + "body-parser": "^1.17.1", + "cors": "^2.8.3", + "express": "^4.15.2", + "pug": "^2.0.0-rc.1" + }, + "scripts": { + "package-deploy": "npm run set-config && npm run package && npm run deploy", + "set-config": "npm config set STACK_NAME ${STACK_NAME:-sam-example-api-cognito-auth} && npm config set COGNITO_USER_POOL_CLIENT_NAME ${COGNITO_USER_POOL_CLIENT_NAME:-ui-client}", + "xpackage": "aws cloudformation package --template-file template.yaml --output-template-file template.packaged.yaml --s3-bucket $S3_BUCKET_NAME", + "xdeploy": "aws cloudformation deploy --template-file ./template.packaged.yaml --stack-name $STACK_NAME --capabilities CAPABILITY_IAM", + "package": "../../../bin/sam-translate.py package --template-file=template.yaml --s3-bucket artifacts-123nsef", + "deploy": "aws cloudformation deploy --template-file ./transformed-template.json --stack-name $(npm config get STACK_NAME) --capabilities CAPABILITY_IAM", + "configure-cognito-user-pool": "npm run set-cognito-user-pool-id && npm run set-api-id && npm run set-api-url && npm run create-user-pool-client && npm run create-user-pool-domain", + "set-cognito-user-pool-id": "npm config set COGNITO_USER_POOL_ID $(aws cloudformation describe-stacks --stack-name $(npm config get STACK_NAME) --query 'Stacks[].Outputs[?OutputKey==`CognitoUserPoolId`].OutputValue' --output text)", + "set-api-url": "npm config set API_URL $(aws cloudformation describe-stacks --stack-name sam-example-api-cognito-auth --query 'Stacks[].Outputs[?OutputKey==`ApiUrl`].OutputValue' --output text)", + "set-api-id": "npm config set API_ID $(aws cloudformation describe-stacks --stack-name sam-example-api-cognito-auth --query 'Stacks[].Outputs[?OutputKey==`ApiId`].OutputValue' --output text)", + "create-user-pool-client": "npm config set COGNITO_USER_POOL_CLIENT_ID $(aws cognito-idp create-user-pool-client --user-pool-id $(npm config get COGNITO_USER_POOL_ID) --client-name $(npm config get COGNITO_USER_POOL_CLIENT_NAME) --supported-identity-providers COGNITO --callback-urls \"[\\\"$(npm config get API_URL)\\\"]\" --allowed-o-auth-flows code implicit --allowed-o-auth-scopes openid email --allowed-o-auth-flows-user-pool-client --query 'UserPoolClient.ClientId' --output text)", + "create-user-pool-domain": "aws cognito-idp create-user-pool-domain --domain $(npm config get API_ID) --user-pool-id $(npm config get COGNITO_USER_POOL_ID)", + "open-signup-page": "open \"https://$(npm config get API_ID).auth.us-east-1.amazoncognito.com/signup?response_type=code&client_id=$(npm config get COGNITO_USER_POOL_CLIENT_ID)&redirect_uri=$(npm config get API_URL)\"", + "open-login-page": "open \"https://$(npm config get API_ID).auth.us-east-1.amazoncognito.com/login?response_type=code&client_id=$(npm config get COGNITO_USER_POOL_CLIENT_ID)&redirect_uri=$(npm config get API_URL)\"", + "open-api-ui": "open \"$(npm config get API_URL)\"" + } +} diff --git a/examples/2016-10-31/api_cognito_auth/src/app.js b/examples/2016-10-31/api_cognito_auth/src/app.js index 875dabb062..5fd292592b 100644 --- a/examples/2016-10-31/api_cognito_auth/src/app.js +++ b/examples/2016-10-31/api_cognito_auth/src/app.js @@ -15,6 +15,7 @@ router.use(awsServerlessExpressMiddleware.eventContext()) router.get('/', (req, res) => { res.render('index', { + apiId: req.apiGateway ? req.apiGateway.event.requestContext.apiId : null, apiUrl: req.apiGateway ? `https://${req.apiGateway.event.headers.Host}/${req.apiGateway.event.requestContext.stage}` : 'http://localhost:3000' }) }) diff --git a/examples/2016-10-31/api_cognito_auth/src/package.json b/examples/2016-10-31/api_cognito_auth/src/package.json deleted file mode 100644 index 2286492244..0000000000 --- a/examples/2016-10-31/api_cognito_auth/src/package.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "name": "api_cognito_auth", - "version": "1.0.0", - "description": "Example using API Gateway with Cognito Authorizer.", - "main": "lambda.js", - "license": "Apache-2.0", - "dependencies": { - "aws-serverless-express": "^3.3.3", - "body-parser": "^1.17.1", - "cors": "^2.8.3", - "express": "^4.15.2", - "pug": "^2.0.0-rc.1" - } -} diff --git a/examples/2016-10-31/api_cognito_auth/src/views/index.pug b/examples/2016-10-31/api_cognito_auth/src/views/index.pug index 7e7de5e773..30b2fbbb1a 100644 --- a/examples/2016-10-31/api_cognito_auth/src/views/index.pug +++ b/examples/2016-10-31/api_cognito_auth/src/views/index.pug @@ -62,9 +62,10 @@ html spand.request__endpoint /users section.response code - script. + + const apiId = "#{apiId}" const cognitoIdentityTokenMatch = document.location.hash.match(/id_token=(.*)&access_token=/) const cognitoIdentityToken = cognitoIdentityTokenMatch ? cognitoIdentityTokenMatch[1] : null const form = document.querySelector('form') @@ -94,7 +95,8 @@ html }) function setResponseText(response) { - if (response.headers.get('content-type').contains('application/json')) { + const contentType = response.headers.get('content-type') + if (contentType.includes('application/json')) { return response.json().then(json => { document.querySelector('code').innerText = JSON.stringify(json, null, 4) }) diff --git a/examples/2016-10-31/api_cognito_auth/template.yaml b/examples/2016-10-31/api_cognito_auth/template.yaml index 801355a3d8..effb5444ae 100644 --- a/examples/2016-10-31/api_cognito_auth/template.yaml +++ b/examples/2016-10-31/api_cognito_auth/template.yaml @@ -3,7 +3,7 @@ Transform: AWS::Serverless-2016-10-31 Description: API Gateway + Cognito User Pools Auth Parameters: - UserPoolName: + CognitoUserPoolName: Type: String Default: MyUserPool @@ -81,7 +81,7 @@ Resources: MyCognitoUserPool: Type: AWS::Cognito::UserPool Properties: - UserPoolName: !Ref UserPoolName + UserPoolName: !Ref CognitoUserPoolName LambdaConfig: PreSignUp: !GetAtt PreSignupLambdaFunction.Arn Policies: @@ -120,6 +120,10 @@ Resources: SourceArn: !Sub 'arn:aws:cognito-idp:${AWS::Region}:${AWS::AccountId}:userpool/${MyCognitoUserPool}' Outputs: + ApiId: + Description: "API ID" + Value: !Ref MyApi + ApiUrl: Description: "API endpoint URL for Prod environment" Value: !Sub 'https://${MyApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/' From e9d6c25b9464cdf5c5261afa4d124963ea324e75 Mon Sep 17 00:00:00 2001 From: Brett Andrews Date: Thu, 13 Sep 2018 12:05:03 -0700 Subject: [PATCH 13/19] update to use authorization_code flow --- .../2016-10-31/api_cognito_auth/package.json | 13 ++-- .../2016-10-31/api_cognito_auth/src/app.js | 3 +- .../api_cognito_auth/src/views/index.pug | 71 +++++++++++++++---- .../2016-10-31/api_cognito_auth/template.yaml | 34 +++++++-- 4 files changed, 92 insertions(+), 29 deletions(-) diff --git a/examples/2016-10-31/api_cognito_auth/package.json b/examples/2016-10-31/api_cognito_auth/package.json index 74d4d8a32a..997222c19a 100644 --- a/examples/2016-10-31/api_cognito_auth/package.json +++ b/examples/2016-10-31/api_cognito_auth/package.json @@ -13,16 +13,15 @@ }, "scripts": { "package-deploy": "npm run set-config && npm run package && npm run deploy", - "set-config": "npm config set STACK_NAME ${STACK_NAME:-sam-example-api-cognito-auth} && npm config set COGNITO_USER_POOL_CLIENT_NAME ${COGNITO_USER_POOL_CLIENT_NAME:-ui-client}", - "xpackage": "aws cloudformation package --template-file template.yaml --output-template-file template.packaged.yaml --s3-bucket $S3_BUCKET_NAME", - "xdeploy": "aws cloudformation deploy --template-file ./template.packaged.yaml --stack-name $STACK_NAME --capabilities CAPABILITY_IAM", - "package": "../../../bin/sam-translate.py package --template-file=template.yaml --s3-bucket artifacts-123nsef", - "deploy": "aws cloudformation deploy --template-file ./transformed-template.json --stack-name $(npm config get STACK_NAME) --capabilities CAPABILITY_IAM", - "configure-cognito-user-pool": "npm run set-cognito-user-pool-id && npm run set-api-id && npm run set-api-url && npm run create-user-pool-client && npm run create-user-pool-domain", + "set-config": "npm config set STACK_NAME ${STACK_NAME:-sam-example-api-cognito-auth}", + "package": "aws cloudformation package --template-file template.yaml --output-template-file template.packaged.yaml --s3-bucket $S3_BUCKET_NAME", + "deploy": "aws cloudformation deploy --template-file ./template.packaged.yaml --stack-name $STACK_NAME --capabilities CAPABILITY_IAM", + "configure-cognito-user-pool": "npm run set-cognito-user-pool-id && npm run set-cognito-user-pool-client-id && npm run set-api-id && npm run set-api-url && npm run update-user-pool-client && npm run create-user-pool-domain", "set-cognito-user-pool-id": "npm config set COGNITO_USER_POOL_ID $(aws cloudformation describe-stacks --stack-name $(npm config get STACK_NAME) --query 'Stacks[].Outputs[?OutputKey==`CognitoUserPoolId`].OutputValue' --output text)", + "set-cognito-user-pool-client-id": "npm config set COGNITO_USER_POOL_CLIENT_ID $(aws cloudformation describe-stacks --stack-name $(npm config get STACK_NAME) --query 'Stacks[].Outputs[?OutputKey==`CognitoUserPoolClientId`].OutputValue' --output text)", "set-api-url": "npm config set API_URL $(aws cloudformation describe-stacks --stack-name sam-example-api-cognito-auth --query 'Stacks[].Outputs[?OutputKey==`ApiUrl`].OutputValue' --output text)", "set-api-id": "npm config set API_ID $(aws cloudformation describe-stacks --stack-name sam-example-api-cognito-auth --query 'Stacks[].Outputs[?OutputKey==`ApiId`].OutputValue' --output text)", - "create-user-pool-client": "npm config set COGNITO_USER_POOL_CLIENT_ID $(aws cognito-idp create-user-pool-client --user-pool-id $(npm config get COGNITO_USER_POOL_ID) --client-name $(npm config get COGNITO_USER_POOL_CLIENT_NAME) --supported-identity-providers COGNITO --callback-urls \"[\\\"$(npm config get API_URL)\\\"]\" --allowed-o-auth-flows code implicit --allowed-o-auth-scopes openid email --allowed-o-auth-flows-user-pool-client --query 'UserPoolClient.ClientId' --output text)", + "update-user-pool-client": "aws cognito-idp update-user-pool-client --user-pool-id $(npm config get COGNITO_USER_POOL_ID) --client-id $(npm config get COGNITO_USER_POOL_CLIENT_ID) --supported-identity-providers COGNITO --callback-urls \"[\\\"$(npm config get API_URL)\\\"]\" --allowed-o-auth-flows code implicit --allowed-o-auth-scopes openid email --allowed-o-auth-flows-user-pool-client", "create-user-pool-domain": "aws cognito-idp create-user-pool-domain --domain $(npm config get API_ID) --user-pool-id $(npm config get COGNITO_USER_POOL_ID)", "open-signup-page": "open \"https://$(npm config get API_ID).auth.us-east-1.amazoncognito.com/signup?response_type=code&client_id=$(npm config get COGNITO_USER_POOL_CLIENT_ID)&redirect_uri=$(npm config get API_URL)\"", "open-login-page": "open \"https://$(npm config get API_ID).auth.us-east-1.amazoncognito.com/login?response_type=code&client_id=$(npm config get COGNITO_USER_POOL_CLIENT_ID)&redirect_uri=$(npm config get API_URL)\"", diff --git a/examples/2016-10-31/api_cognito_auth/src/app.js b/examples/2016-10-31/api_cognito_auth/src/app.js index 5fd292592b..b37b1497b2 100644 --- a/examples/2016-10-31/api_cognito_auth/src/app.js +++ b/examples/2016-10-31/api_cognito_auth/src/app.js @@ -16,7 +16,8 @@ router.use(awsServerlessExpressMiddleware.eventContext()) router.get('/', (req, res) => { res.render('index', { apiId: req.apiGateway ? req.apiGateway.event.requestContext.apiId : null, - apiUrl: req.apiGateway ? `https://${req.apiGateway.event.headers.Host}/${req.apiGateway.event.requestContext.stage}` : 'http://localhost:3000' + apiUrl: req.apiGateway ? `https://${req.apiGateway.event.headers.Host}/${req.apiGateway.event.requestContext.stage}` : 'http://localhost:3000', + cognitoUserPoolClientId: process.env.COGNITO_USER_POOL_CLIENT_ID }) }) diff --git a/examples/2016-10-31/api_cognito_auth/src/views/index.pug b/examples/2016-10-31/api_cognito_auth/src/views/index.pug index 30b2fbbb1a..7634e6a9cf 100644 --- a/examples/2016-10-31/api_cognito_auth/src/views/index.pug +++ b/examples/2016-10-31/api_cognito_auth/src/views/index.pug @@ -65,12 +65,63 @@ html script. - const apiId = "#{apiId}" - const cognitoIdentityTokenMatch = document.location.hash.match(/id_token=(.*)&access_token=/) - const cognitoIdentityToken = cognitoIdentityTokenMatch ? cognitoIdentityTokenMatch[1] : null + const apiId = '#{apiId}' + const apiUrl = '#{apiUrl}/' + const cognitoUserPoolClientId = '#{cognitoUserPoolClientId}' + + const queryStringParams = new URLSearchParams(window.location.search) + const cognitoCode = queryStringParams.get('code') + let cognitoIdentityToken = localStorage.getItem('cognitoIdentityToken') + const form = document.querySelector('form') + form.addEventListener('submit', onApiInvokeFormSubmit) - form.addEventListener('submit', function(event) { + fetch('users') + .then(setResponseText) + .catch(setResponseText) + + if (cognitoCode) { + exchangeCodeForAccessToken() + .then(response => response.json()) + .then(json => { + if (json.id_token) { + cognitoIdentityToken = json.id_token + localStorage.setItem('cognitoIdentityToken', cognitoIdentityToken) + } + }) + } + + function convertJsonToFormUrlEncoded(json) { + const oAuthTokenBodyArray = Object.entries(json).map(([key, value]) => { + const encodedKey = encodeURIComponent(key) + const encodedValue = encodeURIComponent(value) + + return `${encodedKey}=${encodedValue}` + }) + + return oAuthTokenBodyArray.join('&') + } + + function exchangeCodeForAccessToken() { + const oauthTokenBodyJson = { + grant_type: 'authorization_code', + client_id: cognitoUserPoolClientId, + code: cognitoCode, + redirect_uri: apiUrl + } + const oauthTokenBody = convertJsonToFormUrlEncoded(oauthTokenBodyJson) + + return fetch(`https://${apiId}.auth.us-east-1.amazoncognito.com/oauth2/token`, + { + method: 'POST', + headers: { + ['Content-Type']: 'application/x-www-form-urlencoded' + }, + body: oauthTokenBody + }) + } + + function onApiInvokeFormSubmit (event) { event.preventDefault() const method = document.getElementById('methodField').value const id = document.getElementById('idField').value @@ -92,7 +143,7 @@ html }) .then(setResponseText) .catch(setResponseText) - }) + } function setResponseText(response) { const contentType = response.headers.get('content-type') @@ -105,12 +156,4 @@ html return response.text().then(text => { document.querySelector('code').innerText = text }) - } - - if (!window.fetch) { - alert('Your browser does not have fetch support, which this demo uses. Try again in a modern browser (https://caniuse.com/fetch) or modify the example to use an alternate approach (e.g. XMLHttpRequest or your library of choice.)') - } - - fetch('users') - .then(setResponseText) - .catch(setResponseText) + } \ No newline at end of file diff --git a/examples/2016-10-31/api_cognito_auth/template.yaml b/examples/2016-10-31/api_cognito_auth/template.yaml index effb5444ae..6c701f33e5 100644 --- a/examples/2016-10-31/api_cognito_auth/template.yaml +++ b/examples/2016-10-31/api_cognito_auth/template.yaml @@ -6,6 +6,10 @@ Parameters: CognitoUserPoolName: Type: String Default: MyUserPool + + CognitoUserPoolClientName: + Type: String + Default: MyUserPoolClient Resources: MyApi: @@ -26,6 +30,9 @@ Resources: Handler: lambda.handler MemorySize: 1024 Runtime: nodejs8.10 + Environment: + Variables: + COGNITO_USER_POOL_CLIENT_ID: !Ref MyCognitoUserPoolClient Events: Root: Type: Api @@ -87,10 +94,19 @@ Resources: Policies: PasswordPolicy: MinimumLength: 8 + UsernameAttributes: + - email Schema: - AttributeDataType: String Name: email Required: false + + MyCognitoUserPoolClient: + Type: AWS::Cognito::UserPoolClient + Properties: + UserPoolId: !Ref MyCognitoUserPool + ClientName: !Ref CognitoUserPoolClientName + GenerateSecret: false PreSignupLambdaFunction: Type: AWS::Serverless::Function @@ -104,12 +120,6 @@ Resources: MemorySize: 128 Runtime: nodejs8.10 Timeout: 3 - # TODO: - # Events: - # CognitoUserPoolPreSignup: - # Type: CognitoUserPool - # Properties: - # UserPool: !Ref MyCognitoUserPool LambdaCognitoUserPoolExecutionPermission: Type: AWS::Lambda::Permission @@ -118,6 +128,12 @@ Resources: FunctionName: !GetAtt PreSignupLambdaFunction.Arn Principal: cognito-idp.amazonaws.com SourceArn: !Sub 'arn:aws:cognito-idp:${AWS::Region}:${AWS::AccountId}:userpool/${MyCognitoUserPool}' + # TODO: Add a CognitoUserPool Event Source to SAM to create this permission for you. + # Events: + # CognitoUserPoolPreSignup: + # Type: CognitoUserPool + # Properties: + # UserPool: !Ref MyCognitoUserPool Outputs: ApiId: @@ -130,4 +146,8 @@ Outputs: CognitoUserPoolId: Description: "Cognito User Pool Id" - Value: !Ref MyCognitoUserPool \ No newline at end of file + Value: !Ref MyCognitoUserPool + + CognitoUserPoolClientId: + Description: "Cognito User Pool Client Id" + Value: !Ref MyCognitoUserPoolClient \ No newline at end of file From 987279676a6f72e028d006dbf676409a871e618d Mon Sep 17 00:00:00 2001 From: Brett Andrews Date: Fri, 14 Sep 2018 15:14:47 -0700 Subject: [PATCH 14/19] add tests for cognito authorizers --- samtranslator/model/apigateway.py | 10 +- samtranslator/model/sam_resources.py | 1 - samtranslator/swagger/swagger.py | 4 +- .../input/api_with_auth_all_maximum.yaml | 105 ++++ .../input/api_with_auth_all_minimum.yaml | 59 ++ .../input/api_with_auth_no_default.yaml | 56 ++ .../output/api_with_auth_all_maximum.json | 505 +++++++++++++++++ .../output/api_with_auth_all_minimum.json | 444 +++++++++++++++ .../output/api_with_auth_no_default.json | 435 +++++++++++++++ .../aws-cn/api_with_auth_all_maximum.json | 513 ++++++++++++++++++ .../aws-cn/api_with_auth_all_minimum.json | 468 ++++++++++++++++ .../aws-cn/api_with_auth_no_default.json | 459 ++++++++++++++++ .../aws-us-gov/api_with_auth_all_maximum.json | 513 ++++++++++++++++++ .../aws-us-gov/api_with_auth_all_minimum.json | 468 ++++++++++++++++ .../aws-us-gov/api_with_auth_no_default.json | 459 ++++++++++++++++ tests/translator/test_translator.py | 3 + 16 files changed, 4491 insertions(+), 11 deletions(-) create mode 100644 tests/translator/input/api_with_auth_all_maximum.yaml create mode 100644 tests/translator/input/api_with_auth_all_minimum.yaml create mode 100644 tests/translator/input/api_with_auth_no_default.yaml create mode 100644 tests/translator/output/api_with_auth_all_maximum.json create mode 100644 tests/translator/output/api_with_auth_all_minimum.json create mode 100644 tests/translator/output/api_with_auth_no_default.json create mode 100644 tests/translator/output/aws-cn/api_with_auth_all_maximum.json create mode 100644 tests/translator/output/aws-cn/api_with_auth_all_minimum.json create mode 100644 tests/translator/output/aws-cn/api_with_auth_no_default.json create mode 100644 tests/translator/output/aws-us-gov/api_with_auth_all_maximum.json create mode 100644 tests/translator/output/aws-us-gov/api_with_auth_all_minimum.json create mode 100644 tests/translator/output/aws-us-gov/api_with_auth_no_default.json diff --git a/samtranslator/model/apigateway.py b/samtranslator/model/apigateway.py index 24650fe554..6b5204378c 100644 --- a/samtranslator/model/apigateway.py +++ b/samtranslator/model/apigateway.py @@ -102,10 +102,6 @@ def __init__(self, api_logical_id=None, name=None, user_pool_arn=None, function_ raise InvalidResourceException(api_logical_id, name + " Authorizer must specify Identity with at least one " "of Headers, QueryStrings, StageVariables, or Context.") - if function_payload_type == 'REQUEST' and not identity.get('Headers') and not identity.get('QueryStrings') and not identity.get('StageVariables') and not identity.get('Context'): - raise InvalidResourceException(api_logical_id, name + " Authorizer must specify Identity with at least one " - "of Headers, QueryStrings, StageVariables, or Context.") - self.api_logical_id = api_logical_id self.name = name self.user_pool_arn = user_pool_arn @@ -125,7 +121,7 @@ def _is_missing_identity_source(self, identity): if not headers and not query_strings and not stage_variables and not context: return True - + return False def generate_swagger(self): @@ -164,6 +160,7 @@ def generate_swagger(self): if self._get_function_payload_type() == 'REQUEST': swagger[APIGATEWAY_AUTHORIZER_KEY]['identitySource'] = self._get_identity_source() + # Authorizer Validation Expression is only allowed on COGNITO_USER_POOLS and LAMBDA_TOKEN is_lambda_token_authorizer = authorizer_type == 'LAMBDA' and self._get_function_payload_type() == 'TOKEN' if authorizer_type == 'COGNITO_USER_POOLS' or is_lambda_token_authorizer: @@ -178,9 +175,6 @@ def _get_identity_validation_expression(self): return self.identity and self.identity.get('ValidationExpression') def _get_identity_source(self): - if not self.identity: - return None - identity_source_headers = [] identity_source_query_strings = [] identity_source_stage_variables = [] diff --git a/samtranslator/model/sam_resources.py b/samtranslator/model/sam_resources.py index 1eb631aa7d..4e8c537a3d 100644 --- a/samtranslator/model/sam_resources.py +++ b/samtranslator/model/sam_resources.py @@ -346,7 +346,6 @@ def _construct_code_dict_code_uri(self): s3_pointer = parse_s3_uri(self.CodeUri) if s3_pointer is None: - print('s3_pointer here', s3_pointer) raise InvalidResourceException(self.logical_id, '\'CodeUri\' is not a valid S3 Uri of the form ' '"s3://bucket/key" with optional versionId query parameter.') diff --git a/samtranslator/swagger/swagger.py b/samtranslator/swagger/swagger.py index 17c97b3252..c90f57a7a7 100644 --- a/samtranslator/swagger/swagger.py +++ b/samtranslator/swagger/swagger.py @@ -311,8 +311,8 @@ def set_path_default_authorizer(self, path, default_authorizer, authorizers): def add_auth_to_method(self, path, method_name, auth, api): """ - Adds auth settings for this path/method. Auth settings currently consist solely of Authorization - (aka Custom Authorizers) but this method will eventually include setting other auth settings such as API Key, + Adds auth settings for this path/method. Auth settings currently consist solely of Authorizers + but this method will eventually include setting other auth settings such as API Key, Resource Policy, etc. :param string path: Path name diff --git a/tests/translator/input/api_with_auth_all_maximum.yaml b/tests/translator/input/api_with_auth_all_maximum.yaml new file mode 100644 index 0000000000..89c94fdc7e --- /dev/null +++ b/tests/translator/input/api_with_auth_all_maximum.yaml @@ -0,0 +1,105 @@ +Resources: + MyApi: + Type: "AWS::Serverless::Api" + Properties: + StageName: Prod + Auth: + DefaultAuthorizer: MyCognitoAuth + Authorizers: + MyCognitoAuth: + UserPoolArn: arn:aws:1 + Identity: + Header: MyAuthorizationHeader + ValidationExpression: myauthvalidationexpression + + MyCognitoAuthMultipleUserPools: + UserPoolArn: + - arn:aws:2 + - arn:aws:3 + Identity: + Header: MyAuthorizationHeader2 + ValidationExpression: myauthvalidationexpression2 + + MyLambdaTokenAuth: + FunctionPayloadType: TOKEN + FunctionArn: arn:aws + FunctionInvokeRole: arn:aws:iam::123456789012:role/S3Access + Identity: + Header: MyCustomAuthHeader + ValidationExpression: mycustomauthexpression + ReauthorizeEvery: 20 + + MyLambdaTokenAuthNoneFunctionInvokeRole: + FunctionArn: arn:aws + FunctionInvokeRole: NONE + Identity: + ReauthorizeEvery: 0 + + MyLambdaRequestAuth: + FunctionPayloadType: REQUEST + FunctionArn: arn:aws + FunctionInvokeRole: arn:aws:iam::123456789012:role/S3Access + Identity: + Headers: + - Authorization1 + QueryStrings: + - Authorization2 + StageVariables: + - Authorization3 + Context: + - Authorization4 + ReauthorizeEvery: 0 + + MyFunction: + Type: AWS::Serverless::Function + Properties: + CodeUri: s3://sam-demo-bucket/thumbnails.zip + Handler: index.handler + Runtime: nodejs8.10 + Events: + WithNoAuthorizer: + Type: Api + Properties: + RestApiId: !Ref MyApi + Path: / + Method: get + Auth: + Authorizer: NONE + WithCognitoMultipleUserPoolsAuthorizer: + Type: Api + Properties: + RestApiId: !Ref MyApi + Path: /users + Method: post + Auth: + Authorizer: MyCognitoAuthMultipleUserPools + WithLambdaTokenAuthorizer: + Type: Api + Properties: + RestApiId: !Ref MyApi + Path: /users + Method: get + Auth: + Authorizer: MyLambdaTokenAuth + WithLambdaTokenAuthorizer: + Type: Api + Properties: + RestApiId: !Ref MyApi + Path: /users + Method: patch + Auth: + Authorizer: MyLambdaTokenAuthNoneFunctionInvokeRole + WithLambdaRequestAuthorizer: + Type: Api + Properties: + RestApiId: !Ref MyApi + Path: /users + Method: delete + Auth: + Authorizer: MyLambdaRequestAuth + WithDefaultAuthorizer: + Type: Api + Properties: + RestApiId: !Ref MyApi + Path: /users + Method: put \ No newline at end of file diff --git a/tests/translator/input/api_with_auth_all_minimum.yaml b/tests/translator/input/api_with_auth_all_minimum.yaml new file mode 100644 index 0000000000..2c1d03ad43 --- /dev/null +++ b/tests/translator/input/api_with_auth_all_minimum.yaml @@ -0,0 +1,59 @@ +Resources: + MyApiWithCognitoAuth: + Type: "AWS::Serverless::Api" + Properties: + StageName: Prod + Auth: + DefaultAuthorizer: MyCognitoAuth + Authorizers: + MyCognitoAuth: + UserPoolArn: !GetAtt MyUserPool.Arn + + MyApiWithLambdaTokenAuth: + Type: "AWS::Serverless::Api" + Properties: + StageName: Prod + Auth: + DefaultAuthorizer: MyLambdaTokenAuth + Authorizers: + MyLambdaTokenAuth: + FunctionArn: !GetAtt MyAuthFn.Arn + + MyApiWithLambdaRequestAuth: + Type: "AWS::Serverless::Api" + Properties: + StageName: Prod + Auth: + DefaultAuthorizer: MyLambdaRequestAuth + Authorizers: + MyLambdaRequestAuth: + FunctionPayloadType: REQUEST + FunctionArn: !GetAtt MyAuthFn.Arn + Identity: + Headers: + - Authorization1 + MyFn: + Type: AWS::Serverless::Function + Properties: + CodeUri: s3://bucket/key + Handler: index.handler + Runtime: nodejs8.10 + Events: + Cognito: + Type: Api + Properties: + RestApiId: !Ref MyApiWithCognitoAuth + Method: get + Path: /cognito + LambdaToken: + Type: Api + Properties: + RestApiId: !Ref MyApiWithLambdaTokenAuth + Method: get + Path: /lambda-token + LambdaRequest: + Type: Api + Properties: + RestApiId: !Ref MyApiWithLambdaRequestAuth + Method: get + Path: /lambda-request \ No newline at end of file diff --git a/tests/translator/input/api_with_auth_no_default.yaml b/tests/translator/input/api_with_auth_no_default.yaml new file mode 100644 index 0000000000..f3d3b81d88 --- /dev/null +++ b/tests/translator/input/api_with_auth_no_default.yaml @@ -0,0 +1,56 @@ +Resources: + MyApiWithCognitoAuth: + Type: "AWS::Serverless::Api" + Properties: + StageName: Prod + Auth: + Authorizers: + MyCognitoAuth: + UserPoolArn: !GetAtt MyUserPool.Arn + + MyApiWithLambdaTokenAuth: + Type: "AWS::Serverless::Api" + Properties: + StageName: Prod + Auth: + Authorizers: + MyLambdaTokenAuth: + FunctionArn: !GetAtt MyAuthFn.Arn + + MyApiWithLambdaRequestAuth: + Type: "AWS::Serverless::Api" + Properties: + StageName: Prod + Auth: + Authorizers: + MyLambdaRequestAuth: + FunctionPayloadType: REQUEST + FunctionArn: !GetAtt MyAuthFn.Arn + Identity: + Headers: + - Authorization1 + MyFn: + Type: AWS::Serverless::Function + Properties: + CodeUri: s3://bucket/key + Handler: index.handler + Runtime: nodejs8.10 + Events: + Cognito: + Type: Api + Properties: + RestApiId: !Ref MyApiWithCognitoAuth + Method: get + Path: /cognito + LambdaToken: + Type: Api + Properties: + RestApiId: !Ref MyApiWithLambdaTokenAuth + Method: get + Path: /lambda-token + LambdaRequest: + Type: Api + Properties: + RestApiId: !Ref MyApiWithLambdaRequestAuth + Method: get + Path: /lambda-request \ No newline at end of file diff --git a/tests/translator/output/api_with_auth_all_maximum.json b/tests/translator/output/api_with_auth_all_maximum.json new file mode 100644 index 0000000000..61b4a6b398 --- /dev/null +++ b/tests/translator/output/api_with_auth_all_maximum.json @@ -0,0 +1,505 @@ +{ + "Resources": { + "MyFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "thumbnails.zip" + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "MyFunctionRole", + "Arn" + ] + }, + "Runtime": "nodejs8.10", + "Tags": [{ + "Value": "SAM", + "Key": "lambda:createdBy" + }] + } + }, + "MyFunctionWithLambdaTokenAuthorizerPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunction" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/PATCH/users", + { + "__Stage__": "Prod", + "__ApiId__": { + "Ref": "MyApi" + } + } + ] + } + } + }, + "MyApiMyLambdaRequestAuthAuthorizerPermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": "arn:aws", + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/authorizers/*", + { + "__ApiId__": { + "Ref": "MyApi" + } + } + ] + } + } + }, + "MyFunctionWithLambdaTokenAuthorizerPermissionTest": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunction" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/PATCH/users", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApi" + } + } + ] + } + } + }, + "MyFunctionWithDefaultAuthorizerPermissionTest": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunction" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/PUT/users", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApi" + } + } + ] + } + } + }, + "MyFunctionWithCognitoMultipleUserPoolsAuthorizerPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunction" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/POST/users", + { + "__Stage__": "Prod", + "__ApiId__": { + "Ref": "MyApi" + } + } + ] + } + } + }, + "MyFunctionWithNoAuthorizerPermissionTest": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunction" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApi" + } + } + ] + } + } + }, + "MyFunctionRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "ManagedPolicyArns": [ + "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [{ + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + }] + } + } + }, + "MyApi": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/": { + "get": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFunction.Arn}/invocations" + } + }, + "security": [{ + "NONE": [] + }], + "responses": {} + } + }, + "/users": { + "put": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFunction.Arn}/invocations" + } + }, + "security": [{ + "MyCognitoAuth": [] + }], + "responses": {} + }, + "post": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFunction.Arn}/invocations" + } + }, + "security": [{ + "MyCognitoAuthMultipleUserPools": [] + }], + "responses": {} + }, + "patch": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFunction.Arn}/invocations" + } + }, + "security": [{ + "MyLambdaTokenAuthNoneFunctionInvokeRole": [] + }], + "responses": {} + }, + "delete": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFunction.Arn}/invocations" + } + }, + "security": [{ + "MyLambdaRequestAuth": [] + }], + "responses": {} + } + } + }, + "swagger": "2.0", + "securityDefinitions": { + "MyCognitoAuth": { + "in": "header", + "type": "apiKey", + "name": "MyAuthorizationHeader", + "x-amazon-apigateway-authorizer": { + "identityValidationExpression": "myauthvalidationexpression", + "providerARNs": [ + "arn:aws:1" + ], + "type": "cognito_user_pools" + }, + "x-amazon-apigateway-authtype": "cognito_user_pools" + }, + "MyLambdaTokenAuthNoneFunctionInvokeRole": { + "in": "header", + "type": "apiKey", + "name": "Authorization", + "x-amazon-apigateway-authorizer": { + "type": "token", + "authorizerResultTtlInSeconds": 0, + "authorizerUri": { + "Fn::Sub": [ + "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${__FunctionArn__}/invocations", + { + "__FunctionArn__": "arn:aws" + } + ] + } + }, + "x-amazon-apigateway-authtype": "custom" + }, + "MyCognitoAuthMultipleUserPools": { + "in": "header", + "type": "apiKey", + "name": "MyAuthorizationHeader2", + "x-amazon-apigateway-authorizer": { + "identityValidationExpression": "myauthvalidationexpression2", + "providerARNs": [ + "arn:aws:2", + "arn:aws:3" + ], + "type": "cognito_user_pools" + }, + "x-amazon-apigateway-authtype": "cognito_user_pools" + }, + "MyLambdaTokenAuth": { + "in": "header", + "type": "apiKey", + "name": "MyCustomAuthHeader", + "x-amazon-apigateway-authorizer": { + "type": "token", + "authorizerResultTtlInSeconds": 20, + "authorizerUri": { + "Fn::Sub": [ + "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${__FunctionArn__}/invocations", + { + "__FunctionArn__": "arn:aws" + } + ] + }, + "authorizerCredentials": "arn:aws:iam::123456789012:role/S3Access", + "identityValidationExpression": "mycustomauthexpression" + }, + "x-amazon-apigateway-authtype": "custom" + }, + "MyLambdaRequestAuth": { + "in": "header", + "type": "apiKey", + "name": "Unused", + "x-amazon-apigateway-authorizer": { + "type": "request", + "authorizerResultTtlInSeconds": 0, + "identitySource": "method.request.header.Authorization1, method.request.querystring.Authorization2, stageVariables.Authorization3, context.Authorization4", + "authorizerUri": { + "Fn::Sub": [ + "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${__FunctionArn__}/invocations", + { + "__FunctionArn__": "arn:aws" + } + ] + }, + "authorizerCredentials": "arn:aws:iam::123456789012:role/S3Access" + }, + "x-amazon-apigateway-authtype": "custom" + } + } + } + } + }, + "MyApiMyLambdaTokenAuthAuthorizerPermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": "arn:aws", + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/authorizers/*", + { + "__ApiId__": { + "Ref": "MyApi" + } + } + ] + } + } + }, + "MyFunctionWithLambdaRequestAuthorizerPermissionTest": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunction" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/DELETE/users", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApi" + } + } + ] + } + } + }, + "MyFunctionWithLambdaRequestAuthorizerPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunction" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/DELETE/users", + { + "__Stage__": "Prod", + "__ApiId__": { + "Ref": "MyApi" + } + } + ] + } + } + }, + "MyApiDeployment22f365e69f": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "MyApi" + }, + "Description": "RestApi deployment id: 22f365e69fd10c36975a60c0b715eb1340f4b5e6", + "StageName": "Stage" + } + }, + "MyFunctionWithCognitoMultipleUserPoolsAuthorizerPermissionTest": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunction" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/POST/users", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApi" + } + } + ] + } + } + }, + "MyApiMyLambdaTokenAuthNoneFunctionInvokeRoleAuthorizerPermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": "arn:aws", + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/authorizers/*", + { + "__ApiId__": { + "Ref": "MyApi" + } + } + ] + } + } + }, + "MyApiProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "DeploymentId": { + "Ref": "MyApiDeployment22f365e69f" + }, + "RestApiId": { + "Ref": "MyApi" + }, + "StageName": "Prod" + } + }, + "MyFunctionWithNoAuthorizerPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunction" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/", + { + "__Stage__": "Prod", + "__ApiId__": { + "Ref": "MyApi" + } + } + ] + } + } + }, + "MyFunctionWithDefaultAuthorizerPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunction" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/PUT/users", + { + "__Stage__": "Prod", + "__ApiId__": { + "Ref": "MyApi" + } + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/api_with_auth_all_minimum.json b/tests/translator/output/api_with_auth_all_minimum.json new file mode 100644 index 0000000000..b3c4c4fd95 --- /dev/null +++ b/tests/translator/output/api_with_auth_all_minimum.json @@ -0,0 +1,444 @@ +{ + "Resources": { + "MyFnCognitoPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFn" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/cognito", + { + "__Stage__": "Prod", + "__ApiId__": { + "Ref": "MyApiWithCognitoAuth" + } + } + ] + } + } + }, + "MyApiWithCognitoAuth": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/cognito": { + "get": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFn.Arn}/invocations" + } + }, + "security": [{ + "MyCognitoAuth": [] + }], + "responses": {} + } + } + }, + "swagger": "2.0", + "securityDefinitions": { + "MyCognitoAuth": { + "in": "header", + "type": "apiKey", + "name": "Authorization", + "x-amazon-apigateway-authorizer": { + "providerARNs": [{ + "Fn::GetAtt": [ + "MyUserPool", + "Arn" + ] + }], + "type": "cognito_user_pools" + }, + "x-amazon-apigateway-authtype": "cognito_user_pools" + } + } + } + } + }, + "MyApiWithLambdaRequestAuthMyLambdaRequestAuthAuthorizerPermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Fn::GetAtt": [ + "MyAuthFn", + "Arn" + ] + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/authorizers/*", + { + "__ApiId__": { + "Ref": "MyApiWithLambdaRequestAuth" + } + } + ] + } + } + }, + "MyApiWithLambdaRequestAuthProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "DeploymentId": { + "Ref": "MyApiWithLambdaRequestAuthDeployment6e52add211" + }, + "RestApiId": { + "Ref": "MyApiWithLambdaRequestAuth" + }, + "StageName": "Prod" + } + }, + "MyFnLambdaTokenPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFn" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/lambda-token", + { + "__Stage__": "Prod", + "__ApiId__": { + "Ref": "MyApiWithLambdaTokenAuth" + } + } + ] + } + } + }, + "MyApiWithLambdaTokenAuthMyLambdaTokenAuthAuthorizerPermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Fn::GetAtt": [ + "MyAuthFn", + "Arn" + ] + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/authorizers/*", + { + "__ApiId__": { + "Ref": "MyApiWithLambdaTokenAuth" + } + } + ] + } + } + }, + "MyFnLambdaRequestPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFn" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/lambda-request", + { + "__Stage__": "Prod", + "__ApiId__": { + "Ref": "MyApiWithLambdaRequestAuth" + } + } + ] + } + } + }, + "MyApiWithLambdaTokenAuth": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/lambda-token": { + "get": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFn.Arn}/invocations" + } + }, + "security": [{ + "MyLambdaTokenAuth": [] + }], + "responses": {} + } + } + }, + "swagger": "2.0", + "securityDefinitions": { + "MyLambdaTokenAuth": { + "in": "header", + "type": "apiKey", + "name": "Authorization", + "x-amazon-apigateway-authorizer": { + "type": "token", + "authorizerUri": { + "Fn::Sub": [ + "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${__FunctionArn__}/invocations", + { + "__FunctionArn__": { + "Fn::GetAtt": [ + "MyAuthFn", + "Arn" + ] + } + } + ] + } + }, + "x-amazon-apigateway-authtype": "custom" + } + } + } + } + }, + "MyApiWithCognitoAuthDeployment62312fa971": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "MyApiWithCognitoAuth" + }, + "Description": "RestApi deployment id: 62312fa9711ad898b40e76b7a4ae1358305b0bcd", + "StageName": "Stage" + } + }, + "MyFnLambdaRequestPermissionTest": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFn" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/lambda-request", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApiWithLambdaRequestAuth" + } + } + ] + } + } + }, + "MyFnLambdaTokenPermissionTest": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFn" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/lambda-token", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApiWithLambdaTokenAuth" + } + } + ] + } + } + }, + "MyFnCognitoPermissionTest": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFn" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/cognito", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApiWithCognitoAuth" + } + } + ] + } + } + }, + "MyApiWithLambdaRequestAuth": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/lambda-request": { + "get": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFn.Arn}/invocations" + } + }, + "security": [{ + "MyLambdaRequestAuth": [] + }], + "responses": {} + } + } + }, + "swagger": "2.0", + "securityDefinitions": { + "MyLambdaRequestAuth": { + "in": "header", + "type": "apiKey", + "name": "Unused", + "x-amazon-apigateway-authorizer": { + "type": "request", + "identitySource": "method.request.header.Authorization1", + "authorizerUri": { + "Fn::Sub": [ + "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${__FunctionArn__}/invocations", + { + "__FunctionArn__": { + "Fn::GetAtt": [ + "MyAuthFn", + "Arn" + ] + } + } + ] + } + }, + "x-amazon-apigateway-authtype": "custom" + } + } + } + } + }, + "MyApiWithLambdaTokenAuthDeployment445c6c96e7": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "MyApiWithLambdaTokenAuth" + }, + "Description": "RestApi deployment id: 445c6c96e7f43bd49f83bd67ae0d6813c517348f", + "StageName": "Stage" + } + }, + "MyApiWithLambdaTokenAuthProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "DeploymentId": { + "Ref": "MyApiWithLambdaTokenAuthDeployment445c6c96e7" + }, + "RestApiId": { + "Ref": "MyApiWithLambdaTokenAuth" + }, + "StageName": "Prod" + } + }, + "MyFnRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "ManagedPolicyArns": [ + "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [{ + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + }] + } + } + }, + "MyApiWithLambdaRequestAuthDeployment6e52add211": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "MyApiWithLambdaRequestAuth" + }, + "Description": "RestApi deployment id: 6e52add211cda52ae10a7cc0e0afcf4afc682f9f", + "StageName": "Stage" + } + }, + "MyApiWithCognitoAuthProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "DeploymentId": { + "Ref": "MyApiWithCognitoAuthDeployment62312fa971" + }, + "RestApiId": { + "Ref": "MyApiWithCognitoAuth" + }, + "StageName": "Prod" + } + }, + "MyFn": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": "bucket", + "S3Key": "key" + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "MyFnRole", + "Arn" + ] + }, + "Runtime": "nodejs8.10", + "Tags": [{ + "Value": "SAM", + "Key": "lambda:createdBy" + }] + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/api_with_auth_no_default.json b/tests/translator/output/api_with_auth_no_default.json new file mode 100644 index 0000000000..2a981e6ed6 --- /dev/null +++ b/tests/translator/output/api_with_auth_no_default.json @@ -0,0 +1,435 @@ +{ + "Resources": { + "MyFnCognitoPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFn" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/cognito", + { + "__Stage__": "Prod", + "__ApiId__": { + "Ref": "MyApiWithCognitoAuth" + } + } + ] + } + } + }, + "MyApiWithCognitoAuth": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/cognito": { + "get": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFn.Arn}/invocations" + } + }, + "responses": {} + } + } + }, + "swagger": "2.0", + "securityDefinitions": { + "MyCognitoAuth": { + "in": "header", + "type": "apiKey", + "name": "Authorization", + "x-amazon-apigateway-authorizer": { + "providerARNs": [{ + "Fn::GetAtt": [ + "MyUserPool", + "Arn" + ] + }], + "type": "cognito_user_pools" + }, + "x-amazon-apigateway-authtype": "cognito_user_pools" + } + } + } + } + }, + "MyApiWithCognitoAuthDeployment039b508d89": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "MyApiWithCognitoAuth" + }, + "Description": "RestApi deployment id: 039b508d8974255326ad180948c0f232635032d8", + "StageName": "Stage" + } + }, + "MyApiWithLambdaRequestAuthMyLambdaRequestAuthAuthorizerPermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Fn::GetAtt": [ + "MyAuthFn", + "Arn" + ] + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/authorizers/*", + { + "__ApiId__": { + "Ref": "MyApiWithLambdaRequestAuth" + } + } + ] + } + } + }, + "MyApiWithLambdaRequestAuthDeployment7d0d103fdf": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "MyApiWithLambdaRequestAuth" + }, + "Description": "RestApi deployment id: 7d0d103fdf357021c9e3f88a03f27a766045308f", + "StageName": "Stage" + } + }, + "MyFnLambdaTokenPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFn" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/lambda-token", + { + "__Stage__": "Prod", + "__ApiId__": { + "Ref": "MyApiWithLambdaTokenAuth" + } + } + ] + } + } + }, + "MyFnLambdaRequestPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFn" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/lambda-request", + { + "__Stage__": "Prod", + "__ApiId__": { + "Ref": "MyApiWithLambdaRequestAuth" + } + } + ] + } + } + }, + "MyApiWithLambdaTokenAuth": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/lambda-token": { + "get": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFn.Arn}/invocations" + } + }, + "responses": {} + } + } + }, + "swagger": "2.0", + "securityDefinitions": { + "MyLambdaTokenAuth": { + "in": "header", + "type": "apiKey", + "name": "Authorization", + "x-amazon-apigateway-authorizer": { + "type": "token", + "authorizerUri": { + "Fn::Sub": [ + "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${__FunctionArn__}/invocations", + { + "__FunctionArn__": { + "Fn::GetAtt": [ + "MyAuthFn", + "Arn" + ] + } + } + ] + } + }, + "x-amazon-apigateway-authtype": "custom" + } + } + } + } + }, + "MyApiWithLambdaTokenAuthDeployment50695ee60b": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "MyApiWithLambdaTokenAuth" + }, + "Description": "RestApi deployment id: 50695ee60b97eeade77bcc6137fa5dabc526938d", + "StageName": "Stage" + } + }, + "MyFnLambdaRequestPermissionTest": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFn" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/lambda-request", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApiWithLambdaRequestAuth" + } + } + ] + } + } + }, + "MyApiWithLambdaRequestAuthProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "DeploymentId": { + "Ref": "MyApiWithLambdaRequestAuthDeployment7d0d103fdf" + }, + "RestApiId": { + "Ref": "MyApiWithLambdaRequestAuth" + }, + "StageName": "Prod" + } + }, + "MyFnLambdaTokenPermissionTest": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFn" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/lambda-token", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApiWithLambdaTokenAuth" + } + } + ] + } + } + }, + "MyFnCognitoPermissionTest": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFn" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/cognito", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApiWithCognitoAuth" + } + } + ] + } + } + }, + "MyApiWithLambdaRequestAuth": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/lambda-request": { + "get": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFn.Arn}/invocations" + } + }, + "responses": {} + } + } + }, + "swagger": "2.0", + "securityDefinitions": { + "MyLambdaRequestAuth": { + "in": "header", + "type": "apiKey", + "name": "Unused", + "x-amazon-apigateway-authorizer": { + "type": "request", + "identitySource": "method.request.header.Authorization1", + "authorizerUri": { + "Fn::Sub": [ + "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${__FunctionArn__}/invocations", + { + "__FunctionArn__": { + "Fn::GetAtt": [ + "MyAuthFn", + "Arn" + ] + } + } + ] + } + }, + "x-amazon-apigateway-authtype": "custom" + } + } + } + } + }, + "MyApiWithLambdaTokenAuthMyLambdaTokenAuthAuthorizerPermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Fn::GetAtt": [ + "MyAuthFn", + "Arn" + ] + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/authorizers/*", + { + "__ApiId__": { + "Ref": "MyApiWithLambdaTokenAuth" + } + } + ] + } + } + }, + "MyApiWithLambdaTokenAuthProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "DeploymentId": { + "Ref": "MyApiWithLambdaTokenAuthDeployment50695ee60b" + }, + "RestApiId": { + "Ref": "MyApiWithLambdaTokenAuth" + }, + "StageName": "Prod" + } + }, + "MyFnRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "ManagedPolicyArns": [ + "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [{ + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + }] + } + } + }, + "MyApiWithCognitoAuthProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "DeploymentId": { + "Ref": "MyApiWithCognitoAuthDeployment039b508d89" + }, + "RestApiId": { + "Ref": "MyApiWithCognitoAuth" + }, + "StageName": "Prod" + } + }, + "MyFn": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": "bucket", + "S3Key": "key" + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "MyFnRole", + "Arn" + ] + }, + "Runtime": "nodejs8.10", + "Tags": [{ + "Value": "SAM", + "Key": "lambda:createdBy" + }] + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/aws-cn/api_with_auth_all_maximum.json b/tests/translator/output/aws-cn/api_with_auth_all_maximum.json new file mode 100644 index 0000000000..781c9b8bef --- /dev/null +++ b/tests/translator/output/aws-cn/api_with_auth_all_maximum.json @@ -0,0 +1,513 @@ +{ + "Resources": { + "MyFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "thumbnails.zip" + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "MyFunctionRole", + "Arn" + ] + }, + "Runtime": "nodejs8.10", + "Tags": [{ + "Value": "SAM", + "Key": "lambda:createdBy" + }] + } + }, + "MyFunctionWithLambdaTokenAuthorizerPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunction" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/PATCH/users", + { + "__Stage__": "Prod", + "__ApiId__": { + "Ref": "MyApi" + } + } + ] + } + } + }, + "MyApiMyLambdaRequestAuthAuthorizerPermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": "arn:aws", + "SourceArn": { + "Fn::Sub": [ + "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/authorizers/*", + { + "__ApiId__": { + "Ref": "MyApi" + } + } + ] + } + } + }, + "MyFunctionWithLambdaTokenAuthorizerPermissionTest": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunction" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/PATCH/users", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApi" + } + } + ] + } + } + }, + "MyFunctionWithDefaultAuthorizerPermissionTest": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunction" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/PUT/users", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApi" + } + } + ] + } + } + }, + "MyApiDeployment8401165542": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "MyApi" + }, + "Description": "RestApi deployment id: 8401165542e94cea0086026b427b10c966b84f1d", + "StageName": "Stage" + } + }, + "MyFunctionWithCognitoMultipleUserPoolsAuthorizerPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunction" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/POST/users", + { + "__Stage__": "Prod", + "__ApiId__": { + "Ref": "MyApi" + } + } + ] + } + } + }, + "MyFunctionWithNoAuthorizerPermissionTest": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunction" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApi" + } + } + ] + } + } + }, + "MyFunctionRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "ManagedPolicyArns": [ + "arn:aws-cn:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [{ + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + }] + } + } + }, + "MyApi": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/": { + "get": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws-cn:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFunction.Arn}/invocations" + } + }, + "security": [{ + "NONE": [] + }], + "responses": {} + } + }, + "/users": { + "put": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws-cn:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFunction.Arn}/invocations" + } + }, + "security": [{ + "MyCognitoAuth": [] + }], + "responses": {} + }, + "post": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws-cn:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFunction.Arn}/invocations" + } + }, + "security": [{ + "MyCognitoAuthMultipleUserPools": [] + }], + "responses": {} + }, + "patch": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws-cn:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFunction.Arn}/invocations" + } + }, + "security": [{ + "MyLambdaTokenAuthNoneFunctionInvokeRole": [] + }], + "responses": {} + }, + "delete": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws-cn:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFunction.Arn}/invocations" + } + }, + "security": [{ + "MyLambdaRequestAuth": [] + }], + "responses": {} + } + } + }, + "swagger": "2.0", + "securityDefinitions": { + "MyCognitoAuth": { + "in": "header", + "type": "apiKey", + "name": "MyAuthorizationHeader", + "x-amazon-apigateway-authorizer": { + "identityValidationExpression": "myauthvalidationexpression", + "providerARNs": [ + "arn:aws:1" + ], + "type": "cognito_user_pools" + }, + "x-amazon-apigateway-authtype": "cognito_user_pools" + }, + "MyLambdaTokenAuthNoneFunctionInvokeRole": { + "in": "header", + "type": "apiKey", + "name": "Authorization", + "x-amazon-apigateway-authorizer": { + "type": "token", + "authorizerResultTtlInSeconds": 0, + "authorizerUri": { + "Fn::Sub": [ + "arn:aws-cn:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${__FunctionArn__}/invocations", + { + "__FunctionArn__": "arn:aws" + } + ] + } + }, + "x-amazon-apigateway-authtype": "custom" + }, + "MyCognitoAuthMultipleUserPools": { + "in": "header", + "type": "apiKey", + "name": "MyAuthorizationHeader2", + "x-amazon-apigateway-authorizer": { + "identityValidationExpression": "myauthvalidationexpression2", + "providerARNs": [ + "arn:aws:2", + "arn:aws:3" + ], + "type": "cognito_user_pools" + }, + "x-amazon-apigateway-authtype": "cognito_user_pools" + }, + "MyLambdaTokenAuth": { + "in": "header", + "type": "apiKey", + "name": "MyCustomAuthHeader", + "x-amazon-apigateway-authorizer": { + "type": "token", + "authorizerResultTtlInSeconds": 20, + "authorizerUri": { + "Fn::Sub": [ + "arn:aws-cn:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${__FunctionArn__}/invocations", + { + "__FunctionArn__": "arn:aws" + } + ] + }, + "authorizerCredentials": "arn:aws:iam::123456789012:role/S3Access", + "identityValidationExpression": "mycustomauthexpression" + }, + "x-amazon-apigateway-authtype": "custom" + }, + "MyLambdaRequestAuth": { + "in": "header", + "type": "apiKey", + "name": "Unused", + "x-amazon-apigateway-authorizer": { + "type": "request", + "authorizerResultTtlInSeconds": 0, + "identitySource": "method.request.header.Authorization1, method.request.querystring.Authorization2, stageVariables.Authorization3, context.Authorization4", + "authorizerUri": { + "Fn::Sub": [ + "arn:aws-cn:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${__FunctionArn__}/invocations", + { + "__FunctionArn__": "arn:aws" + } + ] + }, + "authorizerCredentials": "arn:aws:iam::123456789012:role/S3Access" + }, + "x-amazon-apigateway-authtype": "custom" + } + } + }, + "EndpointConfiguration": { + "Types": [ + "REGIONAL" + ] + }, + "Parameters": { + "endpointConfigurationTypes": "REGIONAL" + } + } + }, + "MyApiMyLambdaTokenAuthAuthorizerPermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": "arn:aws", + "SourceArn": { + "Fn::Sub": [ + "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/authorizers/*", + { + "__ApiId__": { + "Ref": "MyApi" + } + } + ] + } + } + }, + "MyFunctionWithLambdaRequestAuthorizerPermissionTest": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunction" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/DELETE/users", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApi" + } + } + ] + } + } + }, + "MyFunctionWithLambdaRequestAuthorizerPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunction" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/DELETE/users", + { + "__Stage__": "Prod", + "__ApiId__": { + "Ref": "MyApi" + } + } + ] + } + } + }, + "MyFunctionWithCognitoMultipleUserPoolsAuthorizerPermissionTest": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunction" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/POST/users", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApi" + } + } + ] + } + } + }, + "MyApiMyLambdaTokenAuthNoneFunctionInvokeRoleAuthorizerPermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": "arn:aws", + "SourceArn": { + "Fn::Sub": [ + "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/authorizers/*", + { + "__ApiId__": { + "Ref": "MyApi" + } + } + ] + } + } + }, + "MyApiProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "DeploymentId": { + "Ref": "MyApiDeployment8401165542" + }, + "RestApiId": { + "Ref": "MyApi" + }, + "StageName": "Prod" + } + }, + "MyFunctionWithNoAuthorizerPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunction" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/", + { + "__Stage__": "Prod", + "__ApiId__": { + "Ref": "MyApi" + } + } + ] + } + } + }, + "MyFunctionWithDefaultAuthorizerPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunction" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/PUT/users", + { + "__Stage__": "Prod", + "__ApiId__": { + "Ref": "MyApi" + } + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/aws-cn/api_with_auth_all_minimum.json b/tests/translator/output/aws-cn/api_with_auth_all_minimum.json new file mode 100644 index 0000000000..4ee847bdbb --- /dev/null +++ b/tests/translator/output/aws-cn/api_with_auth_all_minimum.json @@ -0,0 +1,468 @@ +{ + "Resources": { + "MyFnCognitoPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFn" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/cognito", + { + "__Stage__": "Prod", + "__ApiId__": { + "Ref": "MyApiWithCognitoAuth" + } + } + ] + } + } + }, + "MyApiWithCognitoAuth": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/cognito": { + "get": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws-cn:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFn.Arn}/invocations" + } + }, + "security": [{ + "MyCognitoAuth": [] + }], + "responses": {} + } + } + }, + "swagger": "2.0", + "securityDefinitions": { + "MyCognitoAuth": { + "in": "header", + "type": "apiKey", + "name": "Authorization", + "x-amazon-apigateway-authorizer": { + "providerARNs": [{ + "Fn::GetAtt": [ + "MyUserPool", + "Arn" + ] + }], + "type": "cognito_user_pools" + }, + "x-amazon-apigateway-authtype": "cognito_user_pools" + } + } + }, + "EndpointConfiguration": { + "Types": [ + "REGIONAL" + ] + }, + "Parameters": { + "endpointConfigurationTypes": "REGIONAL" + } + } + }, + "MyApiWithLambdaRequestAuthMyLambdaRequestAuthAuthorizerPermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Fn::GetAtt": [ + "MyAuthFn", + "Arn" + ] + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/authorizers/*", + { + "__ApiId__": { + "Ref": "MyApiWithLambdaRequestAuth" + } + } + ] + } + } + }, + "MyApiWithLambdaTokenAuthDeploymenta48b731095": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "MyApiWithLambdaTokenAuth" + }, + "Description": "RestApi deployment id: a48b7310952ed029bd212c380e89a1bd39c74eae", + "StageName": "Stage" + } + }, + "MyApiWithLambdaRequestAuthProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "DeploymentId": { + "Ref": "MyApiWithLambdaRequestAuthDeploymentd3ee2721bc" + }, + "RestApiId": { + "Ref": "MyApiWithLambdaRequestAuth" + }, + "StageName": "Prod" + } + }, + "MyFnLambdaTokenPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFn" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/lambda-token", + { + "__Stage__": "Prod", + "__ApiId__": { + "Ref": "MyApiWithLambdaTokenAuth" + } + } + ] + } + } + }, + "MyFnLambdaRequestPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFn" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/lambda-request", + { + "__Stage__": "Prod", + "__ApiId__": { + "Ref": "MyApiWithLambdaRequestAuth" + } + } + ] + } + } + }, + "MyApiWithLambdaTokenAuth": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/lambda-token": { + "get": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws-cn:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFn.Arn}/invocations" + } + }, + "security": [{ + "MyLambdaTokenAuth": [] + }], + "responses": {} + } + } + }, + "swagger": "2.0", + "securityDefinitions": { + "MyLambdaTokenAuth": { + "in": "header", + "type": "apiKey", + "name": "Authorization", + "x-amazon-apigateway-authorizer": { + "type": "token", + "authorizerUri": { + "Fn::Sub": [ + "arn:aws-cn:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${__FunctionArn__}/invocations", + { + "__FunctionArn__": { + "Fn::GetAtt": [ + "MyAuthFn", + "Arn" + ] + } + } + ] + } + }, + "x-amazon-apigateway-authtype": "custom" + } + } + }, + "EndpointConfiguration": { + "Types": [ + "REGIONAL" + ] + }, + "Parameters": { + "endpointConfigurationTypes": "REGIONAL" + } + } + }, + "MyApiWithCognitoAuthDeploymenta9cf768eaa": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "MyApiWithCognitoAuth" + }, + "Description": "RestApi deployment id: a9cf768eaa1ac6804c7a7b05b79d7ee79d369fcf", + "StageName": "Stage" + } + }, + "MyFnLambdaRequestPermissionTest": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFn" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/lambda-request", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApiWithLambdaRequestAuth" + } + } + ] + } + } + }, + "MyApiWithLambdaRequestAuthDeploymentd3ee2721bc": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "MyApiWithLambdaRequestAuth" + }, + "Description": "RestApi deployment id: d3ee2721bcff60c4d00d26138ccf8007434bb862", + "StageName": "Stage" + } + }, + "MyFnLambdaTokenPermissionTest": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFn" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/lambda-token", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApiWithLambdaTokenAuth" + } + } + ] + } + } + }, + "MyFnCognitoPermissionTest": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFn" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/cognito", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApiWithCognitoAuth" + } + } + ] + } + } + }, + "MyApiWithLambdaRequestAuth": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/lambda-request": { + "get": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws-cn:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFn.Arn}/invocations" + } + }, + "security": [{ + "MyLambdaRequestAuth": [] + }], + "responses": {} + } + } + }, + "swagger": "2.0", + "securityDefinitions": { + "MyLambdaRequestAuth": { + "in": "header", + "type": "apiKey", + "name": "Unused", + "x-amazon-apigateway-authorizer": { + "type": "request", + "identitySource": "method.request.header.Authorization1", + "authorizerUri": { + "Fn::Sub": [ + "arn:aws-cn:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${__FunctionArn__}/invocations", + { + "__FunctionArn__": { + "Fn::GetAtt": [ + "MyAuthFn", + "Arn" + ] + } + } + ] + } + }, + "x-amazon-apigateway-authtype": "custom" + } + } + }, + "EndpointConfiguration": { + "Types": [ + "REGIONAL" + ] + }, + "Parameters": { + "endpointConfigurationTypes": "REGIONAL" + } + } + }, + "MyApiWithLambdaTokenAuthMyLambdaTokenAuthAuthorizerPermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Fn::GetAtt": [ + "MyAuthFn", + "Arn" + ] + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/authorizers/*", + { + "__ApiId__": { + "Ref": "MyApiWithLambdaTokenAuth" + } + } + ] + } + } + }, + "MyApiWithLambdaTokenAuthProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "DeploymentId": { + "Ref": "MyApiWithLambdaTokenAuthDeploymenta48b731095" + }, + "RestApiId": { + "Ref": "MyApiWithLambdaTokenAuth" + }, + "StageName": "Prod" + } + }, + "MyFnRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "ManagedPolicyArns": [ + "arn:aws-cn:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [{ + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + }] + } + } + }, + "MyApiWithCognitoAuthProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "DeploymentId": { + "Ref": "MyApiWithCognitoAuthDeploymenta9cf768eaa" + }, + "RestApiId": { + "Ref": "MyApiWithCognitoAuth" + }, + "StageName": "Prod" + } + }, + "MyFn": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": "bucket", + "S3Key": "key" + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "MyFnRole", + "Arn" + ] + }, + "Runtime": "nodejs8.10", + "Tags": [{ + "Value": "SAM", + "Key": "lambda:createdBy" + }] + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/aws-cn/api_with_auth_no_default.json b/tests/translator/output/aws-cn/api_with_auth_no_default.json new file mode 100644 index 0000000000..b8df0978df --- /dev/null +++ b/tests/translator/output/aws-cn/api_with_auth_no_default.json @@ -0,0 +1,459 @@ +{ + "Resources": { + "MyFnCognitoPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFn" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/cognito", + { + "__Stage__": "Prod", + "__ApiId__": { + "Ref": "MyApiWithCognitoAuth" + } + } + ] + } + } + }, + "MyApiWithCognitoAuth": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/cognito": { + "get": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws-cn:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFn.Arn}/invocations" + } + }, + "responses": {} + } + } + }, + "swagger": "2.0", + "securityDefinitions": { + "MyCognitoAuth": { + "in": "header", + "type": "apiKey", + "name": "Authorization", + "x-amazon-apigateway-authorizer": { + "providerARNs": [{ + "Fn::GetAtt": [ + "MyUserPool", + "Arn" + ] + }], + "type": "cognito_user_pools" + }, + "x-amazon-apigateway-authtype": "cognito_user_pools" + } + } + }, + "EndpointConfiguration": { + "Types": [ + "REGIONAL" + ] + }, + "Parameters": { + "endpointConfigurationTypes": "REGIONAL" + } + } + }, + "MyApiWithLambdaRequestAuthDeployment93e0147508": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "MyApiWithLambdaRequestAuth" + }, + "Description": "RestApi deployment id: 93e01475088ff4675852021a99279d60fc93cd6a", + "StageName": "Stage" + } + }, + "MyApiWithCognitoAuthDeployment6a169547ee": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "MyApiWithCognitoAuth" + }, + "Description": "RestApi deployment id: 6a169547eef02f4a0cd9fdc97aca9d1e8a106b11", + "StageName": "Stage" + } + }, + "MyApiWithLambdaRequestAuthProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "DeploymentId": { + "Ref": "MyApiWithLambdaRequestAuthDeployment93e0147508" + }, + "RestApiId": { + "Ref": "MyApiWithLambdaRequestAuth" + }, + "StageName": "Prod" + } + }, + "MyFnLambdaTokenPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFn" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/lambda-token", + { + "__Stage__": "Prod", + "__ApiId__": { + "Ref": "MyApiWithLambdaTokenAuth" + } + } + ] + } + } + }, + "MyFnLambdaRequestPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFn" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/lambda-request", + { + "__Stage__": "Prod", + "__ApiId__": { + "Ref": "MyApiWithLambdaRequestAuth" + } + } + ] + } + } + }, + "MyApiWithLambdaTokenAuth": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/lambda-token": { + "get": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws-cn:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFn.Arn}/invocations" + } + }, + "responses": {} + } + } + }, + "swagger": "2.0", + "securityDefinitions": { + "MyLambdaTokenAuth": { + "in": "header", + "type": "apiKey", + "name": "Authorization", + "x-amazon-apigateway-authorizer": { + "type": "token", + "authorizerUri": { + "Fn::Sub": [ + "arn:aws-cn:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${__FunctionArn__}/invocations", + { + "__FunctionArn__": { + "Fn::GetAtt": [ + "MyAuthFn", + "Arn" + ] + } + } + ] + } + }, + "x-amazon-apigateway-authtype": "custom" + } + } + }, + "EndpointConfiguration": { + "Types": [ + "REGIONAL" + ] + }, + "Parameters": { + "endpointConfigurationTypes": "REGIONAL" + } + } + }, + "MyApiWithLambdaTokenAuthProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "DeploymentId": { + "Ref": "MyApiWithLambdaTokenAuthDeploymente838608f2f" + }, + "RestApiId": { + "Ref": "MyApiWithLambdaTokenAuth" + }, + "StageName": "Prod" + } + }, + "MyFnLambdaRequestPermissionTest": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFn" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/lambda-request", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApiWithLambdaRequestAuth" + } + } + ] + } + } + }, + "MyFnLambdaTokenPermissionTest": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFn" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/lambda-token", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApiWithLambdaTokenAuth" + } + } + ] + } + } + }, + "MyFnCognitoPermissionTest": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFn" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/cognito", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApiWithCognitoAuth" + } + } + ] + } + } + }, + "MyApiWithLambdaRequestAuth": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/lambda-request": { + "get": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws-cn:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFn.Arn}/invocations" + } + }, + "responses": {} + } + } + }, + "swagger": "2.0", + "securityDefinitions": { + "MyLambdaRequestAuth": { + "in": "header", + "type": "apiKey", + "name": "Unused", + "x-amazon-apigateway-authorizer": { + "type": "request", + "identitySource": "method.request.header.Authorization1", + "authorizerUri": { + "Fn::Sub": [ + "arn:aws-cn:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${__FunctionArn__}/invocations", + { + "__FunctionArn__": { + "Fn::GetAtt": [ + "MyAuthFn", + "Arn" + ] + } + } + ] + } + }, + "x-amazon-apigateway-authtype": "custom" + } + } + }, + "EndpointConfiguration": { + "Types": [ + "REGIONAL" + ] + }, + "Parameters": { + "endpointConfigurationTypes": "REGIONAL" + } + } + }, + "MyApiWithLambdaTokenAuthMyLambdaTokenAuthAuthorizerPermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Fn::GetAtt": [ + "MyAuthFn", + "Arn" + ] + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/authorizers/*", + { + "__ApiId__": { + "Ref": "MyApiWithLambdaTokenAuth" + } + } + ] + } + } + }, + "MyApiWithLambdaRequestAuthMyLambdaRequestAuthAuthorizerPermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Fn::GetAtt": [ + "MyAuthFn", + "Arn" + ] + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-cn:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/authorizers/*", + { + "__ApiId__": { + "Ref": "MyApiWithLambdaRequestAuth" + } + } + ] + } + } + }, + "MyFnRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "ManagedPolicyArns": [ + "arn:aws-cn:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [{ + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + }] + } + } + }, + "MyApiWithCognitoAuthProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "DeploymentId": { + "Ref": "MyApiWithCognitoAuthDeployment6a169547ee" + }, + "RestApiId": { + "Ref": "MyApiWithCognitoAuth" + }, + "StageName": "Prod" + } + }, + "MyFn": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": "bucket", + "S3Key": "key" + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "MyFnRole", + "Arn" + ] + }, + "Runtime": "nodejs8.10", + "Tags": [{ + "Value": "SAM", + "Key": "lambda:createdBy" + }] + } + }, + "MyApiWithLambdaTokenAuthDeploymente838608f2f": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "MyApiWithLambdaTokenAuth" + }, + "Description": "RestApi deployment id: e838608f2f6897932f7883ba5afaa855145e38f5", + "StageName": "Stage" + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/aws-us-gov/api_with_auth_all_maximum.json b/tests/translator/output/aws-us-gov/api_with_auth_all_maximum.json new file mode 100644 index 0000000000..7faead9d31 --- /dev/null +++ b/tests/translator/output/aws-us-gov/api_with_auth_all_maximum.json @@ -0,0 +1,513 @@ +{ + "Resources": { + "MyFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "thumbnails.zip" + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "MyFunctionRole", + "Arn" + ] + }, + "Runtime": "nodejs8.10", + "Tags": [{ + "Value": "SAM", + "Key": "lambda:createdBy" + }] + } + }, + "MyFunctionWithLambdaTokenAuthorizerPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunction" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/PATCH/users", + { + "__Stage__": "Prod", + "__ApiId__": { + "Ref": "MyApi" + } + } + ] + } + } + }, + "MyApiMyLambdaRequestAuthAuthorizerPermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": "arn:aws", + "SourceArn": { + "Fn::Sub": [ + "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/authorizers/*", + { + "__ApiId__": { + "Ref": "MyApi" + } + } + ] + } + } + }, + "MyFunctionWithLambdaTokenAuthorizerPermissionTest": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunction" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/PATCH/users", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApi" + } + } + ] + } + } + }, + "MyFunctionWithDefaultAuthorizerPermissionTest": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunction" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/PUT/users", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApi" + } + } + ] + } + } + }, + "MyFunctionWithCognitoMultipleUserPoolsAuthorizerPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunction" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/POST/users", + { + "__Stage__": "Prod", + "__ApiId__": { + "Ref": "MyApi" + } + } + ] + } + } + }, + "MyFunctionWithNoAuthorizerPermissionTest": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunction" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApi" + } + } + ] + } + } + }, + "MyFunctionRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "ManagedPolicyArns": [ + "arn:aws-us-gov:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [{ + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + }] + } + } + }, + "MyApi": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/": { + "get": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws-us-gov:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFunction.Arn}/invocations" + } + }, + "security": [{ + "NONE": [] + }], + "responses": {} + } + }, + "/users": { + "put": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws-us-gov:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFunction.Arn}/invocations" + } + }, + "security": [{ + "MyCognitoAuth": [] + }], + "responses": {} + }, + "post": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws-us-gov:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFunction.Arn}/invocations" + } + }, + "security": [{ + "MyCognitoAuthMultipleUserPools": [] + }], + "responses": {} + }, + "patch": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws-us-gov:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFunction.Arn}/invocations" + } + }, + "security": [{ + "MyLambdaTokenAuthNoneFunctionInvokeRole": [] + }], + "responses": {} + }, + "delete": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws-us-gov:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFunction.Arn}/invocations" + } + }, + "security": [{ + "MyLambdaRequestAuth": [] + }], + "responses": {} + } + } + }, + "swagger": "2.0", + "securityDefinitions": { + "MyCognitoAuth": { + "in": "header", + "type": "apiKey", + "name": "MyAuthorizationHeader", + "x-amazon-apigateway-authorizer": { + "identityValidationExpression": "myauthvalidationexpression", + "providerARNs": [ + "arn:aws:1" + ], + "type": "cognito_user_pools" + }, + "x-amazon-apigateway-authtype": "cognito_user_pools" + }, + "MyLambdaTokenAuthNoneFunctionInvokeRole": { + "in": "header", + "type": "apiKey", + "name": "Authorization", + "x-amazon-apigateway-authorizer": { + "type": "token", + "authorizerResultTtlInSeconds": 0, + "authorizerUri": { + "Fn::Sub": [ + "arn:aws-us-gov:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${__FunctionArn__}/invocations", + { + "__FunctionArn__": "arn:aws" + } + ] + } + }, + "x-amazon-apigateway-authtype": "custom" + }, + "MyCognitoAuthMultipleUserPools": { + "in": "header", + "type": "apiKey", + "name": "MyAuthorizationHeader2", + "x-amazon-apigateway-authorizer": { + "identityValidationExpression": "myauthvalidationexpression2", + "providerARNs": [ + "arn:aws:2", + "arn:aws:3" + ], + "type": "cognito_user_pools" + }, + "x-amazon-apigateway-authtype": "cognito_user_pools" + }, + "MyLambdaTokenAuth": { + "in": "header", + "type": "apiKey", + "name": "MyCustomAuthHeader", + "x-amazon-apigateway-authorizer": { + "type": "token", + "authorizerResultTtlInSeconds": 20, + "authorizerUri": { + "Fn::Sub": [ + "arn:aws-us-gov:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${__FunctionArn__}/invocations", + { + "__FunctionArn__": "arn:aws" + } + ] + }, + "authorizerCredentials": "arn:aws:iam::123456789012:role/S3Access", + "identityValidationExpression": "mycustomauthexpression" + }, + "x-amazon-apigateway-authtype": "custom" + }, + "MyLambdaRequestAuth": { + "in": "header", + "type": "apiKey", + "name": "Unused", + "x-amazon-apigateway-authorizer": { + "type": "request", + "authorizerResultTtlInSeconds": 0, + "identitySource": "method.request.header.Authorization1, method.request.querystring.Authorization2, stageVariables.Authorization3, context.Authorization4", + "authorizerUri": { + "Fn::Sub": [ + "arn:aws-us-gov:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${__FunctionArn__}/invocations", + { + "__FunctionArn__": "arn:aws" + } + ] + }, + "authorizerCredentials": "arn:aws:iam::123456789012:role/S3Access" + }, + "x-amazon-apigateway-authtype": "custom" + } + } + }, + "EndpointConfiguration": { + "Types": [ + "REGIONAL" + ] + }, + "Parameters": { + "endpointConfigurationTypes": "REGIONAL" + } + } + }, + "MyApiMyLambdaTokenAuthAuthorizerPermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": "arn:aws", + "SourceArn": { + "Fn::Sub": [ + "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/authorizers/*", + { + "__ApiId__": { + "Ref": "MyApi" + } + } + ] + } + } + }, + "MyFunctionWithLambdaRequestAuthorizerPermissionTest": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunction" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/DELETE/users", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApi" + } + } + ] + } + } + }, + "MyFunctionWithLambdaRequestAuthorizerPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunction" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/DELETE/users", + { + "__Stage__": "Prod", + "__ApiId__": { + "Ref": "MyApi" + } + } + ] + } + } + }, + "MyApiDeploymentf9e0be23dc": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "MyApi" + }, + "Description": "RestApi deployment id: f9e0be23dccfaabdc2729c4d2221a3eeaa8e87db", + "StageName": "Stage" + } + }, + "MyFunctionWithCognitoMultipleUserPoolsAuthorizerPermissionTest": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunction" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/POST/users", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApi" + } + } + ] + } + } + }, + "MyApiMyLambdaTokenAuthNoneFunctionInvokeRoleAuthorizerPermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": "arn:aws", + "SourceArn": { + "Fn::Sub": [ + "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/authorizers/*", + { + "__ApiId__": { + "Ref": "MyApi" + } + } + ] + } + } + }, + "MyApiProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "DeploymentId": { + "Ref": "MyApiDeploymentf9e0be23dc" + }, + "RestApiId": { + "Ref": "MyApi" + }, + "StageName": "Prod" + } + }, + "MyFunctionWithNoAuthorizerPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunction" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/", + { + "__Stage__": "Prod", + "__ApiId__": { + "Ref": "MyApi" + } + } + ] + } + } + }, + "MyFunctionWithDefaultAuthorizerPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFunction" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/PUT/users", + { + "__Stage__": "Prod", + "__ApiId__": { + "Ref": "MyApi" + } + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/aws-us-gov/api_with_auth_all_minimum.json b/tests/translator/output/aws-us-gov/api_with_auth_all_minimum.json new file mode 100644 index 0000000000..999138fa0f --- /dev/null +++ b/tests/translator/output/aws-us-gov/api_with_auth_all_minimum.json @@ -0,0 +1,468 @@ +{ + "Resources": { + "MyFnCognitoPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFn" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/cognito", + { + "__Stage__": "Prod", + "__ApiId__": { + "Ref": "MyApiWithCognitoAuth" + } + } + ] + } + } + }, + "MyApiWithCognitoAuth": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/cognito": { + "get": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws-us-gov:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFn.Arn}/invocations" + } + }, + "security": [{ + "MyCognitoAuth": [] + }], + "responses": {} + } + } + }, + "swagger": "2.0", + "securityDefinitions": { + "MyCognitoAuth": { + "in": "header", + "type": "apiKey", + "name": "Authorization", + "x-amazon-apigateway-authorizer": { + "providerARNs": [{ + "Fn::GetAtt": [ + "MyUserPool", + "Arn" + ] + }], + "type": "cognito_user_pools" + }, + "x-amazon-apigateway-authtype": "cognito_user_pools" + } + } + }, + "EndpointConfiguration": { + "Types": [ + "REGIONAL" + ] + }, + "Parameters": { + "endpointConfigurationTypes": "REGIONAL" + } + } + }, + "MyApiWithLambdaRequestAuthMyLambdaRequestAuthAuthorizerPermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Fn::GetAtt": [ + "MyAuthFn", + "Arn" + ] + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/authorizers/*", + { + "__ApiId__": { + "Ref": "MyApiWithLambdaRequestAuth" + } + } + ] + } + } + }, + "MyApiWithLambdaRequestAuthProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "DeploymentId": { + "Ref": "MyApiWithLambdaRequestAuthDeploymentca86749bcd" + }, + "RestApiId": { + "Ref": "MyApiWithLambdaRequestAuth" + }, + "StageName": "Prod" + } + }, + "MyFnLambdaTokenPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFn" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/lambda-token", + { + "__Stage__": "Prod", + "__ApiId__": { + "Ref": "MyApiWithLambdaTokenAuth" + } + } + ] + } + } + }, + "MyFnLambdaRequestPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFn" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/lambda-request", + { + "__Stage__": "Prod", + "__ApiId__": { + "Ref": "MyApiWithLambdaRequestAuth" + } + } + ] + } + } + }, + "MyApiWithLambdaTokenAuth": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/lambda-token": { + "get": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws-us-gov:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFn.Arn}/invocations" + } + }, + "security": [{ + "MyLambdaTokenAuth": [] + }], + "responses": {} + } + } + }, + "swagger": "2.0", + "securityDefinitions": { + "MyLambdaTokenAuth": { + "in": "header", + "type": "apiKey", + "name": "Authorization", + "x-amazon-apigateway-authorizer": { + "type": "token", + "authorizerUri": { + "Fn::Sub": [ + "arn:aws-us-gov:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${__FunctionArn__}/invocations", + { + "__FunctionArn__": { + "Fn::GetAtt": [ + "MyAuthFn", + "Arn" + ] + } + } + ] + } + }, + "x-amazon-apigateway-authtype": "custom" + } + } + }, + "EndpointConfiguration": { + "Types": [ + "REGIONAL" + ] + }, + "Parameters": { + "endpointConfigurationTypes": "REGIONAL" + } + } + }, + "MyApiWithLambdaTokenAuthProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "DeploymentId": { + "Ref": "MyApiWithLambdaTokenAuthDeployment5192789870" + }, + "RestApiId": { + "Ref": "MyApiWithLambdaTokenAuth" + }, + "StageName": "Prod" + } + }, + "MyFnLambdaRequestPermissionTest": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFn" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/lambda-request", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApiWithLambdaRequestAuth" + } + } + ] + } + } + }, + "MyFnLambdaTokenPermissionTest": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFn" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/lambda-token", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApiWithLambdaTokenAuth" + } + } + ] + } + } + }, + "MyFnCognitoPermissionTest": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFn" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/cognito", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApiWithCognitoAuth" + } + } + ] + } + } + }, + "MyApiWithLambdaRequestAuth": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/lambda-request": { + "get": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws-us-gov:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFn.Arn}/invocations" + } + }, + "security": [{ + "MyLambdaRequestAuth": [] + }], + "responses": {} + } + } + }, + "swagger": "2.0", + "securityDefinitions": { + "MyLambdaRequestAuth": { + "in": "header", + "type": "apiKey", + "name": "Unused", + "x-amazon-apigateway-authorizer": { + "type": "request", + "identitySource": "method.request.header.Authorization1", + "authorizerUri": { + "Fn::Sub": [ + "arn:aws-us-gov:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${__FunctionArn__}/invocations", + { + "__FunctionArn__": { + "Fn::GetAtt": [ + "MyAuthFn", + "Arn" + ] + } + } + ] + } + }, + "x-amazon-apigateway-authtype": "custom" + } + } + }, + "EndpointConfiguration": { + "Types": [ + "REGIONAL" + ] + }, + "Parameters": { + "endpointConfigurationTypes": "REGIONAL" + } + } + }, + "MyApiWithLambdaRequestAuthDeploymentca86749bcd": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "MyApiWithLambdaRequestAuth" + }, + "Description": "RestApi deployment id: ca86749bcd339b4d6564954e2e12b20ebf9fb2ff", + "StageName": "Stage" + } + }, + "MyApiWithLambdaTokenAuthMyLambdaTokenAuthAuthorizerPermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Fn::GetAtt": [ + "MyAuthFn", + "Arn" + ] + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/authorizers/*", + { + "__ApiId__": { + "Ref": "MyApiWithLambdaTokenAuth" + } + } + ] + } + } + }, + "MyApiWithLambdaTokenAuthDeployment5192789870": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "MyApiWithLambdaTokenAuth" + }, + "Description": "RestApi deployment id: 5192789870157c12b0fb5a78c7e570d22c4e46f5", + "StageName": "Stage" + } + }, + "MyFnRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "ManagedPolicyArns": [ + "arn:aws-us-gov:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [{ + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + }] + } + } + }, + "MyApiWithCognitoAuthDeployment9b695a6dd5": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "MyApiWithCognitoAuth" + }, + "Description": "RestApi deployment id: 9b695a6dd5c12bb346b4163227f398c34128a49a", + "StageName": "Stage" + } + }, + "MyApiWithCognitoAuthProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "DeploymentId": { + "Ref": "MyApiWithCognitoAuthDeployment9b695a6dd5" + }, + "RestApiId": { + "Ref": "MyApiWithCognitoAuth" + }, + "StageName": "Prod" + } + }, + "MyFn": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": "bucket", + "S3Key": "key" + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "MyFnRole", + "Arn" + ] + }, + "Runtime": "nodejs8.10", + "Tags": [{ + "Value": "SAM", + "Key": "lambda:createdBy" + }] + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/aws-us-gov/api_with_auth_no_default.json b/tests/translator/output/aws-us-gov/api_with_auth_no_default.json new file mode 100644 index 0000000000..2f19c769ac --- /dev/null +++ b/tests/translator/output/aws-us-gov/api_with_auth_no_default.json @@ -0,0 +1,459 @@ +{ + "Resources": { + "MyFnCognitoPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFn" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/cognito", + { + "__Stage__": "Prod", + "__ApiId__": { + "Ref": "MyApiWithCognitoAuth" + } + } + ] + } + } + }, + "MyApiWithCognitoAuth": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/cognito": { + "get": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws-us-gov:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFn.Arn}/invocations" + } + }, + "responses": {} + } + } + }, + "swagger": "2.0", + "securityDefinitions": { + "MyCognitoAuth": { + "in": "header", + "type": "apiKey", + "name": "Authorization", + "x-amazon-apigateway-authorizer": { + "providerARNs": [{ + "Fn::GetAtt": [ + "MyUserPool", + "Arn" + ] + }], + "type": "cognito_user_pools" + }, + "x-amazon-apigateway-authtype": "cognito_user_pools" + } + } + }, + "EndpointConfiguration": { + "Types": [ + "REGIONAL" + ] + }, + "Parameters": { + "endpointConfigurationTypes": "REGIONAL" + } + } + }, + "MyApiWithCognitoAuthDeployment2da3114321": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "MyApiWithCognitoAuth" + }, + "Description": "RestApi deployment id: 2da3114321f3a31e83ea7029e5b167e14f36e7fb", + "StageName": "Stage" + } + }, + "MyApiWithLambdaRequestAuthMyLambdaRequestAuthAuthorizerPermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Fn::GetAtt": [ + "MyAuthFn", + "Arn" + ] + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/authorizers/*", + { + "__ApiId__": { + "Ref": "MyApiWithLambdaRequestAuth" + } + } + ] + } + } + }, + "MyApiWithLambdaTokenAuthDeployment613e605d96": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "MyApiWithLambdaTokenAuth" + }, + "Description": "RestApi deployment id: 613e605d962dfc3e8ac8e456357d807af4264223", + "StageName": "Stage" + } + }, + "MyApiWithLambdaRequestAuthProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "DeploymentId": { + "Ref": "MyApiWithLambdaRequestAuthDeployment9a21d88fe2" + }, + "RestApiId": { + "Ref": "MyApiWithLambdaRequestAuth" + }, + "StageName": "Prod" + } + }, + "MyFnLambdaTokenPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFn" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/lambda-token", + { + "__Stage__": "Prod", + "__ApiId__": { + "Ref": "MyApiWithLambdaTokenAuth" + } + } + ] + } + } + }, + "MyFnLambdaRequestPermissionProd": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFn" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/lambda-request", + { + "__Stage__": "Prod", + "__ApiId__": { + "Ref": "MyApiWithLambdaRequestAuth" + } + } + ] + } + } + }, + "MyApiWithLambdaTokenAuth": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/lambda-token": { + "get": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws-us-gov:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFn.Arn}/invocations" + } + }, + "responses": {} + } + } + }, + "swagger": "2.0", + "securityDefinitions": { + "MyLambdaTokenAuth": { + "in": "header", + "type": "apiKey", + "name": "Authorization", + "x-amazon-apigateway-authorizer": { + "type": "token", + "authorizerUri": { + "Fn::Sub": [ + "arn:aws-us-gov:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${__FunctionArn__}/invocations", + { + "__FunctionArn__": { + "Fn::GetAtt": [ + "MyAuthFn", + "Arn" + ] + } + } + ] + } + }, + "x-amazon-apigateway-authtype": "custom" + } + } + }, + "EndpointConfiguration": { + "Types": [ + "REGIONAL" + ] + }, + "Parameters": { + "endpointConfigurationTypes": "REGIONAL" + } + } + }, + "MyApiWithLambdaTokenAuthProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "DeploymentId": { + "Ref": "MyApiWithLambdaTokenAuthDeployment613e605d96" + }, + "RestApiId": { + "Ref": "MyApiWithLambdaTokenAuth" + }, + "StageName": "Prod" + } + }, + "MyFnLambdaRequestPermissionTest": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFn" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/lambda-request", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApiWithLambdaRequestAuth" + } + } + ] + } + } + }, + "MyFnLambdaTokenPermissionTest": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFn" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/lambda-token", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApiWithLambdaTokenAuth" + } + } + ] + } + } + }, + "MyFnCognitoPermissionTest": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Ref": "MyFn" + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/${__Stage__}/GET/cognito", + { + "__Stage__": "*", + "__ApiId__": { + "Ref": "MyApiWithCognitoAuth" + } + } + ] + } + } + }, + "MyApiWithLambdaRequestAuth": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Body": { + "info": { + "version": "1.0", + "title": { + "Ref": "AWS::StackName" + } + }, + "paths": { + "/lambda-request": { + "get": { + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::Sub": "arn:aws-us-gov:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyFn.Arn}/invocations" + } + }, + "responses": {} + } + } + }, + "swagger": "2.0", + "securityDefinitions": { + "MyLambdaRequestAuth": { + "in": "header", + "type": "apiKey", + "name": "Unused", + "x-amazon-apigateway-authorizer": { + "type": "request", + "identitySource": "method.request.header.Authorization1", + "authorizerUri": { + "Fn::Sub": [ + "arn:aws-us-gov:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${__FunctionArn__}/invocations", + { + "__FunctionArn__": { + "Fn::GetAtt": [ + "MyAuthFn", + "Arn" + ] + } + } + ] + } + }, + "x-amazon-apigateway-authtype": "custom" + } + } + }, + "EndpointConfiguration": { + "Types": [ + "REGIONAL" + ] + }, + "Parameters": { + "endpointConfigurationTypes": "REGIONAL" + } + } + }, + "MyApiWithLambdaTokenAuthMyLambdaTokenAuthAuthorizerPermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:invokeFunction", + "Principal": "apigateway.amazonaws.com", + "FunctionName": { + "Fn::GetAtt": [ + "MyAuthFn", + "Arn" + ] + }, + "SourceArn": { + "Fn::Sub": [ + "arn:aws-us-gov:execute-api:${AWS::Region}:${AWS::AccountId}:${__ApiId__}/authorizers/*", + { + "__ApiId__": { + "Ref": "MyApiWithLambdaTokenAuth" + } + } + ] + } + } + }, + "MyApiWithLambdaRequestAuthDeployment9a21d88fe2": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "MyApiWithLambdaRequestAuth" + }, + "Description": "RestApi deployment id: 9a21d88fe25a74e9f2ca61175f4dd4d281b61d12", + "StageName": "Stage" + } + }, + "MyFnRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "ManagedPolicyArns": [ + "arn:aws-us-gov:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [{ + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + }] + } + } + }, + "MyApiWithCognitoAuthProdStage": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "DeploymentId": { + "Ref": "MyApiWithCognitoAuthDeployment2da3114321" + }, + "RestApiId": { + "Ref": "MyApiWithCognitoAuth" + }, + "StageName": "Prod" + } + }, + "MyFn": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": "bucket", + "S3Key": "key" + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "MyFnRole", + "Arn" + ] + }, + "Runtime": "nodejs8.10", + "Tags": [{ + "Value": "SAM", + "Key": "lambda:createdBy" + }] + } + } + } +} \ No newline at end of file diff --git a/tests/translator/test_translator.py b/tests/translator/test_translator.py index 5bf0561047..d6bbf5abab 100644 --- a/tests/translator/test_translator.py +++ b/tests/translator/test_translator.py @@ -103,6 +103,9 @@ class TestTranslatorEndToEnd(TestCase): 'implicit_api', 'explicit_api', 'api_endpoint_configuration', + 'api_with_auth_all_maximum', + 'api_with_auth_all_minimum', + 'api_with_auth_no_default', 'api_with_method_settings', 'api_with_binary_media_types', 'api_with_resource_refs', From 90b3a39412fe232fbc3ed890d46e9b787eab4a37 Mon Sep 17 00:00:00 2001 From: Brett Andrews Date: Mon, 17 Sep 2018 16:15:16 -0700 Subject: [PATCH 15/19] convert result of map() to list for py3 support --- samtranslator/model/apigateway.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/samtranslator/model/apigateway.py b/samtranslator/model/apigateway.py index 6b5204378c..3d2e3dcb59 100644 --- a/samtranslator/model/apigateway.py +++ b/samtranslator/model/apigateway.py @@ -181,16 +181,16 @@ def _get_identity_source(self): identity_source_context = [] if self.identity.get('Headers'): - identity_source_headers = map(lambda h: 'method.request.header.' + h, self.identity.get('Headers')) + identity_source_headers = list(map(lambda h: 'method.request.header.' + h, self.identity.get('Headers'))) if self.identity.get('QueryStrings'): - identity_source_query_strings = map(lambda qs: 'method.request.querystring.' + qs, self.identity.get('QueryStrings')) + identity_source_query_strings = list(map(lambda qs: 'method.request.querystring.' + qs, self.identity.get('QueryStrings'))) if self.identity.get('StageVariables'): - identity_source_stage_variables = map(lambda sv: 'stageVariables.' + sv, self.identity.get('StageVariables')) + identity_source_stage_variables = list(map(lambda sv: 'stageVariables.' + sv, self.identity.get('StageVariables'))) if self.identity.get('Context'): - identity_source_context = map(lambda c: 'context.' + c, self.identity.get('Context')) + identity_source_context = list(map(lambda c: 'context.' + c, self.identity.get('Context'))) identity_source_array = identity_source_headers + identity_source_query_strings + identity_source_stage_variables + identity_source_context identity_source = ', '.join(identity_source_array) From 6dca4c7b3582045f167b89520c18b2cecb307f88 Mon Sep 17 00:00:00 2001 From: Brett Andrews Date: Tue, 18 Sep 2018 11:05:19 -0700 Subject: [PATCH 16/19] update spec to include API Auth and Function Auth --- versions/2016-10-31.md | 61 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 58 insertions(+), 3 deletions(-) diff --git a/versions/2016-10-31.md b/versions/2016-10-31.md index f7189794e0..05b6752de1 100644 --- a/versions/2016-10-31.md +++ b/versions/2016-10-31.md @@ -217,6 +217,7 @@ MethodSettings | [CloudFormation MethodSettings property](https://docs.aws.amazo EndpointConfiguration | `string` | Specify the type of endpoint for API endpoint. Value is either `REGIONAL`, `EDGE`, or `PRIVATE`. BinaryMediaTypes | List of `string` | List of MIME types that your API could return. Use this to enable binary support for APIs. Use `~1` instead of `/` in the mime types (See examples in [template.yaml](../examples/2016-10-31/implicit_api_settings/template.yaml)). Cors | `string` or [Cors Configuration](#cors-configuration) | Enable CORS for all your APIs. Specify the domain to allow as a string or specify a dictionary with additional [Cors Configuration](#cors-configuration). NOTE: Cors requires SAM to modify your Swagger definition. Hence it works only inline swagger defined with `DefinitionBody`. +Auth | [API Auth Object](#api-auth-object) | Auth configuration for this API. Define Lambda and Cognito `Authorizers` and specify a `DefaultAuthorizer` for this API. ##### Return values @@ -405,6 +406,7 @@ Property Name | Type | Description Path | `string` | **Required.** Uri path for which this function is invoked. MUST start with `/`. Method | `string` | **Required.** HTTP method for which this function is invoked. RestApiId | `string` | Identifier of a RestApi resource which MUST contain an operation with the given path and method. Typically, this is set to [reference](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-ref.html) an `AWS::Serverless::Api` resource defined in this template. If not defined, a default `AWS::Serverless::Api` resource is created using a generated Swagger document contains a union of all paths and methods defined by `Api` events defined in this template that do not specify a RestApiId. +Auth | [Function Auth Object](#function-auth-object) | Auth configuration for this specific Api+Path+Method. Useful for overriding the API's `DefaultAuthorizer` or setting auth config on an individual path when no `DefaultAuthorizer` is specified. ##### Example: Api event source object @@ -569,11 +571,14 @@ Properties: ``` ### Data Types + #### S3 Location Object + Specifies the location of an S3 object as a dictionary containing `Bucket`, `Key`, and optional `Version` properties. Example: -``` + +```yaml CodeUri: Bucket: mybucket-name Key: code.zip @@ -584,7 +589,8 @@ CodeUri: Specifies an SQS queue or SNS topic that AWS Lambda (Lambda) sends events to when it can't process them. For more information about DLQ functionality, refer to the officiall documentation at http://docs.aws.amazon.com/lambda/latest/dg/dlq.html. SAM will automatically add appropriate permission to your Lambda function execution role to give Lambda service access to the resource. `sqs:SendMessage` will be added for SQS queues and `sns:Publish` for SNS topics. Syntax: -``` + +```yaml DeadLetterQueue: Type: `SQS` or `SNS` TargetArn: ARN of the SQS queue or SNS topic to use as DLQ. @@ -611,7 +617,6 @@ DeploymentPreference: Enable and configure CORS for the APIs. Enabling CORS will allow your API to be called from other domains. Assume your API is served from 'www.example.com' and you want to allow. ```yaml - Cors: AllowMethods: Optional. String containing the HTTP methods to allow. # For example, "'GET,POST,DELETE'". If you omit this property, then SAM will automatically allow all the methods configured for each API. @@ -628,3 +633,53 @@ Cors: ``` > NOTE: HTTP spec requires the value of Allow properties to be a quoted string. So don't forget the additional quotes in the value. ie. "'www.example.com'" is correct whereas "www.example.com" is wrong + +#### API Auth Object + +Configure Auth on APIs. Define Lambda and Cognito `Authorizers` and specify a `DefaultAuthorizer`. + +```yaml +Auth: + DefaultAuthorizer: MyCognitoAuth # OPTIONAL + Authorizers: + MyCognitoAuth: + Type: COGNITO_USER_POOLS # OPTIONAL; Defaults to 'COGNITO_USER_POOLS' when `UserPoolArn` is specified + UserPoolArn: !GetAtt MyCognitoUserPool.Arn # Can also accept an array + Identity: # OPTIONAL + Header: MyAuthorizationHeader # OPTIONAL; Default: 'Authorization' + ValidationExpression: myauthvalidationexpression # OPTIONAL + + MyLambdaTokenAuth: + FunctionPayloadType: TOKEN # OPTIONAL; Defaults to 'TOKEN' when `FunctionArn` is specified + FunctionArn: !GetAtt MyAuthFunction.Arn + FunctionInvokeRole: arn:aws:iam::123456789012:role/S3Access # OPTIONAL + Identity: + Header: MyCustomAuthHeader # OPTIONAL; Default: 'Authorization' + ValidationExpression: mycustomauthexpression # OPTIONAL + ReauthorizeEvery: 20 # OPTIONAL; Service Default: 300 + + MyLambdaRequestAuth: + FunctionPayloadType: REQUEST + FunctionArn: !GetAtt MyAuthFunction.Arn + FunctionInvokeRole: arn:aws:iam::123456789012:role/S3Access # OPTIONAL + Identity: + # Must specify at least one of Headers, QueryStrings, StageVariables, or Context + Headers: # OPTIONAL + - Authorization1 + QueryStrings: # OPTIONAL + - Authorization2 + StageVariables: # OPTIONAL + - Authorization3 + Context: # OPTIONAL + - Authorization4 + ReauthorizeEvery: 0 # OPTIONAL; Service Default: 300 +``` + +#### Function Auth Object + +Configure Auth for a specific Api+Path+Method. + +```yaml +Auth: + Authorizer: MyCognitoAuth # OPTIONAL +``` \ No newline at end of file From c28ae1aaa6e93558874677d4d3256244eb9ff5c7 Mon Sep 17 00:00:00 2001 From: Brett Andrews Date: Tue, 18 Sep 2018 14:05:59 -0700 Subject: [PATCH 17/19] add TOC for Data Types in spec --- versions/2016-10-31.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/versions/2016-10-31.md b/versions/2016-10-31.md index 05b6752de1..fdde540f47 100644 --- a/versions/2016-10-31.md +++ b/versions/2016-10-31.md @@ -572,6 +572,12 @@ Properties: ### Data Types +- [S3 Location Object](#s3-location-object) +- [DeadLetterQueue Object](#deadletterqueue-object) +- [Cors Configuration](#cors-configuration) +- [API Auth Object](#api-auth-object) +- [Function Auth Object](#function-auth-object) + #### S3 Location Object Specifies the location of an S3 object as a dictionary containing `Bucket`, `Key`, and optional `Version` properties. From 847646d6b468c4d4e53f6118f6f9e4050c38a804 Mon Sep 17 00:00:00 2001 From: Brett Andrews Date: Thu, 20 Sep 2018 10:43:45 -0700 Subject: [PATCH 18/19] address documentation change requests --- versions/2016-10-31.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/versions/2016-10-31.md b/versions/2016-10-31.md index fdde540f47..497f0d773f 100644 --- a/versions/2016-10-31.md +++ b/versions/2016-10-31.md @@ -208,8 +208,8 @@ Property Name | Type | Description ---|:---:|--- Name | `string` | A name for the API Gateway RestApi resource. StageName | `string` | **Required** The name of the stage, which API Gateway uses as the first path segment in the invoke Uniform Resource Identifier (URI). -DefinitionUri | `string` | [S3 Location Object](#s3-location-object) | S3 URI or location to the Swagger document describing the API. Either one of `DefinitionUri` or `DefinitionBody` must be specified. -DefinitionBody | `JSON or YAML Object` | Swagger specification that describes your API. Either one of `DefinitionUri` or `DefinitionBody` must be specified. +DefinitionUri | `string` | [S3 Location Object](#s3-location-object) | S3 URI or location to the Swagger document describing the API. If neither `DefinitionUri` nor `DefinitionBody` are specified, SAM will generate a `DefinitionBody` for you based on your template configuration. +DefinitionBody | `JSON or YAML Object` | Swagger specification that describes your API. If neither `DefinitionUri` nor `DefinitionBody` are specified, SAM will generate a `DefinitionBody` for you based on your template configuration. CacheClusterEnabled | `boolean` | Indicates whether cache clustering is enabled for the stage. CacheClusterSize | `string` | The stage's cache cluster size. Variables | Map of `string` to `string` | A map (string to string map) that defines the stage variables, where the variable name is the key and the variable value is the value. Variable names are limited to alphanumeric characters. Values must match the following regular expression: `[A-Za-z0-9._~:/?#&=,-]+`. @@ -642,14 +642,13 @@ Cors: #### API Auth Object -Configure Auth on APIs. Define Lambda and Cognito `Authorizers` and specify a `DefaultAuthorizer`. +Configure Auth on APIs. Define Lambda and Cognito `Authorizers` and specify a `DefaultAuthorizer`. For more information, see the documentation on [Lambda Authorizers](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-use-lambda-authorizer.html) and [Amazon Cognito User Pool Authorizers](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-integrate-with-cognito.html). ```yaml Auth: DefaultAuthorizer: MyCognitoAuth # OPTIONAL Authorizers: MyCognitoAuth: - Type: COGNITO_USER_POOLS # OPTIONAL; Defaults to 'COGNITO_USER_POOLS' when `UserPoolArn` is specified UserPoolArn: !GetAtt MyCognitoUserPool.Arn # Can also accept an array Identity: # OPTIONAL Header: MyAuthorizationHeader # OPTIONAL; Default: 'Authorization' From f6ac94bc4c74e090ea73b1a53a19a7a58250e44c Mon Sep 17 00:00:00 2001 From: Brett Andrews Date: Fri, 21 Sep 2018 13:12:30 -0700 Subject: [PATCH 19/19] address PR change requests --- examples/2016-10-31/api_lambda_request_auth/template.yaml | 3 +-- examples/2016-10-31/api_lambda_token_auth/template.yaml | 3 +-- samtranslator/model/api/api_generator.py | 5 ++--- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/examples/2016-10-31/api_lambda_request_auth/template.yaml b/examples/2016-10-31/api_lambda_request_auth/template.yaml index 9a5d7e3e3d..0bf1da16a0 100644 --- a/examples/2016-10-31/api_lambda_request_auth/template.yaml +++ b/examples/2016-10-31/api_lambda_request_auth/template.yaml @@ -16,6 +16,7 @@ Resources: Identity: QueryStrings: - auth + # NOTE: Additional options: # Headers: # - Authorization # StageVariables: @@ -45,8 +46,6 @@ Resources: RestApiId: !Ref MyApi Path: /users Method: get - # Auth: - # Authorizer: MyOtherAuthorizer MyAuthFunction: Type: AWS::Serverless::Function diff --git a/examples/2016-10-31/api_lambda_token_auth/template.yaml b/examples/2016-10-31/api_lambda_token_auth/template.yaml index e4bd94e37a..50d18a3195 100644 --- a/examples/2016-10-31/api_lambda_token_auth/template.yaml +++ b/examples/2016-10-31/api_lambda_token_auth/template.yaml @@ -11,6 +11,7 @@ Resources: Authorizers: MyLambdaTokenAuthorizer: FunctionArn: !GetAtt MyAuthFunction.Arn + # NOTE: Additional options: # FunctionInvokeRole: !Ref MyRole # Identity: # Header: Auth @@ -38,8 +39,6 @@ Resources: RestApiId: !Ref MyApi Path: /users Method: get - # Auth: - # Authorizer: MyOtherAuthorizer MyAuthFunction: Type: AWS::Serverless::Function diff --git a/samtranslator/model/api/api_generator.py b/samtranslator/model/api/api_generator.py index 3f1388e02f..5625cab747 100644 --- a/samtranslator/model/api/api_generator.py +++ b/samtranslator/model/api/api_generator.py @@ -224,8 +224,6 @@ def _add_auth(self): if not self.auth: return - INVALID_ERROR = "Invalid value for 'Auth' property" - if self.auth and not self.definition_body: raise InvalidResourceException(self.logical_id, "Auth works only with inline Swagger specified in " @@ -233,7 +231,8 @@ def _add_auth(self): # Make sure keys in the dict are recognized if not all(key in AuthProperties._fields for key in self.auth.keys()): - raise InvalidResourceException(self.logical_id, INVALID_ERROR) + raise InvalidResourceException( + self.logical_id, "Invalid value for 'Auth' property") if not SwaggerEditor.is_valid(self.definition_body): raise InvalidResourceException(self.logical_id, "Unable to add Auth configuration because "