【问题标题】:IAM permission boundary for CDK apps with conditions具有条件的 CDK 应用程序的 IAM 权限边界
【发布时间】:2021-08-18 21:32:55
【问题描述】:

我正在为部署我们的Cloud Development Kit (CDK) 应用程序的 CI/CD 用户编写 IAM 角色。 CDK 应用程序由 lambda 函数、Fargate 等组成。问题是,CDK 不允许我指定它需要的所有角色。相反,它自己创建了一些。

几个例子:

  1. 每个具有日志保留的 lambda 函数都有另一个由 CDK 创建的 lambda,它将日志保留设置为日志组和日志流。
  2. 执行步进函数的 CloudTrail 事件需要具有states:StartExecution 权限的角色。

CDK 自动创建这些角色,并为它们设置内联策略。这迫使我授予我的 CI/CD 角色权限以创建角色和附加策略。因此,如果有人可以访问 CI/CD 用户(例如,如果我们的 GitHub 凭据泄露),攻击者可以创建新角色并授予他们管理员权限。

我尝试自己在一个单独的堆栈中创建所有角色,然后在 CDK 应用程序中使用这些角色。但正如我上面提到的(见上面的例子),它不可能无处不在......

我还尝试了部署者角色的 IAM 权限边界,但我不知道如何限制 iam:PutRolePolicy 的权限。 CDK 本质上做了以下工作:

  1. iam:CreateRole
  2. iam:PutRolePolicy

根据AWS documentation,条件是非常基本的字符串比较。我需要能够选择传递给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:CreateLogStreamlogs:PutLogEvents,仅此而已。

我已经为此奋斗了很长一段时间,我不想退回到授予不必要的更多权限。提前谢谢大家!

【问题讨论】:

  • 如何导入Conditions和StringEquals?请

标签: amazon-web-services amazon-cloudformation amazon-iam aws-cdk


【解决方案1】:

以下是受@matthewtapper 在 GitHub 上的代码启发的 CDK 1.4.0 的 Python 解决方案。这允许您为堆栈中的所有角色设置权限边界。

不用说它很丑,因为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))

【讨论】:

  • 第一次看到这个(并且还关注了 Github 问题讨论),绝望地进入了胎儿的位置。然而,它的工作非常出色!礼物马和所有...仍然希望 AWS 将其正确集成到 CDK 本身中。
  • 我认为这是引用的 github 代码? github.com/aws/aws-cdk/issues/3242#issuecomment-509530635
【解决方案2】:

这是 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"
    )
)

【讨论】:

    【解决方案3】:

    任何人在某些情况下仍在苦苦挣扎或想要一个 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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-01-01
      • 1970-01-01
      • 2019-11-28
      • 2020-03-10
      • 1970-01-01
      • 2021-08-02
      • 2016-10-30
      • 2016-07-14
      相关资源
      最近更新 更多