无法在具有无服务器和 DynamoDB/Cognito/API 网关的 lambda 策略中使用 $cognito-identity.amazonaws.com:sub

Posted

技术标签:

【中文标题】无法在具有无服务器和 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 【发布时间】:2020-11-11 09:49:30 【问题描述】:

目标:

    使用 Cognito 进行身份验证(使用下面的 serverless.yml 配置) 点击经过身份验证的端点 GET /users 以触发 lambda 作业。 根据 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,
;

【问题讨论】:

【参考方案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 的组身份验证角色,并完全绕过身份池。

【讨论】:

以上是关于无法在具有无服务器和 DynamoDB/Cognito/API 网关的 lambda 策略中使用 $cognito-identity.amazonaws.com:sub的主要内容,如果未能解决你的问题,请参考以下文章

具有无状态服务器且无服务器端呈现的 cookie 中的 JWT 身份验证

jQuery、CORS、JSON(无填充)和身份验证问题

Cloudera安装未能检测到CentOS上的root特权我试图将新主机添加到CentOS集群中。安装失败,状态为“安装失败。无法检测root特权”。我知道Cloudera需要用户具有无密码特权(“需

MySQL 错误 2014 的原因在其他无缓冲查询处于活动状态时无法执行查询

iphone核心数据:无法删除具有一对多关系的数据

具有存储过程支持的 MS(正在进行?)嵌入式(无服务器)SQL Server 版本的名称是啥?