如何通过环境变量部署对AWS的反应

发布于 2025-02-06 03:25:03 字数 8271 浏览 3 评论 0原文

上下文

我有一个React Web应用程序,可以通过Codepipeline部署到AWS。我的codepipeline与我的React GitHub存储库相连,因此每当我将更改推向GitHub时,我的CodePipeline都会重新构建工件并将其部署到S3桶中。

问题

现在我创建了不同的.env文件来存储环境变量。我所做的与这些非常相似:

因此带有.env.env.production文件的网站工件。

由于安全原因,我们不应该将.env文件添加到github。我应该如何设置环境变量,以便在管道的构建阶段中可以从某个地方获取.env文件?

我的CDK代码

import * as CDK from "aws-cdk-lib";
import * as YAML from "yaml";
import * as FS from "fs";
import * as CodeBuild from "aws-cdk-lib/aws-codebuild";
import * as S3 from "aws-cdk-lib/aws-s3";
import * as CloudFront from "aws-cdk-lib/aws-cloudfront";
import * as ACM from "aws-cdk-lib/aws-certificatemanager";
import * as Route53 from "aws-cdk-lib/aws-route53";
import * as Route53Targets from "aws-cdk-lib/aws-route53-targets";
import * as CloudFrontOrigins from "aws-cdk-lib/aws-cloudfront-origins";
import * as IAM from "aws-cdk-lib/aws-iam";
import * as codepipeline from "aws-cdk-lib/aws-codepipeline";
import * as codepipeline_actions from "aws-cdk-lib/aws-codepipeline-actions";

export interface CodePipelineStackProps extends CDK.StackProps {
  // Built in Stack props
  readonly env: CDK.Environment;
  readonly description: string;
  readonly websiteDomain: string;
}

export class CodePipelineStack extends CDK.Stack {
  constructor(scope: CDK.App, id: string, props: CodePipelineStackProps) {
    super(scope, id, props);

    // AWS CodeBuild artifacts
    const outputSources = new codepipeline.Artifact();
    const outputWebsite = new codepipeline.Artifact();

    // AWS CodePipeline pipeline
    const pipeline = new codepipeline.Pipeline(this, "Pipeline", {
      pipelineName: "PandaWebsite",
      restartExecutionOnUpdate: true,
    });

    this.addSourceStage(pipeline, outputSources);
    this.addBuildStage(pipeline, outputSources, outputWebsite);

    // Amazon S3 bucket to host the store  website artifact
    const websiteBucket = new S3.Bucket(this, "PandaWebsite", {
      bucketName: `${props.websiteDomain}-${props.env.account}-${props.env.region}`,
      websiteIndexDocument: "index.html",
      websiteErrorDocument: "error.html",
      removalPolicy: CDK.RemovalPolicy.DESTROY,
      autoDeleteObjects: true,
      accessControl: S3.BucketAccessControl.PRIVATE,
      encryption: S3.BucketEncryption.S3_MANAGED,
      publicReadAccess: false,
      blockPublicAccess: S3.BlockPublicAccess.BLOCK_ALL,
    });

    const hostedZone: Route53.IHostedZone = Route53.HostedZone.fromLookup(
      this,
      "HostedZoneId",
      {
        domainName: props.websiteDomain,
      }
    );

    const cloudFrontDistribution: CloudFront.Distribution =
      this.createCloudFrontDistribution(
        props.websiteDomain,
        websiteBucket,
        hostedZone
      );

    new Route53.ARecord(this, "Route53RecordSet", {
      recordName: props.websiteDomain,
      zone: hostedZone,
      target: Route53.RecordTarget.fromAlias(
        new Route53Targets.CloudFrontTarget(cloudFrontDistribution)
      ),
    });

    // AWS CodePipeline stage to deployt website
    pipeline.addStage({
      stageName: "Deploy",
      actions: [
        // AWS CodePipeline action to deploy website to S3 bucket
        new codepipeline_actions.S3DeployAction({
          actionName: "PandaWebsite",
          input: outputWebsite,
          bucket: websiteBucket,
        }),
      ],
    });

    new CDK.CfnOutput(this, "DeployURL", {
      value: `https://${props.websiteDomain}`,
      description: "Website URL",
    });
  }

  private addSourceStage(
    pipeline: codepipeline.Pipeline,
    outputSources: codepipeline.Artifact
  ) {
    // AWS CodePipeline stage to clone sources from GitHub repository
    pipeline.addStage({
      stageName: "Source",
      actions: [
        new codepipeline_actions.GitHubSourceAction({
          actionName: "Checkout",
          owner: "yangliu",
          repo: "PandaWebsite",
          branch: "main",
          oauthToken: CDK.SecretValue.secretsManager(
            "PandaWebsite-GitHubToken"
          ),
          output: outputSources,
          trigger: codepipeline_actions.GitHubTrigger.WEBHOOK,
        }),
      ],
    });
  }

  private addBuildStage(
    pipeline: codepipeline.Pipeline,
    outputSources: codepipeline.Artifact,
    outputWebsite: codepipeline.Artifact
  ) {
    const buildspecFile = FS.readFileSync("./config/buildspec.yml", "utf-8");
    const buildspecFileYaml = YAML.parse(buildspecFile, {
      prettyErrors: true,
    });
    pipeline.addStage({
      stageName: "Build",
      actions: [
        new codepipeline_actions.CodeBuildAction({
          actionName: "BuildeWebsite",
          project: new CodeBuild.PipelineProject(this, "BuildWebsite", {
            projectName: "BuildeWebsite",
            environment: {
              buildImage: CodeBuild.LinuxBuildImage.STANDARD_5_0,
            },
            buildSpec: CodeBuild.BuildSpec.fromObjectToYaml(buildspecFileYaml),
          }),
          input: outputSources,
          outputs: [outputWebsite],
        }),
      ],
    });
  }

  private createCloudFrontDistribution(
    websiteDomain: string,
    websiteBucket: S3.Bucket,
    hostedZone: Route53.IHostedZone
  ) {
    const certificateManagerCertificate = new ACM.Certificate(
      this,
      "CertificateManagerCertificate",
      {
        domainName: websiteDomain,
        validation: ACM.CertificateValidation.fromDns(hostedZone),
      }
    );
    // Create a special CloudFront user called an origin access identity (OAI)
    // and associate it with the CloudFront distribution.
    const cloudFrontOAI = new CloudFront.OriginAccessIdentity(
      this,
      "PandaWebsiteOriginAccessIdentityID",
      {
        comment: "OriginAccessIdentityID for PandaWebsite"
      }
    );
    const cloudfrontUserAccessPolicy = new IAM.PolicyStatement();
    cloudfrontUserAccessPolicy.addActions("s3:GetObject");
    cloudfrontUserAccessPolicy.addPrincipals(cloudFrontOAI.grantPrincipal);
    cloudfrontUserAccessPolicy.addResources(websiteBucket.arnForObjects("*"));
    websiteBucket.addToResourcePolicy(cloudfrontUserAccessPolicy);
    return new CloudFront.Distribution(this, "CloudFrontDistribution", {
      domainNames: [websiteDomain],
      defaultBehavior: {
        origin: new CloudFrontOrigins.S3Origin(websiteBucket, {
          // CloudFront can use the OAI to access the files in the S3 bucket
          // and serve them to users. Users can’t use a direct URL to the
          // S3 bucket to access a file there.
          originAccessIdentity: cloudFrontOAI,
        }),
        compress: true,
        allowedMethods: CloudFront.AllowedMethods.ALLOW_GET_HEAD,
        cachedMethods: CloudFront.CachedMethods.CACHE_GET_HEAD,
        viewerProtocolPolicy: CloudFront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
        cachePolicy: CloudFront.CachePolicy.CACHING_OPTIMIZED,
      },
      errorResponses: [
        {
          httpStatus: 403,
          responsePagePath: "/index.html",
          responseHttpStatus: 200,
          ttl: CDK.Duration.minutes(0),
        },
        {
          httpStatus: 404,
          responsePagePath: "/index.html",
          responseHttpStatus: 200,
          ttl: CDK.Duration.minutes(0),
        },
      ],
      priceClass: CloudFront.PriceClass.PRICE_CLASS_ALL,
      enabled: true,
      certificate: certificateManagerCertificate,
      minimumProtocolVersion: CloudFront.SecurityPolicyProtocol.TLS_V1_2_2021,
      httpVersion: CloudFront.HttpVersion.HTTP2,
      defaultRootObject: "index.html",
      enableIpv6: true,
    });
  }
}



Context

I have a react web application which I'm able to deploy to AWS with CodePipeline. My codepipeline is hooked with my React GitHub repository so that whenever I push a change to the GitHub, my codepipeline will re-build the artifact and deploy it to S3 bucket.

Problem

Now I created different .env files to store environment variables. What I did is quite similar to these:

Thus yarn build:prod will build the website artifact with .env.production file.

As we should not add .env files to GitHub for security reasons. How should I setup environment variables so that in the build stage of my pipeline it can get the .env files from somewhere?
enter image description here

My CDK Code

import * as CDK from "aws-cdk-lib";
import * as YAML from "yaml";
import * as FS from "fs";
import * as CodeBuild from "aws-cdk-lib/aws-codebuild";
import * as S3 from "aws-cdk-lib/aws-s3";
import * as CloudFront from "aws-cdk-lib/aws-cloudfront";
import * as ACM from "aws-cdk-lib/aws-certificatemanager";
import * as Route53 from "aws-cdk-lib/aws-route53";
import * as Route53Targets from "aws-cdk-lib/aws-route53-targets";
import * as CloudFrontOrigins from "aws-cdk-lib/aws-cloudfront-origins";
import * as IAM from "aws-cdk-lib/aws-iam";
import * as codepipeline from "aws-cdk-lib/aws-codepipeline";
import * as codepipeline_actions from "aws-cdk-lib/aws-codepipeline-actions";

export interface CodePipelineStackProps extends CDK.StackProps {
  // Built in Stack props
  readonly env: CDK.Environment;
  readonly description: string;
  readonly websiteDomain: string;
}

export class CodePipelineStack extends CDK.Stack {
  constructor(scope: CDK.App, id: string, props: CodePipelineStackProps) {
    super(scope, id, props);

    // AWS CodeBuild artifacts
    const outputSources = new codepipeline.Artifact();
    const outputWebsite = new codepipeline.Artifact();

    // AWS CodePipeline pipeline
    const pipeline = new codepipeline.Pipeline(this, "Pipeline", {
      pipelineName: "PandaWebsite",
      restartExecutionOnUpdate: true,
    });

    this.addSourceStage(pipeline, outputSources);
    this.addBuildStage(pipeline, outputSources, outputWebsite);

    // Amazon S3 bucket to host the store  website artifact
    const websiteBucket = new S3.Bucket(this, "PandaWebsite", {
      bucketName: `${props.websiteDomain}-${props.env.account}-${props.env.region}`,
      websiteIndexDocument: "index.html",
      websiteErrorDocument: "error.html",
      removalPolicy: CDK.RemovalPolicy.DESTROY,
      autoDeleteObjects: true,
      accessControl: S3.BucketAccessControl.PRIVATE,
      encryption: S3.BucketEncryption.S3_MANAGED,
      publicReadAccess: false,
      blockPublicAccess: S3.BlockPublicAccess.BLOCK_ALL,
    });

    const hostedZone: Route53.IHostedZone = Route53.HostedZone.fromLookup(
      this,
      "HostedZoneId",
      {
        domainName: props.websiteDomain,
      }
    );

    const cloudFrontDistribution: CloudFront.Distribution =
      this.createCloudFrontDistribution(
        props.websiteDomain,
        websiteBucket,
        hostedZone
      );

    new Route53.ARecord(this, "Route53RecordSet", {
      recordName: props.websiteDomain,
      zone: hostedZone,
      target: Route53.RecordTarget.fromAlias(
        new Route53Targets.CloudFrontTarget(cloudFrontDistribution)
      ),
    });

    // AWS CodePipeline stage to deployt website
    pipeline.addStage({
      stageName: "Deploy",
      actions: [
        // AWS CodePipeline action to deploy website to S3 bucket
        new codepipeline_actions.S3DeployAction({
          actionName: "PandaWebsite",
          input: outputWebsite,
          bucket: websiteBucket,
        }),
      ],
    });

    new CDK.CfnOutput(this, "DeployURL", {
      value: `https://${props.websiteDomain}`,
      description: "Website URL",
    });
  }

  private addSourceStage(
    pipeline: codepipeline.Pipeline,
    outputSources: codepipeline.Artifact
  ) {
    // AWS CodePipeline stage to clone sources from GitHub repository
    pipeline.addStage({
      stageName: "Source",
      actions: [
        new codepipeline_actions.GitHubSourceAction({
          actionName: "Checkout",
          owner: "yangliu",
          repo: "PandaWebsite",
          branch: "main",
          oauthToken: CDK.SecretValue.secretsManager(
            "PandaWebsite-GitHubToken"
          ),
          output: outputSources,
          trigger: codepipeline_actions.GitHubTrigger.WEBHOOK,
        }),
      ],
    });
  }

  private addBuildStage(
    pipeline: codepipeline.Pipeline,
    outputSources: codepipeline.Artifact,
    outputWebsite: codepipeline.Artifact
  ) {
    const buildspecFile = FS.readFileSync("./config/buildspec.yml", "utf-8");
    const buildspecFileYaml = YAML.parse(buildspecFile, {
      prettyErrors: true,
    });
    pipeline.addStage({
      stageName: "Build",
      actions: [
        new codepipeline_actions.CodeBuildAction({
          actionName: "BuildeWebsite",
          project: new CodeBuild.PipelineProject(this, "BuildWebsite", {
            projectName: "BuildeWebsite",
            environment: {
              buildImage: CodeBuild.LinuxBuildImage.STANDARD_5_0,
            },
            buildSpec: CodeBuild.BuildSpec.fromObjectToYaml(buildspecFileYaml),
          }),
          input: outputSources,
          outputs: [outputWebsite],
        }),
      ],
    });
  }

  private createCloudFrontDistribution(
    websiteDomain: string,
    websiteBucket: S3.Bucket,
    hostedZone: Route53.IHostedZone
  ) {
    const certificateManagerCertificate = new ACM.Certificate(
      this,
      "CertificateManagerCertificate",
      {
        domainName: websiteDomain,
        validation: ACM.CertificateValidation.fromDns(hostedZone),
      }
    );
    // Create a special CloudFront user called an origin access identity (OAI)
    // and associate it with the CloudFront distribution.
    const cloudFrontOAI = new CloudFront.OriginAccessIdentity(
      this,
      "PandaWebsiteOriginAccessIdentityID",
      {
        comment: "OriginAccessIdentityID for PandaWebsite"
      }
    );
    const cloudfrontUserAccessPolicy = new IAM.PolicyStatement();
    cloudfrontUserAccessPolicy.addActions("s3:GetObject");
    cloudfrontUserAccessPolicy.addPrincipals(cloudFrontOAI.grantPrincipal);
    cloudfrontUserAccessPolicy.addResources(websiteBucket.arnForObjects("*"));
    websiteBucket.addToResourcePolicy(cloudfrontUserAccessPolicy);
    return new CloudFront.Distribution(this, "CloudFrontDistribution", {
      domainNames: [websiteDomain],
      defaultBehavior: {
        origin: new CloudFrontOrigins.S3Origin(websiteBucket, {
          // CloudFront can use the OAI to access the files in the S3 bucket
          // and serve them to users. Users can’t use a direct URL to the
          // S3 bucket to access a file there.
          originAccessIdentity: cloudFrontOAI,
        }),
        compress: true,
        allowedMethods: CloudFront.AllowedMethods.ALLOW_GET_HEAD,
        cachedMethods: CloudFront.CachedMethods.CACHE_GET_HEAD,
        viewerProtocolPolicy: CloudFront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
        cachePolicy: CloudFront.CachePolicy.CACHING_OPTIMIZED,
      },
      errorResponses: [
        {
          httpStatus: 403,
          responsePagePath: "/index.html",
          responseHttpStatus: 200,
          ttl: CDK.Duration.minutes(0),
        },
        {
          httpStatus: 404,
          responsePagePath: "/index.html",
          responseHttpStatus: 200,
          ttl: CDK.Duration.minutes(0),
        },
      ],
      priceClass: CloudFront.PriceClass.PRICE_CLASS_ALL,
      enabled: true,
      certificate: certificateManagerCertificate,
      minimumProtocolVersion: CloudFront.SecurityPolicyProtocol.TLS_V1_2_2021,
      httpVersion: CloudFront.HttpVersion.HTTP2,
      defaultRootObject: "index.html",
      enableIpv6: true,
    });
  }
}



如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

你怎么这么可爱啊 2025-02-13 03:25:03

遵循SándorBakos的评论,我可以解决这个问题。

我更新我的buildspec.yml:

version: 0.2
env:
  variables:
    REACT_APP_DOMAIN: https://<DomainName>
    REACT_APP_BACKEND_SERVICE_API: https://<DomainName>/api
  secrets-manager:
    REACT_APP_GOOGLE_MAP_API_KEY: "REACT_APP_GOOGLE_MAP_API_KEY"
phases:
  install:
    runtime-versions:
      nodejs: 14
    commands:
      - echo Performing yarn install
      - yarn install
  build:
    commands:
      - yarn build

artifacts:
  base-directory: ./build
  files:
    - "**/*"

cache:
  paths:
    - "./node_modules/**/*"

for Pipeline的CDK代码:

import * as CDK from "aws-cdk-lib";
import * as YAML from "yaml";
import * as FS from "fs";
import * as CodeBuild from "aws-cdk-lib/aws-codebuild";
import * as S3 from "aws-cdk-lib/aws-s3";
import * as CloudFront from "aws-cdk-lib/aws-cloudfront";
import * as ACM from "aws-cdk-lib/aws-certificatemanager";
import * as Route53 from "aws-cdk-lib/aws-route53";
import * as Route53Targets from "aws-cdk-lib/aws-route53-targets";
import * as CloudFrontOrigins from "aws-cdk-lib/aws-cloudfront-origins";
import * as IAM from "aws-cdk-lib/aws-iam";
import * as codepipeline from "aws-cdk-lib/aws-codepipeline";
import * as codepipeline_actions from "aws-cdk-lib/aws-codepipeline-actions";
import * as SecretsManager from "aws-cdk-lib/aws-secretsmanager";


export interface CodePipelineStackProps extends CDK.StackProps {
  // Built in Stack props
  readonly env: CDK.Environment;
  readonly description: string;
  readonly websiteDomain: string;
}

export class CodePipelineStack extends CDK.Stack {
  constructor(scope: CDK.App, id: string, props: CodePipelineStackProps) {
    super(scope, id, props);

    // AWS CodeBuild artifacts
    const outputSources = new codepipeline.Artifact();
    const outputWebsite = new codepipeline.Artifact();

    // AWS CodePipeline pipeline
    const pipeline = new codepipeline.Pipeline(this, "Pipeline", {
      pipelineName: "pandaWebsite",
      restartExecutionOnUpdate: true,
    });

    this.addSourceStage(pipeline, outputSources);
    this.addBuildStage(pipeline, outputSources, outputWebsite);

    // Amazon S3 bucket to host the store  website artifact
    const websiteBucket = new S3.Bucket(this, "pandaWebsite", {
      bucketName: `${props.websiteDomain}-${props.env.account}-${props.env.region}`,
      websiteIndexDocument: "index.html",
      websiteErrorDocument: "error.html",
      removalPolicy: CDK.RemovalPolicy.DESTROY,
      autoDeleteObjects: true,
      accessControl: S3.BucketAccessControl.PRIVATE,
      encryption: S3.BucketEncryption.S3_MANAGED,
      publicReadAccess: false,
      blockPublicAccess: S3.BlockPublicAccess.BLOCK_ALL,
    });

    const hostedZone: Route53.IHostedZone = Route53.HostedZone.fromLookup(
      this,
      "HostedZoneId",
      {
        domainName: props.websiteDomain,
      }
    );

    const cloudFrontDistribution: CloudFront.Distribution =
      this.createCloudFrontDistribution(
        props.websiteDomain,
        websiteBucket,
        hostedZone
      );

    new Route53.ARecord(this, "Route53RecordSet", {
      recordName: props.websiteDomain,
      zone: hostedZone,
      target: Route53.RecordTarget.fromAlias(
        new Route53Targets.CloudFrontTarget(cloudFrontDistribution)
      ),
    });

    // AWS CodePipeline stage to deploy website
    pipeline.addStage({
      stageName: "Deploy",
      actions: [
        // AWS CodePipeline action to deploy website to S3 bucket
        new codepipeline_actions.S3DeployAction({
          actionName: "pandaWebsite",
          input: outputWebsite,
          bucket: websiteBucket,
        }),
      ],
    });

    new CDK.CfnOutput(this, "DeployURL", {
      value: `https://${props.websiteDomain}`,
      description: "Website URL",
    });
  }

  private addSourceStage(
    pipeline: codepipeline.Pipeline,
    outputSources: codepipeline.Artifact
  ) {
    // AWS CodePipeline stage to clone sources from GitHub repository
    pipeline.addStage({
      stageName: "Source",
      actions: [
        new codepipeline_actions.GitHubSourceAction({
          actionName: "Checkout",
          owner: "yangliunewyork",
          repo: "pandaWebsite",
          branch: "main",
          oauthToken: CDK.SecretValue.secretsManager(
            "pandaWebsite-GitHubToken"
          ),
          output: outputSources,
          trigger: codepipeline_actions.GitHubTrigger.WEBHOOK,
        }),
      ],
    });
  }

  private addBuildStage(
    pipeline: codepipeline.Pipeline,
    outputSources: codepipeline.Artifact,
    outputWebsite: codepipeline.Artifact
  ) {
    const buildspecFile = FS.readFileSync("./config/buildspec.yml", "utf-8");
    const buildspecFileYaml = YAML.parse(buildspecFile, {
      prettyErrors: true,
    });
    const pipelineProject = new CodeBuild.PipelineProject(
      this,
      "BuildWebsite",
      {
        projectName: "BuildeWebsite",
        environment: {
          buildImage: CodeBuild.LinuxBuildImage.STANDARD_5_0,
        },
        buildSpec: CodeBuild.BuildSpec.fromObjectToYaml(buildspecFileYaml),
      }
    );

    // Below doesn't work yet https://github.com/aws/aws-cdk/issues/18555
    const googleMapApiKey = SecretsManager.Secret.fromSecretNameV2(this, "GoogleMapApiKey", "REACT_APP_GOOGLE_MAP_API_KEY");
    // add policy to allow fetching from secrets manager
    pipelineProject.addToRolePolicy(
      new IAM.PolicyStatement({
        effect: IAM.Effect.ALLOW,
        actions: [
          "secretsmanager:GetRandomPassword",
          "secretsmanager:GetResourcePolicy",
          "secretsmanager:GetSecretValue",
          "secretsmanager:DescribeSecret",
          "secretsmanager:ListSecretVersionIds",
        ],
        //resources: [googleMapApiKey.secretArn],
        resources: ["arn:aws:secretsmanager:us-east-1:587395118549:secret:REACT_APP_GOOGLE_MAP_API_KEY-arSAPR"],
      })
    );
    pipeline.addStage({
      stageName: "Build",
      actions: [
        new codepipeline_actions.CodeBuildAction({
          actionName: "BuildeWebsite",
          project: pipelineProject,
          input: outputSources,
          outputs: [outputWebsite],
        }),
      ],
    });
  }

  private createCloudFrontDistribution(
    websiteDomain: string,
    websiteBucket: S3.Bucket,
    hostedZone: Route53.IHostedZone
  ) {
    const certificateManagerCertificate = new ACM.Certificate(
      this,
      "CertificateManagerCertificate",
      {
        domainName: websiteDomain,
        validation: ACM.CertificateValidation.fromDns(hostedZone),
      }
    );
    // Create a special CloudFront user called an origin access identity (OAI)
    // and associate it with the CloudFront distribution.
    const cloudFrontOAI = CloudFront.OriginAccessIdentity.fromOriginAccessIdentityName(
      this,
      "websiteOriginAccessIdentityID",
      "ABABX0123X0"
    );
    const cloudfrontUserAccessPolicy = new IAM.PolicyStatement();
    cloudfrontUserAccessPolicy.addActions("s3:GetObject");
    cloudfrontUserAccessPolicy.addPrincipals(cloudFrontOAI.grantPrincipal);
    cloudfrontUserAccessPolicy.addResources(websiteBucket.arnForObjects("*"));
    websiteBucket.addToResourcePolicy(cloudfrontUserAccessPolicy);
    return new CloudFront.Distribution(this, "CloudFrontDistribution", {
      domainNames: [websiteDomain],
      defaultBehavior: {
        origin: new CloudFrontOrigins.S3Origin(websiteBucket, {
          // CloudFront can use the OAI to access the files in the S3 bucket
          // and serve them to users. Users can’t use a direct URL to the
          // S3 bucket to access a file there.
          originAccessIdentity: cloudFrontOAI,
        }),
        compress: true,
        allowedMethods: CloudFront.AllowedMethods.ALLOW_GET_HEAD,
        cachedMethods: CloudFront.CachedMethods.CACHE_GET_HEAD,
        viewerProtocolPolicy: CloudFront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
        cachePolicy: CloudFront.CachePolicy.CACHING_OPTIMIZED,
      },
      errorResponses: [
        {
          httpStatus: 403,
          responsePagePath: "/index.html",
          responseHttpStatus: 200,
          ttl: CDK.Duration.minutes(0),
        },
        {
          httpStatus: 404,
          responsePagePath: "/index.html",
          responseHttpStatus: 200,
          ttl: CDK.Duration.minutes(0),
        },
      ],
      priceClass: CloudFront.PriceClass.PRICE_CLASS_ALL,
      enabled: true,
      certificate: certificateManagerCertificate,
      minimumProtocolVersion: CloudFront.SecurityPolicyProtocol.TLS_V1_2_2021,
      httpVersion: CloudFront.HttpVersion.HTTP2,
      defaultRootObject: "index.html",
      enableIpv6: true,
    });
  }

}

Follow Sándor Bakos's comment, I'm able to address the issue.

I update my buildspec.yml with:

version: 0.2
env:
  variables:
    REACT_APP_DOMAIN: https://<DomainName>
    REACT_APP_BACKEND_SERVICE_API: https://<DomainName>/api
  secrets-manager:
    REACT_APP_GOOGLE_MAP_API_KEY: "REACT_APP_GOOGLE_MAP_API_KEY"
phases:
  install:
    runtime-versions:
      nodejs: 14
    commands:
      - echo Performing yarn install
      - yarn install
  build:
    commands:
      - yarn build

