Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions examples/2016-10-31/api_aws_iam_auth/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
exports.handler = async (event) => {
return {
statusCode: 200,
body: JSON.stringify(event),
headers: {}
}
}
30 changes: 30 additions & 0 deletions examples/2016-10-31/api_aws_iam_auth/template.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: API Gateway with AWS IAM Authorizer
Resources:
MyApi:
Type: AWS::Serverless::Api
Properties:
StageName: Prod
Auth:
DefaultAuthorizer: AWS_IAM
InvokeRole: CALLER_CREDENTIALS

MyFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: .
Handler: index.handler
Runtime: nodejs8.10
Events:
GetRoot:
Type: Api
Properties:
RestApiId: !Ref MyApi
Path: /
Method: get

Outputs:
ApiURL:
Description: "API URL"
Value: !Sub 'https://${MyApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/'
22 changes: 15 additions & 7 deletions samtranslator/model/api/api_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@
# Default the Cors Properties to '*' wildcard and False AllowCredentials. Other properties are actually Optional
CorsProperties.__new__.__defaults__ = (None, None, _CORS_WILDCARD, None, False)

AuthProperties = namedtuple("_AuthProperties", ["Authorizers", "DefaultAuthorizer"])
AuthProperties.__new__.__defaults__ = (None, None)
AuthProperties = namedtuple("_AuthProperties", ["Authorizers", "DefaultAuthorizer", "InvokeRole"])
AuthProperties.__new__.__defaults__ = (None, None, None)


class ApiGenerator(object):
Expand Down Expand Up @@ -266,7 +266,7 @@ def _add_auth(self):
"'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)
authorizers = self._get_authorizers(auth_properties.Authorizers, auth_properties.DefaultAuthorizer)

if authorizers:
swagger_editor.add_authorizers(authorizers)
Expand All @@ -275,14 +275,23 @@ def _add_auth(self):
# Assign the Swagger back to template
self.definition_body = swagger_editor.swagger

def _get_authorizers(self, authorizers_config):
def _get_authorizers(self, authorizers_config, default_authorizer=None):
authorizers = {}
if default_authorizer == 'AWS_IAM':
authorizers[default_authorizer] = ApiGatewayAuthorizer(
api_logical_id=self.logical_id,
name=default_authorizer,
is_aws_iam_authorizer=True
)

if not authorizers_config:
if 'AWS_IAM' in authorizers:
return authorizers
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():
authorizers[authorizer_name] = ApiGatewayAuthorizer(
Expand All @@ -294,7 +303,6 @@ def _get_authorizers(self, authorizers_config):
function_payload_type=authorizer.get('FunctionPayloadType'),
function_invoke_role=authorizer.get('FunctionInvokeRole')
)

return authorizers

def _get_permission(self, authorizer_name, authorizer_lambda_function_arn):
Expand Down Expand Up @@ -346,7 +354,7 @@ def _set_default_authorizer(self, swagger_editor, authorizers, default_authorize
if not default_authorizer:
return

if not authorizers.get(default_authorizer):
if not authorizers.get(default_authorizer) and default_authorizer != 'AWS_IAM':
raise InvalidResourceException(self.logical_id, "Unable to set DefaultAuthorizer because '" +
default_authorizer + "' was not defined in 'Authorizers'")

Expand Down
26 changes: 19 additions & 7 deletions samtranslator/model/apigateway.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ 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):
function_payload_type=None, function_invoke_role=None, is_aws_iam_authorizer=False):
if function_payload_type not in ApiGatewayAuthorizer._VALID_FUNCTION_PAYLOAD_TYPES:
raise InvalidResourceException(api_logical_id, name + " Authorizer has invalid "
"'FunctionPayloadType': " + function_payload_type)
Expand All @@ -113,6 +113,7 @@ def __init__(self, api_logical_id=None, name=None, user_pool_arn=None, function_
self.identity = identity
self.function_payload_type = function_payload_type
self.function_invoke_role = function_invoke_role
self.is_aws_iam_authorizer = is_aws_iam_authorizer

def _is_missing_identity_source(self, identity):
if not identity:
Expand All @@ -135,16 +136,19 @@ def generate_swagger(self):
"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()
}
"x-amazon-apigateway-authtype": self._get_swagger_authtype()
}

if authorizer_type == 'COGNITO_USER_POOLS':
swagger[APIGATEWAY_AUTHORIZER_KEY]['providerARNs'] = self._get_user_pool_arn_array()
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',
Expand Down Expand Up @@ -217,6 +221,9 @@ def _get_swagger_header_name(self):
return self._get_identity_header()

def _get_type(self):
if self.is_aws_iam_authorizer:
return 'AWS_IAM'

if self.user_pool_arn:
return 'COGNITO_USER_POOLS'

Expand All @@ -242,8 +249,13 @@ def _get_function_invoke_role(self):

def _get_swagger_authtype(self):
authorizer_type = self._get_type()
if authorizer_type == 'AWS_IAM':
return 'awsSigv4'

if authorizer_type == 'COGNITO_USER_POOLS':
return 'cognito_user_pools'

return 'cognito_user_pools' if authorizer_type == 'COGNITO_USER_POOLS' else 'custom'
return 'custom'

def _get_function_payload_type(self):
return 'TOKEN' if not self.function_payload_type else self.function_payload_type
Expand Down
47 changes: 24 additions & 23 deletions samtranslator/model/eventsources/push.py
Original file line number Diff line number Diff line change
Expand Up @@ -529,7 +529,7 @@ def _add_swagger_integration(self, api, function):
if CONDITION in function.resource_attributes:
condition = function.resource_attributes[CONDITION]

