Home > Software design >  CodeBuild in private subnets have connection issue to S3
CodeBuild in private subnets have connection issue to S3

Time:10-07

I added CodeBuild in my VPC's private subnets, because I want to give it access to RDS cluster in the private subnets.

However, I got below error in the CodeBuild:

CLIENT_ERROR: RequestError: send request failed caused by: Get "https://abcdatabase-schema-abcdatabaseschemap-jatjfe01aqps.s3.amazonaws.com/abcDatabase-Sc/Artifact_S/LrJBqSR.zip": dial tcp 52.217.128.121:443: i/o timeout for primary source and source version arn:aws:s3:::abcdatabase-schema-abcdatabaseschemap-jatjfe01aqps/abcDatabase-Sc/Artifact_S/LrJBqSR.zip

It is a TCP 443 timeout.

It looks like the CodeBuild is trying to download the pipeline artifact from S3 bucket but have a connection timeout, which means that there is a network connection issue between my CodeBuild and S3. However, I added S3 VPC endpoint in my VPC, that is suppose to provide the network connection. https://docs.aws.amazon.com/codebuild/latest/userguide/use-vpc-endpoints-with-codebuild.html.

According to CodeBuild unable to fetch an S3 object despite having Administrator Access, as long as I have s3 VPC endpoint set up then I don't need NAT.

You can see the code below to see how I added the S3 VPC endpoint.

Code

VPC stack

  private readonly coreVpc: EC2.Vpc;
  constructor(scope: CDK.App, id: string, props?: VpcStackStackProps) {
    super(scope, id, props);

    const vpcName: string = "CoreVpc";

    // Create VPC
    this.coreVpc = new EC2.Vpc(this, "CoreVpc", {
      vpcName: vpcName,
      cidr: "10.0.0.0/16",
      enableDnsHostnames: true,
      enableDnsSupport: true,
      maxAzs: 3, // 3 availability zones
      // Each zone will have one public subnet and one private subnet.
      subnetConfiguration: [
        {
          cidrMask: 19,
          name: "PublicSubnet",
          subnetType: EC2.SubnetType.PUBLIC,
        },
        {
          cidrMask: 19,
          name: "PrivateSubnet",
          subnetType: EC2.SubnetType.PRIVATE_ISOLATED,
        },
      ],
    });

    // Create security group for the VPC
    const vpcEndpointSecurityGroup = new EC2.SecurityGroup(
      this,
      `${vpcName}-VPCEndpointSecurityGroup`,
      {
        securityGroupName: `${vpcName}-VPCEndpointSecurityGroup`,
        vpc: this.coreVpc,
        description: "Security group for granting AWS services access to the CoreVpc",
        allowAllOutbound: false,
      }
    );
    vpcEndpointSecurityGroup.addIngressRule(
      EC2.Peer.ipv4(this.coreVpc.vpcCidrBlock),
      EC2.Port.tcp(443),
      "Allow HTTPS ingress traffic"
    );

    vpcEndpointSecurityGroup.addEgressRule(
      EC2.Peer.ipv4(this.coreVpc.vpcCidrBlock),
      EC2.Port.tcp(443),
      "Allow HTTPS egress traffic"
    );

    const privateSubnets = this.coreVpc.selectSubnets(
      {
        subnetType: EC2.SubnetType.PRIVATE_ISOLATED
      }
    );

    // Grant AWS CodeBuild service access to the VPC's private subnets.
    new EC2.InterfaceVpcEndpoint(
      this, 'CodeBuildInterfaceVpcEndpoint', {
        service: EC2.InterfaceVpcEndpointAwsService.CODEBUILD,
        vpc: this.coreVpc,
        privateDnsEnabled: true,
        securityGroups: [vpcEndpointSecurityGroup],
        subnets: privateSubnets
      }
    );

    // Grant VPC access to S3 service.
    new EC2.GatewayVpcEndpoint(
      this, 'S3InterfaceVpcEndpoint', {
        service: EC2.GatewayVpcEndpointAwsService.S3,
        vpc: this.coreVpc,
      }
    );
  }
}

CodeBuild stack

export class CodeBuildStack extends CDK.Stack {
  constructor(scope: Construct, id: string, props: CodeBuildStackProps) {
    super(scope, id, props);

    const buildspecFile = FS.readFileSync("./config/buildspec.yml", "utf-8");
    const buildspecFileYaml = YAML.parse(buildspecFile, {
      prettyErrors: true,
    });

    // Grant write permissions to the DeploymentRole to the artifact S3 bucket.
    const deploymentRoleArn: string = `arn:aws:iam::${props.env?.account}:role/${props.pipelineName}-DeploymentRole`;
    const deploymentRole = IAM.Role.fromRoleArn(
      this,
      `CodeBuild${props.pipelineStageInfo.stageName}DeploymentRoleConstructID`,
      deploymentRoleArn,
      {
        mutable: false,
        // Causes CDK to update the resource policy where required, instead of the Role
        addGrantsToResources: true,
      }
    );

    const coreVpc: EC2.IVpc = EC2.Vpc.fromLookup(
      this,
      `${props.pipelineStageInfo.stageName}VpcLookupId`,
      {
        vpcName: "CoreVpc",
      }
    );
    
    const securityGroupForVpc: EC2.ISecurityGroup =
      EC2.SecurityGroup.fromLookupByName(
        this,
        "SecurityGroupLookupForVpcEndpoint",
        "CoreVpc-VPCEndpointSecurityGroup",
        coreVpc
      );

    new CodeBuild.Project(
      this,
      `${props.pipelineName}-${props.pipelineStageInfo.stageName}-ColdBuild`,
      {
        projectName: `${props.pipelineName}-${props.pipelineStageInfo.stageName}-ColdBuild`,
        environment: {
          buildImage: CodeBuild.LinuxBuildImage.STANDARD_5_0,
        },
        buildSpec: CodeBuild.BuildSpec.fromObjectToYaml(buildspecFileYaml),
        vpc: coreVpc,
        securityGroups: [securityGroupForVpc],
        role: deploymentRole,
      }
    );
  }
}

CodePudding user response:

The issue in my code is this part. The security group add ingress rule saying that allow TCP egress traffic from my VPC's CIDR block. But CodeBuild is not running inside my VPC's CIDR Block.

vpcEndpointSecurityGroup.addEgressRule(
      EC2.Peer.ipv4(this.coreVpc.vpcCidrBlock),
      EC2.Port.tcp(443),
      "Allow TCP egress traffic"
    );

It works by changing to :

vpcEndpointSecurityGroup.addEgressRule(
      EC2.Peer.anyIpv4(),
      EC2.Port.allTcp(),
      "Allow TCP egress traffic"
    );

There are other ways to make it work:

Alternative 1:

Changed based on @gshpychka's comments also works.

Alternative 2:

Make allowAllOutbound true. In this way you don't need to specify any egress rule any more.

If this is set to true, there will only be a single egress rule which allows all outbound traffic. If this is set to false, no outbound traffic will be allowed by default and all egress traffic must be explicitly authorized.

    const vpcEndpointSecurityGroup = new EC2.SecurityGroup(
      this,
      `${vpcName}-VPCEndpointSecurityGroup`,
      {
        securityGroupName: `${vpcName}-VPCEndpointSecurityGroup`,
        vpc: this.coreVpc,
        description: "Security group for granting AWS services access to the CoreVpc",
        allowAllOutbound: true,
      }
    );
  • Related