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

AWS HTTP API: Support IAM and Lambda authorizers #8210

Closed
jack1902 opened this issue Sep 10, 2020 · 37 comments · Fixed by #9195
Closed

AWS HTTP API: Support IAM and Lambda authorizers #8210

jack1902 opened this issue Sep 10, 2020 · 37 comments · Fixed by #9195

Comments

@jack1902
Copy link

jack1902 commented Sep 10, 2020

Use case description

AWS have announced support for an IAM or lambda authorizer on the HTTP API (much like the API Gateway ones)

https://aws.amazon.com/about-aws/whats-new/2020/09/api-gateway-http-apis-now-supports-lambda-and-iam-authorization-options/

Proposed solution

Use the same logic from the Rest API (API Gateway) for the HTTP API resource creation

Implementation proposal (in progress)

IAM

Currently, when using JWT authorizer, it first has to be defined in provider.httpApi.provider section. As AWS_IAM is not its own configurable Authorizer, there is no point in declaring it in the above section and we could reuse the same pattern as for http event (REST API):

functions:
  create:
    handler: posts.create
    events:
      - httpApi:
          path: /posts/create
          method: post
          authorizer: aws_iam

or

functions:
  create:
    handler: posts.create
    events:
      - httpApi:
          path: /posts/create
          method: post
          authorizer:
            type: aws_iam

Implementation should correctly assign AuthorizationType for AWS::ApiGatewayV2::Route resource:

Type: 'AWS::ApiGatewayV2::Route',

Corresponding REST API Implementation (could be used as inspiration as the mechanism is similar): https://github.com/serverless/serverless/blob/master/lib/plugins/aws/package/compile/events/apiGateway/lib/method/index.js#L66

Lambda

At the moment, Authorizer for HTTP API does not support TOKEN, but only REQUEST type for custom Lambda authorizers (see CloudFormation docs: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigatewayv2-authorizer.html#cfn-apigatewayv2-authorizer-authorizertype). When using such authorizer, we should support a similar approach as for JWT, with definition of an authorizer in provider.httpApi.authorizers.

The proposed structure could look like the following:

provider:
  httpApi:
    authorizers:
      someLambdaAuthorizer:
        type: request
        name: <name-of-authorizer-function> (mutually exclusive with 'arn')
        arn: <arn-of-existing-lambda-function> (mutually exclusive with 'name')
        managedExternally: true (Optional)
        identitySource: <...> (Optional)
        resultTtlInSeconds: 0 (Optional)
        enableSimpleResponses: true (Optional)
        payloadVersion: 2.0
        
functions:
  create:
    handler: posts.create
    events:
      - httpApi:
          path: /posts/create
          method: post
          authorizer:
            name: someLambdaAuthorizer

The implementation should correctly construct AWS::ApiGatewayV2::Authorizer during events compilation, similarly to how it's currently done for JWT authorizer:

For reference on how the underlying resource should be constructed, please refer to the CloudFormation documentation: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigatewayv2-authorizer.html

@jack1902 jack1902 changed the title add IAM and lambda authorizer support to HTRP API add IAM and lambda authorizer support to HTTP API Sep 10, 2020
@medikoo
Copy link
Contributor

medikoo commented Sep 10, 2020

@jack1902 great thanks for report, we definitely should have that!

Before we jump into PR, it'll be good to outline on how configuration settings should be defined and how they should translate to CloudFormation template.

It might be also wise to tackle those two authorizes with two different PR's

@medikoo medikoo changed the title add IAM and lambda authorizer support to HTTP API AWS HTTP API: Support IAM and Lambda authorizers Sep 10, 2020
@jack1902
Copy link
Author

@medikoo Agree on tackling the two authorizers as two seperate PRs.

I believe this is the underlying resource for the authorizer
https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigatewayv2-authorizer.html

@medikoo
Copy link
Contributor

medikoo commented Sep 14, 2020

Thanks @jack1902 and what would be exact service config to CF template properties mappings for both IAM and Lambda authorizrers?

It'll be great to have spec, as e.g. outlined in this description: #8117

@ranneyd
Copy link

ranneyd commented Sep 18, 2020

@medikoo could it not be exactly the same as the normal config for REST apis? At least for the lambda version.

https://www.serverless.com/framework/docs/providers/aws/events/apigateway/#http-endpoints-with-custom-authorizers

@boarnoah
Copy link

I've tried to use it directly the last week or so, and even though the documentation says its identical to rest APIs, the actual process to restrict invoke by IAM is different (for one it's not attached as a resource policy to the api gateway).

