Skip to content
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

[apigateway] add support for lambda token authorizer #1402

Closed
brianfoody opened this issue Dec 19, 2018 · 42 comments · Fixed by #5197
Closed

[apigateway] add support for lambda token authorizer #1402

brianfoody opened this issue Dec 19, 2018 · 42 comments · Fixed by #5197
Assignees
Labels
@aws-cdk/aws-apigateway Related to Amazon API Gateway feature-request A feature should be added or improved. in-progress This issue is being actively worked on.

Comments

@brianfoody
Copy link

When I specify an authorizerType of CUSTOM for my Lambda integration it doesn't actually populate when deployed to AWS and I have to manually update.

This is the authorizer spec;

this.aggIdApi.addMethod("GET", new LambdaIntegration(getterFn), {
      authorizationType: AuthorizationType.Custom,
      authorizerId: authorizer.authorizerId
});

When deployed it only has an authorizerId
screen shot 2018-12-19 at 11 10 34 pm

however when I manually update the authorizerType is populated and everything works perfectly;
screen shot 2018-12-19 at 11 11 43 pm

@eladb eladb added @aws-cdk/aws-apigateway Related to Amazon API Gateway gap labels Dec 19, 2018
@eladb
Copy link
Contributor

eladb commented Dec 19, 2018

Looks like a bug

@brianfoody
Copy link
Author

Happy to do a PR if you can point me in the right direction @eladb

@eladb
Copy link
Contributor

eladb commented Dec 20, 2018

The code the synthesizes the method resource is here. Not 100% sure why this is not propagating.

@rix0rrr rix0rrr added gap and removed gap labels Jan 4, 2019
@brianfoody
Copy link
Author

brianfoody commented Jan 8, 2019

Hi @eladb . Just revisiting this and I wasn't able to see any issue with the code there either. I noticed you removed the gap label. I've done a bit more digging and it looks like the authorizationType is getting populated now but I'm still getting an error. The only difference I can see is that the requestParameters and requestModels is being populated with an empty object in CDK deployment.

screen shot 2019-01-08 at 1 24 44 pm

@eladb eladb added the bug This issue is a bug. label Jan 8, 2019
@eladb eladb self-assigned this Jan 8, 2019
@eladb
Copy link
Contributor

eladb commented Jan 8, 2019

Looking into it

@rix0rrr
Copy link
Contributor

rix0rrr commented Jan 8, 2019

The label was me, I must have misclicked in the UI. Sorry about that.

@eladb
Copy link
Contributor

eladb commented Jan 8, 2019

@brianfoody I am trying to understand what the issue is exactly at this point. Any chance you can submit a more complete code snippet so we can try to reproduce?

@brianfoody
Copy link
Author

Hi @eladb . Yes sorry it's been a confusing one to hunt down. I turned on CloudWatch logs for my API Gateway method and I can see now that the actual error is that APIGateway doesn't have permission to invoke the authorizer. I'm guessing when I do it manually it adds the permissions as well.

I just need to figure out how to give the API Gateway method permission to call the authorizer now through CDK.

screen shot 2019-01-08 at 6 21 20 pm

@rix0rrr
Copy link
Contributor

rix0rrr commented Jan 8, 2019

Probably ties into #1465 somewhat? Or am I wrong?

@eladb
Copy link
Contributor

eladb commented Jan 8, 2019

Authorizers are simply not supported by our L2 library yet, so yes- manual permission grants are needed.

@eladb
Copy link
Contributor

eladb commented Jan 8, 2019

@brianfoody
Copy link
Author

Well 6 hours later I have resolved the issue. The grantInvoke didn't actually work for me. I had to create a custom script to attach the right permissions in the end as it required a weird /authorizers sourceARN string. This was the code that fixed the issue for me in the end and replicated what was happening when I manually attached an authorizer.

screen shot 2019-01-09 at 2 54 56 pm

I guess I should close for now if it's a known issue based on L2 library but it would be good if this could be documented somewhere.

@eladb
Copy link
Contributor

