Skip to content

Unable to use APIGatewayV2Request with Lambda authorizer #39

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

Closed
GeorgePreece opened this issue Dec 14, 2023 · 13 comments
Closed

Unable to use APIGatewayV2Request with Lambda authorizer #39

GeorgePreece opened this issue Dec 14, 2023 · 13 comments

Comments

@GeorgePreece
Copy link

Expected behavior

Ability to process context from a Lambda authorizer in APIGatewayV2Request

Actual behavior

Fails trying to deserialize jwt from the Authorizer struct which isn't present for Lambda authorizers, from CloudWatch logs:

warning Lambda : lifecycleIteration=0 [AWSLambdaRuntimeCore] lambda handler returned an error: requestDecoding(Swift.DecodingError.keyNotFound(CodingKeys(stringValue: "jwt", intValue: nil), Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "requestContext", intValue: nil), CodingKeys(stringValue: "authorizer", intValue: nil)], debugDescription: "No value associated with key CodingKeys(stringValue: \"jwt\", intValue: nil) (\"jwt\").", underlyingError: nil)))

Also, would be nice to have structs for Lambda authorizer payloads

Steps to reproduce

  1. Create API Gateway (HTTP API)
  2. Create route in gateway and attach both Lambda authorizer (not JWT authorizer) and integration
  3. Call the route

If possible, minimal yet complete reproducer code (or URL to code)

Looks like

needs to be made optional (guessing this will be a breaking change because of Swift's type safety when compiling)

SwiftAWSLambdaRuntime version/commit hash

3ac078f4d8fe6d9ae8dd05b680a284a423e1578d

Swift & OS version (output of swift --version && uname -a)

swift-driver version: 1.87.1 Apple Swift version 5.9 (swiftlang-5.9.0.128.108 clang-1500.0.40.1)
Target: arm64-apple-macosx14.0
Darwin 192.168.1.121 23.1.0 Darwin Kernel Version 23.1.0: Mon Oct 9 21:27:24 PDT 2023; root:xnu-10002.41.9~6/RELEASE_ARM64_T6000 arm64

@sebsto
Copy link
Contributor

sebsto commented Dec 14, 2023

@GeorgePreece funny timing, I just realized it doesn't support iam authorizer neither.
I just submited PR #41

Can you share here the raw JSON inside authorizer (sanitized to not include confidential information) ?

I can make a second PR now that I have a dev setup in place

@sebsto
Copy link
Contributor

sebsto commented Dec 14, 2023

Here is the doc for Lambda Authorizers payload
https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-lambda-authorizer.html

I guess we would need to add support for whatever is inside the authorization object.
The docs says

      "clientCert": {
        "clientCertPem": "CERT_CONTENT",
        "subjectDN": "www.example.com",
        "issuerDN": "Example issuer",
        "serialNumber": "1",
        "validity": {
          "notBefore": "May 28 12:30:02 2019 GMT",
          "notAfter": "Aug  5 09:36:04 2021 GMT"
        }

but I'm not sure it always pass a certificate.

The APIGatewayV2Response is not applicable.
We need to add a new type APIGatewayV2LambdaAuthorizerResponse (?) to support responses like

{
  "isAuthorized": true/false,
  "context": {
    "exampleKey": "exampleValue"
  }
}

@GeorgePreece
Copy link
Author

@GeorgePreece funny timing, I just realized it doesn't support iam authorizer neither. I just submited PR #41

amazing! thank you @sebsto :)

Can you share here the raw JSON inside authorizer (sanitized to not include confidential information) ?

absolutely! here's an example:

"authorizer": {
    "lambda": {
        "abc": "xyz"
    }
}

@sebsto
Copy link
Contributor

sebsto commented Dec 15, 2023

Thank you @GeorgePreece
What is the type of the content for the lambda object ? Is it a generic [String:String] ?

@GeorgePreece
Copy link
Author

Thank you @GeorgePreece What is the type of the content for the lambda object ? Is it a generic [String:String] ?

yep, that's right
it is the same as the context object set in the authorizer response

@sebsto
Copy link
Contributor

sebsto commented Dec 16, 2023

@GeorgePreece can you test the code change on this branch ?
https://github.com/sebsto/swift-aws-lambda-events/tree/sebsto/lambda_authorizers

If it works, I'll send the PR to the main project.
If it doesn't work, please share the full JSON you receive

There is a new struct to support the authorizer's response:

public struct APIGatewayLambdaAuthorizerResponse: Decodable {
    public let isAuthorized: Bool
    public let context: LambdaAuthorizerContext?
}

@GeorgePreece
Copy link
Author

@GeorgePreece can you test the code change on this branch ? https://github.com/sebsto/swift-aws-lambda-events/tree/sebsto/lambda_authorizers

If it works, I'll send the PR to the main project. If it doesn't work, please share the full JSON you receive

There is a new struct to support the authorizer's response:

public struct APIGatewayLambdaAuthorizerResponse: Decodable {
    public let isAuthorized: Bool
    public let context: LambdaAuthorizerContext?
}

hey @sebsto, I'm unable to initialise the struct I believe because it's conforming to Decodable rather than Encodable
also here is a sanitised request that is sent to the Lambda authorizer from API Gateway:

{
    "version": "2.0",
    "type": "REQUEST",
    "routeArn": "arn:aws:execute-api:eu-north-1:000000000000:0000000000/dev/GET/applications",
    "identitySource": [
        "abc.xyz.123"
    ],
    "routeKey": "GET /applications",
    "rawPath": "/dev/applications",
    "rawQueryString": "",
    "headers": {
        "accept": "*/*",
        "authorization": "abc.xyz.123",
        "content-length": "0",
        "host": "0000000000.execute-api.eu-north-1.amazonaws.com",
        "user-agent": "curl/8.1.2",
        "x-amzn-trace-id": "Root=1-00000000-000000000000000000000000",
        "x-forwarded-for": "0.0.0.0",
        "x-forwarded-port": "443",
        "x-forwarded-proto": "https"
    },
    "requestContext": {
        "accountId": "000000000000",
        "apiId": "0000000000",
        "domainName": "0000000000.execute-api.eu-north-1.amazonaws.com",
        "domainPrefix": "0000000000",
        "http": {
            "method": "GET",
            "path": "/dev/applications",
            "protocol": "HTTP/1.1",
            "sourceIp": "0.0.0.0",
            "userAgent": "curl/8.1.2"
        },
        "requestId": "QHACgr8sig0MELg=",
        "routeKey": "GET /applications",
        "stage": "dev",
        "time": "15/Dec/2023:20:35:03 +0000",
        "timeEpoch": 1702672503230
    }
}

@sebsto
Copy link
Contributor

sebsto commented Dec 16, 2023

of course, silly me.
It's corrected.

In addition to APIGatewayV2Request to support lambda objects in authorizer, there are two new struct to use with the authorizer function :

  • APIGatewayLambdaAuthorizerRequest
  • APIGatewayLambdaAuthorizerResponse

Same branch : https://github.com/sebsto/swift-aws-lambda-events/tree/sebsto/lambda_authorizers

@GeorgePreece
Copy link
Author

of course, silly me. It's corrected.

In addition to APIGatewayV2Request to support lambda objects in authorizer, there are two new struct to use with the authorizer function :

* `APIGatewayLambdaAuthorizerRequest`

* `APIGatewayLambdaAuthorizerResponse`

Same branch : https://github.com/sebsto/swift-aws-lambda-events/tree/sebsto/lambda_authorizers

thanks! deserialisation is now working for both the request objects :) only ask here is, would you be able to expose identitySource so I can access the tokens mapped from gateway?
also, still having issues with initialising the response unfortunately, I think a public initialiser needs to be defined

