🧠

ChatGPTを使ってCDKでデプロイするwebアプリケーションを作ってみる

2023/06/09に公開

最近、AWS CDKを触る機会が増えて楽しいな〜と思う反面、なんで自分はこれできないんや...と思うことも多くなってきました。CDKのコードを書くためには、AWSのサービスの知識やプログラミングのスキルが必要であり、初心者にはハードルが高いと感じることもあります。

そこで、今回はOpenAIが開発した言語モデル「ChatGPT」を使って、CDKのコードを自動生成して本当にデプロイできるかを試してみました。ただ試すのはもうすでにいろんな方がやられていると思うので、DX向上するようにデプロイパイプラインとかも整備してもらいつつ、ちょっと複雑な要件にも対応できるのかな?というところを実験しています。

今回試した環境

  • Ubuntu 22.04.2 LTS (MacBook Air M1のLima 0.16.0上で動かしてます)
  • aws cdk: 2.82.0
  • ChatGPT: 3.5 (free版)

先に結論

chatgptオンリーでいきなりデプロイできるレベルのものを、というのは今回は難しそうでした。

今回作ってみたものは下記になります。

https://github.com/okojomoeko/cdk-webapp-chatgpt

cloudformationの可視化
デプロイしたcloudformationの可視化

細かく機能・非機能要件を設定してみましたが、過去の会話内容をなんとなく忘れてしまったりしていたので、自分がこれだ!と思うものを直接出してもらうのは難しそうでした。結局人間が介入して手直ししちゃったりしていますが、細かくstep by stepで活用すればいいものが出来上がりそうです。

やりたいこととか前提条件とか

タイトルのとおりですが、chat gptにwebアプリケーションを作ってもらいます。なんでこれを題材にしたかって言うと、なんとなくcdkで定義しておけば今後自分がwebアプリケーション作るときに参考になるかな〜って期待を少し込めたりしていました。

前提条件

  • 東京リージョンでやる
  • chat gptへ質問する人のレベル(自分)は下記の通り
    • AWS SAAレベルであればわかる
    • aws cdkなんとなく触っている
    • IT経験は普通?

以下ChatGPTとの戦いの記録

1st Round(初っ端の解答はどんな?)

多少のプロンプトエンジニアリングの知識でそれっぽくいい感じの解答が得られるように誘導します。

あなたはプロのクラウドエンジニア・ソフトウェアエンジニアです。
以下の機能・非機能要件を元に、typescriptとaws cdk 2.0を使ってwebアプリケーションをAWS上にデプロイするためのtypescript と aws cdkのStackコードを生成してください。コード量が最小限になるようにしてください。

機能要件
・s3に静的ホスティング機能を使ってwebアプリケーションをホストすること
・ログイン情報などはDynamoDBに保管すること
・ログイン情報などを登録するためのlambdaを用意すること
・ログイン情報などを登録するlambdaを動かすためのAPI Gatewayを用意すること
・webアプリケーションはログインして特定のページを表示し、特定のページからログアウトできること
・ログイン情報にはユーザ名とパスワードを含み、それらは暗号化される必要があること
・特定のIPからのリクエストが多く発生した場合にファイヤーウォールでブロックできること
・s3上にホスティングされているwebアプリケーションがDDoS攻撃などのセキュリティ脅威から守られる構成であること

開発のための非機能要件
・CI/CD実現のために、クラウドインフラストラクチャとwebアプリケーションに変更が発生した場合にデプロイできるデプロイパイプラインを構築すること
・環境変数でテスト用環境、本番環境を切り替えられること
・cdkから削除命令があってもs3のリソースが消えないようにすること
・デプロイパイプラインは例えばcodeシリーズなどのawsのサービスを利用すること
・テスト用環境は本番環境と異なり、すべてのリソースに`DEV_`という接頭語をつけること

下記回答です。code blockの部分が途中で途切れましたが、「続き」といって生成させたものを繋げただけの生回答です。

import * as cdk from 'aws-cdk-lib';
import * as s3 from 'aws-cdk-lib/aws-s3';
import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as apigateway from 'aws-cdk-lib/aws-apigateway';
import * as iam from 'aws-cdk-lib/aws-iam';
import * as wafv2 from 'aws-cdk-lib/aws-wafv2';
import * as cloudfront from 'aws-cdk-lib/aws-cloudfront';

export class WebApplicationStack extends cdk.Stack {
  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // S3 Bucket for static hosting
    const webBucket = new s3.Bucket(this, 'WebBucket', {
      websiteIndexDocument: 'index.html',
      publicReadAccess: true,
      removalPolicy: cdk.RemovalPolicy.RETAIN,
    });

    // DynamoDB table for storing login information
    const loginTable = new dynamodb.Table(this, 'LoginTable', {
      partitionKey: { name: 'username', type: dynamodb.AttributeType.STRING },
      encryption: dynamodb.TableEncryption.AWS_MANAGED,
      removalPolicy: cdk.RemovalPolicy.RETAIN,
    });

    // Lambda function for registering login information
    const registerFunction = new lambda.Function(this, 'RegisterFunction', {
      runtime: lambda.Runtime.NODEJS_14_X,
      code: lambda.Code.fromAsset('lambda/register'),
      handler: 'index.handler',
      environment: {
        TABLE_NAME: loginTable.tableName,
      },
    });

    // API Gateway for the register function
    const api = new apigateway.RestApi(this, 'ApiGateway', {
      deployOptions: {
        stageName: 'prod',
      },
    });
    const registerIntegration = new apigateway.LambdaIntegration(
      registerFunction
    );
    api.root.addMethod('POST', registerIntegration);

    // IAM policy for accessing the DynamoDB table
    const dynamoDBPolicy = new iam.PolicyStatement({
      effect: iam.Effect.ALLOW,
      actions: ['dynamodb:PutItem'],
      resources: [loginTable.tableArn],
    });
    registerFunction.addToRolePolicy(dynamoDBPolicy);

    // Web application CloudFront distribution
    const cloudFrontOAI = new cloudfront.OriginAccessIdentity(
      this,
      'CloudFrontOAI'
    );
    webBucket.grantRead(cloudFrontOAI);

    const distribution = new cloudfront.CloudFrontWebDistribution(
      this,
      'CloudFrontDistribution',
      {
        originConfigs: [
          {
            s3OriginSource: {
              s3BucketSource: webBucket,
              originAccessIdentity: cloudFrontOAI,
            },
            behaviors: [{ isDefaultBehavior: true }],
          },
        ],
        viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
        errorConfigurations: [
          {
            errorCode: 403,
            responseCode: 200,
            responsePagePath: '/index.html',
          },
          {
            errorCode: 404,
            responseCode: 200,
            responsePagePath: '/index.html',
          },
        ],
      }
    );
    // WAFv2 WebACL for DDoS protection and IP blocking
    const webACL = new wafv2.CfnWebACL(this, 'WebACL', {
      defaultAction: { allow: {} },
      scope: 'CLOUDFRONT',
      visibilityConfig: {
        cloudWatchMetricsEnabled: true,
        metricName: 'WebACL',
        sampledRequestsEnabled: true,
      },
      rules: [
        {
          name: 'BlockIPRule',
          priority: 0,
          action: { block: {} },
          statement: {
            ipSetReferenceStatement: {
              arn: '<ARN_OF_IP_SET>', // Replace with the ARN of the IP set to block
            },
          },
          visibilityConfig: {
            cloudWatchMetricsEnabled: true,
            metricName: 'BlockIPRule',
            sampledRequestsEnabled: true,
          },
        },
      ],
    });

    // Deployment pipeline using AWS CodeSeries
    const pipeline = new codepipeline.Pipeline(this, 'Pipeline', {
      pipelineName: 'WebAppPipeline',
    });

    // Environment variables for different environments
    const envVariables = {
      DEV: {
        TABLE_NAME: `DEV_${loginTable.tableName}`,
      },
      PROD: {
        TABLE_NAME: loginTable.tableName,
      },
    };

    // CDK deployment stage for dev environment
    const devStage = pipeline.addStage({ stageName: 'Dev' });
    const devStack = new WebApplicationStack(this, 'DevStack', {
      env: { account: 'DEV_ACCOUNT_ID', region: 'DEV_REGION' },
    });
    devStack.addEnvironmentVariables(envVariables.DEV);
    devStage.addAction(
      new codepipelineActions.CloudFormationCreateUpdateStackAction({
        actionName: 'DevStack',
        stack: devStack,
        adminPermissions: true,
      })
    );

    // CDK deployment stage for prod environment
    const prodStage = pipeline.addStage({ stageName: 'Prod' });
    const prodStack = new WebApplicationStack(this, 'ProdStack', {
      env: { account: 'PROD_ACCOUNT_ID', region: 'PROD_REGION' },
    });
    prodStack.addEnvironmentVariables(envVariables.PROD);
    prodStage.addAction(
      new codepipelineActions.CloudFormationCreateUpdateStackAction({
        actionName: 'ProdStack',
        stack: prodStack,
        adminPermissions: true,
      })
    );
  }
}

2nd Round(なんか動かなさそうなので修正していく)

ぱっと見は良さそうな感じがしてはぇ〜と思いますが、ところどころおかしいのでそこ、わかりやすい部分(静的解析時になんかおかしくないみたいなところとか、そもそもルールとしてだめだよねみたいなところ)を指摘していこうかなと思います。

生成されたコードの全体について、下記の問題点が見つかったので、修正コードを生成してください。
ただし、修正版コードの全体像ではなく、正しく修正した箇所を出力してくれればいいです。


問題点
- import関係
    - `cdk.Construct`から`Construct`のimport
    - `codepipeline`, `codepipelineActions`のimportがない
- WAFv2をCloudFrontで使うときは`webACL`の作成は`us-east-1`regionで行われる必要がある
- `devStack.addEnvironmentVariables`という謎の処理が追加されている
- `webBucket.grantRead(cloudFrontOAI);`ではなくて、リソースベースポリシーでcloudfrontから読込みしないとだめ

ChatGPTたん

import { Construct, RemovalPolicy } from 'aws-cdk-lib';  // import修正

import * as codepipeline from 'aws-cdk-lib/aws-codepipeline';  // import追加
import * as codepipelineActions from 'aws-cdk-lib/aws-codepipeline-actions';  // import追加

// ...

export class WebApplicationStack extends Construct {  // import修正

  // ...

  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    // ...
  }

  // ...
  // Grant read access to CloudFront
  const bucketPolicy = new iam.PolicyStatement({
    effect: iam.Effect.ALLOW,
    actions: ['s3:GetObject'],
    resources: [webBucket.bucketArn + '/*'],
    principals: [new iam.CanonicalUserPrincipal(cloudFrontOAI.cloudFrontOriginAccessIdentityS3CanonicalUserId)],
  });
  webBucket.addToResourcePolicy(bucketPolicy);

  // WAFv2 WebACL for DDoS protection and IP blocking
  const webACL = new wafv2.CfnWebACL(this, 'WebACL', {
    defaultAction: { allow: {} },
    scope: 'CLOUDFRONT',
    visibilityConfig: {
      cloudWatchMetricsEnabled: true,
      metricName: 'WebACL',
      sampledRequestsEnabled: true,
    },
    rules: [
      // ...
    ],
    region: 'us-east-1',  // 追加: regionをus-east-1に指定
  });

  // ...

  // CDK deployment stage for dev environment
  const devStage = pipeline.addStage({ stageName: 'Dev' });
  const devStack = new WebApplicationStack(this, 'DevStack', {
    env: { account: 'DEV_ACCOUNT_ID', region: 'DEV_REGION' },
  });

  // devStack.addEnvironmentVariables(envVariables.DEV);  // 削除: 不要な処理
  devStage.addAction(
    new codepipelineActions.CloudFormationCreateUpdateStackAction({
      actionName: 'DevStack',
      stack: devStack,
      adminPermissions: true,
    })
  );

  // ...
}

