Home > Net >  How to link a trail to functions by partial name in CDK?
How to link a trail to functions by partial name in CDK?

Time:03-15

The original code created a bucket and trail in a single stack per deployment, and linked them to a function in the same stack:

trail_bucket = aws_s3.Bucket(
    self,
    "cloudtrail-bucket",
    access_control=aws_s3.BucketAccessControl.PRIVATE,
    block_public_access=aws_s3.BlockPublicAccess.BLOCK_ALL,
    auto_delete_objects=True,
    removal_policy=RemovalPolicy.DESTROY,
)
trail = aws_cloudtrail.Trail(
    self,
    "cloudtrail",
    send_to_cloud_watch_logs=True,
    bucket=trail_bucket,
    cloud_watch_log_group=aws_logs.LogGroup(self, "api-user-log"),
    removal_policy=RemovalPolicy.DESTROY,
)
trail.add_lambda_event_selector(
    [import_status_endpoint_lambda],
    include_management_events=False,
)

This has become a problem for CI, since we regularly hit the limit of five trails per account (all CI deployments are to the same account). So I'm looking into the possibility of pulling the trail into its own stack and linking it to the relevant functions via a wildcard. Basically, I want to log any events from ARNs matching arn:aws:lambda:*:*:function:*import-status. Is this possible?

The documentation for Trail.add_event_selector speaks of the data resource values as being specifically ARNs, and DataResource seems to indicate that my only choices are

  • log events for all functions (too much) or
  • log events for a specific ARN (unknown at deployment time).

CodePudding user response:

CloudTrail has Advanced Event Selectors like EndsWith to filter ARNs. This fits your use case, where you want a shared Trail for all Lambda ARNs ending with import-status.

OK, but how do you set Advanced Event Selectors with the CDK? Unfortunately, the CloudFormation AWS::CloudTrail::Trail resource (= CDK L1 CfnTrail construct, which underlies the L2 Trail) appears not to support them in the eventSelectors prop. Fortunately, CDK (and CloudFormation) has Custom Resources to plug such gaps.

In your new trail_stack, create a Trail and a AWSCustomResource construct, which makes arbitrary SDK calls. It will call the PutEventsSelectors API on your Trail, setting the Advanced Event Selectors. It will be called once, on resource create:

# trail_stack.py
aws_custom = cr.AwsCustomResource(self, "aws-custom-advanced-selectors",
    on_create=cr.AwsSdkCall(
        service="CloudTrail",
        action="putEventSelectors",
        parameters= parameters: {
          TrailName: my_trail.trail_arn,
          AdvancedEventSelectors: [
            {
              Name: 'Log import-status lambdas only',
              FieldSelectors: [
                { Field: 'eventCategory', Equals: ['Data'] },
                { Field: 'resources.type', Equals: ['AWS::Lambda::Function'] },
                {
                  Field: 'resources.ARN',
                  EndsWith: ['import-status'],
                },
              ],
            },
          ],
        },
        physical_resource_id=cr.PhysicalResourceId.of("aws-custom-advanced-selectors")
    ),
    policy=cr.AwsCustomResourcePolicy.from_sdk_calls(
        resources=my_trail.trail_arn
    )
)
  • Related