def _codedeploy_iam_role(self): iam_role = IAMRole(CODE_DEPLOY_SERVICE_ROLE_LOGICAL_ID) iam_role.AssumeRolePolicyDocument = { "Version": "2012-10-17", "Statement": [ { "Action": ["sts:AssumeRole"], "Effect": "Allow", "Principal": {"Service": ["codedeploy.amazonaws.com"]}, } ], } # CodeDeploy has a new managed policy. We cannot update any existing partitions, without customer reach out # that support AWSCodeDeployRoleForLambda since this could regress stacks that are currently deployed. if ArnGenerator.get_partition_name() in ["aws-iso", "aws-iso-b"]: iam_role.ManagedPolicyArns = [ ArnGenerator.generate_aws_managed_policy_arn("service-role/AWSCodeDeployRoleForLambdaLimited") ] else: iam_role.ManagedPolicyArns = [ ArnGenerator.generate_aws_managed_policy_arn("service-role/AWSCodeDeployRoleForLambda") ] return iam_role
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, attributes=self.resource_attributes) 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", attributes=self.passthrough_resource_attributes ) lambda_permission.Action = "lambda:InvokeFunction" lambda_permission.FunctionName = authorizer_lambda_function_arn lambda_permission.Principal = "apigateway.amazonaws.com" lambda_permission.SourceArn = source_arn return lambda_permission
def _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_role(self, managed_policy_map): """Constructs a Lambda execution role based on this SAM function's Policies property. :returns: the generated IAM Role :rtype: model.iam.IAMRole """ execution_role = IAMRole(self.logical_id + 'Role') execution_role.AssumeRolePolicyDocument = IAMRolePolicies.lambda_assume_role_policy() managed_policy_arns = [ArnGenerator.generate_aws_managed_policy_arn('service-role/AWSLambdaBasicExecutionRole')] if self.Tracing: managed_policy_arns.append(ArnGenerator.generate_aws_managed_policy_arn('AWSXrayWriteOnlyAccess')) function_policies = FunctionPolicies({"Policies": self.Policies}, # No support for policy templates in the "core" policy_template_processor=None) policy_documents = [] if self.DeadLetterQueue: policy_documents.append(IAMRolePolicies.dead_letter_queue_policy( self.dead_letter_queue_policy_actions[self.DeadLetterQueue['Type']], self.DeadLetterQueue['TargetArn'])) for index, policy_entry in enumerate(function_policies.get()): if policy_entry.type is PolicyTypes.POLICY_STATEMENT: policy_documents.append({ 'PolicyName': execution_role.logical_id + 'Policy' + str(index), 'PolicyDocument': policy_entry.data }) elif policy_entry.type is PolicyTypes.MANAGED_POLICY: # There are three options: # Managed Policy Name (string): Try to convert to Managed Policy ARN # Managed Policy Arn (string): Insert it directly into the list # Intrinsic Function (dict): Insert it directly into the list # # When you insert into managed_policy_arns list, de-dupe to prevent same ARN from showing up twice # policy_arn = policy_entry.data if isinstance(policy_entry.data, string_types) and policy_entry.data in managed_policy_map: policy_arn = managed_policy_map[policy_entry.data] # De-Duplicate managed policy arns before inserting. Mainly useful # when customer specifies a managed policy which is already inserted # by SAM, such as AWSLambdaBasicExecutionRole if policy_arn not in managed_policy_arns: managed_policy_arns.append(policy_arn) else: # Policy Templates are not supported here in the "core" raise InvalidResourceException( self.logical_id, "Policy at index {} in the 'Policies' property is not valid".format(index)) execution_role.ManagedPolicyArns = list(managed_policy_arns) execution_role.Policies = policy_documents or None return execution_role
def to_cloudformation(self, **kwargs): function = kwargs.get('function') if not function: raise TypeError("Missing required keyword argument: function") resources = [] resource = 'rule/${RuleName}' partition = ArnGenerator.get_partition_name() source_arn = fnSub( ArnGenerator.generate_arn(partition=partition, service='iot', resource=resource), {'RuleName': ref(self.logical_id)}) source_account = fnSub('${AWS::AccountId}') resources.append( self._construct_permission(function, source_arn=source_arn, source_account=source_account)) resources.append(self._construct_iot_rule(function)) return resources
def _get_permission(self, resources_to_link, stage, suffix): # It turns out that APIGW doesn't like trailing slashes in paths (#665) # and removes as a part of their behaviour, but this isn't documented. # The regex removes the tailing slash to ensure the permission works as intended path = re.sub(r'^(.+)/$', r'\1', self.Path) if not stage or not suffix: raise RuntimeError("Could not add permission to lambda function.") path = re.sub(r'{([a-zA-Z0-9._-]+|proxy\+)}', '*', path) method = '*' if self.Method.lower() == 'any' else self.Method.upper() api_id = self.RestApiId # RestApiId can be a simple string or intrinsic function like !Ref. Using Fn::Sub will handle both cases resource = '${__ApiId__}/' + '${__Stage__}/' + method + path partition = ArnGenerator.get_partition_name() source_arn = fnSub( ArnGenerator.generate_arn(partition=partition, service='execute-api', resource=resource), { "__ApiId__": api_id, "__Stage__": stage }) return self._construct_permission(resources_to_link['function'], source_arn=source_arn, suffix=suffix)
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(), } if authorizer_type == "COGNITO_USER_POOLS": swagger[APIGATEWAY_AUTHORIZER_KEY] = { "type": self._get_swagger_authorizer_type(), "providerARNs": self._get_user_pool_arn_array(), } elif authorizer_type == "LAMBDA": swagger[APIGATEWAY_AUTHORIZER_KEY] = { "type": self._get_swagger_authorizer_type() } partition = ArnGenerator.get_partition_name() 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() 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 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: identity_validation_expression = self._get_identity_validation_expression( ) if identity_validation_expression: swagger[APIGATEWAY_AUTHORIZER_KEY][ "identityValidationExpression"] = identity_validation_expression return swagger
def get_source_arn(self): resource = "log-group:${__LogGroupName__}:*" partition = ArnGenerator.get_partition_name() return fnSub( ArnGenerator.generate_arn(partition=partition, service="logs", resource=resource), {"__LogGroupName__": self.LogGroupName}, )
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() 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() 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 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: identity_validation_expression = self._get_identity_validation_expression( ) if identity_validation_expression: swagger[APIGATEWAY_AUTHORIZER_KEY][ 'identityValidationExpression'] = identity_validation_expression return swagger
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() } if authorizer_type == 'COGNITO_USER_POOLS': swagger[APIGATEWAY_AUTHORIZER_KEY] = { 'type': self._get_swagger_authorizer_type(), 'providerARNs': self._get_user_pool_arn_array() } elif authorizer_type == 'LAMBDA': swagger[APIGATEWAY_AUTHORIZER_KEY] = { 'type': self._get_swagger_authorizer_type() } partition = ArnGenerator.get_partition_name() 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() 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 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: identity_validation_expression = self._get_identity_validation_expression() if identity_validation_expression: swagger[APIGATEWAY_AUTHORIZER_KEY]['identityValidationExpression'] = identity_validation_expression return swagger
def _get_permission(self, resources_to_link, stage, suffix): if not stage or not suffix: raise RuntimeError("Could not add permission to lambda function.") path = self.Path.replace('{proxy+}', '*') api_id = self.RestApiId # RestApiId can be a simple string or intrinsic function like !Ref. Using Fn::Sub will handle both cases resource = '${__ApiId__}/' + '${__Stage__}/' + self.Method.upper() + path partition = ArnGenerator.get_partition_name() source_arn = fnSub(ArnGenerator.generate_arn(partition=partition, service='execute-api', resource=resource), {"__ApiId__": api_id, "__Stage__": stage}) return self._construct_permission(resources_to_link['function'], source_arn=source_arn, suffix=suffix)
def get_xray_managed_policy_name(): # use previous (old) policy name for regular regions # for china and gov regions, use the newer policy name partition_name = ArnGenerator.get_partition_name() if partition_name == "aws": return "AWSXrayWriteOnlyAccess" return "AWSXRayDaemonWriteAccess"
def _add_swagger_integration(self, api, function): """Adds the path and method for this Api event source to the Swagger body for the provided RestApi. :param model.apigateway.ApiGatewayRestApi rest_api: the RestApi to which the path and method should be added. """ swagger_body = api.get("DefinitionBody") if swagger_body is None: return function_arn = function.get_runtime_attr('arn') partition = ArnGenerator.get_partition_name() uri = fnSub( 'arn:' + partition + ':apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/' + make_shorthand(function_arn) + '/invocations') editor = SwaggerEditor(swagger_body) if editor.has_integration(self.Path, self.Method): # Cannot add the Lambda Integration, if it is already present raise InvalidEventException( self.relative_id, 'API method "{method}" defined multiple times for path "{path}".' .format(method=self.Method, path=self.Path)) editor.add_lambda_integration(self.Path, self.Method, uri) api["DefinitionBody"] = editor.swagger
def _get_permission(self, resources_to_link, stage, suffix): if not stage or not suffix: raise RuntimeError("Could not add permission to lambda function.") path = self.Path.replace('{proxy+}', '*') method = '*' if self.Method.lower() == 'any' else self.Method.upper() api_id = self.RestApiId # RestApiId can be a simple string or intrinsic function like !Ref. Using Fn::Sub will handle both cases resource = '${__ApiId__}/' + '${__Stage__}/' + method + path partition = ArnGenerator.get_partition_name() source_arn = fnSub(ArnGenerator.generate_arn(partition=partition, service='execute-api', resource=resource), {"__ApiId__": api_id, "__Stage__": stage}) return self._construct_permission(resources_to_link['function'], source_arn=source_arn, suffix=suffix)
def test_get_partition_name_from_boto_session(self): ArnGenerator.BOTO_SESSION_REGION_NAME = "us-east-1" actual = ArnGenerator.get_partition_name() self.assertEqual(actual, "aws") ArnGenerator.BOTO_SESSION_REGION_NAME = None
def _add_swagger_integration(self, api, function): """Adds the path and method for this Api event source to the Swagger body for the provided RestApi. :param model.apigateway.ApiGatewayRestApi rest_api: the RestApi to which the path and method should be added. """ swagger_body = api.get("DefinitionBody") if swagger_body is None: return function_arn = function.get_runtime_attr('arn') partition = ArnGenerator.get_partition_name() uri = fnSub('arn:'+partition+':apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/' + make_shorthand(function_arn) + '/invocations') editor = SwaggerEditor(swagger_body) if editor.has_integration(self.Path, self.Method): # Cannot add the Lambda Integration, if it is already present raise InvalidEventException( self.relative_id, 'API method "{method}" defined multiple times for path "{path}".'.format( 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 ' '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
def test_get_partition_name_from_boto_session(self): boto_session_mock = Mock() boto_session_mock.region_name = "us-east-1" ArnGenerator.class_boto_session = boto_session_mock actual = ArnGenerator.get_partition_name() self.assertEqual(actual, "aws") ArnGenerator.class_boto_session = None
def to_cloudformation(self, **kwargs): function = kwargs.get('function') if not function: raise TypeError("Missing required keyword argument: function") resources = [] resource = 'rule/${RuleName}' partition = ArnGenerator.get_partition_name() source_arn = fnSub(ArnGenerator.generate_arn(partition=partition, service='iot', resource=resource), {'RuleName': ref(self.logical_id)}) source_account = fnSub('${AWS::AccountId}') resources.append(self._construct_permission(function, source_arn=source_arn, source_account=source_account)) resources.append(self._construct_iot_rule(function)) return resources
def _get_permission(self, resources_to_link, stage, suffix): # It turns out that APIGW doesn't like trailing slashes in paths (#665) # and removes as a part of their behaviour, but this isn't documented. # The regex removes the tailing slash to ensure the permission works as intended path = re.sub(r'^(.+)/$', r'\1', self.Path) if not stage or not suffix: raise RuntimeError("Could not add permission to lambda function.") path = path.replace('{proxy+}', '*') method = '*' if self.Method.lower() == 'any' else self.Method.upper() api_id = self.RestApiId # RestApiId can be a simple string or intrinsic function like !Ref. Using Fn::Sub will handle both cases resource = '${__ApiId__}/' + '${__Stage__}/' + method + path partition = ArnGenerator.get_partition_name() source_arn = fnSub(ArnGenerator.generate_arn(partition=partition, service='execute-api', resource=resource), {"__ApiId__": api_id, "__Stage__": stage}) return self._construct_permission(resources_to_link['function'], source_arn=source_arn, suffix=suffix)
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 _get_permission(self, resources_to_link, stage, suffix): # It turns out that APIGW doesn't like trailing slashes in paths (#665) # and removes as a part of their behaviour, but this isn't documented. # The regex removes the tailing slash to ensure the permission works as intended path = re.sub(r"^(.+)/$", r"\1", self.Path) if not stage or not suffix: raise RuntimeError("Could not add permission to lambda function.") path = SwaggerEditor.get_path_without_trailing_slash(path) method = "*" if self.Method.lower() == "any" else self.Method.upper() api_id = self.RestApiId # RestApiId can be a simple string or intrinsic function like !Ref. Using Fn::Sub will handle both cases resource = "${__ApiId__}/" + "${__Stage__}/" + method + path partition = ArnGenerator.get_partition_name() source_arn = fnSub( ArnGenerator.generate_arn(partition=partition, service="execute-api", resource=resource), {"__ApiId__": api_id, "__Stage__": stage}, ) return self._construct_permission(resources_to_link["function"], source_arn=source_arn, suffix=suffix)
def _codedeploy_iam_role(self): iam_role = IAMRole(CODE_DEPLOY_SERVICE_ROLE_LOGICAL_ID) iam_role.AssumeRolePolicyDocument = { 'Version': '2012-10-17', 'Statement': [{ 'Action': ['sts:AssumeRole'], 'Effect': 'Allow', 'Principal': {'Service': ['codedeploy.amazonaws.com']} }] } iam_role.ManagedPolicyArns = [ ArnGenerator.generate_aws_managed_policy_arn('service-role/AWSCodeDeployRoleForLambda') ] return iam_role
def add_pseudo_parameter_values(self, session=None): """ Add pseudo parameter values :return: parameter values that have pseudo parameter in it """ if session is None: session = boto3.session.Session() if not session.region_name: raise NoRegionFound("AWS Region cannot be found") if "AWS::Region" not in self.parameter_values: self.parameter_values["AWS::Region"] = session.region_name if "AWS::Partition" not in self.parameter_values: self.parameter_values[ "AWS::Partition"] = ArnGenerator.get_partition_name( session.region_name)
def _codedeploy_iam_role(self): iam_role = IAMRole(CODE_DEPLOY_SERVICE_ROLE_LOGICAL_ID) iam_role.AssumeRolePolicyDocument = { "Version": "2012-10-17", "Statement": [{ "Action": ["sts:AssumeRole"], "Effect": "Allow", "Principal": { "Service": ["codedeploy.amazonaws.com"] }, }], } iam_role.ManagedPolicyArns = [ ArnGenerator.generate_aws_managed_policy_arn( "service-role/AWSCodeDeployRoleForLambda") ] return iam_role
def _codedeploy_iam_role(self): iam_role = IAMRole(CODE_DEPLOY_SERVICE_ROLE_LOGICAL_ID) iam_role.AssumeRolePolicyDocument = { 'Version': '2012-10-17', 'Statement': [{ 'Action': ['sts:AssumeRole'], 'Effect': 'Allow', 'Principal': { 'Service': ['codedeploy.amazonaws.com'] } }] } iam_role.ManagedPolicyArns = [ ArnGenerator.generate_aws_managed_policy_arn( 'service-role/AWSCodeDeployRoleForLambda') ] return iam_role
def _get_permission(self, resources_to_link, stage): # It turns out that APIGW doesn't like trailing slashes in paths (#665) # and removes as a part of their behaviour, but this isn't documented. # The regex removes the tailing slash to ensure the permission works as intended path = re.sub(r"^(.+)/$", r"\1", self.Path) editor = None if resources_to_link["explicit_api"].get("DefinitionBody"): try: editor = OpenApiEditor(resources_to_link["explicit_api"].get("DefinitionBody")) except ValueError as e: api_logical_id = self.ApiId.get("Ref") if isinstance(self.ApiId, dict) else self.ApiId raise InvalidResourceException(api_logical_id, e) # If this is using the new $default path, keep path blank and add a * permission if path == OpenApiEditor._DEFAULT_PATH: path = "" elif editor and resources_to_link.get("function").logical_id == editor.get_integration_function_logical_id( OpenApiEditor._DEFAULT_PATH, OpenApiEditor._X_ANY_METHOD ): # Case where default exists for this function, and so the permissions for that will apply here as well # This can save us several CFN resources (not duplicating permissions) return else: path = OpenApiEditor.get_path_without_trailing_slash(path) # Handle case where Method is already the ANY ApiGateway extension if self.Method.lower() == "any" or self.Method.lower() == OpenApiEditor._X_ANY_METHOD: method = "*" else: method = self.Method.upper() api_id = self.ApiId # ApiId can be a simple string or intrinsic function like !Ref. Using Fn::Sub will handle both cases resource = "${__ApiId__}/" + "${__Stage__}/" + method + path source_arn = fnSub( ArnGenerator.generate_arn(partition="${AWS::Partition}", service="execute-api", resource=resource), {"__ApiId__": api_id, "__Stage__": stage}, ) return self._construct_permission(resources_to_link["function"], source_arn=source_arn)
def generate_openapi(self): """ Generates OAS for the securitySchemes section """ authorizer_type = self._get_auth_type() if authorizer_type == "JWT": openapi = {"type": "oauth2"} openapi[APIGATEWAY_AUTHORIZER_KEY] = { "jwtConfiguration": self.jwt_configuration, "identitySource": self.id_source, "type": "jwt", } if authorizer_type == "REQUEST": openapi = { "type": "apiKey", "name": "Unused", "in": "header", } openapi[APIGATEWAY_AUTHORIZER_KEY] = {"type": "request"} # Generate the lambda arn partition = ArnGenerator.get_partition_name() 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}, ) openapi[APIGATEWAY_AUTHORIZER_KEY][ "authorizerUri"] = authorizer_uri # Set authorizerCredentials if present function_invoke_role = self._get_function_invoke_role() if function_invoke_role: openapi[APIGATEWAY_AUTHORIZER_KEY][ "authorizerCredentials"] = function_invoke_role # Set authorizerResultTtlInSeconds if present reauthorize_every = self._get_reauthorize_every() if reauthorize_every is not None: openapi[APIGATEWAY_AUTHORIZER_KEY][ "authorizerResultTtlInSeconds"] = reauthorize_every # Set identitySource if present if self.identity: openapi[APIGATEWAY_AUTHORIZER_KEY][ "identitySource"] = self._get_identity_source() # Set authorizerPayloadFormatVersion. It's a required parameter openapi[APIGATEWAY_AUTHORIZER_KEY][ "authorizerPayloadFormatVersion"] = self.authorizer_payload_format_version # Set authorizerPayloadFormatVersion. It's a required parameter if self.enable_simple_responses: openapi[APIGATEWAY_AUTHORIZER_KEY][ "enableSimpleResponses"] = self.enable_simple_responses return openapi
def get_policy_arn(self): return ArnGenerator.generate_aws_managed_policy_arn( "service-role/AWSLambdaSQSQueueExecutionRole")
def get_policy_arn(self): return ArnGenerator.generate_aws_managed_policy_arn('service-role/AWSLambdaDynamoDBExecutionRole')
def get_source_arn(self): resource = "log-group:${__LogGroupName__}:*" partition = ArnGenerator.get_partition_name() return fnSub(ArnGenerator.generate_arn(partition=partition, service='logs', resource=resource), {'__LogGroupName__': self.LogGroupName})
def test_get_partition_name_raise_NoRegionFound(self): with self.assertRaises(NoRegionFound): ArnGenerator.get_partition_name(None)
def test_get_partition_name(self, region, expected): actual = ArnGenerator.get_partition_name(region) self.assertEqual(actual, expected)
def test_get_partition_name(self, region, expected_partition): self.assertEqual(expected_partition, ArnGenerator.get_partition_name(region=region))
def _add_swagger_integration(self, api, function): """Adds the path and method for this Api event source to the Swagger body for the provided RestApi. :param model.apigateway.ApiGatewayRestApi rest_api: the RestApi to which the path and method should be added. """ swagger_body = api.get("DefinitionBody") if swagger_body is None: return function_arn = function.get_runtime_attr('arn') partition = ArnGenerator.get_partition_name() uri = fnSub('arn:' + partition + ':apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/' + make_shorthand(function_arn) + '/invocations') editor = SwaggerEditor(swagger_body) if editor.has_integration(self.Path, self.Method): # Cannot add the Lambda Integration, if it is already present raise InvalidEventException( self.relative_id, 'API method "{method}" defined multiple times for path "{path}".'.format( method=self.Method, path=self.Path)) condition = None if CONDITION in function.resource_attributes: condition = function.resource_attributes[CONDITION] editor.add_lambda_integration(self.Path, self.Method, uri, self.Auth, api.get('Auth'), condition=condition) if self.Auth: method_authorizer = self.Auth.get('Authorizer') api_auth = api.get('Auth') if method_authorizer: api_authorizers = api_auth and api_auth.get('Authorizers') if method_authorizer != 'AWS_IAM': if method_authorizer != 'NONE' and 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 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)) apikey_required_setting = self.Auth.get('ApiKeyRequired') apikey_required_setting_is_false = apikey_required_setting is not None and not apikey_required_setting if apikey_required_setting_is_false and not api_auth.get('ApiKeyRequired'): raise InvalidEventException( self.relative_id, 'Unable to set ApiKeyRequired [False] on API method [{method}] for path [{path}] ' 'because the related API does not specify any ApiKeyRequired.'.format( method=self.Method, path=self.Path)) if method_authorizer or apikey_required_setting is not None: editor.add_auth_to_method(api=api, path=self.Path, method_name=self.Method, auth=self.Auth) if self.Auth.get('ResourcePolicy'): resource_policy = self.Auth.get('ResourcePolicy') editor.add_resource_policy(resource_policy=resource_policy, path=self.Path, api_id=self.RestApiId.get('Ref'), stage=self.Stage) if self.RequestModel: method_model = self.RequestModel.get('Model') if method_model: api_models = api.get('Models') if not api_models: raise InvalidEventException( self.relative_id, 'Unable to set RequestModel [{model}] on API method [{method}] for path [{path}] ' 'because the related API does not define any Models.'.format( model=method_model, method=self.Method, path=self.Path)) if not api_models.get(method_model): raise InvalidEventException( self.relative_id, 'Unable to set RequestModel [{model}] on API method [{method}] for path [{path}] ' 'because it wasn\'t defined in the API\'s Models.'.format( model=method_model, method=self.Method, path=self.Path)) editor.add_request_model_to_method(path=self.Path, method_name=self.Method, request_model=self.RequestModel) if self.RequestParameters: default_value = { 'Required': False, 'Caching': False } parameters = [] for parameter in self.RequestParameters: if isinstance(parameter, dict): parameter_name, parameter_value = next(iter(parameter.items())) if not re.match('method\.request\.(querystring|path|header)\.', parameter_name): raise InvalidEventException( self.relative_id, "Invalid value for 'RequestParameters' property. Keys must be in the format " "'method.request.[querystring|path|header].{value}', " "e.g 'method.request.header.Authorization'.") if not isinstance(parameter_value, dict) or not all(key in REQUEST_PARAMETER_PROPERTIES for key in parameter_value.keys()): raise InvalidEventException( self.relative_id, "Invalid value for 'RequestParameters' property. Values must be an object, " "e.g { Required: true, Caching: false }") settings = default_value.copy() settings.update(parameter_value) settings.update({'Name': parameter_name}) parameters.append(settings) elif isinstance(parameter, string_types): if not re.match('method\.request\.(querystring|path|header)\.', parameter): raise InvalidEventException( self.relative_id, "Invalid value for 'RequestParameters' property. Keys must be in the format " "'method.request.[querystring|path|header].{value}', " "e.g 'method.request.header.Authorization'.") settings = default_value.copy() settings.update({'Name': parameter}) parameters.append(settings) else: raise InvalidEventException( self.relative_id, "Invalid value for 'RequestParameters' property. Property must be either a string or an object") editor.add_request_parameters_to_method(path=self.Path, method_name=self.Method, request_parameters=parameters) api["DefinitionBody"] = editor.swagger
def test_get_partition_name_when_region_not_provided( self, region, expected_partition): with patch("boto3.session.Session.region_name", region): self.assertEqual(expected_partition, ArnGenerator.get_partition_name())
def _add_swagger_integration(self, api, function): """Adds the path and method for this Api event source to the Swagger body for the provided RestApi. :param model.apigateway.ApiGatewayRestApi rest_api: the RestApi to which the path and method should be added. """ swagger_body = api.get("DefinitionBody") if swagger_body is None: return function_arn = function.get_runtime_attr('arn') partition = ArnGenerator.get_partition_name() uri = fnSub( 'arn:' + partition + ':apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/' + make_shorthand(function_arn) + '/invocations') editor = SwaggerEditor(swagger_body) if editor.has_integration(self.Path, self.Method): # Cannot add the Lambda Integration, if it is already present raise InvalidEventException( self.relative_id, 'API method "{method}" defined multiple times for path "{path}".' .format(method=self.Method, path=self.Path)) condition = None if CONDITION in function.resource_attributes: condition = function.resource_attributes[CONDITION] editor.add_lambda_integration(self.Path, self.Method, uri, self.Auth, api.get('Auth'), condition=condition) if self.Auth: method_authorizer = self.Auth.get('Authorizer') api_auth = api.get('Auth') if method_authorizer: api_authorizers = api_auth and api_auth.get('Authorizers') if method_authorizer != 'AWS_IAM': 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 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)) apikey_required_setting = self.Auth.get('ApiKeyRequired') apikey_required_setting_is_false = apikey_required_setting is not None and not apikey_required_setting if apikey_required_setting_is_false and not api_auth.get( 'ApiKeyRequired'): raise InvalidEventException( self.relative_id, 'Unable to set ApiKeyRequired [False] on API method [{method}] for path [{path}] ' 'because the related API does not specify any ApiKeyRequired.' .format(method=self.Method, path=self.Path)) if method_authorizer or apikey_required_setting is not None: editor.add_auth_to_method(api=api, path=self.Path, method_name=self.Method, auth=self.Auth) if self.RequestModel: method_model = self.RequestModel.get('Model') if method_model: api_models = api.get('Models') if not api_models: raise InvalidEventException( self.relative_id, 'Unable to set RequestModel [{model}] on API method [{method}] for path [{path}] ' 'because the related API does not define any Models.'. format(model=method_model, method=self.Method, path=self.Path)) if not api_models.get(method_model): raise InvalidEventException( self.relative_id, 'Unable to set RequestModel [{model}] on API method [{method}] for path [{path}] ' 'because it wasn\'t defined in the API\'s Models.'. format(model=method_model, method=self.Method, path=self.Path)) editor.add_request_model_to_method( path=self.Path, method_name=self.Method, request_model=self.RequestModel) api["DefinitionBody"] = editor.swagger
def _add_swagger_integration(self, api, function): """Adds the path and method for this Api event source to the Swagger body for the provided RestApi. :param model.apigateway.ApiGatewayRestApi rest_api: the RestApi to which the path and method should be added. """ swagger_body = api.get("DefinitionBody") if swagger_body is None: return function_arn = function.get_runtime_attr('arn') partition = ArnGenerator.get_partition_name() uri = fnSub( 'arn:' + partition + ':apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/' + make_shorthand(function_arn) + '/invocations') editor = SwaggerEditor(swagger_body) if editor.has_integration(self.Path, self.Method): # Cannot add the Lambda Integration, if it is already present raise InvalidEventException( self.relative_id, 'API method "{method}" defined multiple times for path "{path}".' .format(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 ' '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