31

I'm deploying a Lambda function that will be used by CloudFront. The execution role for the function therefore needs to be assumed by edgelambda.amazonaws.com and lambda.amazonaws.com. If I was doing this by hand, the policy would look like this:

{
    "Version": "2012-10-17",
    "Statement": [
    {
        "Effect": "Allow",
        "Principal": {
            "Service": [
                "edgelambda.amazonaws.com",
                "lambda.amazonaws.com"
            ]
        },
        "Action": "sts:AssumeRole"
    }
    ]
}

When setting this up in AWS CDK, the iam.Role class only allows you to specify one assuming principal initially, e.g.:

lambda_role = iam.Role(
    self,
    "lambda_redirect_role",
    assumed_by=iam.ServicePrincipal("edgelambda.amazonaws.com"),
    managed_policies=[
        iam.ManagedPolicy.from_aws_managed_policy_name("service-role/AWSLambdaBasicExecutionRole"),
        iam.ManagedPolicy.from_aws_managed_policy_name("AWSXrayWriteOnlyAccess")
    ],
)

So I'm trying to find the best/cleanest way of adding the second principal. The documentation says that I can use assume_role_policy to retrieve the policy document and then manipulate that.

So I've tried:

policy = lambda_role.assume_role_policy
policy.add_statements(
    iam.PolicyStatement(
        actions=["sts:AssumeRole"],
        effect=iam.Effect.ALLOW,
        principals=[
            iam.ServicePrincipal("lambda.amazonaws.com")
        ]
    )
)

but that gives me this rather less-than-optimal policy:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "edgelambda.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    },
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "lambda.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

I've tried manipulating the existing statement within the policy but I can't figure out how to then get that back into the Role definition:

policy = lambda_role.assume_role_policy
# This gives us a read-only policy so we need to do
# some manipulation in order to add the lambda principal
policy_json = policy.to_json()
print(policy_json)
# That gets us a dict (even though it says json) so now
# we extract the one and only statement ...
statement = policy_json["Statement"][0]
# Turn it back into a CDK object we can manipulate
statement_obj = iam.PolicyStatement.from_json(statement)
# Add the extra principal
statement_obj.add_principals(
    iam.ServicePrincipal("lambda.amazonaws.com")
)
# Put it all back ...
policy_json["Statement"][0] = statement_obj.to_json()
policy.from_json(policy_json)

Is there a way to get to the "cleaner" policy statement with CDK or am I stuck with the two statements it is currently generating?

Philip Colmer
  • 1,426
  • 2
  • 17
  • 30
  • If you are using CDK, why are you worry about it's build code ? Just follow CDK way to write IAM role, which should be easy to read and extend. I generally created IAM role with `ServicePrincipal` which will invoked by some resources automatically and then create `PolicyStatement` statement for each service that this role will use. It provides extensibility in terms of lets say in future I need to add/remove service, it can be done without actually harming the existing policy – Atul Kumar Jul 10 '21 at 11:23
  • @AtulKumar you make a fair point. The reason I was trying to get to the desired policy was for consistency with existing roles and trust policy statements. Most of our existing infrastructure has been deployed by hand so I wanted to be as consistent with that design/look as possible. – Philip Colmer Jul 11 '21 at 10:54

1 Answers1

82

This can be done using CompositePrincipal:

lambda_role = iam.Role(
    self,
    "lambda_redirect_role",
    assumed_by=iam.CompositePrincipal(
        iam.ServicePrincipal("edgelambda.amazonaws.com"),
        iam.ServicePrincipal("lambda.amazonaws.com"),
    ),
    ...
)
Okonos
  • 836
  • 5
  • 2