AWS + Serverless - 如何获取 Cognito 用户池生成的密钥

问题描述 投票:0回答:4

我一直在关注https://serverless-stack.com/chapters/configure-cognito-user-pool-in-serverless.html

的无服务器教程

我有以下无服务器 yaml 片段

Resources:
  CognitoUserPool:
    Type: AWS::Cognito::UserPool
    Properties:
      # Generate a name based on the stage
      UserPoolName: ${self:custom.stage}-moochless-user-pool
      # Set email as an alias
      UsernameAttributes:
      - email
      AutoVerifiedAttributes:
      - email

  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
      # >>>>> HOW DO I GET THIS VALUE IN OUTPUT <<<<<
      GenerateSecret: true

# Print out the Id of the User Pool that is created
Outputs:
  UserPoolId:
    Value:
      Ref: CognitoUserPool

  UserPoolClientId:
    Value:
      Ref: CognitoUserPoolClient
  #UserPoolSecret:
  #   WHAT GOES HERE?

我将所有其他配置变量导出到 json 文件(由移动应用程序使用,因此我需要密钥)。

如何让生成的密钥出现在我的输出列表中?

amazon-web-services aws-cloudformation amazon-cognito serverless
4个回答
13
投票

检索密钥的理想方法是在 cloudformation 模板中使用“CognitoUserPoolClient.ClientSecret”。

UserPoolClientIdSecret:
  Value:    
   !GetAtt CognitoUserPoolClient.ClientSecret

但是不支持,如here所述,并给出如图所示的消息: 您可以运行以下 CLI 命令来检索密钥作为解决方法:

aws cognito-idp describe-user-pool-client --user-pool-id "us-west-XXXXXX"  --region us-west-2 --client-id "XXXXXXXXXXXXX" --query 'UserPoolClient.ClientSecret' --output text

0
投票

正如 Prabhakar Reddy 指出的那样,目前您无法在 CloudFormation 模板中使用

!GetAtt
获取 Cognito 客户端密钥。但是,有一种方法可以避免使用 AWS 命令行来获取机密的手动步骤。适用于 CloudFormation 的 AWS Command Runner 实用程序允许您从 CloudFormation 模板运行 AWS CLI 命令,因此您可以运行 CLI 命令来获取 CloudFormation 模板中的密钥,然后使用
 在模板中的其他位置使用该命令的输出!GetAtt
。基本上,CommandRunner 会启动 EC2 实例并运行您指定的命令,并在 CloudFormation 模板运行时将命令的输出保存到实例上的文件中,以便稍后可以使用
!GetAtt
检索它。请注意,CommandRunner 是一种特殊的自定义 CloudFormation 类型,需要作为单独的步骤为 AWS 账户“安装”。下面是一个示例 CloudFormation 模板,它将获取 Cognito 客户端密钥并将其保存到 AWS 密钥管理器。 Resources: CommandRunnerRole: Type: AWS::IAM::Role Properties: # the AssumeRolePolicyDocument specifies which services can assume this role, for CommandRunner this needs to be ec2 AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: - ec2.amazonaws.com Action: 'sts:AssumeRole' Path: / Policies: - PolicyName: CommandRunnerPolicy PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - 'logs:CancelUploadArchive' - 'logs:GetBranch' - 'logs:GetCommit' - 'cognito-idp:*' Resource: '*' CommandRunnerInstanceProfile: Type: AWS::IAM::InstanceProfile Properties: Roles: - !Ref CommandRunnerRole GetCognitoClientSecretCommand: Type: AWSUtility::CloudFormation::CommandRunner Properties: Command: aws cognito-idp describe-user-pool-client --user-pool-id <user_pool_id> --region us-east-2 --client-id <client_id> --query UserPoolClient.ClientSecret --output text > /command-output.txt Role: !Ref CommandRunnerInstanceProfile InstanceType: "t2.nano" LogGroup: command-runner-logs CognitoClientSecret: Type: AWS::SecretsManager::Secret DependsOn: GetCognitoClientSecretCommand Properties: Name: "command-runner-secret" SecretString: !GetAtt GetCognitoClientSecretCommand.Output

请注意,您需要将 
<user_pool_id>

<client_id>
替换为您的用户池和客户端池 ID。完整的 CloudFormation 模板可能会创建 Cognito 用户池和用户池客户端,并且可以使用
!Ref
作为创建整个命令的
!Join
语句的一部分,从这些资源中检索用户池和客户端 ID 值,例如
Command: !Join [' ', ['aws cognito-idp describe-user-pool-client --user-pool-id', !Ref CognitoUserPool, '--region', !Ref AWS::Region, '--client-id',  !Ref CognitoUserPoolClient, '--query UserPoolClient.ClientSecret --output text > /command-output.txt']]

最后一点,根据您的操作系统,CommandRunner 的安装/注册可能会在尝试创建所需的 S3 存储桶时失败。这是因为它尝试使用 
uuidgen

生成存储桶名称,如果未安装