@medikoo
Copy link
Contributor

medikoo commented Sep 18, 2020

@medikoo could it not be exactly the same as the normal config for REST apis?

I believe we now need to differentiate authorizers, so we should introduce type. Having that it'll be good to specify:

  • What type values we support
  • What properties are supporter per each type (and whether we should also support some global configuration for given type)
  • How internally we will resolve a CloudFormation properties out of supported configuration

@viniciusvasti
Copy link

Hi guys.
Is this feature coming soon?

@jack1902
Copy link
Author

@viniciusvasti do you know if these resources are available in Cloudformation? Since until a resource is available in Cloudformation, Serverless is powerless to do anything in regards to supporting it

@jansinger
Copy link

@jack1902 The Cloudformation resource AWS::ApiGatewayV2::Authorizer allows at least Lambda Authorizers, see AuthorizerType: REQUEST and AuthorizerUri properties.

At the moment we are creating the Authorizer itself as a resource through Serverless and attach it to the route with an ApiGatewayV2.updateRoute call in an output handler triggered by the serverless-output plugin.

@ranneyd
Copy link

ranneyd commented Oct 28, 2020

@jack1902 The Cloudformation resource AWS::ApiGatewayV2::Authorizer allows at least Lambda Authorizers, see AuthorizerType: REQUEST and AuthorizerUri properties.

At the moment we are creating the Authorizer itself as a resource through Serverless and attach it to the route with an ApiGatewayV2.updateRoute call in an output handler triggered by the serverless-output plugin.

That's pretty smart!

@eshikerya
Copy link

@jansinger could you provide an example, pls? it would be helpful for me to do the same:) at least as temporary solution

@jansinger
Copy link

jansinger commented Oct 28, 2020

@eshikerya sure

Source code for serverless.yaml

serverless.yaml (only relevant parts)

plugins:
  - serverless-stack-output

custom:
  # configuration for serverless-stack-output
  output:
    handler: output.handler

functions:
  # Lambda used as custom authorizer
  custom-authorize:
    handler: authorizer.handler
    description: "Custom Lambda Authorizer for API Gateway"
  # httpApi Lambda
  hello-world:
    handler: hello-world.handler
    description: "Example httpApi event"
    events:
      - httpApi:
          path: /hello
          method: GET
resources:
  Resources:
    # Allow Apigateway to call the authorizer function
    # Note the serverless nameing schema for functions
    CustomDashAuthorizeLambdaFunctionHttpApi:
      Type: AWS::Lambda::Permission
      Properties:
        FunctionName:
          Fn::GetAtt:
          - CustomDashAuthorizeLambdaFunction
          - Arn
        Action: lambda:InvokeFunction
        Principal: apigateway.amazonaws.com
        SourceArn:
          Fn::Join:
          - ''
          - - 'arn:'
            - Ref: AWS::Partition
            - ":execute-api:"
            - Ref: AWS::Region
            - ":"
            - Ref: AWS::AccountId
            - ":"
            - Ref: HttpApi
            - "/*"
    # The Apigateway Authorizer Resource
    CustomAuthorizer:
      Type: AWS::ApiGatewayV2::Authorizer
      Properties: 
        Name: "custom-authorizer"
        ApiId: !Ref HttpApi
        AuthorizerPayloadFormatVersion: "2.0"
        AuthorizerResultTtlInSeconds: 300
        AuthorizerType: "REQUEST"
        IdentitySource: [ "$request.header.Authorization" ]
        AuthorizerUri: !Sub "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${CustomDashAuthorizeLambdaFunction.Arn}/invocations"
        EnableSimpleResponses: true
  # Needed for output handler
  Outputs:
    ApiID:
      Value:
        Ref: HttpApi
    HelloApiRouteID:
      Value:
        Ref: HttpApiRouteGetHello
    CustomAuthorizerID:
      Value:
        Ref:  CustomAuthorizer
Source code for output.js

output.js

const AWS = require('aws-sdk');

const apiGw = new AWS.ApiGatewayV2({
    region: 'eu-central-1'
});

function attachAuthorizerToRoute(apiId, routeId, authorizerId) {
    const params = {
        ApiId: apiId,
        RouteId: routeId,
        AuthorizationType: 'CUSTOM',
        AuthorizerId: authorizerId
    };
    return apiGw.updateRoute(params).promise();
}

async function handler(data, serverless, options) {
    const actions = [];
    // Attach authorizer to /hello route
    actions.push(attachAuthorizerToRoute(data.ApiID, data.HelloApiRouteID, data.CustomAuthorizerID));
    try {
        const result = await Promise.all(actions);
        console.log('Success!', result);
    } catch (error) {
        console.warn(error);
    }
}

module.exports = { handler };
Disclaimer: this is a not tested modified extract from our sources.

@TheMechanic
Copy link

Hi guys,
any news about this ? I' m using RestAPI for now, with custom lambda authorizer. HttpApi seems to be a really good thing, I'm thinking to migrate to it, but I'm stuck by this

@medikoo
Copy link
Contributor

medikoo commented Dec 30, 2020

@TheMechanic we're grouping implementation spec now, and questions asked here: #8210 (comment) remain pending

@BryanCrotaz
Copy link

This is really biting my project. Where is the discussion being held on the comment and implementation spec?

@BryanCrotaz
Copy link

@jansinger

Source code for serverless.yaml
Source code for output.js

How does output.js get run? I can't see how that fits with serverless-output?

@jansinger
Copy link

@BryanCrotaz

Look at handler: output.handler in serverless.yaml, this refers to <filename>.<function>, so async function handler(...) inside output.js is configured as the handler.

@ed-sparkes
Copy link

Keen for an update on this one too

@jstrese
Copy link

jstrese commented Jan 26, 2021

Any updates on this? Is there a branch or fork with the proposed implementation?

@pgrzesik
Copy link
Contributor

pgrzesik commented Feb 1, 2021

Hello folks, thanks to everyone for your patience on this one. Unfortunately, due to other ongoing tasks, this initiative didn't get proper attention, but we're aiming to change that. Below I present an implementation proposal for both iam and lambda authorizers, I'll also edit the original post with that proposal and will use it to track the final version of it.

IAM

Currently, when using JWT authorizer, it first has to be defined in provider.httpApi.provider section. As AWS_IAM is not its own configurable Authorizer, there is no point in declaring it in the above section and we could reuse the same pattern as for http event (REST API):

functions:
  create:
    handler: posts.create
    events:
      - httpApi:
          path: /posts/create
          method: post
          authorizer: aws_iam

or alternatively, but I think we should first stick to the above version

functions:
  create:
    handler: posts.create
    events:
      - httpApi:
          path: /posts/create
          method: post
          authorizer:
            type: aws_iam

Implementation should correctly assign AuthorizationType for AWS::ApiGatewayV2::Route resource:

Type: 'AWS::ApiGatewayV2::Route',

Corresponding REST API Implementation (could be used as inspiration as the mechanism is similar): https://github.com/serverless/serverless/blob/master/lib/plugins/aws/package/compile/events/apiGateway/lib/method/index.js#L66

Lambda

At the moment, Authorizer for HTTP API does not support TOKEN, but only REQUEST type for custom Lambda authorizers (see CloudFormation docs: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigatewayv2-authorizer.html#cfn-apigatewayv2-authorizer-authorizertype). When using such authorizer, we should support a similar approach as for JWT, with definition of an authorizer in provider.httpApi.authorizers.

The proposed structure could look like the following:

provider:
  httpApi:
    authorizers:
      someLambdaAuthorizer:
        type: request
        name: <name-of-authorizer-function> (mutually exclusive with 'arn')
        arn: <arn-of-existing-lambda-function> (mutually exclusive with 'name')
        managedExternally: true (Optional)
        identitySource: <...> (Optional)
        resultTtlInSeconds: 0 (Optional)
        enableSimpleResponses: true (Optional)
        payloadVersion: 2.0
        
functions:
  create:
    handler: posts.create
    events:
      - httpApi:
          path: /posts/create
          method: post
          authorizer:
            name: someLambdaAuthorizer

The implementation should correctly construct AWS::ApiGatewayV2::Authorizer during events compilation, similarly to how it's currently done for JWT authorizer:

For reference on how the underlying resource should be constructed, please refer to the CloudFormation documentation: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigatewayv2-authorizer.html

We're looking for your feedback on the above proposal 🎉 After we'll finalize the implementation approach, we'd be more than happy to accept a PR for either of the authorization methods. 🙇 Otherwise, I plan to work on the actual implementation in the upcoming weeks, depending on other priorities. Thanks everyone 🙇

@pgrzesik
Copy link
Contributor

pgrzesik commented Feb 1, 2021

I'd love to hear your opinion on this one @medikoo 🙇

@medikoo
Copy link
Contributor

medikoo commented Feb 1, 2021

@pgrzesik thank you. Proposal looks great to me!

I have just one minor doubt about authorizer: aws_iam notation. While obviously it's a shortest path, it creates an ambiguity and design quirk in my eyes.

Currently string notation for authorizer is translated to name of authorizer to be taken from provider.authorizers section, introducing a special handling for case where string is aws_iam doesn't feel clean (it can be confusing)

I think it'll be more solid if we support IAM authorizer by second proposed notation, so:

functions:
  function:
    events:
      - httpApi:
          authorizer:
            type: "iam"

Such configuration at least, shouldn't leave a doubt on what we're configuring

@pgrzesik
Copy link
Contributor

pgrzesik commented Feb 1, 2021

Thanks @medikoo - that's a great point and suggestion - being more explicit here is definitely a better choice as the "short" version is ambiguous. I had a problem with iam in general, as it's not really an "authorizer" object that we're provisioning, which is different than all other supported cases, but I cannot think of a better way to express it in the configuration.

@ranneyd
Copy link

ranneyd commented Feb 1, 2021

Looks pretty good. A few questions:

  1. For the "lambda" version, what does name do and is it necessary if we provide an arn?
  2. Currently under events -> http -> authorizer we put arn: ____, managedExternally: true and resultTtlInSeconds: 0. Presumably the managedExternally: true is implied here. For the resultTtlInSeconds: 0 part (for managing the authorizers cache) would that be supported and/or not apply here?

@pgrzesik
Copy link
Contributor

pgrzesik commented Feb 2, 2021

Hello @ranneyd - great questions!

  1. I've followed a similar approach as for http event, where by providing name, you could specify function name that is defined in serverless.yml to be used as authorizer function. However, I see that it's a bit ambiguous and maybe functionName would be a better name for that property? As for arn - I didn't make it clear, sorry, it's mutually exclusive with name (or functionName) property - either you directly specify arn (or maybe also a property like functionArn would be more appropriate?) or you reference function via name (or functionName).
  2. As for managedExternally - I've missed that setting and I believe we should support it in a similar way as for http event by skipping creation of AWS::Lambda::Permission resource if it's set to true. As for the resultTtlInSeconds - it's supported and I've added it to the spec above - is there anything specific about this setting that should work differently from how it works for http events?

@ranneyd
Copy link

ranneyd commented Feb 2, 2021

@pgrzesik thank you!

  1. Mutual exclusivity was really my question. I don't actually think the names are bad; arn is pretty obvious and functionName doesn't add much to name in my opinion.
  2. Perfect for managedExternally. For resultTtlInSeconds I'd want it to work exactly the same as it does now for http, so perfect!

This is all great. Looking forward to it!

@pgrzesik
Copy link
Contributor

pgrzesik commented Feb 2, 2021

Thanks for your feedback @ranneyd 🙇 I've updated the proposal to include managedExternally 💯

@jack1902
Copy link
Author

jack1902 commented Feb 2, 2021

@pgrzesik Implementation within the yaml so far looks good 🚀. Thanks for keeping my original post up to date

@MikeRippon
Copy link

Thank you @jansinger , this seemed to work and is a lifesaver for me! In our case I wanted to make sure that the authorizer was attached to all routes, so I modified the output.js file for this case (I also had a credentials issue, so I changed that too).

I also had to npm install aws-sdk --save-dev. Maybe that is standard, but I'm new to serverless so thought I'd point it out!

const AWS = require('aws-sdk');

async function handler({ profile, region, apiId, customAuthorizerId }) {
    AWS.config.credentials = new AWS.SharedIniFileCredentials({ profile: profile });
    const apiGw = new AWS.ApiGatewayV2({ region: region });

    const routesResponse = await apiGw.getRoutes({
        ApiId: apiId
    }).promise();

    const routesToUpdate = routesResponse.Items.filter(route => !route.AuthorizerId);

    console.log(`Attaching authorizer to ${routesToUpdate.length} routes`);

    if (routesToUpdate.length) {
        const updateActions = routesToUpdate.map(route => apiGw.updateRoute({
            ApiId: apiId,
            RouteId: route.RouteId,
            AuthorizationType: 'CUSTOM',
            AuthorizerId: customAuthorizerId
        }).promise());

        const routeNames = routesToUpdate.map(route => route.RouteKey);
        console.log(routeNames.join('\n'));

        await Promise.all(updateActions);

        console.log("Success!");

    }
}

module.exports = { handler };

@mdrijwan
Copy link

mdrijwan commented Jan 3, 2022

following. this support is absolutely necessary

@pgrzesik
Copy link
Contributor

pgrzesik commented Jan 3, 2022

@mdrijwan What is missing for you? Support for this has been released a long time ago

@AdamAH
Copy link

AdamAH commented Jan 17, 2022

hey @pgrzesik - mine is failing too despite following the docs exactly at: https://www.serverless.com/framework/docs/providers/aws/events/http-api

My yml:

provider: name: aws runtime: nodejs14.x lambdaHashingVersion: "20201221" httpApi: authorizers: customAuthorizer: type: request functionName: authorizer payloadVersion: 2.0

But I keep getting that identitySources couldnt be null. So when I add it in
identitySource: method.request.header.Authorization

I think get this:

Error: The CloudFormation template is invalid: [/Resources/HttpApiAuthorizerAuthorizer/Type/JwtConfiguration/Audience/0] 'null' values are not allowed in templates

Been staring at this for the last several hours and I don't know what I'm doing wrong. Please advise! I just need to create a simple lambda authorizer

@pgrzesik
Copy link
Contributor

Hello @AdamAH - make sure you're using the latest version of the Framework - what version are you on? Could you please post a reproducible example and output of sls deploy as well?

@mnapoli
Copy link
Contributor

mnapoli commented Jan 17, 2022

👍 and please do that in a separate issue as a bug report to avoid pinging the 15 other participants in this old discussion.

@AdamAH
Copy link

AdamAH commented Jan 17, 2022

Hello @AdamAH - make sure you're using the latest version of the Framework - what version are you on? Could you please post a reproducible example and output of sls deploy as well?

Gotcha - i was on 1.83.3 - this was from the boilerplate apps created when i setup project a month ago (ts + http API). I updated it to frameworkVersion: "2" and everything broke. All my routes just return null now so I had to revert back.

I tried installing serverless offline to test but nothing everything is fine here. Not sure how to debug this without impacting the environment

@AdamAH
Copy link

AdamAH commented Jan 17, 2022

👍 and please do that in a separate issue as a bug report to avoid pinging the 15 other participants in this old discussion.

ah sorry will re-paste text in a new issue

@asfernandes
Copy link

Where should be put the permission for the API Gateway to call the lambda authorizer?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.