Skip to content
This repository has been archived by the owner on Jun 10, 2022. It is now read-only.

Google Groups-controlled access to API Gateway endpoints

Notifications You must be signed in to change notification settings

guardian/google-directory-lambda-authorizer

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

5 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

google directory lambda authorizer

This project enables Google Groups-controlled access to API Gateway endpoints. You can create a API Gateway custom authorizer that reads a user's email address from an OAuth token and makes a call to the Directory API to check that the user is a member of a set of predefined groups.

An example use case would be a client-side web app using Google Sign-In for Websites backed by API Gateway. With the custom authorizer provided by this repository, the client can pass the ID token generated by Google Sign-In to the authorizer function, granting access to the API if group membership requirements are satisfied.

More information about the way the authorizer receives Google ID can be found here.

requirements

There are a few things to set up in order for all of this to work. You'll need:

1. a Google Cloud project

  • enable the Admin SDK API
  • create a service account
  • generate a P12 key from the above service account and upload it to S3
  • add the admin.directory.group.readonly scope to the service account (you'll need to ask a G Suite administrator/IT support)

2. a lambda to use as the custom authorizer

  • create an object that extends RequestStreamHandler and wraps an instance of GoogleDirectoryLambdaAuthorizer...
object MyCustomAuthorizer extends RequestStreamHandler {
  def initAuthorizer(): Either[Throwable, GoogleDirectoryLambdaAuthorizer] =
    GoogleDirectoryLambdaAuthorizer.forServiceAccount(
      applicationName = "My great application",
      requiredGroups = Set("powerusers@guardian.co.uk", "security@guardian.co.uk"),
      oauthClientId = ClientId("abc123"),
      s3Client = amazonS3Client,
      p12KeyPath = S3Path("private-bucket", "credentials.p12"),
      serviceAccountId = EmailAddress("service@myapp.iam.gserviceaccount.com"),
      userToImpersonate = EmailAddress("admin@guardian.co.uk")
    )

  override def handleRequest(input: InputStream, output: OutputStream, context: Context): Unit =
    initAuthorizer().fold(
      err => throw err,
      _.handleRequest(input, output, context)
    )
}

...and create a lambda that uses this object for its handler. Note the S3Path must contain the key uploaded from step 1, with read access for the lambda.

3. an API Gateway API configured to use the authorizer

Create an authorizer using the function from 2. in your API. For example, with the CLI:

aws apigateway create-authorizer --rest-api-id 1234123412 \
    --name 'GoogleDirectoryAuthorizer' \
    --type TOKEN \
    --authorizer-uri 'arn:aws:apigateway:eu-west-1:lambda:path/2015-03-31/functions/arn:aws:lambda:eu-west-1:123412341234:function:customAuthFunction/invocations' \
    --identity-source 'method.request.header.Authorization' \
    --identity-validation-expression 'Bearer (.*)' \
    --authorizer-result-ttl-in-seconds 0

Note that the authorizer must be of TOKEN type and you must disable caching (TTL 0) since the method ARN is included in the policy returned by the authorizer.

You can now send requests to your API including an Authorization header in the requests:

Authorization: Bearer YOUR_TOKEN_HERE

cloudformation example

This is an example template (using AWS SAM) that defines an API with a single method, function, and the custom authorizer defined above.

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31

Resources:
  SampleAPI:
    Type: AWS::Serverless::Api
    Properties:
      Name: Sample_API
      StageName: PROD
      Cors:
        AllowMethods: "'*'"
        AllowHeaders: "'*'"
        AllowOrigin: "'*'"
      DefinitionBody:
        swagger: "2.0"
        securityDefinitions:
          googleAuthorizer:
            type: apiKey
            name: Authorization
            in: header
            x-amazon-apigateway-authtype: oauth2
            x-amazon-apigateway-authorizer:
              authorizerUri:
                Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${GoogleDirectoryAuthorizer.Arn}/invocations
              identitySource: method.request.header.Authorization
              identityValidationExpression: "Bearer (.*)"
              name: Google OAuth
              type: TOKEN
        info:
          title:
            Fn::Sub: Sample API
          description: API to demonstrate custom Google Directory authorizer
        paths:
          /ping:
            get:
              summary: ping
              responses:
                '204':
                  description: OK
              security:
                - googleAuthorizer: []
              x-amazon-apigateway-integration:
                httpMethod: POST
                type: aws_proxy
                uri:
                  Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${Ping.Arn}/invocations
                responses:
                  '204':
                    statusCode: 204

  GoogleDirectoryAuthorizer:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: google-directory-authorizer
      Description: Authorize API Gateway requests
      Handler: com.gu.demo.MyCustomAuthorizer::handleRequest
      Runtime: java8
      CodeUri: target/scala-2.12/sample.jar
      Policies:
        - S3ReadPolicy:
            BucketName: private-bucket

  Ping:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: ping
      Description: ping
      Handler: com.gu.demo.Ping::handleRequest
      Runtime: java8
      CodeUri: target/scala-2.12/sample.jar

About

Google Groups-controlled access to API Gateway endpoints

Resources

Code of conduct

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages