跳至内容

如何使用 AwsSdkCall 接口从 CDK 项目进行 AWS SDK 调用?

3 分钟阅读
0

我想使用 AwsSdkCall 接口从 AWS Cloud Development Kit (AWS CDK) 项目进行 AWS SDK 调用。

简述

您可以将 AWS 软件开发工具包 (AWS SDK) 调用用于以下堆栈部署场景:

  • 在创建和更新堆栈时动态检索配置。
  • 检索 AWS CloudFormation 在返回值中不支持的资源的属性。
  • 进行 AWS CloudFormation 不支持的小修补或配置更改。

可以在发生不同的 CloudFormation 堆栈操作事件(例如创建、删除或更新)时触发 AWS SDK 调用。无需为部署管线设置访问权限和工具即可进行 AWS SDK 调用。AWS SDK 运行时由 AWS CDK 设置。您可以自定义和限制自定义资源背后的 AWS Lambda 函数访问权限。

解决方法

注意: 以下示例使用 TypeScript。确保使用特定于您正在使用的编程语言的语法。

要使用 AwsSdkCall 进行 AWS SDK 调用,请使用以下参数:

  1. 对于 Service(服务),使用您打算调用的 AWS 服务。值区分大小写。
  2. 对于 Action(操作),使用您要进行的 API 调用或操作调用。这通常遵循驼峰式大小写模式,并且值区分大小写。
  3. 对于 Parameters(参数),使用您在发出 API 调用或操作调用时传递的可选参数。可以将参数声明为变量,然后将其传递给接口,类似于 API 有效载荷的 JSON 对象。有关详细信息,请参阅 AWS SDK for JavaScript(适用于 JavaScript 的 AWS SDK)。

示例 1: 动态检索配置

以下示例在堆栈创建期间从 AWS Systems Manager Parameter Store 中检索亚马逊机器映像 ID。其中展示如何通过 getResponseField 方法以及点号表示法引用 API 调用的响应,从而逐层访问 SDK 的响应结构:

import * as cdk from "aws-cdk-lib/core";
import { Construct } from "constructs";
import * as cr from "aws-cdk-lib/custom-resources";

export class Example1Stack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const getAmiId = new cr.AwsCustomResource(this, "GetAMIId", {
      onCreate: {
        service: "SSM",
        action: "getParameter",
        parameters: {
          Name: "/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64",
        },
        physicalResourceId: cr.PhysicalResourceId.of("get-ami-id"),
        },
        policy: cr.AwsCustomResourcePolicy.fromSdkCalls({
         resources: cr.AwsCustomResourcePolicy.ANY_RESOURCE,
        }),
     });

     new cdk.CfnOutput(this, "ImageId", {
        value: getAmiId.getResponseField("Parameter.Value"),
     });
    }
}

示例 2: 检索 CloudFormation 中不可用的资源属性

CloudFormation 不会通过 Fn::GetAtt 公开所有资源属性。如果 CloudFormation 未返回您需要的值,则使用 SDK 调用直接从服务 API 中检索该值。您还可以使用此方法检索位于 CloudFormation 之外的资源及其未存储在堆栈导出、SSM 参数或 Secrets Manager 中的属性。

例如,AWS::Organizations::Organization 将 Id 和 ManagementAccountId 作为返回值公开,但是 CloudFormation 并不创建大多数组织。对 describeOrganization 的 SDK 调用可让您从成员账户检索这些值,且无需将组织包含在堆栈中。

import * as cdk from "aws-cdk-lib/core";
import { Construct } from "constructs";
import * as cr from "aws-cdk-lib/custom-resources";

export class Example2Stack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const orgInfo = new cr.AwsCustomResource(this, "DescribeOrg", {
      onCreate: {
        service: "Organizations",
        action: "describeOrganization",
        physicalResourceId: cr.PhysicalResourceId.of("describe-org"),
        outputPaths: ["Organization.Id", "Organization.MasterAccountId"],
      },
      policy: cr.AwsCustomResourcePolicy.fromSdkCalls({
        resources: cr.AwsCustomResourcePolicy.ANY_RESOURCE,
      }),
    });

    new cdk.CfnOutput(this, "OrgId", {
      value: orgInfo.getResponseField("Organization.Id"),
    });

    new cdk.CfnOutput(this, "ManagementAccountId", {
      value: orgInfo.getResponseField("Organization.MasterAccountId"),
    });
  }
}

