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 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 _add_openapi_integration(self, api, function, manage_swagger=False): """Adds the path and method for this Api event source to the OpenApi body for the provided RestApi. :param model.apigateway.ApiGatewayRestApi rest_api: the RestApi to which the path and method should be added. """ open_api_body = api.get("DefinitionBody") if open_api_body is None: return function_arn = function.get_runtime_attr('arn') uri = fnSub('arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/' + make_shorthand(function_arn) + '/invocations') editor = OpenApiEditor(open_api_body) if manage_swagger and 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: self._add_auth_to_openapi_integration(api, editor) api["DefinitionBody"] = editor.openapi
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): # 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 deployment_group(self, function_logical_id): """ :param function_logical_id: logical_id of the function this deployment group belongs to :return: CodeDeployDeploymentGroup resource """ deployment_preference = self.get(function_logical_id) deployment_group = CodeDeployDeploymentGroup(self.deployment_group_logical_id(function_logical_id)) if deployment_preference.alarms is not None: deployment_group.AlarmConfiguration = {'Enabled': True, 'Alarms': [{'Name': alarm} for alarm in deployment_preference.alarms]} deployment_group.ApplicationName = self.codedeploy_application.get_runtime_attr('name') deployment_group.AutoRollbackConfiguration = {'Enabled': True, 'Events': ['DEPLOYMENT_FAILURE', 'DEPLOYMENT_STOP_ON_ALARM', 'DEPLOYMENT_STOP_ON_REQUEST']} deployment_group.DeploymentConfigName = fnSub("CodeDeployDefault.Lambda${ConfigName}", {"ConfigName": deployment_preference.deployment_type}) deployment_group.DeploymentStyle = {'DeploymentType': 'BLUE_GREEN', 'DeploymentOption': 'WITH_TRAFFIC_CONTROL'} deployment_group.ServiceRoleArn = self.codedeploy_iam_role.get_runtime_attr("arn") if deployment_preference.role: deployment_group.ServiceRoleArn = deployment_preference.role return deployment_group
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 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 _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 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 _replace_deployment_types(self, value): if isinstance(value, list): for i in range(len(value)): value[i] = self._replace_deployment_types(value[i]) return value elif is_instrinsic(value): for (k, v) in value.items(): value[k] = self._replace_deployment_types(v) return value else: if value in CODEDEPLOY_PREDEFINED_CONFIGURATIONS_LIST: return fnSub("CodeDeployDefault.Lambda${ConfigName}", {"ConfigName": value}) return value
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_method_path_uri_list(self, path, api_id, stage): """ It turns out that APIGW doesn't like trailing slashes in paths (#665) and removes as a part of their behavior, but this isn't documented. The regex removes the trailing slash to ensure the permission works as intended """ methods = list(self.get_path(path).keys()) uri_list = [] path = SwaggerEditor.get_path_without_trailing_slash(path) for m in methods: method = '*' if (m.lower() == self._X_ANY_METHOD or m.lower() == 'any') else m.upper() resource = "execute-api:/${__Stage__}/" + method + path resource = fnSub(resource, {"__Stage__": stage}) uri_list.extend([resource]) return uri_list
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 deployment_group(self, function_logical_id): """ :param function_logical_id: logical_id of the function this deployment group belongs to :return: CodeDeployDeploymentGroup resource """ deployment_preference = self.get(function_logical_id) deployment_group = CodeDeployDeploymentGroup( self.deployment_group_logical_id(function_logical_id)) if deployment_preference.alarms is not None: deployment_group.AlarmConfiguration = { 'Enabled': True, 'Alarms': [{ 'Name': alarm } for alarm in deployment_preference.alarms] } deployment_group.ApplicationName = self.codedeploy_application.get_runtime_attr( 'name') deployment_group.AutoRollbackConfiguration = { 'Enabled': True, 'Events': [ 'DEPLOYMENT_FAILURE', 'DEPLOYMENT_STOP_ON_ALARM', 'DEPLOYMENT_STOP_ON_REQUEST' ] } deployment_group.DeploymentConfigName = fnSub( "CodeDeployDefault.Lambda${ConfigName}", {"ConfigName": deployment_preference.deployment_type}) deployment_group.DeploymentStyle = { 'DeploymentType': 'BLUE_GREEN', 'DeploymentOption': 'WITH_TRAFFIC_CONTROL' } deployment_group.ServiceRoleArn = self.codedeploy_iam_role.get_runtime_attr( "arn") if deployment_preference.role: deployment_group.ServiceRoleArn = deployment_preference.role return deployment_group
def _generate_request_template(self, resource): """Generates the Body mapping request template for the Api. This allows for the input request to the Api to be passed as the execution input to the associated state machine resource. :param model.stepfunctions.resources.StepFunctionsStateMachine resource; the state machine resource to which the Api event source must be associated :returns: a body mapping request which passes the Api input to the state machine execution :rtype: dict """ request_templates = { "application/json": fnSub( json.dumps({ "input": "$util.escapeJavaScript($input.json('$'))", "stateMachineArn": "${" + resource.logical_id + "}", })) } return request_templates
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 _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 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_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 _add_swagger_integration(self, api, resource, role, intrinsics_resolver): """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 resource_arn = resource.get_runtime_attr("arn") integration_uri = fnSub( "arn:${AWS::Partition}:apigateway:${AWS::Region}:states:action/StartExecution" ) editor = SwaggerEditor(swagger_body) if editor.has_integration(self.Path, self.Method): # Cannot add the 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 resource.resource_attributes: condition = resource.resource_attributes[CONDITION] editor.add_state_machine_integration( self.Path, self.Method, integration_uri, role.get_runtime_attr("arn"), self._generate_request_template(resource), condition=condition, ) # Note: Refactor and combine the section below with the Api eventsource for functions if self.Auth: method_authorizer = self.Auth.get("Authorizer") api_auth = api.get("Auth") api_auth = intrinsics_resolver.resolve_parameter_refs(api_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": if not api_auth or 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), ) if self.Auth.get("AuthorizationScopes") and not isinstance( self.Auth.get("AuthorizationScopes"), list): raise InvalidEventException( self.relative_id, "Unable to set Authorizer on API method [{method}] for path [{path}] because " "'AuthorizationScopes' must be a list of strings.".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 or 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 resource_policy.get("CustomStatements"): editor.add_custom_statements( resource_policy.get("CustomStatements")) 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
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)) 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