Настройка общего лямбда-авторизатора в Serverless Framework

Я пытаюсь создать собственный Lambda-авторизатор, который будет разделен между несколькими различными стеками служб / серверов. Если я понимаю документацию здесь https://serverless.com/framework/docs/providers/aws/events/apigateway/, это означает, что мне нужно создать ресурс общего авторизатора в службе / стеке "общих ресурсов", а затем обратитесь к этому общему авторизатору из других моих служб. Прежде всего: правильно ли мое понимание?

Если мое понимание верно, мой следующий вопрос звучит так: как мне это сделать? Документация не дает четкого примера для лямбда-авторизаторов, поэтому вот как я пытался ее настроить:

functions:
authorizerFunc:
handler: authorizer/authorizer.handler
runtime: nodejs8.10

resources:
Resources:
authorizer:
Type: AWS::ApiGateway::Authorizer
Properties:
AuthorizerResultTtlInSeconds: 0
Name: Authorizer
Type: REQUEST
AuthorizerUri: ???
RestApiId:
Fn::ImportValue: myRestApiId

Я не понимаю, какой синтаксис для AuthorizerUri должен быть. Я пробовал "Ref: authorizerFunc", "Fn::GetAtt: [authorizerFunc, Arn]" и т. Д. Безрезультатно.

Когда я получаю работающий authorizerUri, я просто добавляю Вывод для моего ресурса авторизатора, а затем Fn::ImportValue из сервисов, содержащих мой Lambdas API?

Ссылка на мой вопрос на форуме о сервере для потомков: https://forum.serverless.com/t/shared-lambda-authorizer/6447

0 ответов

В конце концов я заставил его работать, вот как я настроил serverless.yml моего аутентификатора:

service: user-admin-authorizer

custom:
  region: ${file(serverless.env.yml):${opt:stage}.REGION}

provider:
  name: aws
  region: ${self:custom.region}

functions:
  authorizer:
    handler: src/authorizer.handler
    runtime: nodejs8.10

resources:
  Resources:
    Authorizer:
      Type: AWS::ApiGateway::Authorizer
      Properties:
        Name: Authorizer
        Type: REQUEST
        AuthorizerUri:
          Fn::Join: [ "",
            [
              "arn:aws:apigateway:",
              "${self:custom.region}",
              ":lambda:path/",
              "2015-03-31/functions/",
              Fn::GetAtt: ["AuthorizerLambdaFunction", "Arn" ],
              "/invocations"
            ]]
        RestApiId:
          Fn::ImportValue: api-gateway:${opt:stage}:rest-api-id
    apiGatewayLambdaPermissions:
      Type: AWS::Lambda::Permission
      Properties:
        FunctionName:
          Fn::GetAtt: [ AuthorizerLambdaFunction, Arn]
        Action: lambda:InvokeFunction
        Principal:
          Fn::Join: [ "",
          [
            "apigateway.",
            Ref: AWS::URLSuffix
          ]]

  Outputs:
    AuthorizerRef:
      Value:
        Ref: Authorizer
      Export:
        Name: authorizer-ref:${opt:stage}

Вещи, на которые следует обратить внимание: несмотря на то, что функция Authorizer называется "Authorizer", вам необходимо использовать заглавную букву и добавлять "LambdaFunction" к ее имени при использовании его с GetAtt, поэтому "Authorizer" становится "AuthorizerLambdaFunction" по некоторым причинам. Я также должен был добавить ресурс разрешения лямбды.

Ресурсу шлюза API также требуется два выхода: его идентификатор API и идентификатор корневого ресурса API. Вот как настроен serverless.yml моего шлюза API:

resources:
  Resources:
    ApiGateway:
      Type: AWS::ApiGateway::RestApi
      Properties:
        Name: ApiGateway

  Outputs:
    ApiGatewayRestApiId:
      Value:
        Ref: ApiGateway
      Export:
        Name: api-gateway:${opt:stage}:rest-api-id
    ApiGatewayRestApiRootResourceId:
      Value:
        Fn::GetAtt:
          - ApiGateway
          - RootResourceId
      Export:
        Name: api-gateway:${opt:stage}:root-resource-id

Теперь вам просто нужно указать другим сервисам, что они должны использовать этот API-шлюз (импортированные значения являются выходными данными API-шлюза):

provider:
  name: aws
  apiGateway:
    restApiId:
      Fn::ImportValue: api-gateway:${opt:stage}:rest-api-id
    restApiRootResourceId:
      Fn::ImportValue: api-gateway:${opt:stage}:root-resource-id

После этого авторизатор может быть добавлен к отдельным функциям в этом сервисе так:

          authorizer:
            type: CUSTOM
            authorizerId:
              Fn::ImportValue: authorizer-ref:${opt:stage}

У меня была та же проблема, которую вы описали. Или, по крайней мере, я так думаю. И мне удалось решить эту проблему, следуя документации по предоставленным вами ссылкам.

В серверной документации указано, что формат авторизатора должен быть

authorizer:
  # Provide both type and authorizerId
  type: COGNITO_USER_POOLS # TOKEN or COGNITO_USER_POOLS, same as AWS Cloudformation documentation
  authorizerId: 
    Ref: ApiGatewayAuthorizer  # or hard-code Authorizer ID

Насколько я понимаю, мое решение (предоставлено ниже) следует жестко закодированному подходу ID авторизатора.

В службе, имеющей общий авторизатор, он объявлен в serverless.yml обычным способом, т.е.

functions:
  myCustomAuthorizer:
    handler: path/to/authorizer.handler
    name: my-shared-custom-authorizer

