【问题标题】: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}
【发布时间】:2020-11-11 09:49:30
【问题描述】:

目标:

  1. 使用 Cognito 进行身份验证(使用下面的 serverless.yml 配置)
  2. 点击经过身份验证的端点 GET /users 以触发 lambda 作业。
  3. 根据 IAM 策略,使用 LeadingKey Condition 限制对基于 cognito 用户 cognito-identity.amazonaws.com:sub 查询的 DynamoDB 表的访问。

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

我的 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 网关与 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},但似乎没有任何效果。

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

谢谢!

编辑:(附加信息)

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

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

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

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

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

 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,
};

【问题讨论】:

    标签: amazon-web-services amazon-dynamodb aws-api-gateway amazon-cognito serverless-framework


    【解决方案1】:

    我不太清楚你是否假设了一个身份(将你的 ID 令牌从用户池交换为 STS 令牌)。

    令人困惑的是,cognito-identity.amazonaws.com:sub 解析为 ID 池身份 ID,而不是来自用户池的 ID 令牌中的主题 ID。请参阅此页面上的注释部分:https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_examples_s3_cognito-bucket.html

    要获取身份凭证,请查看https://docs.aws.amazon.com/cognitoidentity/latest/APIReference/API_GetCredentialsForIdentity.html

    【讨论】:

    • @JDClark 感谢您的回复。澄清一下,我正在获取身份凭据并在我的登录 lambda 函数中获取 identityId。但我不确定下一步是什么。看来我的 IdToken 和访问令牌即使在之后保持不变,最重要的是,它似乎没有 cognito 承担我的身份验证提供的角色,因为它似乎仍在使用他承担的我的 lambda 执行策略的角色。如果您有时间看一下,这是我的登录功能。 forums.aws.amazon.com/thread.jspa?messageID=950278#950278
    • 在上面的登录函数中,我登录并获取凭据,但由于我在邮递员中执行此操作,因此我只是在所有呼叫中使用 Bearer 。如果我只是通过用户池授权提供的 Bearer ,它会保持注册的身份吗?
    • 在作为 GetCredentialsForIdentity API 的一部分将您的 JWT 交换为凭证时,您将收到返回的凭证(本质上是一个 STS 令牌,就像您直接担任该角色一样)。这些将与 ID 池确定的角色相关联(如何确定该角色是可配置的)。然后,您可以使用此令牌使用 sigv4 签署 HTTP 请求,并通过 IAM 授权方访问 lambda,或创建 AWS 会话以使用服务作为令牌相关的角色等。如果您只使用默认会话,它将是与你的 lambda 角色。
    【解决方案2】:

    事实证明,如果您将 AWS Gateway 与 Lambda 结合使用,您不能使用这些变量。

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

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

    【讨论】:

      猜你喜欢
      • 2018-09-07
      • 2019-06-22
      • 2017-09-04
      • 1970-01-01
      • 2021-01-29
      • 2017-07-25
      • 2019-11-18
      • 1970-01-01
      • 2019-12-15
      相关资源
      最近更新 更多