无法在具有无服务器和 DynamoDB/Cognito/API 网关的 lambda 策略中使用 ${cognito-identity.amazonaws.com:sub} [英] Unable to use ${cognito-identity.amazonaws.com:sub} in lambda policy with serverless and DynamoDB/Cognito/API Gateway

本文介绍了无法在具有无服务器和 DynamoDB/Cognito/API 网关的 lambda 策略中使用 ${cognito-identity.amazonaws.com:sub}的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

目标:

  1. 使用 Cognito 进行身份验证(使用下面的 serverless.yml 进行配置)
  2. 点击经过身份验证的端点 GET/users 以触发 lambda 作业.
  3. 根据 IAM 策略,使用 LeadingKey Condition 限制对基于 cognito 用户 cognito-identity.amazonaws.com:sub 查询的 DynamoDB 表的访问.
  1. Authenticate with Cognito (configured with serverless.yml below)
  2. Hit an authenticated endpoint GET /users to trigger a lambda job.
  3. Based on IAM policy, restrict access to DynamoDB table queried based on the cognito users cognito-identity.amazonaws.com:sub using LeadingKey Condition.

问题:我的策略似乎没有填充认知变量 ${cognito-identity.amazonaws.com:sub}.如果我手动指定 dynamodb:LeadingKeys 一个值,它工作得很好.所以看来我只需要 Cognito 来正确填充 sub 值,我到处找,找不到解决方案.

The problem: It does not appear that my policy is populating the cognito variable ${cognito-identity.amazonaws.com:sub}. If I manually specify dynamodb:LeadingKeys with a value, it works just fine. So it appears I just need Cognito to populate the sub value in properly, and I have looked everywhere and cannot find a solution.

我的 lambda 角色/策略(将生成的版本从无服务器修改为具有 Cognito 和 DynamoDB 规则的信任策略):

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": [
                "logs:CreateLogStream",
                "logs:CreateLogGroup"
            ],
            "Resource": [
                "arn:aws:logs:us-east-1:xxx:log-group:/aws/lambda/exeampleservice*:*"
            ],
            "Effect": "Allow"
        },
        {
            "Action": [
                "logs:PutLogEvents"
            ],
            "Resource": [
                "arn:aws:logs:us-east-1:xxxx:log-group:/aws/lambda/exampleservice*:*:*"
            ],
            "Effect": "Allow"
        },
        {
            "Effect": "Allow",
            "Action": [
                "dynamodb:PutItem",
                "dynamodb:GetItem",
                "dynamodb:Query"
            ],
            "Resource": "*",
            "Condition": {
                "ForAllValues:StringEquals": {
                    "dynamodb:LeadingKeys": "${cognito-identity.amazonaws.com:sub}"
                }
            }
        }
    ]
}

有信任关系:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "lambda.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    },
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "cognito-identity.amazonaws.com"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "cognito-identity.amazonaws.com:aud": "us-east-1:<identity pool id>"
        }
      }
    }
  ]
}

