aws / aws-cdk

The AWS Cloud Development Kit is a framework for defining cloud infrastructure in code
https://aws.amazon.com/cdk
Apache License 2.0
11.68k stars 3.92k forks source link

elbv2: How to configure a connection log #30708

Closed jrobbins-LiveData closed 4 months ago

jrobbins-LiveData commented 4 months ago

Describe the issue

Application Load Balancer has a connection log feature (see doc) But the L2 construct doesn't seem to surface it. I tried to use a L1 "escape hatch" to enable it, but failed to do so.

I didn't know if this was a doc issue or a feature request, or simply my missing something.

Links

https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_elasticloadbalancingv2-readme.html (which documents the "access log" feature, but not the "connection log" feature)

pahud commented 4 months ago

We need to simplify it with relevant document but this works for me in us-east-1 per the doc.

export class DummyStack extends Stack {
  readonly cluster: rds.DatabaseCluster;
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);

    const connectionLogBucket = new s3.Bucket(this, 'ConnectionLogBucket', {
      removalPolicy: RemovalPolicy.RETAIN_ON_UPDATE_OR_DELETE,
    });

    const logPrefix = 'MY_PREFIX';

    const region = region_info.RegionInfo.get(this.region);

    // create a bucket policy for this bucket
    const accessPolicy = new iam.PolicyStatement({
      effect: iam.Effect.ALLOW,
      actions: ['s3:PutObject'],
      resources: [`arn:aws:s3:::${connectionLogBucket.bucketName}/${logPrefix}/AWSLogs/${Aws.ACCOUNT_ID}/*`],
      principals: [new iam.AccountPrincipal(region.elbv2Account) ],
    });

    connectionLogBucket.addToResourcePolicy(accessPolicy);

    // create a dummy ALB
    const alb = new elbv2.ApplicationLoadBalancer(this, 'ALB', { vpc: ec2.Vpc.fromLookup(this, 'Vpc', { isDefault: true }), internetFacing: true });
    alb.setAttribute('connection_logs.s3.enabled', 'true');
    alb.setAttribute('connection_logs.s3.bucket', connectionLogBucket.bucketName);
    alb.setAttribute('connection_logs.s3.prefix', logPrefix);

    alb.node.addDependency(connectionLogBucket);

  }
} 

Let me know if it works for you.

jrobbins-LiveData commented 4 months ago

@pahud, this is really great, thank you! We're writing our stacks in Python, so my code below is a little different from your helpful example.

        alb_logs_bucket.add_to_resource_policy(
            iam.PolicyStatement(
                effect=iam.Effect.ALLOW,
                actions=['s3:PutObject'],
                resources=[
                    f'arn:aws:s3:::{alb_logs_bucket.bucket_name}/access/AWSLogs/{self.account}/*',
                    f'arn:aws:s3:::{alb_logs_bucket.bucket_name}/connection/AWSLogs/{self.account}/*',
                ],
                principals=[iam.AccountPrincipal(RegionInfo.get(self.region).elbv2_account)]
                # principals=[iam.ServicePrincipal('logdelivery.elasticloadbalancing.amazonaws.com')]
            )
        )

What I'm curious about is what is the difference between the principals=[iam.AccountPrincipal(RegionInfo.get(self.region).elbv2_account)] (my translation of your example into Python) vs the commented-out # principals=[iam.ServicePrincipal('logdelivery.elasticloadbalancing.amazonaws.com')]? Either line of code appears to work, but I don't know if either one is "better" from a security point of view? What do you think?

jrobbins-LiveData commented 4 months ago

@pahud I was unable to combine my Python conversion of your code the the CDK's built-in ..log_access_logs(bucket) L2 method. I ended up with permission errors after I destroyed my CDK stack and tried to deploy clean.

The following code works for me to setup both an access_log and a connection_log, in case it helps anyone.

        alb_logs_bucket.add_to_resource_policy(
            iam.PolicyStatement(
                effect=iam.Effect.ALLOW,
                actions=['s3:PutObject'],
                resources=[
                    f'{alb_logs_bucket.bucket_arn}/{ACCESS_LOG_PREFIX}/AWSLogs/{Aws.ACCOUNT_ID}/*',
                    f'{alb_logs_bucket.bucket_arn}/{CONNECTION_LOG_PREFIX}/AWSLogs/{Aws.ACCOUNT_ID}/*',
                ],
                principals=[iam.ServicePrincipal('logdelivery.elasticloadbalancing.amazonaws.com')]
            )
        )

        alb.set_attribute('access_logs.s3.enabled', 'true')
        alb.set_attribute('access_logs.s3.bucket', alb_logs_bucket.bucket_name)
        alb.set_attribute('access_logs.s3.prefix', ACCESS_LOG_PREFIX)

        alb.set_attribute('connection_logs.s3.enabled', 'true')
        alb.set_attribute('connection_logs.s3.bucket', alb_logs_bucket.bucket_name)
        alb.set_attribute('connection_logs.s3.prefix', CONNECTION_LOG_PREFIX)

        logs_delivery_service_principal = iam.ServicePrincipal('delivery.logs.amazonaws.com')
        account = RegionInfo.get(cdk.Stack.of(self).region).elbv2_account
        if not account:
            account_principal = iam.ServicePrincipal('logdelivery.elasticloadbalancing.amazonaws.com')
        else:
            account_principal = iam.AccountPrincipal(account)

        alb_logs_bucket.add_to_resource_policy(
            iam.PolicyStatement(
                actions=['s3:PutObject'],
                principals=[account_principal],
                resources=[
                    alb_logs_bucket.arn_for_objects(f'{ACCESS_LOG_PREFIX}/AWSLogs/{cdk.Stack.of(self).account}/*'),
                    alb_logs_bucket.arn_for_objects(f'{CONNECTION_LOG_PREFIX}/AWSLogs/{cdk.Stack.of(self).account}/*')
                ],
            )
        )

        alb_logs_bucket.add_to_resource_policy(
            iam.PolicyStatement(
                actions=['s3:PutObject'],
                principals=[logs_delivery_service_principal],
                resources=[
                    alb_logs_bucket.arn_for_objects(f'{ACCESS_LOG_PREFIX}/AWSLogs/{self.account}/*'),
                    alb_logs_bucket.arn_for_objects(f'{CONNECTION_LOG_PREFIX}/AWSLogs/{self.account}/*')
                ],
                conditions={
                    'StringEquals': {'s3:x-amz-acl': 'bucket-owner-full-control'}
                }
            )
        )

        alb_logs_bucket.add_to_resource_policy(
            iam.PolicyStatement(
                actions=['s3:GetBucketAcl'],
                principals=[logs_delivery_service_principal],
                resources=[alb_logs_bucket.bucket_arn]
            )
        )

        alb_cfn = alb.node.default_child
        bucket_policy_cfn = alb_logs_bucket.policy.node.default_child
        alb_cfn.add_dependency(bucket_policy_cfn)
pahud commented 4 months ago

Thank you for your code sharing.

Resolving this issue for now. Feel free to open a new one if it's still relevant.

github-actions[bot] commented 4 months ago

⚠️COMMENT VISIBILITY WARNING⚠️

Comments on closed issues are hard for our team to see. If you need more assistance, please either tag a team member or open a new issue that references this one. If you wish to keep having a conversation with other community members under this issue feel free to do so.

aws-cdk-automation commented 3 months ago

Comments on closed issues and PRs are hard for our team to see. If you need help, please open a new issue that references this one.