Затем в службе, которая хочет использовать этот общий авторизатор, функция в servlerless.yml объявляется как

functions:
  foo:
    # some properties ...
    events:
      - http:
          # ... other properties ...
          authorizer:
            name: authorize
            arn:
              Fn::Join:
                - ""
                - - "arn:aws:lambda"
                  # References to values such as region, account id, stage, etc
                  # Can be done with Pseudo Parameter Reference
                  - ":"
                  - "function:myCustomAuthorizer"

Крайне важно было добавить имя свойства. Это не будет работать без него, по крайней мере, на данный момент.

Подробнее см.

К сожалению, я не могу сказать, имеет ли этот подход некоторые ограничения по сравнению с вашим предложением определить авторизатор как ресурс. Фактически, это может упростить повторное использование одного и того же авторизатора в нескольких функциях одного и того же сервиса.

Serverless 1.35.1 Для людей, спотыкающихся в этой теме, вот новый способ

Где бы вы ни создавали пул пользователей, вы можете пойти дальше и добавить ApiGatewayAuthorizer

# create a user pool as normal
CognitoUserPoolClient:
  Type: AWS::Cognito::UserPoolClient
  Properties:
    # Generate an app client name based on the stage
    ClientName: ${self:custom.stage}-user-pool-client
    UserPoolId:
      Ref: CognitoUserPool
   ExplicitAuthFlows:
   - ADMIN_NO_SRP_AUTH
   GenerateSecret: true

# then add an authorizer you can reference later
ApiGatewayAuthorizer:
  DependsOn:
  # this is pre-defined by serverless
  - ApiGatewayRestApi
  Type: AWS::ApiGateway::Authorizer
  Properties:
    Name: cognito_auth
    # apparently ApiGatewayRestApi is a global string
    RestApiId: { "Ref" : "ApiGatewayRestApi" }
    IdentitySource: method.request.header.Authorization
    Type: COGNITO_USER_POOLS
    ProviderARNs:
    - Fn::GetAtt: [CognitoUserPool, Arn]

Затем, когда вы определяете свои функции

graphql:
  handler: src/app.graphqlHandler
  events:
  - http:
    path: /
    method: post
    cors: true
    integration: lambda
    # add this and just reference the authorizer
    authorizer:
      type: COGNITO_USER_POOLS
      authorizerId:
        Ref: ApiGatewayAuthorizer

Переход на общий пользовательский API Gateway Lambda Authorizer был несложным, когда он работал как часть службы. В этот момент нужно было просто добавить arn: к развернутой лямбда-выражению (авторизатору) и удалить определение "авторизатор" из службы в отдельную развертываемую службу.

  myLambdaName:
    handler: handler.someNodeFunction
    name: something-someNodeFunction
    events:
      - http:
          path: /path/to/resource
          method: get
          cors: true
          authorizer: 
            name: myCustomAuthorizer
            # forwarding lambda proxy event stuff to the custom authorizer
            IdentitySource: method.request.header.Authorization, context.path
            type: request
            arn:  'arn:aws:lambda:region:##:function:something-else-myCustomAuthorizer'

Тогда другая "служба" просто имеет несколько настраиваемых авторизаторов, совместно используемых несколькими развернутыми микросервисами.

functions:

  myCustomAuthorizer:
    name: something-else-myCustomAuthorizer
    handler: handler.myCustomAuthorizer

Примечание:

Если вам нужен авторизатор типа токена, который пересылает "authorization: bearer xyzsddfsf" как простое событие:

{
    "type": "TOKEN",
    "methodArn": "arn:aws:execute-api:region:####:apigwIdHere/dev/GET/path/to/resource",
    "authorizationToken": "Bearer ...."
}
          authorizer: 
            arn:  'arn:aws:lambda:region:##:function:something-else-myCustomAuthorizer'

Вот как я настроил, так как ответ, опубликованный выше, у меня не сработал. Может быть, кому-то это пригодится.

resources:
  Resources:
    ApiGatewayAuthorizer:
      Type: AWS::ApiGateway::Authorizer
      Properties:
        AuthorizerResultTtlInSeconds: 0
        IdentitySource: method.request.header.Authorization
        AuthorizerUri:
          Fn::Join: ["",
            [
              "arn:aws:apigateway:",
              "${self:custom.region}",
              ":lambda:path/",
              "2015-03-31/functions/",
              Fn::GetAtt: ["YourFunctionNameLambdaFunction", "Arn" ],
              "/invocations"
            ]]
        RestApiId:
          Fn::ImportValue: ${self:custom.stage}-ApiGatewayRestApiId
        Name: api-${self:custom.stage}-authorizer 
        Type: REQUEST
    ApiGatewayAuthorizerPermission:
      Type: AWS::Lambda::Permission
      Properties:
        FunctionName:
          Fn::GetAtt: ["YourFunctionNameLambdaFunction", "Arn"]
        Action: lambda:InvokeFunction
        Principal:  
          Fn::Join: ["",["apigateway.", { Ref: "AWS::URLSuffix"}]]

  Outputs:
    AuthorizerRef:
      Value:
        Ref: ApiGatewayAuthorizer
      Export:
        Name: authorizer-ref:${self:custom.stage}

Надеюсь, вы знаете, как добавить шлюз API и импортировать его сюда, например

RestApiId:
      Fn::ImportValue: ${self:custom.stage}-ApiGatewayRestApiId

, поскольку он уже указан в принятом ответе

И в моем случае я передал значение в заголовке события как тип авторизации, чтобы получить его в лямбда-функции авторизатора, и мой тип - ЗАПРОС

Другие вопросы по тегам