@sebsto
Copy link
Contributor

sebsto commented Dec 17, 2023

Apologies for the delayed response. I built my own Lambda Authorizer lambda function in Swift to actually test these changes myself instead of asking you to test them.

Here are the new structs

  • APIGatewayLambdaAuthorizerRequest the request - unmodified since last push
  • APIGatewayLambdaAuthorizerSimpleResponse for simple responses (isAuthorized: true/false)
  • APIGatewayLambdaAuthorizerPolicyResponse for IAM policy responses

Here is a sample simple authorizer :

@main
struct MySimpleLambda: SimpleLambdaHandler {
    func handle(_: APIGatewayLambdaAuthorizerRequest,
                context _: LambdaContext) async throws
        -> APIGatewayLambdaAuthorizerSimpleResponse
    {

        return APIGatewayLambdaAuthorizerSimpleResponse(
            isAuthorized: true,
            context: ["abc1": "xyz1"]
        )
    }
}

Here is an IAM policy authorizers :

@main
struct MyPolicyLambda: SimpleLambdaHandler {
    func handle(_: APIGatewayLambdaAuthorizerRequest,
                context _: LambdaContext) async throws
        -> APIGatewayLambdaAuthorizerPolicyResponse
    {

        let resp = APIGatewayLambdaAuthorizerPolicyResponse(
            principalId: "John Appleseed",

            policyDocument: .init(statement: [
                .init(action: "execute-api:Invoke",
                      effect: .allow,
                      resource: "*"),

            ]),

            context: [
                "abc1": "xyz1",
                "abc2": "xyz2",
            ]
        )

        return resp
    }
}

Here is how I attach a Lambda authorizer to another Lambda function (I use SAM)

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: SAM Template for QuoteService

