AWS-CDK:跨账户资源访问和资源参考
AWS-CDK: Cross account Resource Access and Resource reference
我在 us-east-1
的 Account-1 中的 Secrets Manager
中有一个秘密键值对。此机密使用客户管理的 KMS 密钥加密 - 我们称之为 KMS-Account-1
。所有这些都是通过控制台创建的。
现在我们转向CDK
。我们有 cdk.pipelines.CodePipeline
部署 Lambda
到多个 stages/environments - 所以第一个到 { Account-2, us-east-1 }
然后到 { Account-3, eu-west-1
} 等等。这已经完成了。
上面所有 stages/environments 中的 lambda 代码现在需要更改为使用帐户 1 us-east-1
SecretsManager
中存在的秘密键值对,方法是通过 secretsmanager
客户。该代码可能看起来像这样 (python
):
client = boto3.session.Session().client(
service_name = 'secretsmanager',
region_name = 'us-east-1'
)
resp = client.get_secret_value(
SecretId='arn:aws:secretsmanager:us-east-1:<ACCOUNT-1>:secret:name/of/the/secret'
)
secret = json.loads(resp['SecretString'])
不同账户和区域(即环境)中的所有 lambda 都将具有与上述完全相同的代码,因为需要从 us-east-1
中的账户 1 中获取秘密。
- 首先我希望这在概念上是可行的。是吗?
- 接下来我该如何更改
cdk
代码来实现这一点?代码管道中的代码部署将如何获得导入此自定义 kms
密钥和 SecretManager' secret
的权限,并为 cdk 管道创建的 lambda 应用正确的跨帐户访问权限?
有人可以指点一下吗?
这有点棘手,因为 CloudFormation 和 CDK 不允许跨 account/cross 阶段引用,因为据我所知,CloudFormation 导出不能跨帐户工作。所有这些“集中”资源的模式都属于这一类——即。一个帐户(或 CDK 中的一个阶段)中的资源被其他阶段引用。
如果资源是在 CDK 上下文之外创建的(例如通过控制台),那么您最好对 names/arns/etc 进行硬编码。在整个 CDK 代码中使用它的地方应该足够了。
- 对于能够持有基于资源的策略的资源,它更简单,因为您可以直接将跨帐户访问权限附加到它们 - 同样,通过控制台离线,因为您仍然手动维护它。每次向管道添加阶段(帐户)时,您都需要转到资源并手动添加跨帐户权限。
- 对于没有基于资源的策略的资源,例如 SSM,事情有点迂回,因为您需要创建一个可以跨账户承担的角色,然后访问该资源。在这种情况下,您还必须单独维护 IAM 角色,并在向 CDK 管道添加阶段时手动更新其他账户的信任策略。然后,像往常一样在您的 CDK 代码中硬编码角色 arn,在某些 CustomResource lambda 中假定它并使用它。
如果创建也在 CDK 代码本身中完成(即由 CloudFormation 管理——而不是通过 console/aws-cli 等单独完成),那就更有趣了。在这种情况下,很多时候您不会“知道”确切的 ARN,因为物理 ID 将由 CloudFormation 生成并且可能是 ARN 的一部分。即使您自己影响 physical-id(例如通过对存储桶名称进行硬编码)也可能无法在所有情况下解决问题。例如。 KMS ARNs
和 SecretManager ARNs
将唯一 ID 或某种哈希值附加到 ARN
.
的末尾
与其尝试解决所有问题,最好不要动它,让 CFn
生成它选择的任何随机数 name/arn。然后要引用这些 constructs/ARNs,只需将它们放入 source/central 帐户的 SSM 参数中即可。据我所知,SSM 没有基于资源的策略。因此,另外在 cdk 中创建一个角色来信任您的 cdk 代码中的帐户。完成后,不再需要维护 - 每次您将新的 environments/accounts 添加到 CDK(这里假设它是一个 cdk 管道)时,您将创建的“循环”构造将自动将新帐户添加到信任关系中。
现在您需要做的就是将这个role-arn和SSM Parameternames分发到其他阶段。选择明确的角色名称和 SSM 参数。给定角色名的手动 ARN 构造非常简单。因此,将您的 CDK 代码周围的那个和 SSM 参数分发到其他阶段(编译时间字符串而不是引用)。在目标阶段,创建由 AwsSdkCall
lambda 支持的自定义资源 (AWSCustomResource
) 以简单地承担此角色并进行 SDK 调用以检索 SSM 参数值。这些值可以是您无法轻易猜到的任何值,例如您的 KMS ARN、SecretManager 的完整 ARN 等。现在简单地使用这些。
迂回的方式来做一件简单的事情,但到目前为止,这是我能做的所有事情。
#You need to maintain this list no matter what you do - so it's nothing extra
all_other_accounts = [ <list of accounts that this cdk deploys to> ]
account_principals = [iam.AccountPrincipal(a) for a in all_other_account]
role = iam.Role(
assumed_by = iam.CompositePrincipal(*account_principals), #auto-updated as you change the list above
role_name = some_explicit_name,
...
)
role_arn = f'arn:aws:iam::<account-of-this-stack>:role/{some_explicit_name}'
kms0 = kms.Key(...)
kms0.grant_decrypt(role)
# Because KMS also needs explicit resource policy even if role policy allows access to it
kms0.add_to_role_policy(iam.PolicyStatement(principals = [iam.ArnPrincipal(role_arn)], actions = ...))
kms1 = kms.Key(...)
kms1.grant_decrypt(role)
kms0.add_to_role_policy(... same as above ...)
secrets0 = secretsmanager.Secret(...) #maybe this is based off kms0
secrets0.grant_read(role)
secrets1 = secretsmanager.Secret(...) #maybe this is based off kms1
secrets1.grant_read(role)
# You can turn all this into a loop ofc.
ssm0 = ssm.StingParameter(self, '...', parameter_name = 'kms0_arn', string_value = kms0.key_arn, ...)
ssm0.grant_read(role)
ssm1 = ssm.StingParameter(self, '...', parameter_name = 'kms1_arn', string_value = kms1.key_arn, ...)
ssm1.grant_read(role)
ssm2 = ssm.StingParameter(self, '...', parameter_name = 'secrets0_arn', string_value = secrets0.secret_full_arn, ...)
ssm2.grant_read(role)
...
#Now simply pass around the role and ssm parameter names
for env in environments:
MyApplicationStage(self, <...>, ..., role_arn = role_arn, params = [ 'kms0_arn', 'kms1_arn', ... ], ...)
然后在目标阶段:
for parm in params:
fn = AwsSdkCall('ssm', 'get_parameter', { "Name": param }, ...)
acr = AwsCustomResource(..., on_create = fn, on_update = fn, ...)
collect['param'] = acr.get_response_field('Parameter.Value')
现在用收集到的工件做任何你想做的事,包括将它们作为环境变量提供给你的主要服务 lambda(这将在部署时解决)。
请记住,它们都是令牌,仅在部署时解析,但任何资源都是如此,无论是否通过自定义资源,这都不重要。
这是一个适用于任何情况的通用模式。
(GitHub link where this question was asked and I had answered it there too)
我在 us-east-1
的 Account-1 中的 Secrets Manager
中有一个秘密键值对。此机密使用客户管理的 KMS 密钥加密 - 我们称之为 KMS-Account-1
。所有这些都是通过控制台创建的。
现在我们转向CDK
。我们有 cdk.pipelines.CodePipeline
部署 Lambda
到多个 stages/environments - 所以第一个到 { Account-2, us-east-1 }
然后到 { Account-3, eu-west-1
} 等等。这已经完成了。
上面所有 stages/environments 中的 lambda 代码现在需要更改为使用帐户 1 us-east-1
SecretsManager
中存在的秘密键值对,方法是通过 secretsmanager
客户。该代码可能看起来像这样 (python
):
client = boto3.session.Session().client(
service_name = 'secretsmanager',
region_name = 'us-east-1'
)
resp = client.get_secret_value(
SecretId='arn:aws:secretsmanager:us-east-1:<ACCOUNT-1>:secret:name/of/the/secret'
)
secret = json.loads(resp['SecretString'])
不同账户和区域(即环境)中的所有 lambda 都将具有与上述完全相同的代码,因为需要从 us-east-1
中的账户 1 中获取秘密。
- 首先我希望这在概念上是可行的。是吗?
- 接下来我该如何更改
cdk
代码来实现这一点?代码管道中的代码部署将如何获得导入此自定义kms
密钥和SecretManager' secret
的权限,并为 cdk 管道创建的 lambda 应用正确的跨帐户访问权限?
有人可以指点一下吗?
这有点棘手,因为 CloudFormation 和 CDK 不允许跨 account/cross 阶段引用,因为据我所知,CloudFormation 导出不能跨帐户工作。所有这些“集中”资源的模式都属于这一类——即。一个帐户(或 CDK 中的一个阶段)中的资源被其他阶段引用。
如果资源是在 CDK 上下文之外创建的(例如通过控制台),那么您最好对 names/arns/etc 进行硬编码。在整个 CDK 代码中使用它的地方应该足够了。
- 对于能够持有基于资源的策略的资源,它更简单,因为您可以直接将跨帐户访问权限附加到它们 - 同样,通过控制台离线,因为您仍然手动维护它。每次向管道添加阶段(帐户)时,您都需要转到资源并手动添加跨帐户权限。
- 对于没有基于资源的策略的资源,例如 SSM,事情有点迂回,因为您需要创建一个可以跨账户承担的角色,然后访问该资源。在这种情况下,您还必须单独维护 IAM 角色,并在向 CDK 管道添加阶段时手动更新其他账户的信任策略。然后,像往常一样在您的 CDK 代码中硬编码角色 arn,在某些 CustomResource lambda 中假定它并使用它。
如果创建也在 CDK 代码本身中完成(即由 CloudFormation 管理——而不是通过 console/aws-cli 等单独完成),那就更有趣了。在这种情况下,很多时候您不会“知道”确切的 ARN,因为物理 ID 将由 CloudFormation 生成并且可能是 ARN 的一部分。即使您自己影响 physical-id(例如通过对存储桶名称进行硬编码)也可能无法在所有情况下解决问题。例如。 KMS ARNs
和 SecretManager ARNs
将唯一 ID 或某种哈希值附加到 ARN
.
与其尝试解决所有问题,最好不要动它,让 CFn
生成它选择的任何随机数 name/arn。然后要引用这些 constructs/ARNs,只需将它们放入 source/central 帐户的 SSM 参数中即可。据我所知,SSM 没有基于资源的策略。因此,另外在 cdk 中创建一个角色来信任您的 cdk 代码中的帐户。完成后,不再需要维护 - 每次您将新的 environments/accounts 添加到 CDK(这里假设它是一个 cdk 管道)时,您将创建的“循环”构造将自动将新帐户添加到信任关系中。
现在您需要做的就是将这个role-arn和SSM Parameternames分发到其他阶段。选择明确的角色名称和 SSM 参数。给定角色名的手动 ARN 构造非常简单。因此,将您的 CDK 代码周围的那个和 SSM 参数分发到其他阶段(编译时间字符串而不是引用)。在目标阶段,创建由 AwsSdkCall
lambda 支持的自定义资源 (AWSCustomResource
) 以简单地承担此角色并进行 SDK 调用以检索 SSM 参数值。这些值可以是您无法轻易猜到的任何值,例如您的 KMS ARN、SecretManager 的完整 ARN 等。现在简单地使用这些。
迂回的方式来做一件简单的事情,但到目前为止,这是我能做的所有事情。
#You need to maintain this list no matter what you do - so it's nothing extra
all_other_accounts = [ <list of accounts that this cdk deploys to> ]
account_principals = [iam.AccountPrincipal(a) for a in all_other_account]
role = iam.Role(
assumed_by = iam.CompositePrincipal(*account_principals), #auto-updated as you change the list above
role_name = some_explicit_name,
...
)
role_arn = f'arn:aws:iam::<account-of-this-stack>:role/{some_explicit_name}'
kms0 = kms.Key(...)
kms0.grant_decrypt(role)
# Because KMS also needs explicit resource policy even if role policy allows access to it
kms0.add_to_role_policy(iam.PolicyStatement(principals = [iam.ArnPrincipal(role_arn)], actions = ...))
kms1 = kms.Key(...)
kms1.grant_decrypt(role)
kms0.add_to_role_policy(... same as above ...)
secrets0 = secretsmanager.Secret(...) #maybe this is based off kms0
secrets0.grant_read(role)
secrets1 = secretsmanager.Secret(...) #maybe this is based off kms1
secrets1.grant_read(role)
# You can turn all this into a loop ofc.
ssm0 = ssm.StingParameter(self, '...', parameter_name = 'kms0_arn', string_value = kms0.key_arn, ...)
ssm0.grant_read(role)
ssm1 = ssm.StingParameter(self, '...', parameter_name = 'kms1_arn', string_value = kms1.key_arn, ...)
ssm1.grant_read(role)
ssm2 = ssm.StingParameter(self, '...', parameter_name = 'secrets0_arn', string_value = secrets0.secret_full_arn, ...)
ssm2.grant_read(role)
...
#Now simply pass around the role and ssm parameter names
for env in environments:
MyApplicationStage(self, <...>, ..., role_arn = role_arn, params = [ 'kms0_arn', 'kms1_arn', ... ], ...)
然后在目标阶段:
for parm in params:
fn = AwsSdkCall('ssm', 'get_parameter', { "Name": param }, ...)
acr = AwsCustomResource(..., on_create = fn, on_update = fn, ...)
collect['param'] = acr.get_response_field('Parameter.Value')
现在用收集到的工件做任何你想做的事,包括将它们作为环境变量提供给你的主要服务 lambda(这将在部署时解决)。
请记住,它们都是令牌,仅在部署时解析,但任何资源都是如此,无论是否通过自定义资源,这都不重要。
这是一个适用于任何情况的通用模式。
(GitHub link where this question was asked and I had answered it there too)