artifacts:
  base-directory: ./build
  files:
    - "**/*"

cache:
  paths:
    - "./node_modules/**/*"

CDK Code for pipeline:

import * as CDK from "aws-cdk-lib";
import * as YAML from "yaml";
import * as FS from "fs";
import * as CodeBuild from "aws-cdk-lib/aws-codebuild";
import * as S3 from "aws-cdk-lib/aws-s3";
import * as CloudFront from "aws-cdk-lib/aws-cloudfront";
import * as ACM from "aws-cdk-lib/aws-certificatemanager";
import * as Route53 from "aws-cdk-lib/aws-route53";
import * as Route53Targets from "aws-cdk-lib/aws-route53-targets";
import * as CloudFrontOrigins from "aws-cdk-lib/aws-cloudfront-origins";
import * as IAM from "aws-cdk-lib/aws-iam";
import * as codepipeline from "aws-cdk-lib/aws-codepipeline";
import * as codepipeline_actions from "aws-cdk-lib/aws-codepipeline-actions";
import * as SecretsManager from "aws-cdk-lib/aws-secretsmanager";


export interface CodePipelineStackProps extends CDK.StackProps {
  // Built in Stack props
  readonly env: CDK.Environment;
  readonly description: string;
  readonly websiteDomain: string;
}

export class CodePipelineStack extends CDK.Stack {
  constructor(scope: CDK.App, id: string, props: CodePipelineStackProps) {
    super(scope, id, props);

    // AWS CodeBuild artifacts
    const outputSources = new codepipeline.Artifact();
    const outputWebsite = new codepipeline.Artifact();

    // AWS CodePipeline pipeline
    const pipeline = new codepipeline.Pipeline(this, "Pipeline", {
      pipelineName: "pandaWebsite",
      restartExecutionOnUpdate: true,
    });

    this.addSourceStage(pipeline, outputSources);
    this.addBuildStage(pipeline, outputSources, outputWebsite);

    // Amazon S3 bucket to host the store  website artifact
    const websiteBucket = new S3.Bucket(this, "pandaWebsite", {
      bucketName: `${props.websiteDomain}-${props.env.account}-${props.env.region}`,
      websiteIndexDocument: "index.html",
      websiteErrorDocument: "error.html",
      removalPolicy: CDK.RemovalPolicy.DESTROY,
      autoDeleteObjects: true,
      accessControl: S3.BucketAccessControl.PRIVATE,
      encryption: S3.BucketEncryption.S3_MANAGED,
      publicReadAccess: false,
      blockPublicAccess: S3.BlockPublicAccess.BLOCK_ALL,
    });

    const hostedZone: Route53.IHostedZone = Route53.HostedZone.fromLookup(
      this,
      "HostedZoneId",
      {
        domainName: props.websiteDomain,
      }
    );

    const cloudFrontDistribution: CloudFront.Distribution =
      this.createCloudFrontDistribution(
        props.websiteDomain,
        websiteBucket,
        hostedZone
      );

    new Route53.ARecord(this, "Route53RecordSet", {
      recordName: props.websiteDomain,
      zone: hostedZone,
      target: Route53.RecordTarget.fromAlias(
        new Route53Targets.CloudFrontTarget(cloudFrontDistribution)
      ),
    });

    // AWS CodePipeline stage to deploy website
    pipeline.addStage({
      stageName: "Deploy",
      actions: [
        // AWS CodePipeline action to deploy website to S3 bucket
        new codepipeline_actions.S3DeployAction({
          actionName: "pandaWebsite",
          input: outputWebsite,
          bucket: websiteBucket,
        }),
      ],
    });

    new CDK.CfnOutput(this, "DeployURL", {
      value: `https://${props.websiteDomain}`,
      description: "Website URL",
    });
  }

  private addSourceStage(
    pipeline: codepipeline.Pipeline,
    outputSources: codepipeline.Artifact
  ) {
    // AWS CodePipeline stage to clone sources from GitHub repository
    pipeline.addStage({
      stageName: "Source",
      actions: [
        new codepipeline_actions.GitHubSourceAction({
          actionName: "Checkout",
          owner: "yangliunewyork",
          repo: "pandaWebsite",
          branch: "main",
          oauthToken: CDK.SecretValue.secretsManager(
            "pandaWebsite-GitHubToken"
          ),
          output: outputSources,
          trigger: codepipeline_actions.GitHubTrigger.WEBHOOK,
        }),
      ],
    });
  }