前提としてある程度AWSの知識とか必要というのはあるけども、問題点を指摘すればいい感じに直してくれそうです。ただ、まだそれぞれのパイプラインごとに環境変数を変更するというのが正しく修正されていなかったり、WAFの設定が残ってしまっていたりしています。
一旦ここまでのコードでfixとして、ごちゃごちゃにならないようにWebApplicationStackとPipelineStackを分割していきます。

もちろんChatGPTに担当してもらいます。

3rd Round(論理的にStackを分けてくれるかな?)

提案いただきありがとうございます。
今までの修正点で一旦完成版としたいので、pipelineに関わるStackについてファイル分割してください。その際に必要なプロパティについては適切にアクセス制限を加えてください。まずはpipelinestackを出力してください。

ChatGPTたん

import { Construct } from 'aws-cdk-lib/core';
import { Stack, StackProps } from 'aws-cdk-lib/aws-cloudformation';
import { CodePipeline, CodePipelineSource, ShellStep } from 'aws-cdk-lib/aws-codepipeline';
import { GitHubSourceAction, CodeBuildAction } from 'aws-cdk-lib/aws-codepipeline-actions';
import { WebApplicationStack } from './WebApplicationStack';

export class PipelineStack extends Stack {
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);

    // Source stage
    const sourceOutput = new Artifact();
    const sourceAction = new GitHubSourceAction({
      actionName: 'GitHub_Source',
      owner: 'OWNER',
      repo: 'REPO',
      oauthToken: SecretValue.secretsManager('GITHUB_TOKEN'),
      output: sourceOutput,
      branch: 'main',
    });

    // Build stage
    const buildOutput = new Artifact();
    const buildAction = new CodeBuildAction({
      actionName: 'Build',
      project: new PipelineProject(this, 'BuildProject'),
      input: sourceOutput,
      outputs: [buildOutput],
    });

    // Deployment stages
    const pipeline = new CodePipeline(this, 'Pipeline', {
      pipelineName: 'WebApplicationPipeline',
      crossAccountKeys: false,
      restartExecutionOnUpdate: true,
      synth: new ShellStep('Synth', {
        input: buildOutput,
        commands: ['npm ci', 'npm run build', 'npx cdk synth'],
      }),
    });

    const devStage = pipeline.addStage({ stageName: 'Dev' });
    const prodStage = pipeline.addStage({ stageName: 'Prod' });

    const devStack = new WebApplicationStack(this, 'DevStack', {
      env: { account: 'DEV_ACCOUNT_ID', region: 'DEV_REGION' },
    });
    devStage.addAction(
      new CloudFormationCreateUpdateStackAction({
        actionName: 'DevStack',
        stack: devStack,
        adminPermissions: true,
        parameterOverrides: {
          ...envVariables.DEV,
        },
      })
    );

    const prodStack = new WebApplicationStack(this, 'ProdStack', {
      env: { account: 'PROD_ACCOUNT_ID', region: 'PROD_REGION' },
    });
    prodStage.addAction(
      new CloudFormationCreateUpdateStackAction({
        actionName: 'ProdStack',
        stack: prodStack,
        adminPermissions: true,
        parameterOverrides: {
          ...envVariables.PROD,
        },
      })
    );
  }
}

ちょくちょく怪しい部分がありますね。。aws-cdk-lib/pipelinesaws-cdk-lib/aws-codepipelineを同時に利用しようとしています。いったん捨て置き、続いてWebApplicationStackがどのようになるのか。。。

import { Construct } from 'aws-cdk-lib/core';
import { Stack, StackProps } from 'aws-cdk-lib/aws-cloudformation';
import { CfnOutput } from 'aws-cdk-lib/aws-exports';
import { Bucket } from 'aws-cdk-lib/aws-s3';
import { CloudFrontWebDistribution } from 'aws-cdk-lib/aws-cloudfront';
import { Table, AttributeType } from 'aws-cdk-lib/aws-dynamodb';
import { Function, Runtime, Code } from 'aws-cdk-lib/aws-lambda';
import { LambdaRestApi } from 'aws-cdk-lib/aws-apigateway';
import { Duration } from 'aws-cdk-lib/core';

export class WebApplicationStack extends Stack {
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);

    // S3 Bucket for static hosting
    const bucket = new Bucket(this, 'WebAppBucket', {
      websiteIndexDocument: 'index.html',
      publicReadAccess: true,
      removalPolicy: RemovalPolicy.DESTROY,
    });

    // DynamoDB Table for login information
    const table = new Table(this, 'LoginTable', {
      partitionKey: { name: 'username', type: AttributeType.STRING },
      removalPolicy: RemovalPolicy.DESTROY,
    });

    // Lambda function for login registration
    const registerFunction = new Function(this, 'RegisterFunction', {
      runtime: Runtime.NODEJS_14_X,
      handler: 'index.handler',
      code: Code.fromAsset('lambda'),
      environment: {
        TABLE_NAME: table.tableName,
      },
    });
    table.grantWriteData(registerFunction);

    // API Gateway for lambda integration
    const api = new LambdaRestApi(this, 'ApiGateway', {
      handler: registerFunction,
    });

    // CloudFront distribution for the website
    const distribution = new CloudFrontWebDistribution(this, 'WebDistribution', {
      originConfigs: [
        {
          s3OriginSource: {
            s3BucketSource: bucket,
          },
          behaviors: [{ isDefaultBehavior: true }],
        },
      ],
    });

    // Output the website URL
    new CfnOutput(this, 'WebsiteURL', {
      value: distribution.distributionDomainName,
    });
  }
}

なんか今までと違うものが生成された〜!!!!!!

RemovalPolicy: DESTROYになっているし、WAFも消えてしまっていますね。。。

どうやらchat gptちゃんの記憶力もここまでなんでしょうか。念のため「なんか元々出してくれたやつから大きく差分があるんだけど?」って聞いてみたら下記の返事を頂きました。

申し訳ありません、WAFv2に関するリソースが抜けていたことを見落としていました。WebApplicationStackが元のコードから差分が大きくなった理由は、私が前回の回答でWAFv2に関する要件を見落としていたためです。お詫び申し上げます。

WAFv2は、Webアプリケーションをセキュリティ脅威から保護するための重要な要素です。セキュリティ対策としてWAFv2を導入する必要がある場合、WebApplicationStackにWAFv2のリソースを追加する必要があります。

4th Round(そして手直しへ)

まぁほぼノーコードでここまでやってくれたので、後は自分で手直ししてデプロイできるか確認します。

とりあえず、wafはスタック分けたりしてarnをもってきたりと面倒なのでこの設定は省いて、ひとまずパイプラインをデプロイしてしまいます。

ということでpipelinestack.tsですが、ぶっちゃけサラッと確認したいだけなので、余計になんか色々頑張って作ってもらったものを手直ししていくのではなく、pipelinesを使いたくなってきました。

なのでchat gptたんにaws-cdk-lib/pipelinesを使ってpipelinestack.tsを実装してくださいと頼んだら下記の回答が得られました。

申し訳ありませんが、AWS CDK v2 の aws-cdk-lib パッケージには "pipelines" モジュールが存在しないため、aws-cdk-lib/pipelines のみを使用して完全に実装することはできません。現在の最新の AWS CDK バージョンでは、pipelines を構築するために AWS Construct Library for AWS CodePipeline を使用する必要があります。

とのことなので、最近のことはなかなか覚えてくれないのかな〜?仕方ないね〜?とママの気持ちになったので自分でささっと作りました。

awsコンソールから事前にGitHubとのconnectionを作って、GitHubからパイプラインを叩くようにしています。

import { Stack, StackProps } from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as pipelines from 'aws-cdk-lib/pipelines';
import { WebApplicationAppStage } from './web_application_appstage-stack';