注意: 该示例使用 outputPaths 将响应限制为仅包含所需的字段。这样可以避免达到 4096 字节的 CloudFormation 响应限制。

示例 3:作出 CloudFormation 不支持的配置更改

有些操作没有相应的 CloudFormation 资源或属性。例如,CloudFormation 可以创建 DynamoDB 表,但不能向其中插入项目。要解决此问题,请在部署时使用 AwsSdkCall 播种初始数据。

以下示例使用 onCreate,因此它仅在您首次创建堆栈时插入项目。要同时在堆栈更新时更新项目,请向 onUpdate 添加相同的调用。

import * as cdk from "aws-cdk-lib/core";
import { Construct } from "constructs";
import * as cr from "aws-cdk-lib/custom-resources";
import * as dynamodb from "aws-cdk-lib/aws-dynamodb";

export class Example3Stack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const table = new dynamodb.Table(this, "ConfigTable", {
      partitionKey: { name: "PK", type: dynamodb.AttributeType.STRING },
    });

    new cr.AwsCustomResource(this, "SeedData", {
      onCreate: {
        service: "DynamoDB",
        action: "putItem",
        parameters: {
          TableName: table.tableName,
          Item: {
            PK: { S: "CONFIG" },
            version: { S: "1.0" },
            enabled: { BOOL: true },
          },
        },
        physicalResourceId: cr.PhysicalResourceId.of("seed-data"),
      },
      policy: cr.AwsCustomResourcePolicy.fromSdkCalls({
        resources: [table.tableArn],
      }),
    });
  }
}

处理复杂的权限情境

policy 属性向自定义资源背后的 Lambda 函数授予权限。使用 fromSdkCalls() 会自动将每个 API 调用转换为相应的 IAM 权限。请尽可能将权限范围限定为特定资源 ARN,而不是使用 ANY_RESOURCE(示例 3)。

在某些情况下,fromSdkCalls() 可能无法生成所需的所有权限。例如,如果示例 3 中的 DynamoDB 表使用客户自主管理型 KMS 密钥,则 fromSdkCalls() 仅生成 dynamodb:PutItem,根本不添加 KMS 权限。部署将失败,因为 DynamoDB 需要 kms:Encrypt、kms:Decrypt 和 kms:GenerateDataKey 将项目写入加密表。您可以使用 fromStatements() 来指定确切的权限。

例如:

import * as cdk from "aws-cdk-lib/core";
import { Construct } from "constructs";
import * as cr from "aws-cdk-lib/custom-resources";
import * as dynamodb from "aws-cdk-lib/aws-dynamodb";
import * as kms from "aws-cdk-lib/aws-kms";
import * as iam from "aws-cdk-lib/aws-iam";

export class Example4Stack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const encryptionKey = new kms.Key(this, "TableKey");

    const table = new dynamodb.Table(this, "ConfigTable", {
      partitionKey: { name: "PK", type: dynamodb.AttributeType.STRING },
      encryption: dynamodb.TableEncryption.CUSTOMER_MANAGED,
      encryptionKey,
    });

    new cr.AwsCustomResource(this, "SeedData", {
      onCreate: {
        service: "DynamoDB",
        action: "putItem",
        parameters: {
          TableName: table.tableName,
          Item: {
            PK: { S: "CONFIG" },
            version: { S: "1.0" },
            enabled: { BOOL: true },
          },
        },
        physicalResourceId: cr.PhysicalResourceId.of("seed-data"),
      },
      policy: cr.AwsCustomResourcePolicy.fromStatements([
        new iam.PolicyStatement({
          actions: ["dynamodb:PutItem"],
          resources: [table.tableArn],
        }),
        new iam.PolicyStatement({
          actions: ["kms:Encrypt", "kms:Decrypt", "kms:GenerateDataKey"],
          resources: [encryptionKey.keyArn],
        }),
      ]),
    });
  }
}
AWS 官方已更新 4 个月前