Globals:
  Function:
    Timeout: 60
    CodeUri: .
    Handler: swift.bootstrap
    Runtime: provided.al2
    MemorySize: 512
    Architectures:
      - arm64

Resources:
  # Lambda function
  QuoteService:
    Type: AWS::Serverless::Function
    Properties:
      Events:
        # pass through all HTTP verbs and paths
        Api:
          Type: HttpApi
          Properties:
            ApiId: !Ref MyProtectedApi
            Path: /{proxy+}
            Method: ANY
            Auth:
              Authorizer: MyLambdaAuthorizer

    Metadata:
      BuildMethod: makefile

  MyProtectedApi:
    Type: AWS::Serverless::HttpApi
    Properties:
      Auth:
        DefaultAuthorizer: MyLambdaAuthorizer
        Authorizers:
          MyLambdaAuthorizer:
            AuthorizerPayloadFormatVersion: 2.0
            EnableFunctionDefaultPermissions: true
            EnableSimpleResponses: true
            FunctionArn: arn:aws:lambda:us-east-1:012345678912:function:LambdaAuthorizer
            Identity:
              Headers:
                - Authorization 

# print API endpoint
Outputs:
  SwiftAPIEndpoint:
    Description: "API Gateway endpoint URL for your application"
    Value: !Sub "https://${MyProtectedApi}.execute-api.${AWS::Region}.amazonaws.com"

@GeorgePreece
Copy link
Author

Apologies for the delayed response. I built my own Lambda Authorizer lambda function in Swift to actually test these changes myself instead of asking you to test them.

Here are the new structs

  • APIGatewayLambdaAuthorizerRequest the request - unmodified since last push
  • APIGatewayLambdaAuthorizerSimpleResponse for simple responses (isAuthorized: true/false)
  • APIGatewayLambdaAuthorizerPolicyResponse for IAM policy responses

Here is a sample simple authorizer :

@main
struct MySimpleLambda: SimpleLambdaHandler {
    func handle(_: APIGatewayLambdaAuthorizerRequest,
                context _: LambdaContext) async throws
        -> APIGatewayLambdaAuthorizerSimpleResponse
    {

        return APIGatewayLambdaAuthorizerSimpleResponse(
            isAuthorized: true,
            context: ["abc1": "xyz1"]
        )
    }
}

Here is an IAM policy authorizers :

@main
struct MyPolicyLambda: SimpleLambdaHandler {
    func handle(_: APIGatewayLambdaAuthorizerRequest,
                context _: LambdaContext) async throws
        -> APIGatewayLambdaAuthorizerPolicyResponse
    {

        let resp = APIGatewayLambdaAuthorizerPolicyResponse(
            principalId: "John Appleseed",

            policyDocument: .init(statement: [
                .init(action: "execute-api:Invoke",
                      effect: .allow,
                      resource: "*"),

            ]),

            context: [
                "abc1": "xyz1",
                "abc2": "xyz2",
            ]
        )

        return resp
    }
}

Here is how I attach a Lambda authorizer to another Lambda function (I use SAM)

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: SAM Template for QuoteService

Globals:
  Function:
    Timeout: 60
    CodeUri: .
    Handler: swift.bootstrap
    Runtime: provided.al2
    MemorySize: 512
    Architectures:
      - arm64

Resources:
  # Lambda function
  QuoteService:
    Type: AWS::Serverless::Function
    Properties:
      Events:
        # pass through all HTTP verbs and paths
        Api:
          Type: HttpApi
          Properties:
            ApiId: !Ref MyProtectedApi
            Path: /{proxy+}
            Method: ANY
            Auth:
              Authorizer: MyLambdaAuthorizer

    Metadata:
      BuildMethod: makefile

  MyProtectedApi:
    Type: AWS::Serverless::HttpApi
    Properties:
      Auth:
        DefaultAuthorizer: MyLambdaAuthorizer
        Authorizers:
          MyLambdaAuthorizer:
            AuthorizerPayloadFormatVersion: 2.0
            EnableFunctionDefaultPermissions: true
            EnableSimpleResponses: true
            FunctionArn: arn:aws:lambda:us-east-1:012345678912:function:LambdaAuthorizer
            Identity:
              Headers:
                - Authorization 

# print API endpoint
Outputs:
  SwiftAPIEndpoint:
    Description: "API Gateway endpoint URL for your application"
    Value: !Sub "https://${MyProtectedApi}.execute-api.${AWS::Region}.amazonaws.com"

no problem, works like a charm! thank you 🥳

@sebsto
Copy link
Contributor

sebsto commented Dec 18, 2023

Thank you @GeorgePreece for the review.
PR sent #42

@sebsto
Copy link
Contributor

sebsto commented Dec 18, 2023

closed by #42
Thank you @tomerd for the quick review

@sebsto sebsto closed this as completed Dec 18, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants