-
Notifications
You must be signed in to change notification settings - Fork 2.4k
feat(auth): add support for API Gateway Authorizers #546
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 1 commit
Commits
Show all changes
20 commits
Select commit
Hold shift + click to select a range
cd28c89
feat(auth): add support for API Gateway Authorizers
brettstack 8cb9f63
addressed my own feedback in GitHub comments
brettstack 915760c
improvement: remove check for Auth dict since it's verified by proper…
brettstack 0ea0ce1
test: add error tests for authorizers
brettstack f4ecc38
test: fix missing DefaultAuthorizer in Authorizers test
brettstack e451739
fix: add permissions for API to invoke Authorizer Lambda Function
brettstack 18ecefc
fix: fix error when no Auth defined
brettstack ea7efc8
docs: add Lambda REQUEST Authorizer example
brettstack e0b412f
fix: add error handling when no identity source provided for Lambda R…
brettstack da1867e
docs(examples): improve README commands for api_lambda_request_auth
brettstack 3998447
add API Gateway + Cognito Authorizer example
brettstack 60671cc
simplify setup by using npm scripts
brettstack e9d6c25
update to use authorization_code flow
brettstack 9872796
add tests for cognito authorizers
brettstack 1d1be1c
merge upstream/develop
brettstack 90b3a39
convert result of map() to list for py3 support
brettstack 6dca4c7
update spec to include API Auth and Function Auth
brettstack c28ae1a
add TOC for Data Types in spec
brettstack 847646d
address documentation change requests
brettstack f6ac94b
address PR change requests
brettstack File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
module.exports = async (event) => { | ||
return event | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
AWSTemplateFormatVersion: '2010-09-09' | ||
Transform: AWS::Serverless-2016-10-31 | ||
Description: API Gateway + Cognito User Pools Auth | ||
Resources: | ||
MyApi: | ||
Type: AWS::Serverless::Api | ||
Properties: | ||
StageName: TestStage | ||
Auth: | ||
DefaultAuthorizer: MyLambdaAuthorizer | ||
Authorizers: | ||
MyCognitoAuthorizer: | ||
UserPoolArn: !GetAtt MyCognitoUserPool.Arn | ||
|
||
MyLambdaAuthorizer: | ||
FunctionArn: !GetAtt MyAuthFunction.Arn | ||
FunctionInvokeRole: NONE | ||
Identity: # Optional | ||
Header: Authn # Optional; Default: Authorization | ||
ValidationExpression: myexpresso # Optional | ||
ReauthorizeEvery: 33 | ||
|
||
MyLambdaRequestAuthorizer: | ||
FunctionPayloadType: REQUEST | ||
FunctionArn: !GetAtt MyAuthFunction.Arn | ||
FunctionInvokeRole: !Sub arn:aws:iam::${AWS::AccountId}:role/admin | ||
Identity: | ||
Headers: | ||
- Authorization1 | ||
QueryStrings: | ||
- Authorization2 | ||
StageVariables: | ||
- Authorization3 | ||
Context: | ||
- Authorization4 | ||
ReauthorizeEvery: 100 | ||
|
||
MyFunction: | ||
Type: AWS::Serverless::Function | ||
Properties: | ||
CodeUri: ./src | ||
Handler: index.handler | ||
Runtime: nodejs8.10 | ||
Events: | ||
WithNoAuthorizer: | ||
Type: Api | ||
Properties: | ||
RestApiId: !Ref MyApi | ||
Path: / | ||
Method: get | ||
Auth: | ||
Authorizer: NONE | ||
WithLambdaTokenAuthorizer: | ||
Type: Api | ||
Properties: | ||
RestApiId: !Ref MyApi | ||
Path: /users | ||
Method: get | ||
Auth: | ||
Authorizer: MyLambdaAuthorizer | ||
WithCognitoAuthorizer: | ||
Type: Api | ||
Properties: | ||
RestApiId: !Ref MyApi | ||
Path: /users | ||
Method: post | ||
Auth: | ||
Authorizer: MyCognitoAuthorizer | ||
WithLambdaRequestAuthorizer: | ||
Type: Api | ||
Properties: | ||
RestApiId: !Ref MyApi | ||
Path: /users | ||
Method: delete | ||
Auth: | ||
Authorizer: MyLambdaRequestAuthorizer | ||
WithDefaultAuthorizer: | ||
Type: Api | ||
Properties: | ||
RestApiId: !Ref MyApi | ||
Path: /users | ||
Method: put | ||
|
||
MyAuthFunction: | ||
Type: AWS::Serverless::Function | ||
Properties: | ||
CodeUri: ./src | ||
Handler: index.handler | ||
Runtime: nodejs8.10 | ||
|
||
MyCognitoUserPool: | ||
Type: AWS::Cognito::UserPool | ||
Properties: | ||
UserPoolName: MyUserPool | ||
LambdaConfig: | ||
PreSignUp: !GetAtt PreSignupLambdaFunction.Arn | ||
Policies: | ||
PasswordPolicy: | ||
MinimumLength: 8 | ||
Schema: | ||
- AttributeDataType: String | ||
Name: email | ||
Required: false | ||
|
||
PreSignupLambdaFunction: | ||
Type: AWS::Serverless::Function | ||
Properties: | ||
InlineCode: | | ||
exports.handler = async (event, context, callback) => { | ||
event.response = { autoConfirmUser: true } | ||
return event | ||
} | ||
Handler: index.handler | ||
MemorySize: 128 | ||
Runtime: nodejs8.10 | ||
Timeout: 3 | ||
# TODO: | ||
# Events: | ||
# CognitoUserPoolPreSignup: | ||
# Type: CognitoUserPool | ||
# Properties: | ||
# UserPool: !Ref MyCognitoUserPool | ||
|
||
LambdaCognitoUserPoolExecutionPermission: | ||
Type: AWS::Lambda::Permission | ||
Properties: | ||
Action: lambda:InvokeFunction | ||
FunctionName: !GetAtt PreSignupLambdaFunction.Arn | ||
Principal: cognito-idp.amazonaws.com | ||
SourceArn: !Sub 'arn:aws:cognito-idp:${AWS::Region}:${AWS::AccountId}:userpool/${MyCognitoUserPool}' | ||
|
||
Outputs: | ||
ApiURL: | ||
Description: "API endpoint URL for Prod environment" | ||
Value: !Sub 'https://${MyApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/' |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,7 +3,7 @@ | |
|
||
from samtranslator.model.intrinsics import ref | ||
from samtranslator.model.apigateway import (ApiGatewayDeployment, ApiGatewayRestApi, | ||
ApiGatewayStage) | ||
ApiGatewayStage, ApiGatewayAuthorizer) | ||
from samtranslator.model.exceptions import InvalidResourceException | ||
from samtranslator.model.s3_utils.uri_parser import parse_s3_uri | ||
from samtranslator.region_configuration import RegionConfiguration | ||
|
@@ -15,9 +15,13 @@ | |
# Default the Cors Properties to '*' wildcard. Other properties are actually Optional | ||
CorsProperties.__new__.__defaults__ = (None, None, _CORS_WILDCARD, None) | ||
|
||
AuthProperties = namedtuple("_AuthProperties", ["Authorizers", "DefaultAuthorizer"]) | ||
AuthProperties.__new__.__defaults__ = (None, None) | ||
|
||
|
||
class ApiGenerator(object): | ||
|
||
def __init__(self, logical_id, cache_cluster_enabled, cache_cluster_size, variables, depends_on, definition_body, definition_uri, name, stage_name, endpoint_configuration=None, method_settings=None, binary_media=None, cors=None): | ||
def __init__(self, logical_id, cache_cluster_enabled, cache_cluster_size, variables, depends_on, definition_body, definition_uri, name, stage_name, endpoint_configuration=None, method_settings=None, binary_media=None, cors=None, auth=None): | ||
"""Constructs an API Generator class that generates API Gateway resources | ||
|
||
:param logical_id: Logical id of the SAM API Resource | ||
|
@@ -43,6 +47,7 @@ def __init__(self, logical_id, cache_cluster_enabled, cache_cluster_size, variab | |
self.method_settings = method_settings | ||
self.binary_media = binary_media | ||
self.cors = cors | ||
self.auth = auth | ||
|
||
def _construct_rest_api(self): | ||
"""Constructs and returns the ApiGateway RestApi. | ||
|
@@ -61,12 +66,12 @@ def _construct_rest_api(self): | |
# to Regional which is the only supported config. | ||
self._set_endpoint_configuration(rest_api, "REGIONAL") | ||
|
||
|
||
if self.definition_uri and self.definition_body: | ||
raise InvalidResourceException(self.logical_id, | ||
"Specify either 'DefinitionUri' or 'DefinitionBody' property and not both") | ||
|
||
self._add_cors() | ||
self._add_auth() | ||
|
||
if self.definition_uri: | ||
rest_api.BodyS3Location = self._construct_body_s3_dict() | ||
|
@@ -208,6 +213,73 @@ def _add_cors(self): | |
# Assign the Swagger back to template | ||
self.definition_body = editor.swagger | ||
|
||
def _add_auth(self): | ||
""" | ||
Add Auth configuration to the Swagger file, if necessary | ||
""" | ||
|
||
if not self.auth: | ||
return | ||
|
||
INVALID_ERROR = "Invalid value for 'Auth' property" | ||
keetonian marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
||
if not isinstance(self.auth, dict): | ||
raise InvalidResourceException(self.logical_id, | ||
"Auth must be a dictionary") | ||
|
||
if self.auth and not self.definition_body: | ||
raise InvalidResourceException(self.logical_id, | ||
"Auth works only with inline Swagger specified in " | ||
"'DefinitionBody' property") | ||
|
||
# Make sure keys in the dict are recognized | ||
if not all(key in AuthProperties._fields for key in self.auth.keys()): | ||
raise InvalidResourceException(self.logical_id, INVALID_ERROR) | ||
|
||
if not SwaggerEditor.is_valid(self.definition_body): | ||
raise InvalidResourceException(self.logical_id, "Unable to add Auth configuration because " | ||
"'DefinitionBody' does not contain a valid Swagger") | ||
swagger_editor = SwaggerEditor(self.definition_body) | ||
auth_properties = AuthProperties(**self.auth) | ||
keetonian marked this conversation as resolved.
Show resolved
Hide resolved
|
||
authorizers = self._get_authorizers(auth_properties.Authorizers) | ||
|
||
if authorizers: | ||
keetonian marked this conversation as resolved.
Show resolved
Hide resolved
|
||
swagger_editor.add_authorizers(authorizers) | ||
self._set_default_authorizer(swagger_editor, authorizers, auth_properties.DefaultAuthorizer) | ||
|
||
# Assign the Swagger back to template | ||
self.definition_body = swagger_editor.swagger | ||
|
||
def _get_authorizers(self, authorizers_config): | ||
if not authorizers_config: | ||
return None | ||
|
||
authorizers = {} | ||
|
||
for authorizerName, authorizer in authorizers_config.items(): | ||
authorizers[authorizerName] = ApiGatewayAuthorizer( | ||
api_logical_id=self.logical_id, | ||
name=authorizer.get('Name'), | ||
|
||
user_pool_arn=authorizer.get('UserPoolArn'), | ||
function_arn=authorizer.get('FunctionArn'), | ||
identity=authorizer.get('Identity'), | ||
function_payload_type=authorizer.get('FunctionPayloadType'), | ||
function_invoke_role=authorizer.get('FunctionInvokeRole') | ||
) | ||
|
||
return authorizers | ||
|
||
def _set_default_authorizer(self, swagger_editor, authorizers, default_authorizer): | ||
if not default_authorizer: | ||
return | ||
|
||
if not authorizers.get(default_authorizer): | ||
raise InvalidResourceException(self.logical_id, "Unable to set DefaultAuthorizer because '" + | ||
default_authorizer + "' was not defined in 'Authorizers'") | ||
|
||
for path in swagger_editor.iter_on_path(): | ||
swagger_editor.set_path_default_authorizer(path, default_authorizer, authorizers=authorizers) | ||
|
||
def _set_endpoint_configuration(self, rest_api, value): | ||
""" | ||
Sets endpoint configuration property of AWS::ApiGateway::RestApi resource | ||
|
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.