DEV Community

Andrii Shykhov
Andrii Shykhov

Posted on • Originally published at Medium

Simple serverless architecture using API Gateway, Lambda Authorizer and Secrets Manager

Introduction:

In most cases, it is necessary to protect API Gateway. AWS offers a couple of ways to do it. In this post, you can find a simple way to control access to API Gateway through Lambda authorizer with a Simple response.

About the project:

In this project, we have API Gateway, 2 lambda functions — the “Authorizer” lambda function and “Main” lambda function, Secret Manager for storing authorization token. The main lambda function returns a simple response in case of a successful authorization with the Authorizer lambda. The Authorizer lambda function plays the role of access blocker. The lambda function checks a token passed in the client’s HTTP request and takes the token from Secrets Manager, after that it determines whether the tokens are identical. If yes — returns “true” in response, if no — returns “false”. All infrastructure in AWS is created with CloudFormation.

Token value is passed as a parameter during the process of creation of CloudFormation stack, but it is possible to create token dynamically, more information in AWS documentation.

The infrastructure schema:

simple_serverless_architecture

The CloudFormation code template:

    Parameters:
      AuthorizationTokenName: 
        Type: String
        Description: Authorization token resource name
        Default: 'AuthorizationToken'
      AuthorizationTokenValue:
        Type: String
        Description: Authorization token value
        Default: '' 

    Resources:
    #####################################
    # Secret Manager
    #####################################
      AuthorizationSecret:
        Type: AWS::SecretsManager::Secret
        Properties:
          Name: !Ref AuthorizationTokenName
          Description: Access token for Authorization Lambda
          SecretString:
            Fn::Sub:
              - '{"token": "${AuthorizationTokenValue}"}'
              - AuthorizationTokenValue: !Ref AuthorizationTokenValue

    #####################################
    # Lambda Functions
    #####################################
      AuthorizationLambdaFunction:
        Type: AWS::Lambda::Function
        Properties:
          FunctionName: AuthorizationLambdaFunction
          Runtime: python3.10
          Handler: index.lambda_handler
          Role: !GetAtt AuthorizationLambdaExecutionRole.Arn
          Environment:
            Variables:
              AuthorizationTokenName: !Ref AuthorizationTokenName
          Code:
            ZipFile: |
              import boto3
              from botocore.exceptions import ClientError
              import json
              import os

              def lambda_handler(event, context):

                  secret_name = os.environ['AuthorizationTokenName']
                  region_name = context.invoked_function_arn.split(":")[3]

                  # Create a Secrets Manager client
                  session = boto3.session.Session()
                  client = session.client(
                      service_name='secretsmanager',
                      region_name=region_name
                  )
                  try:
                      get_secret_value_response = client.get_secret_value(
                          SecretId=secret_name
                      )
                      # Take the token value from the responce
                      secret = get_secret_value_response['SecretString']
                      secret_token = json.loads(secret)['token']
                      authorization_token = event.get('headers', {}).get('authorization')

                      # Check if the authorization token match with secret manager token
                      is_authorized = authorization_token in secret_token

                      # Construct the response
                      response = {
                          "isAuthorized": is_authorized
                      }
                      return response
                  except ClientError as e:
                      raise e

      AuthorizationLambdaExecutionRole:
        Type: AWS::IAM::Role
        Properties:
          RoleName: AuthorizationLambdaExecutionRole
          AssumeRolePolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Principal:
                  Service:
                    - lambda.amazonaws.com
                Action:
                  - sts:AssumeRole
          ManagedPolicyArns:
            - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
          Policies:
            - PolicyName: SecretsManagerAccessPolicy
              PolicyDocument:
                Version: '2012-10-17'
                Statement:
                  - Effect: Allow
                    Action:
                      - secretsmanager:GetSecretValue
                      - secretsmanager:DescribeSecret
                    Resource:
                      - !GetAtt AuthorizationSecret.Id
            - PolicyName: ApiGatewayInvokePolicy
              PolicyDocument:
                Version: '2012-10-17'
                Statement:
                  - Effect: Allow
                    Action:
                      - execute-api:Invoke
                    Resource:
                      - !Sub arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${MyApi}/*/*/invoke

      MainLambdaFunction:
        Type: AWS::Lambda::Function
        Properties:
          FunctionName: MainLambdaFunction
          Runtime: python3.10
          Handler: index.lambda_handler
          Role: !GetAtt MainLambdaExecutionRole.Arn
          Code:
            ZipFile: |
              import json

              def lambda_handler(event, context):
                  response = {
                      "statusCode": 200,
                      "body": json.dumps("Simple Main lambda function responce")
                  }
                  return response

      MainLambdaExecutionRole:
        Type: AWS::IAM::Role
        Properties:
          RoleName: MainLambdaExecutionRole
          AssumeRolePolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Principal:
                  Service:
                    - lambda.amazonaws.com
                Action:
                  - sts:AssumeRole
          ManagedPolicyArns:
            - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole

    #####################################
    # API Gateway
    #####################################
      MyApi:
        Type: AWS::ApiGatewayV2::Api
        Properties:
          Name: HttpApi4328
          ProtocolType: HTTP

      HttpApiGatewayAuthorizer:
        Type: AWS::ApiGatewayV2::Authorizer
        Properties:
          Name: HttpApiGatewayAuthorizer
          ApiId: !Ref MyApi
          AuthorizerType: REQUEST
          EnableSimpleResponses: YES
          AuthorizerUri: !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${AuthorizationLambdaFunction.Arn}/invocations
          AuthorizerResultTtlInSeconds: 0
          AuthorizerPayloadFormatVersion: '2.0' 
          AuthorizerCredentialsArn: !GetAtt ApiGatewayInvokeLambdaRole.Arn
          IdentitySource: 
            - "$request.header.Authorization"

      ApiGatewayInvokeLambdaRole:
        Type: AWS::IAM::Role
        DependsOn:
          - AuthorizationLambdaFunction
          - MainLambdaFunction
          - AuthorizationSecret
        Properties:
          RoleName: ApiGatewayInvokeLambdaRole
          AssumeRolePolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Principal:
                  Service:
                    - apigateway.amazonaws.com
                Action:
                  - sts:AssumeRole
          Policies:
            - PolicyName: AuthorizerPermissionsPolicy
              PolicyDocument:
                Version: '2012-10-17'
                Statement:
                  - Effect: Allow
                    Action:
                      - lambda:InvokeFunction
                      - sts:AssumeRole
                    Resource:
                      - !Sub "arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${AuthorizationLambdaFunction}"
                  - Effect: Allow
                    Action:
                      - secretsmanager:GetSecretValue
                    Resource:
                      - !Sub "arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:${AuthorizationSecret}"
                  - Effect: Allow
                    Action:
                      - execute-api:Invoke
                    Resource:
                      - !Sub arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${MyApi}/*/*/invoke

      HttpApiGateway:
        Type: AWS::ApiGatewayV2::Stage
        Properties:
          ApiId: !Ref MyApi
          StageName: dev
          AutoDeploy: true

      HttpApiGatewayRoute:
        Type: AWS::ApiGatewayV2::Route
        Properties:
          ApiId: !Ref MyApi
          RouteKey: ANY /invoke
          AuthorizationType: CUSTOM
          AuthorizerId: !Ref HttpApiGatewayAuthorizer
          Target: !Join
            - '/'
            - - integrations
              - !Ref MyHttpApiIntegration

      MyHttpApiIntegration:
        Type: AWS::ApiGatewayV2::Integration
        Properties:
          ApiId: !Ref MyApi
          IntegrationType: AWS_PROXY
          PayloadFormatVersion: '2.0'
          IntegrationUri: !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MainLambdaFunction.Arn}/invocations

      MainLambdaInvokePermission:
        Type: AWS::Lambda::Permission
        Properties:
          Action: lambda:InvokeFunction
          FunctionName: !GetAtt MainLambdaFunction.Arn
          Principal: apigateway.amazonaws.com
          SourceArn: !Sub arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${MyApi}/*/*/invoke