eladb commented Jan 15, 2019

@brianfoody apologies for the unpleasant experience, and thanks for sharing your solution. I am reopening this issue and repurposing it as “support lambda authorizers”.

@eladb eladb reopened this Jan 15, 2019
@eladb eladb changed the title Authorizer Type missing for Lambda Integration apigateway: missing support for lambda authorizers Jan 15, 2019
@erezrokah
Copy link

erezrokah commented Jan 15, 2019

I managed to get this working with the following code (along with cors support):
https://github.com/erezrokah/cdk-serverless-example/blob/9627a92fab7186dcecd13346b17fd9f62f9ad36a/services/api-service/lib/apiService.ts#L106

The repository is still work in progress...

Edit: updated the link to a new location

@eladb eladb added feature-request A feature should be added or improved. and removed bug This issue is a bug. needs-response labels Apr 10, 2019
@eladb eladb removed their assignment Apr 10, 2019
@AlexRex
Copy link

AlexRex commented May 15, 2019

@erezrokah is this working for you without doing something else? Not working for me at the moment..

@gidimariastorm
Copy link

I want try to undestand why it doesn't work on python.
So in #4532 you told me that it's incorrect use cfnxxx and non cfnxxx construct.
I know, but It seems that all solutions model the cfnxxx.

so I tried to "translate" in python this -> https://github.com/IainCole/aws-cdk/blob/ic_add_apig_authorizer/packages/%40aws-cdk/aws-apigateway/lib/authorizer.ts#L17

class AuthorizerProps(CfnAuthorizerProps):
    pass
class Authorizer (CfnAuthorizer):
    @property
    def authorizer_id(self):
        return self.ref

    def __init__(self, scope: core.Construct, id: str, auth_props: AuthorizerProps, **kwargs) -> None:
        super().__init__(scope, id, 
        rest_api_id=auth_props.rest_api_id,
        type=auth_props.type,
        name=auth_props.name,
        identity_source=auth_props.identity_source,
        identity_validation_expression=auth_props.identity_validation_expression,
        authorizer_uri=auth_props.authorizer_uri, 
        **kwargs)

auth=Authorizer(self,"authorizer",auth_props=AuthorizerProps(
        rest_api_id=apigw_autoscaling.rest_api_id,
        type="TOKEN",
        name="test",
        identity_source="method.request.header.AUTH",
        identity_validation_expression="TESTAUTH",
        authorizer_uri="".join(["arn:aws:apigateway:eu-west-1:lambda:path/2015-03-31/functions/",lamdbda_api_authorizer.function_arn,"/invocations"])
        ))

 auto_scaling_res.add_method('POST', authorization_type=apigw.AuthorizationType.CUSTOM, authorizer=auth.authorizer_id)

but I receive this error:
jsii.errors.JSIIError: Expected object reference, got "${Token[TOKEN.89]}"

why?

please don't tell me "translate from this https://docs.aws.amazon.com/cdk/latest/guide/multiple_languages.html"

I've read the doc. How can I use the IAuthorizer? is it possible or a this time do I have to use CfnMethod with CfnAuthorizer?

@IainCole
Copy link
Contributor

I don't really know python but I feel like it should look like this:

 auto_scaling_res.add_method('POST', authorization_type=apigw.AuthorizationType.CUSTOM, authorizer=auth)

Rather than:

 auto_scaling_res.add_method('POST', authorization_type=apigw.AuthorizationType.CUSTOM, authorizer=auth.authorizer_id)

@gidimariastorm
Copy link

nope with your solution because I need an authorize_id

Invalid authorizer ID specified. Setting the authorization type to CUSTOM or COGNITO_USER_POOLS requires a valid authorizer. (Service: AmazonApiGateway; Status Code: 400; Error Code: BadRequestException; Request ID: **********)

I'm trying with cfn resources

@IainCole
Copy link
Contributor

Right but this is still progress, it gets further along than it did before presumably if it's actually making it to cloudformation rather than an error building the CDK app, but for whatever reason it doesn't like the ID that it's getting. What is auto_scaling_res in this context?

@gidimariastorm
Copy link

it's the resource. inside of that there's the post method

@IainCole
Copy link
Contributor

I don't really have much to go on here. Are you able to paste the generated template from cdk.out when running my suggested fix? Or at least the API gateway parts?

@gidimariastorm
Copy link

ok. I will also send my code and my not working solution with CFN

@gidimariastorm
Copy link

gidimariastorm commented Oct 17, 2019

error:
APIGWAutoscalingUpdateConf/Default/autoscaling_update/POST (APIGWAutoscalingUpdateConfautoscalingupdatePOSTED942890) Invalid authorizer ID specified. Setting the authorization type to CUSTOM or COGNITO_USER_POOLS requires a valid authorizer. (Service: AmazonApiGateway; Status Code: 400; Error Code: BadRequestException; Request ID: 23fc5d97-5764-4446-a1d1-037e880219eb)
workaround_proposed.txt

It didn't upload the autorizer_id in the CF.
intestead with my solution it seesm don't add the Integration:

from aws_cdk import (
   core,
   aws_lambda as _lambda,
   aws_apigateway as apigw
)
from aws_cdk.aws_apigateway import IAuthorizer, CfnAuthorizer, CfnAuthorizerProps

...
....
apigw_autoscaling=apigw.RestApi(self,'APIGWAutoscalingUpdateConf',
       rest_api_name='autoscaling-api',
       api_key_source_type=apigw.ApiKeySourceType.AUTHORIZER
       )
       auto_scaling_res=apigw_autoscaling.root.add_resource('autoscaling_update')
       lambda_integration=apigw.LambdaIntegration(lamdbda_invoke_autoscaling)
       key =apigw_autoscaling.add_api_key('test_key');
       plan=apigw_autoscaling.add_usage_plan('autoscalingusageplan',api_key=key)
       plan.add_api_stage(stage=apigw_autoscaling.deployment_stage)
       
       
       get=auto_scaling_res.add_method('GET',lambda_integration)
       auth=CfnAuthorizer(self,"authorizer",
       rest_api_id=apigw_autoscaling.rest_api_id,
       type="TOKEN",
       name="test",
       identity_source="method.request.header.AUTH",
       identity_validation_expression="TESTAUTH",
       authorizer_uri="".join(["arn:aws:apigateway:eu-west-1:lambda:path/2015-03-31/functions/",lamdbda_api_authorizer.function_arn,"/invocations"])
       )

       
       apigw.CfnMethod(self,
       "POST",
       http_method="POST",
       resource_id=auto_scaling_res.resource_id,
       rest_api_id=auth.rest_api_id,
       authorization_type='CUSTOM',
       authorizer_id=auth.ref,
       api_key_required=True,
       integration=apigw.Integration(
           type=apigw.IntegrationType.AWS,
           integration_http_method='POST',
           uri="".join(["arn:aws:apigateway:eu-west-1:lambda:path/2015-03-31/functions/",lamdbda_api_authorizer.function_arn,"/invocations"])
       ))


this is the piece of yaml:


authorizer:
    Type: AWS::ApiGateway::Authorizer
    Properties:
      RestApiId:
        Ref: APIGWAutoscalingUpdateConf622FBC28
      Type: TOKEN
      AuthorizerUri:
        Fn::Join:
          - ""
          - - arn:aws:apigateway:eu-west-1:lambda:path/2015-03-31/functions/
            - Fn::GetAtt:
                - LambdaApiAuthorizer041A6040
                - Arn
            - /invocations
      IdentitySource: method.request.header.AUTH
      IdentityValidationExpression: TESTAUTH
      Name: test
    Metadata:
      aws:cdk:path: autoscaling-update/authorizer
  POST:
    Type: AWS::ApiGateway::Method
    Properties:
      HttpMethod: POST
      ResourceId:
        Ref: APIGWAutoscalingUpdateConfautoscalingupdateA92E0778
      RestApiId:
        Ref: APIGWAutoscalingUpdateConf622FBC28
      ApiKeyRequired: true
      AuthorizationType: CUSTOM
      AuthorizerId:
        Ref: authorizer
      Integration: {}
  

@gidimariastorm
Copy link

in the code I created two method: GET and POST.
the first one use the add_method and the other one the cfn:

APIGWAutoscalingUpdateConfautoscalingupdateGETD89CA2FC:
    Type: AWS::ApiGateway::Method
    Properties:
      HttpMethod: GET
      ResourceId:
        Ref: APIGWAutoscalingUpdateConfautoscalingupdateA92E0778
      RestApiId:
        Ref: APIGWAutoscalingUpdateConf622FBC28
      AuthorizationType: NONE
      Integration:
        IntegrationHttpMethod: POST
        Type: AWS_PROXY
        Uri:
          Fn::Join:
            - ""
            - - "arn:"
              - Ref: AWS::Partition
              - :apigateway:eu-west-1:lambda:path/2015-03-31/functions/
              - Fn::GetAtt:
                  - LambdaInvokeAutoScaling34D0A3D0
                  - Arn
              - /invocations

  POST:
    Type: AWS::ApiGateway::Method
    Properties:
      HttpMethod: POST
      ResourceId:
        Ref: APIGWAutoscalingUpdateConfautoscalingupdateA92E0778
      RestApiId:
        Ref: APIGWAutoscalingUpdateConf622FBC28
      ApiKeyRequired: true
      AuthorizationType: CUSTOM
      AuthorizerId:
        Ref: authorizer
      Integration: {} ---> it's empty!!!!

@gidimariastorm
Copy link

Solution founded for python:

auth=CfnAuthorizer(self,"authorizer",
        rest_api_id=apigw_autoscaling.rest_api_id,
        type="TOKEN",
        name="test",
        identity_source="method.request.header.AUTH",
        identity_validation_expression="TESTAUTH",
        authorizer_uri="".join(["arn:aws:apigateway:eu-west-1:lambda:path/2015-03-31/functions/",lamdbda_api_authorizer.function_arn,"/invocations"])
        )
        
        apigw.CfnMethod(self,
        "POST",
        http_method="POST",
        resource_id=auto_scaling_res.resource_id,
        rest_api_id=auth.rest_api_id,
        authorization_type='CUSTOM',
        authorizer_id=auth.ref,
        api_key_required=True,
        integration=apigw.CfnMethod.IntegrationProperty(
            type="AWS_PROXY",
            integration_http_method='POST',
            uri="".join(["arn:aws:apigateway:eu-west-1:lambda:path/2015-03-31/functions/",lamdbda_api_authorizer.function_arn,"/invocations"]))
        )  

you have to use CfnAuthorizer to create the authorizer and the use the CfnMethod explicity set the .CfnMethod.IntegrationProperty

@nija-at nija-at changed the title apigateway: missing support for lambda authorizers [apigateway] add support for lambda authorizers Oct 18, 2019
@acomagu
Copy link
Contributor

acomagu commented Oct 21, 2019

Below worked for me(TypeScript):

    const authorizeFunc = new lambda.Function( ... );

    const authorizeFuncExecutionRole = new iam.Role(this, 'authorizeFuncExecution', {
      assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'),
    });
    authorizeFunc.grantInvoke(authorizeFuncExecutionRole);

    const chargeFunc = new lambda.Function( ... );

    const apigwLambdaIntegration = new apigateway.LambdaIntegration(chargeFunc, {
      proxy: true,
    });

    const apigw = new apigateway.RestApi(this, 'api', {
      restApiName: 'api',
    });

    const authorizer = new apigateway.CfnAuthorizer(this, 'authorizer', {
      name: 'authorizer',
      authorizerUri: `arn:aws:apigateway:${this.region}:lambda:path/2015-03-31/functions/${authorizeFunc.functionArn}/invocations`,
      type: 'TOKEN',
      authorizerCredentials: authorizeFuncExecutionRole.roleArn,
      identitySource: 'method.request.header.Authorization',
      restApiId: apigw.restApiId,
    });

    const chargePath = apigw.root.addResource('charge');
    chargePath.addMethod('POST', apigwLambdaIntegration, {
      authorizationType: apigateway.AuthorizationType.CUSTOM,
      authorizer: {authorizerId: authorizer.ref},
    });

I referenced https://stackoverflow.com/a/58170219/8182174.

@gidimariastorm
Copy link

how?

    const chargePath = apigw.root.addResource('charge');
    chargePath.addMethod('POST', apigwLambdaIntegration, {
      authorizationType: apigateway.AuthorizationType.CUSTOM,
      authorizer: {authorizerId: authorizer.ref}, ---> how Can use this??
    });
apigw.CfnMethod(self,
        "POST",
        http_method="POST",
        resource_id=auto_scaling_res.resource_id,
        rest_api_id=auth.rest_api_id,
        authorization_type='CUSTOM',
        authorizer_id=auth.ref,
        api_key_required=True,
        integration=apigw.CfnMethod.IntegrationProperty(
            type="AWS_PROXY",
            integration_http_method='POST',
            uri="".join(["arn:aws:apigateway:eu-west-1:lambda:path/2015-03-31/functions/",lamdbda_api_authorizer.function_arn,"/invocations"]))
        ) 

what's in ts this? -> authorizer: {authorizerId: authorizer.ref}
in the addMethod the reference for that resource is IAuthorize
https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-apigateway.MethodOptions.html

@acomagu
Copy link
Contributor

acomagu commented Oct 22, 2019

@gidimariastorm
Because IAuthorizer accepts an object which has field authorizerId.

export interface IAuthorizer {
  readonly authorizerId: string;
}

https://github.com/aws/aws-cdk/blob/v1.13.1/packages/@aws-cdk/aws-apigateway/lib/authorizer.ts#L4

The code results such template.yml:

  ...
  apichargePOST4F331850:
    Type: AWS::ApiGateway::Method
    Properties:
      ...
      AuthorizerId:
        Ref: authorizer

I'm not sure about the internal detail of CDK but I guess authorizer.ref means the string Ref: authorizer in the context.

@gidimariastorm
Copy link

I know that authorizerId i'ts an attribute of IAuhtorizer :) .
My question is how can I change it? it's only readonly property.
So my question was: in typescript what's this statement:
authorizer: {authorizerId: authorizer.ref}

How can you call the IAuthorizer obj (and change its readonly value) without create an instance of it?

@acomagu
Copy link
Contributor

acomagu commented Oct 22, 2019

@gidimariastorm
Oh, sorry for my misunderstanding of the question.

How can you call the IAuthorizer obj (and change its readonly value) without create an instance of it?

No, it's creating new object.
I'm not familiar with Python, but in TypeScript(and JavaScript), {} means creating new object and it has no difference with an instance of class.

So it's same as:

class MyAuthorizer {
    public authorizerId: string;
    constructor(authorizerId: string) {
        this.authorizerId = authorizerId;
    }
}
...
    authorizer: new MyAuthorizer(authorizer.ref),

@gidimariastorm
Copy link

No, it's creating new object.
I'm not familiar with Python, but in TypeScript(and JavaScript), {} means creating new object and it has no difference with an instance of class.

ok, I don't think that in python is possible

@nija-at
Copy link
Contributor

nija-at commented Nov 9, 2019

I've started to build first class L2 support for Lambda authorizers.

Are there (preferably complete) CDK apps or CF templates that I can use as references for how Lambda request authorizers and Lambda token authorizers are used and set up?

@tomomano
Copy link

tomomano commented Nov 11, 2019

@gidimariastorm I am also facing the same issue. Only solution, it seems to me, is to directly manipulate CloudFormation properties, as you have demonstrated before.

The same issue is also reported here : #723 (comment)

The solution I came up with is a hybrid of add_method() and overriding the Cfn config:

meth = auto_scaling_res.add_method('POST', authorization_type=apigw.AuthorizationType.CUSTOM)
meth.node.find_child("Resource").add_property_override('AuthorizerId', auth.authorizer_id)

The above approach is working in my program. Nonetheless, I really wish I could simply use add_method() just as in TypeScript...

@gidimariastorm
Copy link

I did the same think. the problem is the Deploy because add_method cannot deploy the new version. you have to create a CFN resource for the deploy

@tomomano
Copy link

tomomano commented Nov 11, 2019

I did the same think. the problem is the Deploy because add_method cannot deploy the new version. you have to create a CFN resource for the deploy

What do you mean? I was able to deploy the stack, using the above logic.

My code:

class ApigatewayCognitoStack(core.Stack):

    def __init__(self, scope: core.App, id: str, cognito_arn: str, **kwargs) -> None:
        super().__init__(scope, id, **kwargs)

        api = apigw.RestApi(self, 'my_API')
        test_resource = api.root.add_resource('test')

        # Cognito authorizer
        cfn_authorizer = apigw.CfnAuthorizer(
            self, "my_cognito",
            name='API_authorizer',
            type='COGNITO_USER_POOLS',
            identity_source='method.request.header.Authorization',
            rest_api_id=api.rest_api_id,
            provider_arns=[cognito_arn]
        )

        hello_world_handler = _lambda.Function(
            self, 'my_handler',
            code=_lambda.AssetCode('lambda'),
            handler='index.handler',
            runtime=_lambda.Runtime.PYTHON_3_7
        )

        # attach GET method
        hello_world_integration = apigw.LambdaIntegration(hello_world_handler)
        meth = test_resource.add_method("GET", hello_world_integration,
            authorization_type=apigw.AuthorizationType.COGNITO
        )
        meth.node.find_child('Resource').add_property_override('AuthorizerId', cfn_authorizer.ref)

@nija-at
Copy link
Contributor

nija-at commented Nov 26, 2019

Support for lambda token authorizers - #5197

@SomayaB SomayaB added the in-progress This issue is being actively worked on. label Nov 26, 2019
@JoshM1994
Copy link

In addition to the fantastic solution suggested by @acomagu https://github.com/aws/aws-cdk/issues/1402#issuecomment-544325927, I also found it necessary to add the following line:

    const authorizeFuncExecutionRole = new iam.Role(this, 'authorizeFuncExecution', {
      assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'),
    });
    authorizeFunc.grantInvoke(authorizeFuncExecutionRole);
    authorizeFunc.latestVersion.grantInvoke(authorizeFuncExecutionRole); // This line

which yields the following policy for your authorizeFunc role

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": "lambda:InvokeFunction",
            "Resource": "arn:aws:lambda:eu-central-1:208596384826:function:YOUR_FUNCTION",
            "Effect": "Allow"
        },
        {
            "Action": "lambda:InvokeFunction",
            "Resource": "arn:aws:lambda:eu-central-1:208596384826:function:YOUR_FUNCTION:$LATEST",
            "Effect": "Allow"
        }
    ]
}

Not sure if this is necessary for everyone but I've had to do a similar thing in the past with Firehose invoking Lambdas and not having permission to invoke the latest version. Anyway - it fixed my issue so thought I'd share.

@nija-at nija-at changed the title [apigateway] add support for lambda authorizers [apigateway] add support for lambda token authorizer Jan 2, 2020
@nija-at
Copy link
Contributor

nija-at commented Jan 2, 2020

Re-purposing this issue to add support for lambda token authorizer.

I've opened separate issues for the lambda request authorizer - #5617 - and the cognito user pool authorizer - #5618.

@mergify mergify bot closed this as completed in #5197 Jan 2, 2020
@lielran
Copy link

lielran commented Jan 2, 2020

Thanks @nija-at !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
@aws-cdk/aws-apigateway Related to Amazon API Gateway feature-request A feature should be added or improved. in-progress This issue is being actively worked on.
Projects
None yet
Development

Successfully merging a pull request may close this issue.