具有条件的 CDK 应用程序的 IAM 权限边界
IAM permission boundary for CDK apps with conditions
我正在为部署我们的 Cloud Development Kit (CDK) 应用程序的 CI/CD 用户编写 IAM 角色。 CDK 应用程序由 lambda 函数、Fargate 等组成。问题是,CDK 不允许我指定它需要的所有角色。相反,它自己创建了一些 then 。
几个例子:
- 每个带有日志保留的 lambda 函数都有另一个由 CDK 创建的 lambda,它将日志保留设置到日志组和日志流。
- 执行步骤函数的 CloudTrail 事件需要具有
states:StartExecution
权限的角色。
CDK 自动创建这些角色并为它们设置内联策略。这迫使我授予 CI/CD 角色创建角色和附加策略的权限。因此,如果有人可以访问 CI/CD 用户(例如,如果我们的 GitHub 凭据泄露),攻击者可以创建新角色并授予他们管理员权限。
我尝试在单独的堆栈中自己创建所有角色,然后在 CDK 应用程序中使用这些角色。但是正如我上面提到的(见上面的例子),它不可能无处不在...
我也为部署者角色尝试了 IAM 权限边界,但我不知道如何限制 iam:PutRolePolicy
的权限。 CDK 基本上执行以下操作:
根据AWS documentation,条件是非常基本的字符串比较。我需要能够 select,在传递给 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
限制为仅 selected 操作的正确方法是什么?我想允许 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()
以防止节点上的嵌套子错误,而且 stack.node.apply_aspect()
方法已被 AWS 弃用,因此有一个新的用法实现。
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
我正在为部署我们的 Cloud Development Kit (CDK) 应用程序的 CI/CD 用户编写 IAM 角色。 CDK 应用程序由 lambda 函数、Fargate 等组成。问题是,CDK 不允许我指定它需要的所有角色。相反,它自己创建了一些 then 。
几个例子:
- 每个带有日志保留的 lambda 函数都有另一个由 CDK 创建的 lambda,它将日志保留设置到日志组和日志流。
- 执行步骤函数的 CloudTrail 事件需要具有
states:StartExecution
权限的角色。
CDK 自动创建这些角色并为它们设置内联策略。这迫使我授予 CI/CD 角色创建角色和附加策略的权限。因此,如果有人可以访问 CI/CD 用户(例如,如果我们的 GitHub 凭据泄露),攻击者可以创建新角色并授予他们管理员权限。
我尝试在单独的堆栈中自己创建所有角色,然后在 CDK 应用程序中使用这些角色。但是正如我上面提到的(见上面的例子),它不可能无处不在...
我也为部署者角色尝试了 IAM 权限边界,但我不知道如何限制 iam:PutRolePolicy
的权限。 CDK 基本上执行以下操作:
根据AWS documentation,条件是非常基本的字符串比较。我需要能够 select,在传递给 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
限制为仅 selected 操作的正确方法是什么?我想允许 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()
以防止节点上的嵌套子错误,而且 stack.node.apply_aspect()
方法已被 AWS 弃用,因此有一个新的用法实现。
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