export class PipelineStack extends Stack {
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);

    // Deployment stages
    const pipeline = new pipelines.CodePipeline(this, 'Pipeline', {
      pipelineName: 'WebApplicationPipeline',
      synth: new pipelines.ShellStep('Synth', {
        input: pipelines.CodePipelineSource.connection(
          'okojomoeko/cdk-webapp-chatgpt',
          'main',
          {
            connectionArn:
              'hogehogehogehogehogehogehogehogehogehogehogehoge',
          }
        ),
        commands: ['npm ci', 'npm run build', 'npx cdk synth'],
      }),
    });

    pipeline.addStage(new WebApplicationAppStage(this, 'DEV', {}));
    pipeline.addStage(new WebApplicationAppStage(this, 'PROD', {}));
  }
}

pipelinestack.tsはこれでまぁいいやとして、デプロイしたものと途中までChatGPTに書いてもらったコードの差分(手直し箇所)は下記になります。

WebApplicationStack.ts
import * as s3 from 'aws-cdk-lib/aws-s3';
import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as apigateway from 'aws-cdk-lib/aws-apigateway';
import * as iam from 'aws-cdk-lib/aws-iam';
import * as wafv2 from 'aws-cdk-lib/aws-wafv2';
import * as cloudfront from 'aws-cdk-lib/aws-cloudfront';
import { Construct } from 'constructs';
import { RemovalPolicy, Stack, StackProps } from 'aws-cdk-lib';

export class WebApplicationStack extends Stack {
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);

    // S3 Bucket for static hosting
    const webBucket = new s3.Bucket(this, 'WebBucket', {
      websiteIndexDocument: 'index.html',
      - publicReadAccess: true,
      + removalPolicy: RemovalPolicy.DESTROY,
      - removalPolicy: RemovalPolicy.RETAIN, // 元々の要件はRETAINであっているんだけどdestroyが面倒なので今回はなしで
    });

    // DynamoDB table for storing login information
    const loginTable = new dynamodb.Table(
      this,
      `${this.stackName}-LoginTable`,
      {
        partitionKey: { name: 'username', type: dynamodb.AttributeType.STRING },
        encryption: dynamodb.TableEncryption.AWS_MANAGED,
        removalPolicy: RemovalPolicy.DESTROY,
      }
    );

    // Lambda function for registering login information
    const registerFunction = new lambda.Function(this, 'RegisterFunction', {
      runtime: lambda.Runtime.NODEJS_14_X,
      code: lambda.Code.fromAsset('lambda/register'),
      handler: 'index.handler',
      environment: {
        TABLE_NAME: loginTable.tableName,
      },
    });

    // API Gateway for the register function
    const api = new apigateway.RestApi(this, 'ApiGateway', {
      deployOptions: {
        stageName: 'prod',
      },
    });
    const registerIntegration = new apigateway.LambdaIntegration(
      registerFunction
    );
    api.root.addMethod('POST', registerIntegration);

    // IAM policy for accessing the DynamoDB table
    const dynamoDBPolicy = new iam.PolicyStatement({
      effect: iam.Effect.ALLOW,
      actions: ['dynamodb:PutItem'],
      resources: [loginTable.tableArn],
    });
    registerFunction.addToRolePolicy(dynamoDBPolicy);

    // Web application CloudFront distribution
    const cloudFrontOAI = new cloudfront.OriginAccessIdentity(
      this,
      'CloudFrontOAI'
    );

    // Grant read access to CloudFront
    const bucketPolicy = new iam.PolicyStatement({
      effect: iam.Effect.ALLOW,
      actions: ['s3:GetObject'],
      resources: [webBucket.bucketArn + '/*'],
      principals: [
        new iam.CanonicalUserPrincipal(
          cloudFrontOAI.cloudFrontOriginAccessIdentityS3CanonicalUserId
        ),
      ],
    });
    webBucket.addToResourcePolicy(bucketPolicy);

    const distribution = new cloudfront.CloudFrontWebDistribution(
      this,
      'CloudFrontDistribution',
      {
        originConfigs: [
          {
            s3OriginSource: {
              s3BucketSource: webBucket,
              originAccessIdentity: cloudFrontOAI,
            },
            behaviors: [{ isDefaultBehavior: true }],
          },
        ],
        viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
        errorConfigurations: [
          {
            errorCode: 403,
            responseCode: 200,
            responsePagePath: '/index.html',
          },
          {
            errorCode: 404,
            responseCode: 200,
            responsePagePath: '/index.html',
          },
        ],
        // webACLId: // 今回はなし
      }
    );

    // 本当は別スタックでus-east-1にデプロイしたいけど面倒なので今回ははなし
    // // WAFv2 WebACL for DDoS protection and IP blocking
    // const webACL = new wafv2.CfnWebACL(this, 'WebACL', {
    //   defaultAction: { allow: {} },
    //   scope: 'CLOUDFRONT',
    //   visibilityConfig: {
    //     cloudWatchMetricsEnabled: true,
    //     metricName: 'WebACL',
    //     sampledRequestsEnabled: true,
    //   },
    //   rules: [
    //     {
    //       name: 'BlockIPRule',
    //       priority: 0,
    //       action: { block: {} },
    //       statement: {
    //         ipSetReferenceStatement: {
    //           arn: '<ARN_OF_IP_SET>', // Replace with the ARN of the IP set to block
    //         },
    //       },
    //       visibilityConfig: {
    //         cloudWatchMetricsEnabled: true,
    //         metricName: 'BlockIPRule',
    //         sampledRequestsEnabled: true,
    //       },
    //     },
    //   ],
    // });
  }
}