uuidgen
将失败。我为此在 CommandRunner GitHub 存储库上打开了一个
issue
。在问题解决之前,您可以通过修改 /scripts/register.sh 脚本以使用静态存储桶名称来解决此问题。
    


0
投票
!GetAtt

获取 Cognito 用户池客户端的秘密,我一直在寻找无需手动步骤的替代解决方案,以便基础设施可以自动部署。

我喜欢 clav 的解决方案,但它需要先安装 

Command Runner

所以,我最终所做的是使用

Lambda 支持的自定义资源

。我用 JavaScript 编写,但你也可以用 Python 编写。 以下概述了您需要遵循的 3 个步骤:

创建
    IAM 策略
  1. 并将其添加到 Lambda 函数执行角色
  2. 内联 Lambda 函数
  3. 的创建添加到 CloudFormation 模板。
  4. Lambda 支持的自定义资源
  5. 的创建添加到 CloudFormation 模板。 通过
  6. !GetAtt
  7.  从自定义资源获取输出
    
  8. 详细信息如下:

创建
    IAM 策略
  1. 并将其添加到 Lambda 函数执行角色
  2. # IAM: Policy to describe user pool clients of Cognito user pools CognitoDescribeUserPoolClientsPolicy: Type: AWS::IAM::ManagedPolicy Properties: Description: 'Allows describing Cognito user pool clients.' PolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Action: - 'cognito-idp:DescribeUserPoolClient' Resource: - !Sub 'arn:aws:cognito-idp:${AWS::Region}:${AWS::AccountId}:userpool/*'
如有必要,仅允许某些资源使用。

    内联 Lambda 函数
  1. 的创建添加到 CloudFormation 模板。
  2. # Lambda: Function to get the secret of a Cognito User Pool Client LambdaFunctionGetCognitoUserPoolClientSecret: Type: AWS::Lambda::Function Properties: FunctionName: 'GetCognitoUserPoolClientSecret' Description: 'Lambda function to get the secret of a Cognito User Pool Client.' Handler: index.lambda_handler Role: !Ref LambdaFunctionExecutionRoleArn Runtime: nodejs14.x Timeout: '30' Code: ZipFile: | // Import required modules const response = require('cfn-response'); const { CognitoIdentityServiceProvider } = require('aws-sdk'); // FUNCTION: Lambda Handler exports.lambda_handler = function(event, context) { console.log("Request received:\n" + JSON.stringify(event)); // Read data from input parameters let userPoolId = event.ResourceProperties.UserPoolId; let userPoolClientId = event.ResourceProperties.UserPoolClientId; // Set physical ID let physicalId = `${userPoolId}-${userPoolClientId}-secret`; let errorMessage = `Error at getting secret from cognito user pool client:`; try { let requestType = event.RequestType; if(requestType === 'Create') { console.log(`Request is of type '${requestType}'. Get secret from cognito user pool client.`); // Get secret from cognito user pool client let cognitoIdp = new CognitoIdentityServiceProvider(); cognitoIdp.describeUserPoolClient({ UserPoolId: userPoolId, ClientId: userPoolClientId }).promise() .then(result => { let secret = result.UserPoolClient.ClientSecret; response.send(event, context, response.SUCCESS, {Status: response.SUCCESS, Error: 'No Error', Secret: secret}, physicalId); }).catch(error => { // Error console.log(`${errorMessage}:${error}`); response.send(event, context, response.FAILED, {Status: response.FAILED, Error: error}, physicalId); }); } else { console.log(`Request is of type '${requestType}'. Not doing anything.`); response.send(event, context, response.SUCCESS, {Status: response.SUCCESS, Error: 'No Error'}, physicalId); } } catch (error){ // Error console.log(`${errorMessage}:${error}`); response.send(event, context, response.FAILED, {Status: response.FAILED, Error: error}, physicalId); } };
确保将正确的 Lambda 执行角色传递给参数 
Role

。它应包含步骤 1 中创建的策略。


    Lambda 支持的自定义资源
  1. 的创建添加到 CloudFormation 模板。
  2. # Custom: Cognito user pool client secret UserPoolClientSecret: Type: Custom::UserPoolClientSecret Properties: ServiceToken: !Ref LambdaFunctionGetCognitoUserPoolClientSecret UserPoolId: !Ref UserPool UserPoolClientId: !Ref UserPoolClient
确保将步骤 2 中创建的 Lambda 函数作为 
ServiceToken

传递。另请确保为参数

UserPoolId
UserPoolClientId
传递正确的值。它们应该取自 Cognito 用户池和 Cognito 用户池客户端。

通过
    !GetAtt
  1.  从自定义资源获取输出
    
  2. !GetAtt UserPoolClientSecret.Secret
您可以在任何您想要的地方进行此操作。


0
投票

UserPoolClientIdSecret:{ Value: { 'Fn::GetAtt': ['CognitoUserPoolClient', 'ClientSecret'], } }

或者作为 YAML:

UserPoolClientIdSecret: Value: Fn::GetAtt: - CognitoUserPoolClient - ClientSecret

© www.soinside.com 2019 - 2024. All rights reserved.