Enter fullscreen mode Exit fullscreen mode

Prerequisites:

Before you start, make sure the following requirements are met:

  • An AWS account with permissions to create resources.
  • AWS CLI installed on your local machine.

Deployment:

  1. Clone the repository:
Enter fullscreen mode Exit fullscreen mode
  1. Fill in all necessary parameters in the root.yaml, retrieve_invoke_url.sh files, and create a CloudFormation stack with a CloudFormation template:
    aws cloudformation create-stack \
        --stack-name apigw-lambda-sm \
        --template-body file://root.yaml \
        --capabilities CAPABILITY_NAMED_IAM \
        --parameters ParameterKey=AuthorizationTokenValue,ParameterValue="token_value" \
        --region eu-central-1 \
        --disable-rollback
Enter fullscreen mode Exit fullscreen mode
  1. Retrieve the Invoke URL of the Stage with retrieve_invoke_url.sh script and make a simple test of the API with the CURL command:
    ./retrieve_invoke_url.sh 
    Invoke URL: https://api_id.execute-api.eu-central-1.amazonaws.com/dev

    export APIGW_TOKEN='token_value'
    curl -X GET -H "Authorization: $APIGW_TOKEN" https://api_id.execute-api.eu-central-1.amazonaws.com/dev/invoke
    "Simple Main lambda function responce"

     curl -X GET -H "Authorization: INCORRECT_TOKEN" https://api_id.execute-api.eu-central-1.amazonaws.com/dev/invoke
    {"message":"Forbidden"}
Enter fullscreen mode Exit fullscreen mode
  1. Delete the CloudFormation stack:
    aws cloudformation delete-stack --stack-name apigw-lambda-sm
Enter fullscreen mode Exit fullscreen mode

Conclusion:

Lambda Authorizer with a simple response can be a fast and simple solution for securing API Gateway. Lambda function as Authorizer gives us a high level of customization — we can use different services for storing credentials and not only AWS services, we can configure different logic of the function code work depending on our requirements.

If you found this post helpful and interesting, please click the reaction button to show your support. Feel free to use and share this post!
You can also support me with a virtual coffee https://www.buymeacoffee.com/andrworld1500 .

Top comments (0)