0

I have an app stack that fires off a custom resource lambda on every deploy. Currently the custom resource lambda is in the same app stack and both are running in the same account. Both are built in Python 3.8

This is how its structured now where everything is in one stack running in the same account.

cust_res_lambda = _lambda.Function(
    self, 'crLambda',
    runtime=_lambda.Runtime.PYTHON_3_8,
    code=_lambda.Code.from_asset('..'),
    handler='lambda.lambda_handler',
    function_name='customResourceLambda',
    layers=[..,..], 
)

cust_res_lambda.add_to_role_policy(_iam.PolicyStatement(
    effect=_iam.Effect.ALLOW,
    actions=[
        'dynamodb:Query',
        'dynamodb:GetItem',
        'dynamodb:GetRecords',
        'dynamodb:PutItem',
        'dynamodb:UpdateItem',
        'dynamodb:BatchGetItem',
    ],
    resources=['arn:aws:dynamodb:XregionX:Xaccount-numX:table/stances']
))

res_provider = cr.Provider(
    self,'crProvider',
    on_event_handler= cust_res_lambda
)


now=datetime.now()
time = now.strftime("%H:%M:%S") # forces the execution of custom resource at every stack run.
CustomResource(self, 'cust_res',service_token= res_provider.service_token,properties={"prop1":"aa","prop2":"bb","res_id":time })

Looking for some documentation guidance around how to get the custom resource lambda deployed in a central account A, while the app stack gets deployed in accounts B,C,D? I will shift the lambda into its own stack that is only deployed in account A. The ask is due to the lambda needing to update a DynamoDB in the central account A and accounts B, C, D where the app stack will be deployed are not allowed to touch any resources in account A other than this custom resource lambda.

EDIT: There are some security restrictions why it may not be allowed to have the lambda run in accounts B, C, D in this particular case. Also, we don't know in advance all the other target accounts that this stack will be deployed in. The lambda needs to run from central account A.

user20358
  • 14,182
  • 36
  • 114
  • 186

1 Answers1

0

If the only concern is the ability to modify a DynamoDB table in another account, you can keep the current setup you have with an instance of the custom resource in each account, but grant permissions to allow the lambda to assume a role in Account 'A' using cross-account access.

So, without modifying your current setup, you can create a new stack in account A that grants assume-role permissions to all of the roles used by the lambda in the other accounts.

It might look something like this:

# ...
class CentralStack(Stack):
    # this stack gets created once in the central account
    # has the dynamodb table other accounts need to modify
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.the_table = Table(...)
        # ...


class AppStack(Stack):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.cust_lambda_role = iam.Role(...)
        # give permission to the lambda to assume a role in account A
        self.cust_lambda_role.add_to_policy(
            iam.PolicyStatement(
                actions=['sts:AssumeRole'],
                effect=iam.Effect.ALLOW,
                resources=['arn:aws:iam::A:role/my-central-xacct-role'],
            )
        )
        cust_res_lambda = _lambda.Function(..., role=self.cust_lambda_role)
        # ...

# instantiate the central account main stack
central_stack = CentralStack(app, env=cdk.Environment(account='A', region=MY_REGION))

app_stacks: List[AppStack] = []

# create multiple application stacks in different accounts
for account in ['B', 'C', 'D']:
    app_stack = AppStack(app, env=cdk.Environment(account=account, region=MY_REGION))
    app_stacks.append(app_stack)


class CrossAccountRoleStack(Stack):
    def __init__(self, *args, central_stack, app_stacks, **kwargs):
        super().__init__(*args, **kwargs)
        app_roles = [stack.cust_lambda_role for stack in app_stacks]
        app_role_principals = iam.CompositePrincipal(*app_roles)
        # create a role that can be assumed by lambdas in the app stack/accounts
        cust_resource_x_acct_role = iam.Role(
            self,
            'x-acct-role',
            assumed_by=app_role_principals,
            role_name='my-central-xacct-role',
        )
        # grant this role access to the table
        central_stack.the_table.grant_full_access(cust_resource_x_acct_role)

# create the cross-account role in the central account
# that allows each of the app stacks to assume the role in account A
xacct_role_stack = CrossAccountRoleStack(
    app,
    central_stack=central_stack,
    app_stacks=app_stacks,
    env=cdk.Environment(account='A', region=MY_REGION),
)

# ...
app.synth()

Then your actual lambda function code that runs in each application account (e.g., accounts B,C,D) can call the sts assume-role operation to obtain credentials for the role in the central account A and have access to the dynamoDB table in account A.

sytech
  • 29,298
  • 3
  • 45
  • 86
  • While that would normally work. There are some security restrictions why it may not be allowed in this particular case. Also, we don't know in advance all the other target accounts that this stack will be deployed in. – user20358 Dec 15 '22 at 19:56
  • @user20358 the target accounts do not need to be known in advance. They are introspected in this case. The use of the defined list is only for illustration. Only the central account of concern has to be known (but also could be reorganized to introspect if needed). If the lambda can't run in the various accounts, you could still use the same approach for invoking a lambda in another account using cross-account roles... I find it odd to have this restriction, since the CDK itself often relies on lambda providers in the destination account. – sytech Dec 15 '22 at 20:14
  • Thanks for the reply. If you see the line where I assign the lambda to the custom resource `on_event_handler = cust_res_lambda` . This is possible because I have the lambda created in the same stack a few lines above and hence the variable `cust_res_lambda` is available to use. How can I do this assignment when the lambda is already existing and created from another stack? – user20358 Dec 15 '22 at 22:44
  • @user20358 you can import the lambda function. In the CDK, you can just access the function object from the other stack -- just like how I did in the CrossAcountRoleStack to access the role objects in the other stacks... Or, you can import it explicitly using `_lambda.Function.from_function_arn(...)` There is also nothing in particular that prevents a provider from being used across stacks, so you can also just create the provider in the stack where the lambda is. But it sounds like that's a separate question. If my answer helped answer your original question, consider voting or accepting. – sytech Dec 15 '22 at 23:11