我正在为部署我们的云开发工具包 (CDK( 应用程序的 CI/CD 用户编写一个 IAM 角色。CDK应用程序由lambda函数,Fargate等组成。问题是,CDK 不允许我指定它需要的所有角色。相反,它自己创造了一些。
几个例子:
- 每个具有日志保留的 lambda 函数都有另一个由 CDK 创建的 lambda,该 lambda 将日志保留设置为日志组和日志流。
- 执行步骤函数的 CloudTrail 事件需要具有
states:StartExecution
权限的角色。
CDK 会自动创建这些角色,并为其放置内联策略。这迫使我向我的 CI/CD 角色授予创建角色和附加策略的权限。因此,如果有人可以访问 CI/CD 用户(例如,如果我们的 GitHub 凭据泄漏(,攻击者可以创建新角色并授予他们管理员权限。
我尝试在单独的堆栈中自己创建所有角色,然后在 CDK 应用程序中使用这些角色。但正如我上面提到的(见上面的例子(,不可能到处都是......
我还尝试了部署者角色的 IAM 权限边界,但我无法弄清楚如何限制iam:PutRolePolicy
的权限。CDK 基本上执行以下操作:
iam:CreateRole
iam:PutRolePolicy
根据 AWS 文档,条件是非常基本的字符串比较。我需要能够选择传递给iam:PutRolePolicy
的策略文档中允许哪些操作。
这是我的权限边界示例,允许主体创建角色并放置角色策略。请参阅条件注释。
permission_boundary = aws_iam.ManagedPolicy(
scope=self,
id='DeployerPermissionBoundary',
managed_policy_name='DeployerPermissionBoundary',
statements=[
aws_iam.PolicyStatement(
actions=['iam:CreateRole'],
effect=aws_iam.Effect.ALLOW,
resources=[f'arn:aws:iam::{core.Aws.ACCOUNT_ID}:role/my-project-lambda-role']
),
aws_iam.PolicyStatement(
actions=['iam:PutRolePolicy'],
effect=aws_iam.Effect.ALLOW,
resources=[f'arn:aws:iam::{core.Aws.ACCOUNT_ID}:role/my-project-lambda-role'],
conditions=Conditions([
StringLike('RoleName', 'Required-role-name'),
StringLike('PolicyName', 'Required-policy-name'),
StringEquals('PolicyDocument', '') # I want to allow only specified actions like logs:CreateLogStream and logs:PutLogEvents
])
)
]
)
deployer_role = aws_iam.Role(
scope=self,
id='DeployerRole',
assumed_by=aws_iam.AccountRootPrincipal(),
permissions_boundary=permission_boundary,
inline_policies={
'Deployer': aws_iam.PolicyDocument(
statements=[
aws_iam.PolicyStatement(
actions=['iam:PutRolePolicy'],
effect=aws_iam.Effect.ALLOW,
resources=[f'arn:aws:iam::{core.Aws.ACCOUNT_ID}:role/my-project-lambda-role']
),
...
...
]
)
}
)
将PutRolePolicy
限制为仅选定操作的正确方法是什么?我想允许logs:CreateLogStream
和logs:PutLogEvents
,仅此而已。
我已经为此斗争了很长一段时间,我不想回退到提供不必要的权限。提前感谢大家!
这是 Python 中用于 CDK 1.4.0 的解决方案,灵感来自 GitHub 上@matthewtapper的代码。这允许您为堆栈中的所有角色设置权限边界。
不用说它非常丑陋,因为python CDK不提供各方面的构造对象。我们必须深入研究JSII来解析对象。希望它对某人有所帮助。
from jsii._reference_map import _refs
from jsii._utils import Singleton
import jsii
@jsii.implements(core.IAspect)
class PermissionBoundaryAspect:
def __init__(self, permission_boundary: Union[aws_iam.ManagedPolicy, str]) -> None:
"""
:param permission_boundary: Either aws_iam.ManagedPolicy object or managed policy's ARN as string
"""
self.permission_boundary = permission_boundary
def visit(self, construct_ref: core.IConstruct) -> None:
"""
construct_ref only contains a string reference to an object. To get the actual object, we need to resolve it using JSII mapping.
:param construct_ref: ObjRef object with string reference to the actual object.
:return: None
"""
kernel = Singleton._instances[jsii._kernel.Kernel]
resolve = _refs.resolve(kernel, construct_ref)
def _walk(obj):
if isinstance(obj, aws_iam.Role):
cfn_role = obj.node.find_child('Resource')
policy_arn = self.permission_boundary if isinstance(self.permission_boundary, str) else self.permission_boundary.managed_policy_arn
cfn_role.add_property_override('PermissionsBoundary', policy_arn)
else:
if hasattr(obj, 'permissions_node'):
for c in obj.permissions_node.children:
_walk(c)
if obj.node.children:
for c in obj.node.children:
_walk(c)
_walk(resolve)
用法:
stack.node.apply_aspect(PermissionBoundaryAspect(managed_policy_arn))
这是 CDK 版本 1.9.0 + 的解决方案,增加了额外的try_find_child()
以防止节点上的嵌套子错误,AWS 还弃用了stack.node.apply_aspect()
方法,因此有一个新的使用实施。
from aws_cdk import (
aws_iam as iam,
core,
)
import jsii
from jsii._reference_map import _refs
from jsii._utils import Singleton
from typing import Union
@jsii.implements(core.IAspect)
class PermissionBoundaryAspect:
"""
This aspect finds all aws_iam.Role objects in a node (ie. CDK stack) and sets
permission boundary to the given ARN.
"""
def __init__(self, permission_boundary: Union[iam.ManagedPolicy, str]) -> None:
"""
This initialization method sets the permission boundary attribute.
:param permission_boundary: The provided permission boundary
:type permission_boundary: iam.ManagedPolicy|str
"""
self.permission_boundary = permission_boundary
print(self.permission_boundary)
def visit(self, construct_ref: core.IConstruct) -> None:
"""
construct_ref only contains a string reference to an object.
To get the actual object, we need to resolve it using JSII mapping.
:param construct_ref: ObjRef object with string reference to the actual object.
:return: None
"""
if isinstance(construct_ref, jsii._kernel.ObjRef) and hasattr(
construct_ref, "ref"
):
kernel = Singleton._instances[
jsii._kernel.Kernel
] # The same object is available as: jsii.kernel
resolve = _refs.resolve(kernel, construct_ref)
else:
resolve = construct_ref
def _walk(obj):
if obj.node.try_find_child("Resource") is not None:
if isinstance(obj, iam.Role):
cfn_role = obj.node.find_child("Resource")
policy_arn = (
self.permission_boundary
if isinstance(self.permission_boundary, str)
else self.permission_boundary.managed_policy_arn
)
cfn_role.add_property_override("PermissionsBoundary", policy_arn)
else:
if hasattr(obj, "permissions_node"):
for c in obj.permissions_node.children:
_walk(c)
if hasattr(obj, "node") and obj.node.children:
for c in obj.node.children:
_walk(c)
_walk(resolve)
堆栈的新实现 API 是:
core.Aspects.of(stack).add(
PermissionBoundaryAspect(
f"arn:aws:iam::{target_environment.account}:policy/my-permissions-boundary"
)
)
任何在某些情况下仍在挣扎或想要Java示例的人:
@Slf4j
public class PermissionBoundaryRoleAspect implements IAspect {
private static final String BOUNDED_PATH = "/bounded/";
@Override
public void visit(final @NotNull IConstruct node) {
node.getNode().findAll().stream().filter(iConstruct -> CfnResource.isCfnResource(iConstruct) && iConstruct.toString().contains("AWS::IAM::Role")).forEach(iConstruct -> {
var resource = (CfnResource) iConstruct;
resource.addPropertyOverride("PermissionsBoundary", "arn:aws:iam::xxx:policy/BoundedPermissionsPolicy");
resource.addPropertyOverride("Path", BOUNDED_PATH);
});
if (node instanceof CfnInstanceProfile) {
var instanceProfile = (CfnInstanceProfile) node;
instanceProfile.setPath(BOUNDED_PATH);
}
}
}
我这样做的原因是因为我面临着并非所有正在创建的角色都是 CfnRole 类型的情况
就我而言,我必须创建一个 CfnCloudFormationProvisionedProduct
这个构造函数有一种奇怪的创建角色的方式。此构造函数中的角色类型为 CfnResource,不能强制转换为"CfnRole">
因此,我正在使用iConstruct.toString((.contains("AWS::IAM::Role"(,它适用于每个资源,如果其类型为AWS::IAM::Role和任何CfnRole