├── LICENSE ├── README.md ├── authorizer └── template.yaml ├── consumers ├── consumer-service-openapi.yaml └── template.yaml └── prerequisites └── template.yaml /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Allen Helton 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AWS Cross-Account Lambda Authorizer 2 | Example repository for how to implement a cross account lambda authorizer 3 | 4 | ## Contents 5 | This repository contains three [SAM templates](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-specification.html) to illustrate how to configure a lambda authorizer to use across multiple AWS accounts for your serverless ecosystem. 6 | 7 | * *prerequisites* 8 | * This folder contains a stack that should be deployed in all consuming AWS Accounts. It creates a [CloudFormation macro](https://www.readysetcloud.io/blog/allen.helton/automate-your-automation-with-cloudformation-macros/) to get around a defect in SAM. 9 | * Deploy this stack first! 10 | * *authorizer* 11 | * Contains the lambda authorizer itself and permissions to allow consumption by other AWS accounts 12 | * Deploy this stack in your parent/host account! 13 | * *consumers* 14 | * This folder shows how to consume the lambda authorizer on a serverless API defined with an [Open API Spec](https://www.openapis.org) 15 | * Deploy this stack last and in each of your consuming accounts 16 | 17 | ## Usage 18 | This repository is an example of how to set up the permissions via a SAM template. For a real implementation, you must write the authorizer code itself. [Alex Debrie](https://twitter.com/alexbdebrie) has a [great post](https://www.alexdebrie.com/posts/lambda-custom-authorizers/) on how to build one. 19 | 20 | After writing the authorizer, you must get the account ids for your ecosystem and [override the SAM parameters](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-cli-command-reference-sam-deploy.html) with the `--parameter-overrides` flag. 21 | 22 | **Example Authorizer Deployment Script** 23 | ``` 24 | sam build --parallel 25 | sam package --output-template-file packaged.yaml --s3-bucket {{MY_S3_BUCKET} 26 | sam deploy --template-file packaged.yaml --s3-bucket {{MY_S3_BUCKET}} --stack-name {{MY_STACK_NAME}} --capabilities CAPABILITY_IAM CAPABILITY_NAMED_IAM --parameter-overrides ConsumerOneAccountId={{CONSUMER_ONE_ACCOUNT_ID} ConsumerTwoAccountId={{CONSUMER_TWO_ACCOUNT_ID} 27 | ``` 28 | *Note - You must use CAPABILITY_NAMED_IAM when deploying the lambda authorizer because you are explicitly giving the function a name.* 29 | 30 | **Example Consumer Deployment Script** 31 | ``` 32 | sam build --parallel 33 | sam package --output-template-file packaged.yaml --s3-bucket {{MY_S3_BUCKET} 34 | sam deploy --template-file packaged.yaml --s3-bucket {{MY_S3_BUCKET}} --stack-name {{MY_STACK_NAME}} --capabilities CAPABILITY_IAM --parameter-overrides AuthorizerAccountId={{AUTHORIZER_ACCOUNT_ID} 35 | ``` 36 | 37 | ## Contact 38 | If you have any questions, feel free to reach out or check out my blog for more information. 39 | 40 | [![Twitter][1.1]][1] [![GitHub][2.1]][2] [![LinkedIn][3.1]][3] [![Ready, Set, Cloud!][4.1]][4] 41 | 42 | [1.1]: http://i.imgur.com/tXSoThF.png 43 | [2.1]: http://i.imgur.com/0o48UoR.png 44 | [3.1]: http://i.imgur.com/lGwB1Hk.png 45 | [4.1]: https://readysetcloud.s3.amazonaws.com/logo.png 46 | 47 | [1]: http://www.twitter.com/allenheltondev 48 | [2]: http://www.github.com/allenheltondev 49 | [3]: https://www.linkedin.com/in/allen-helton-85aa9650/ 50 | [4]: https://readysetcloud.io 51 | -------------------------------------------------------------------------------- /authorizer/template.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | Transform: AWS::Serverless-2016-10-31 3 | Description: > 4 | Authorizer Stack. Used to create the lambda authorizer and add permissions for consuming AWS Accounts 5 | 6 | Parameters: 7 | ConsumerOneAccountId: 8 | Type: String 9 | ConsumerTwoAccountId: 10 | Type: String 11 | 12 | Resources: 13 | LambdaAuthorizerCrossAccountFunction: 14 | Type: AWS::Serverless::Function 15 | Properties: 16 | CodeUri: lambdas/lambda-authorizer 17 | Runtime: nodejs12.x 18 | Handler: lambda-authorizer.lambdaHandler 19 | Role: !GetAtt LambdaAuthorizerRole.Arn 20 | FunctionName: LambdaAuthorizer 21 | 22 | LambdaAuthorizerRole: 23 | Type: AWS::IAM::Role 24 | Properties: 25 | AssumeRolePolicyDocument: 26 | Version: 2012-10-17 27 | Statement: 28 | - Effect: Allow 29 | Principal: 30 | Service: 31 | - lambda.amazonaws.com 32 | Action: 33 | - sts:AssumeRole 34 | ManagedPolicyArns: 35 | - !Sub arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole 36 | # Any permissions you need for your specific authorizer to run go here 37 | # Remember to practice the Principle of Least Privilege (POLP) 38 | # https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#grant-least-privilege 39 | 40 | ConsumerOneAuthorizerPermission: 41 | Type: AWS::Lambda::Permission 42 | Properties: 43 | Action: lambda:InvokeFunction 44 | FunctionName: !Ref LambdaAuthorizerCrossAccountFunction 45 | Principal: apigateway.amazonaws.com 46 | SourceArn: !Sub arn:${AWS::Partition}:execute-api:${AWS::Region}:${ConsumerOneAccountId}:*/authorizers/* 47 | 48 | ConsumerTwoAuthorizerPermission: 49 | Type: AWS::Lambda::Permission 50 | Properties: 51 | Action: lambda:InvokeFunction 52 | FunctionName: !Ref LambdaAuthorizerCrossAccountFunction 53 | Principal: apigateway.amazonaws.com 54 | SourceArn: !Sub arn:${AWS::Partition}:execute-api:${AWS::Region}:${ConsumerTwoAccountId}:*/authorizers/* -------------------------------------------------------------------------------- /consumers/consumer-service-openapi.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.0 2 | info: 3 | title: Consumer Service 4 | description: Example Consumer API of an AWS Cross Account Lambda Authorizer 5 | version: 1.0.0 6 | 7 | servers: 8 | - url: https://www.example.com 9 | description: qa 10 | 11 | x-amazon-apigateway-request-validators: 12 | Validate All: 13 | validateRequestParameters: true 14 | validateRequestBody: true 15 | x-amazon-apigateway-gateway-responses: 16 | BAD_REQUEST_BODY: 17 | statusCode: 400 18 | responseTemplates: 19 | application/json: '{ "message": "$context.error.validationErrorString" }' 20 | UNAUTHORIZED: 21 | statusCode: 401 22 | responseTemplates: 23 | application/json: '{ "message": "Unauthorized" }' 24 | responseParameters: 25 | gatewayresponse.header.Access-Control-Allow-Origin: "'*'" 26 | ACCESS_DENIED: 27 | statusCode: 401 28 | responseTemplates: 29 | application/json: '{ "message": "Unauthorized" }' 30 | responseParameters: 31 | gatewayresponse.header.Access-Control-Allow-Origin: "'*'" 32 | 33 | paths: 34 | /hello-world: 35 | get: 36 | summary: Example endpoint in the consumer service 37 | responses: 38 | 200: 39 | $ref: '#/components/responses/OK' 40 | x-amazon-apigateway-request-validator: Validate All 41 | x-amazon-apigateway-integration: 42 | responses: 43 | 200: 44 | statusCode: 200 45 | passthroughBehavior: when_no_match 46 | requestTemplates: 47 | application/json: | 48 | { 49 | 'statusCode': 200 50 | } 51 | type: mock 52 | 53 | components: 54 | responses: 55 | OK: #200 56 | description: Hello world was successful 57 | content: 58 | application/json: 59 | schema: 60 | type: object 61 | properties: 62 | message: 63 | type: string 64 | example: Hello World! 65 | required: 66 | - message -------------------------------------------------------------------------------- /consumers/template.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | Transform: [ AWS::Serverless-2016-10-31, RemoveAuthorizerLambdaPermissions ] 3 | Description: > 4 | Consumer Stack. This AWS Account will consume the authorizer created in the *authorizer stack* 5 | 6 | Parameters: 7 | AuthorizerAccountId: 8 | Type: String 9 | 10 | Resources: 11 | ConsumerServiceApi: 12 | Type: AWS::Serverless::Api 13 | Properties: 14 | StageName: test 15 | Auth: 16 | DefaultAuthorizer: LambdaAuthorizer 17 | AddDefaultAuthorizerToCorsPreflight: false 18 | Authorizers: 19 | LambdaAuthorizer: 20 | FunctionPayloadType: REQUEST 21 | FunctionArn: !Sub arn:${AWS::Partition}:lambda:${AWS::Region}:${AuthorizerAccountId}:function:LambdaAuthorizer 22 | Identity: 23 | Headers: 24 | - Authorization 25 | ReauthorizeEvery: 3600 26 | DefinitionBody: 27 | Fn::Transform: 28 | Name: AWS::Include 29 | Parameters: 30 | Location: ./consumer-service-openapi.yaml 31 | -------------------------------------------------------------------------------- /prerequisites/template.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | Transform: AWS::Serverless-2016-10-31 3 | Description: > 4 | Prerequisite stack with resources needed in every AWS Account 5 | 6 | Resources: 7 | # In order to use cross account lambda authorizer, we need to add the following function and CFN macro to the account to remove a permission 8 | # due to a defect in SAM. https://github.com/aws/serverless-application-model/issues/1637 9 | RemoveAuthorizerLambdaPermissionsFunction: 10 | Type: AWS::Serverless::Function 11 | Properties: 12 | Description: CFN Macro function that removes AWS::Lambda::Permission resources associated with lambda authorizers 13 | Runtime: nodejs12.x 14 | Handler: index.handler 15 | InlineCode: | 16 | exports.handler = async function(event, context){ 17 | let keysToRemove = Object.keys(event.fragment.Resources).filter(key => key.endsWith('AuthorizerPermission')); 18 | for(let i = 0; i < keysToRemove.length; i++){ 19 | delete event.fragment.Resources[keysToRemove[i]]; 20 | } 21 | 22 | return { 23 | requestId: event.requestId, 24 | status: 'success', 25 | fragment: event.fragment 26 | }; 27 | } 28 | 29 | RemoveAuthorizerLambdaPermissionsMacro: 30 | Type: AWS::CloudFormation::Macro 31 | Properties: 32 | Name: RemoveAuthorizerLambdaPermissions 33 | Description: Removes AWS::Lambda::Permission resources associated with lambda authorizers 34 | FunctionName: 35 | Ref: RemoveAuthorizerLambdaPermissionsFunction --------------------------------------------------------------------------------