  private addBuildStage(
    pipeline: codepipeline.Pipeline,
    outputSources: codepipeline.Artifact,
    outputWebsite: codepipeline.Artifact
  ) {
    const buildspecFile = FS.readFileSync("./config/buildspec.yml", "utf-8");
    const buildspecFileYaml = YAML.parse(buildspecFile, {
      prettyErrors: true,
    });
    const pipelineProject = new CodeBuild.PipelineProject(
      this,
      "BuildWebsite",
      {
        projectName: "BuildeWebsite",
        environment: {
          buildImage: CodeBuild.LinuxBuildImage.STANDARD_5_0,
        },
        buildSpec: CodeBuild.BuildSpec.fromObjectToYaml(buildspecFileYaml),
      }
    );

    // Below doesn't work yet https://github.com/aws/aws-cdk/issues/18555
    const googleMapApiKey = SecretsManager.Secret.fromSecretNameV2(this, "GoogleMapApiKey", "REACT_APP_GOOGLE_MAP_API_KEY");
    // add policy to allow fetching from secrets manager
    pipelineProject.addToRolePolicy(
      new IAM.PolicyStatement({
        effect: IAM.Effect.ALLOW,
        actions: [
          "secretsmanager:GetRandomPassword",
          "secretsmanager:GetResourcePolicy",
          "secretsmanager:GetSecretValue",
          "secretsmanager:DescribeSecret",
          "secretsmanager:ListSecretVersionIds",
        ],
        //resources: [googleMapApiKey.secretArn],
        resources: ["arn:aws:secretsmanager:us-east-1:587395118549:secret:REACT_APP_GOOGLE_MAP_API_KEY-arSAPR"],
      })
    );
    pipeline.addStage({
      stageName: "Build",
      actions: [
        new codepipeline_actions.CodeBuildAction({
          actionName: "BuildeWebsite",
          project: pipelineProject,
          input: outputSources,
          outputs: [outputWebsite],
        }),
      ],
    });
  }

  private createCloudFrontDistribution(
    websiteDomain: string,
    websiteBucket: S3.Bucket,
    hostedZone: Route53.IHostedZone
  ) {
    const certificateManagerCertificate = new ACM.Certificate(
      this,
      "CertificateManagerCertificate",
      {
        domainName: websiteDomain,
        validation: ACM.CertificateValidation.fromDns(hostedZone),
      }
    );
    // Create a special CloudFront user called an origin access identity (OAI)
    // and associate it with the CloudFront distribution.
    const cloudFrontOAI = CloudFront.OriginAccessIdentity.fromOriginAccessIdentityName(
      this,
      "websiteOriginAccessIdentityID",
      "ABABX0123X0"
    );
    const cloudfrontUserAccessPolicy = new IAM.PolicyStatement();
    cloudfrontUserAccessPolicy.addActions("s3:GetObject");
    cloudfrontUserAccessPolicy.addPrincipals(cloudFrontOAI.grantPrincipal);
    cloudfrontUserAccessPolicy.addResources(websiteBucket.arnForObjects("*"));
    websiteBucket.addToResourcePolicy(cloudfrontUserAccessPolicy);
    return new CloudFront.Distribution(this, "CloudFrontDistribution", {
      domainNames: [websiteDomain],
      defaultBehavior: {
        origin: new CloudFrontOrigins.S3Origin(websiteBucket, {
          // CloudFront can use the OAI to access the files in the S3 bucket
          // and serve them to users. Users can’t use a direct URL to the
          // S3 bucket to access a file there.
          originAccessIdentity: cloudFrontOAI,
        }),
        compress: true,
        allowedMethods: CloudFront.AllowedMethods.ALLOW_GET_HEAD,
        cachedMethods: CloudFront.CachedMethods.CACHE_GET_HEAD,
        viewerProtocolPolicy: CloudFront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
        cachePolicy: CloudFront.CachePolicy.CACHING_OPTIMIZED,
      },
      errorResponses: [
        {
          httpStatus: 403,
          responsePagePath: "/index.html",
          responseHttpStatus: 200,
          ttl: CDK.Duration.minutes(0),
        },
        {
          httpStatus: 404,
          responsePagePath: "/index.html",
          responseHttpStatus: 200,
          ttl: CDK.Duration.minutes(0),
        },
      ],
      priceClass: CloudFront.PriceClass.PRICE_CLASS_ALL,
      enabled: true,
      certificate: certificateManagerCertificate,
      minimumProtocolVersion: CloudFront.SecurityPolicyProtocol.TLS_V1_2_2021,
      httpVersion: CloudFront.HttpVersion.HTTP2,
      defaultRootObject: "index.html",
      enableIpv6: true,
    });
  }

}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文