また、lambdaは適当なやつを作りました。

import { Context } from 'aws-lambda';
exports.handler = async function (event: string, context: Context) {
  console.log('EVENT: \n' + JSON.stringify(event, null, 2));
  return context.logStreamName;
};

これでなんとえいや〜デプロイできてしまいました。

  • cloudformation
    stack
    パイプラインによってdev, prodのstackがデプロイされている

  • cloudfront
    cloudfront
    dev, prodのdistributionが作成されてる

  • S3
    s3
    dev, prodのwebホスティング用S3バケットがある

  • lambda
    lambda
    dev,prodのlambdaがある

  • dynamodb
    dynamodb
    dev, prodのtableがある

  • api gateway
    apigateway
    dev, prodのapi gatewayがある

ということで、S3に適当なhtmlを上げてcloudfrontがいい感じに動いていることも確認します。

<html>
  <head>
  <meta charset="utf-8">
  <title>タイトル</title>
  </head>
  <body>
    本文
  </body>
</html>

awsコンソールからupload
awsコンソールからupload

デプロイしたサイト
cloudfrontでアクセスできた

いい感じにできたので、これで自分は満足です。PiplineStackとPipelineによってdeployされたPRODとかDEVのstackを削除して終了です(先にアップロードしたhtmlを削除してから)。

まとめ

細かいところは今後も直していく必要がありますが、なんとなく必要なリソースがいい感じに作られているので、本当にモノは使いようだなと思いました。

以下は感想ですが、chatgptでcdkを扱うに当たって気になるところは、やはりwebに流れている情報をツギハギしている感じがあるので、要件によってサービス同士が絡むような場合に整合取れた情報が生成されないのかな〜というころですかね?

たとえば今回だと、s3+lambda+apigateway+dynamodbの構成はわりかし情報がそこら中に転がっているからテンプレとしてすぐ出せるんだけど、そこにs3+cloudfront+wafv2みたいなテンプレ構成が入ってくると少し整合が合わなくなってくる?みたいな?

ある意味今回みたいな構成をフルスタックで出しているようなサイトが増えてたら、一発で目的達成のものが出るかもとか思ったりしました。

数時間くらいで完成できましたが、今度はChatGPT4.0たんに色々任せたらどうなるのかというのを試してみたいと思います。

もうちょっと時間かければ本当に本番で使えるようなものも出力してくれると期待して。。。

Discussion