其他设置信息:

  • 将 API Gateway 与 http 协议结合使用.
  • 在下面的 serverless.yml 中创建了 userPool.
  • 设置 Cognito 身份池(联合).
  • 创建了一个 userPool 组并为其分配了我的身份池 ID.
  • 将池中的用户分配给组.
  • 使用 Cognito 和 id 和访问令牌进行身份验证显示身份 id 令牌:
{
  "sub": "xxxx",
  "cognito:groups": [
    "TestGroup"
  ],
  "email_verified": true,
  "iss": "https://cognito-idp.us-east-1.amazonaws.com/<poolid>",
  "cognito:username": "xxx",
  "cognito:roles": [
    "arn:aws:iam::xxxx:role/Cognito_IdentityPoolAuth_Role"
  ],
  "aud": "xxx",
  "event_id": "xxx",
  "token_use": "id",
  "auth_time": 1595367712,
  "exp": 1595371310,
  "iat": 1595367710,
  "email": "email@example.com"
}

  • 我的简化 Serverless.yml
  • org: exampleorg
    app: exampleapp
    service: exampleservers
    provider:
      name: aws
      stage: dev
      runtime: nodejs12.x
      iamManagedPolicies:
        - 'arn:aws:iam::xxxx:policy/UserAccess'
      iamRoleStatements:
        - Effect: Allow
          Action:
            - dynamodb:Query
            - dynamodb:Scan
            - dynamodb:GetItem
            - dynamodb:PutItem
            - dynamodb:UpdateItem
            - dynamodb:DeleteItem
          Resource:
            - { 'Fn::ImportValue': '${self:provider.stage}-UsersTableArn' }
          Condition:
            {
              'ForAllValues:StringEquals':
                { // use join to avoid conflict with serverless variable syntax. Ouputs 
                  'dynamodb:LeadingKeys':
                    [Fn::Join: ['', ['$', '{cognito-identity.amazonaws.com:sub}']]],
                },
            }
    
      httpApi:
        authorizers:
          serviceAuthorizer:
            identitySource: $request.header.Authorization
            issuerUrl:
              Fn::Join:
                - ''
                - - 'https://cognito-idp.'
                  - '${opt:region, self:provider.region}'
                  - '.amazonaws.com/'
                  - Ref: serviceUserPool
            audience:
              - Ref: serviceUserPoolClient
    functions:
      # auth
      login:
        handler: auth/handler.login
        events:
          - httpApi:
              method: POST
              path: /auth/login
              # authorizer: serviceAuthorizer
    
      # user
      getProfileInfo:
        handler: user/handler.get
        events:
          - httpApi:
              method: GET
              path: /user/profile
              authorizer: serviceAuthorizer
    resources:
      Resources:
        HttpApi:
          DependsOn: serviceUserPool
        serviceUserPool:
          Type: AWS::Cognito::UserPool
          Properties:
            UserPoolName: service-user-pool-${opt:stage, self:provider.stage}
            UsernameAttributes:
              - email
            AutoVerifiedAttributes:
              - email
        serviceUserPoolClient:
          Type: AWS::Cognito::UserPoolClient
          Properties:
            ClientName: service-user-pool-client-${opt:stage, self:provider.stage}
            AllowedOAuthFlows:
              - implicit
            AllowedOAuthFlowsUserPoolClient: true
            AllowedOAuthScopes:
              - phone
              - email
              - openid
              - profile
              - aws.cognito.signin.user.admin
            UserPoolId:
              Ref: serviceUserPool
            CallbackURLs:
              - https://localhost:3000
            ExplicitAuthFlows:
              - ALLOW_USER_SRP_AUTH
              - ALLOW_REFRESH_TOKEN_AUTH
            GenerateSecret: false
            SupportedIdentityProviders:
              - COGNITO
        serviceUserPoolDomain:
          Type: AWS::Cognito::UserPoolDomain
          Properties:
            UserPoolId:
              Ref: serviceUserPool
            Domain: service-user-pool-domain-${opt:stage, self:provider.stage}-${self:provider.environment.DOMAIN_SUFFIX}
    

    我已经尝试了几乎所有方法来获取策略中的变量 ${cognito-identity.amazonaws.com:sub},但似乎没有任何效果.

    I have tried just about everything to get the variable ${cognito-identity.amazonaws.com:sub} in the policy, but nothing seems to work.

    有人知道如何解决这个问题吗?或者我可能会错过什么.(如果我错过了任何重要的信息,我会更新更多信息).

    Does anyone have an idea on how to fix this? or what I could be missing. (I will update with more information if I missed anything critical).

    谢谢!

    (附加信息)

    我的登录功能(lambda + HTTP API)如下,我通过用户/密码授权用户,然后调用 CognitoIdentityCredentials 来注册"我的身份并从池中获取我的 identityId.(我验证我正在注册,因为身份池向用户显示)

    My login function(lambda + HTTP API) is below, where I authorizeUser via user/password, then call CognitoIdentityCredentials to "register" my identity and get my identityId from the pool. (I verified I am registering as the identity pool shows the user)

    然后我的登录调用以 accessToken、idToken、identityId 进行响应.

    My login call then responds with with the accessToken, idToken, identityId.

    我的所有其他 API 调用都在授权我的 Bearer Authorization 调用中使用 idToken,但似乎我的身份池的授权角色没有被假定,它正在使用我的 lambda 角色执行.

    All my other API calls use the idToken in a Bearer Authorization call which authorizes me, however it appears that my Authorized role for my identity pool is not assumed and it is using my lambda role for execution.

    我在这里缺少什么?我认为 Cognito 会处理 Authenticated Identity 池的假定角色,但似乎整个 ?任何帮助表示赞赏!

    What am I missing here? I thought Cognito would handle the assumed role of the Authenticated Identity pool, but it appears that the entire ? Any help is appreciated!

    我的请求上下文(来自我的登录函数,注意身份对象充满了空值):

    My request context(from my login function, note the identity object is full of null values):

     requestContext: {
        accountId: 'xxx',
        apiId: 'xxx',
        domainName: 'xxxx.execute-api.us-east-1.amazonaws.com',
        domainPrefix: 'xxx',
        extendedRequestId: 'xxxx=',
        httpMethod: 'POST',
        identity: {
          accessKey: null,
          accountId: null,
          caller: null,
          cognitoAuthenticationProvider: null,
          cognitoAuthenticationType: null,
          cognitoIdentityId: null,
          cognitoIdentityPoolId: null,
          principalOrgId: null,
          sourceIp: 'xxxx',
          user: null,
          userAgent: 'PostmanRuntime/7.26.1',
          userArn: null
        },
    

    我的登录功能

    const AWS = require('aws-sdk');
    const AmazonCognitoIdentity = require('amazon-cognito-identity-js');
    global.fetch = require('node-fetch').default; // .default for webpack.
    const USER_POOL_ID = process.env.USER_POOL_ID;
    const USER_POOL_CLIENT_ID = process.env.USER_POOL_CLIENT_ID;
    const USER_POOL_IDENTITY_ID = process.env.USER_POOL_IDENTITY_ID; 
    console.log('USER_POOL_ID', USER_POOL_ID);
    console.log('USER_POOL_CLIENT_ID', USER_POOL_CLIENT_ID);
    console.log('USER_POOL_CLIENT_ID', USER_POOL_IDENTITY_ID);
     
    const poolData = {
      UserPoolId: USER_POOL_ID, 
      ClientId: USER_POOL_CLIENT_ID,
    };
     
    const poolRegion = 'us-east-1';
    const userPool = new AmazonCognitoIdentity.CognitoUserPool(poolData);
     
    function login(Username, Password) {
      var authenticationDetails = new AmazonCognitoIdentity.AuthenticationDetails({
        Username,
        Password,
      });
     
      var userData = {
        Username,
        Pool: userPool,
      };
      var cognitoUser = new AmazonCognitoIdentity.CognitoUser(userData);
     
      return new Promise((resolve, reject) => {
        cognitoUser.authenticateUser(authenticationDetails, {
          onSuccess: function (result) {
     
            AWS.config.credentials = new AWS.CognitoIdentityCredentials({
              IdentityPoolId: USER_POOL_IDENTITY_ID, // your identity pool id here
              Logins: {
                // Change the key below according to the specific region your user pool is in.
                [`cognito-idp.${poolRegion}.amazonaws.com/${USER_POOL_ID}`]: result
                  .getIdToken()
                  .getJwtToken(),
              },
            });
     
            //refreshes credentials using AWS.CognitoIdentity.getCredentialsForIdentity()
            AWS.config.credentials.refresh((error) => {
              if (error) {
                console.error(error);
              } else {
                // Instantiate aws sdk service objects now that the credentials have been updated.
                // example: var s3 = new AWS.S3();
                console.log('Successfully Refreshed!');
                AWS.config.credentials.get(() => {
                  // return back all tokens and identityId in login call response body.
                  const identityId = AWS.config.credentials.identityId;
                  const tokens = {
                    accessToken: result.getAccessToken().getJwtToken(),
                    idToken: result.getIdToken().getJwtToken(),
                    refreshToken: result.getRefreshToken().getToken(),
                    identityId,
                  };
                  resolve(tokens);
                });
              }
            });
          },
          onFailure: (err) => {
            console.log(err);
            reject(err);
          },
        });
      });
    }
    module.exports = {
      login,
    };
    

    推荐答案

    事实证明,如果您将 AWS Gateway 与 Lambda 一起使用,您无法使用这些变量.

    It turns out you cannot use these variables if you are using AWS Gateway with Lambda.

    您必须使用 IAM auth(使用 aws amplify 之类的东西)直接从您注册身份的客户端应用程序访问 DynamoDB.

    You must access DynamoDB directly from the client application where you have registered for your identity using IAM auth(with something like aws amplify).

    我最终使用 STS 在我的 lambda 函数中承担 Cognito 的组身份验证角色,并完全绕过身份池.

    I ended up using STS to assume the Cognito's group authenticated role in my lambda function, and completely bypassing identity pools.

    这篇关于无法在具有无服务器和 DynamoDB/Cognito/API 网关的 lambda 策略中使用 ${cognito-identity.amazonaws.com:sub}的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