Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BUG] (compliance_log_bucket_policy doesn't allow CloudFront to put access logs) #76

Open
mishdane opened this issue Jul 16, 2024 · 0 comments
Labels
bug Something isn't working

Comments

@mishdane
Copy link

Describe the bug

compliance_log_bucket_policy (see below link) doesn't allow CloudFront to put access logs. Hence, can't use wrapper provided compliance bucket with CloudFront access logging.
https://github.com/cdklabs/cdk-cicd-wrapper/blob/main/packages/%40cdklabs/cdk-cicd-wrapper/src/stacks/compliance-bucket/lambda-functions/compliance_log_bucket_policy.py

Expected Behavior

update the bucket policy to have ACL for CloudFront to be able to log access logs on compliance bucket

Current Behavior

Resource handler returned message: "Invalid request provided: AWS::CloudFront::Distribution: The S3 bucket that you specified for CloudFront logs does not enable ACL access: compliance-log-xxxxxxx-eu-west-1.s3.eu-west-1.amazonaws.com (Service: CloudFront, Status Code: 400, Request ID:

Reproduction Steps

Just pass complaince bucket to CloudFront construct and use for access log.
e.g.

import * as cdk from 'aws-cdk-lib';
import * as acm from 'aws-cdk-lib/aws-certificatemanager';
import * as cloudfront from 'aws-cdk-lib/aws-cloudfront';
import { S3Origin } from 'aws-cdk-lib/aws-cloudfront-origins';
import * as iam from 'aws-cdk-lib/aws-iam';
import { IBucket, CfnBucket } from 'aws-cdk-lib/aws-s3';
import * as shield from 'aws-cdk-lib/aws-shield';
import * as ssm from 'aws-cdk-lib/aws-ssm';
import * as nag from 'cdk-nag';
import { Construct } from 'constructs';

interface Props {
domainNames?: string[];
webAclArn: string;
certificate?: acm.ICertificate;
staticContentBucket: IBucket;
logBucket?: IBucket;
applicationName: string;
stageName: string;
defaultRootObject?: string;
}

export class CloudFrontConstruct extends Construct {
readonly distribution: cloudfront.Distribution;
readonly staticSourceBucket: IBucket;

constructor(scope: Construct, id: string, props: Props) {
super(scope, id);

if (props.domainNames == undefined && props.certificate) {
  throw new Error(
    'Custom certificate is provided but not custom domain name.',
  );
}

if (props.certificate == undefined && props.domainNames) {
  throw new Error(
    'Custom domain name is provided but not custom certificate.',
  );
}

const originAccessIdentity = new cloudfront.OriginAccessIdentity(
  this,
  'OriginAccessIdentity',
  {
    comment: `${props.applicationName}${props.stageName}OriginAccessIdentity`,
  },
);

this.staticSourceBucket = props.staticContentBucket;
this.staticSourceBucket.grantRead(originAccessIdentity);

this.distribution = new cloudfront.Distribution(this, 'CloudFront', {
  defaultBehavior: {
    origin: new S3Origin(this.staticSourceBucket, { originAccessIdentity }),
    viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
  },
  domainNames: props.domainNames,
  certificate: props.certificate,
  webAclId: props.webAclArn,
  defaultRootObject: props.defaultRootObject ?? 'index.html',
  minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021,
  comment: `${props.applicationName}${props.stageName}Distribution`,
  enableLogging: props.logBucket != undefined,
  sslSupportMethod: cloudfront.SSLMethod.SNI,
  logBucket: props.logBucket,
  errorResponses: [
    {
      httpStatus: 404,
      responseHttpStatus: 404,
      responsePagePath: '/index.html',
      ttl: cdk.Duration.minutes(30),
    },
    {
      httpStatus: 403,
      responseHttpStatus: 403,
      responsePagePath: '/index.html',
      ttl: cdk.Duration.minutes(30),
    },
    {
      httpStatus: 500,
      responseHttpStatus: 500,
      responsePagePath: '/index.html',
      ttl: cdk.Duration.minutes(30),
    },
  ],
});

if (props.logBucket) {
  props.logBucket.addToResourcePolicy(new iam.PolicyStatement({
    effect: iam.Effect.ALLOW,
    principals: [new iam.ServicePrincipal('cloudfront.amazonaws.com')],
    actions: ['s3:PutObject'],
    resources: [`${props.logBucket.bucketArn}/*`],
    conditions: {
      StringEquals: {
        'aws:SecureTransport': 'true',
        's3:x-amz-server-side-encryption': 'AES256',
        'AWS:SourceArn': `arn:aws:cloudfront::${cdk.Aws.ACCOUNT_ID}:distribution/${this.distribution.distributionId}`,
      },
    },
  }));
}

new ssm.StringParameter(this, 'CloudFrontDomainId', {
  parameterName: `/${props.applicationName}/${props.stageName}/SiteCloudFrontDomainId`,
  stringValue: this.distribution.distributionId,
});

new ssm.StringParameter(this, 'CloudFrontDomainName', {
  parameterName: `/${props.applicationName}/${props.stageName}/SiteCloudFrontDomainName`,
  stringValue: this.distribution.domainName,
});

// Attach AWS Shield Advanced for DDoS protection
new shield.CfnProtection(this, 'DDoSProtection', {
  name: 'DDoSProtection',
  resourceArn: this.getDistributionArn(),
});

nag.NagSuppressions.addResourceSuppressions(
  this.distribution,
  [
    ...(props.certificate
      ? []
      : [
        {
          id: 'AwsSolutions-CFR4',
          reason: 'CloudFront distribution is not using custom cert',
        },
      ]),
    {
      id: 'AwsSolutions-CFR1',
      reason: 'CloudFront distribution is not limited by Geo',
    },
  ],
  true,
);

}

private getDistributionArn(): string {
return arn:aws:cloudfront::${cdk.Aws.ACCOUNT_ID}:distribution/${this.distribution.distributionId};
}
}

Possible Solution

Add this to bucket policy =>
{
"Sid": "CloudFrontLogDeliveryPolicy",
"Effect": "Allow",
"Principal": {
"Service": "cloudfront.amazonaws.com"
},
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::compliance-log-211826912675-eu-west-1/",
"Condition": {
"StringEquals": {
"AWS:SourceArn": "arn:aws:cloudfront::211826912675:distribution/
",
"aws:SecureTransport": "true",
"s3:x-amz-server-side-encryption": "AES256"
}
}
},

Additional Information/Context

Ignore this if compliance bucket is not supposed to be used for CLOUDFRONT access logs.

CDK CI/CD Wrapper version used

0.0.12

Environment details (OS name and version, etc.)

MacOS Sonoma 14.5

@mishdane mishdane added the bug Something isn't working label Jul 16, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

1 participant