editor.add_lambda_integration(self.Path, self.Method, uri, condition=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')
Expand All @@ -538,28 +538,29 @@ def _add_swagger_integration(self, api, function):
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)
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))

editor.add_auth_to_method(api=api, path=self.Path, method_name=self.Method, auth=self.Auth)

api["DefinitionBody"] = editor.swagger

Expand Down
41 changes: 38 additions & 3 deletions samtranslator/swagger/swagger.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,8 @@ def add_path(self, path, method=None):

path_dict.setdefault(method, {})

def add_lambda_integration(self, path, method, integration_uri, condition=None):
def add_lambda_integration(self, path, method, integration_uri,
method_auth_config=None, api_auth_config=None, condition=None):
"""
Adds aws_proxy APIGW integration to the given path+method.

Expand All @@ -156,6 +157,15 @@ def add_lambda_integration(self, path, method, integration_uri, condition=None):
'uri': integration_uri
}

method_auth_config = method_auth_config or {}
api_auth_config = api_auth_config or {}
if method_auth_config.get('Authorizer') == 'AWS_IAM' \
or api_auth_config.get('DefaultAuthorizer') == 'AWS_IAM' and not method_auth_config:
self.paths[path][method][self._X_APIGW_INTEGRATION]['credentials'] = self._generate_integration_credentials(
method_invoke_role=method_auth_config.get('InvokeRole'),
api_invoke_role=api_auth_config.get('InvokeRole')
)

# If 'responses' key is *not* present, add it with an empty dict as value
path_dict[method].setdefault('responses', {})

Expand All @@ -169,6 +179,13 @@ def make_path_conditional(self, path, condition):
"""
self.paths[path] = make_conditional(condition, self.paths[path])

def _generate_integration_credentials(self, method_invoke_role=None, api_invoke_role=None):
return self._get_invoke_role(method_invoke_role or api_invoke_role)

def _get_invoke_role(self, invoke_role):
CALLER_CREDENTIALS_ARN = 'arn:aws:iam::*:user/*'
return invoke_role if invoke_role and invoke_role != 'CALLER_CREDENTIALS' else CALLER_CREDENTIALS_ARN

def iter_on_path(self):
"""
Yields all the paths available in the Swagger. As a caller, if you add new paths to Swagger while iterating,
Expand Down Expand Up @@ -409,7 +426,6 @@ 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):
normalized_method_name = self._normalize_method_name(method_name)

# It is possible that the method could have two definitions in a Fn::If block.
for method_definition in self.get_method_contents(self.get_path(path)[normalized_method_name]):

Expand All @@ -418,7 +434,10 @@ def set_method_authorizer(self, path, method_name, authorizer_name, authorizers,
continue
existing_security = method_definition.get('security', [])
# TEST: [{'sigv4': []}, {'api_key': []}])
authorizer_names = set(authorizers.keys())
authorizer_list = ['AWS_IAM']
if authorizers:
authorizer_list.extend(authorizers.keys())
authorizer_names = set(authorizer_list)
existing_non_authorizer_security = []
existing_authorizer_security = []

Expand Down Expand Up @@ -473,6 +492,22 @@ def set_method_authorizer(self, path, method_name, authorizer_name, authorizers,
if security:
method_definition['security'] = security

# The first element of the method_definition['security'] should be AWS_IAM
# because authorizer_list = ['AWS_IAM'] is hardcoded above
if 'AWS_IAM' in method_definition['security'][0]:
aws_iam_security_definition = {
'AWS_IAM': {
'x-amazon-apigateway-authtype': 'awsSigv4',
'type': 'apiKey',
'name': 'Authorization',
'in': 'header'
}
}
if not self.security_definitions:
self.security_definitions = aws_iam_security_definition
elif 'AWS_IAM' not in self.security_definitions:
self.security_definitions.update(aws_iam_security_definition)

@property
def swagger(self):
"""
Expand Down
31 changes: 31 additions & 0 deletions tests/swagger/test_swagger.py
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,37 @@ def test_must_raise_on_existing_integration(self):
with self.assertRaises(ValueError):
self.editor.add_lambda_integration("/bar", "get", "integrationUri")

def test_must_add_credentials_to_the_integration(self):
path = "/newpath"
method = "get"
integration_uri = "something"
expected = 'arn:aws:iam::*:user/*'
api_auth_config = {
"DefaultAuthorizer": "AWS_IAM",
"InvokeRole": "CALLER_CREDENTIALS"
}

self.editor.add_lambda_integration(path, method, integration_uri, None, api_auth_config)
actual = self.editor.swagger["paths"][path][method][_X_INTEGRATION]['credentials']
self.assertEquals(expected, actual)

def test_must_add_credentials_to_the_integration_overrides(self):
path = "/newpath"
method = "get"
integration_uri = "something"
expected = 'arn:aws:iam::*:role/xxxxxx'
api_auth_config = {
"DefaultAuthorizer": "MyAuth",
}
method_auth_config = {
"Authorizer": "AWS_IAM",
"InvokeRole": "arn:aws:iam::*:role/xxxxxx"
}

self.editor.add_lambda_integration(path, method, integration_uri, method_auth_config, api_auth_config)
actual = self.editor.swagger["paths"][path][method][_X_INTEGRATION]['credentials']
self.assertEquals(expected, actual)


class TestSwaggerEditor_iter_on_path(TestCase):

Expand Down
Loading