Help us understand the problem. What is going on with this article?

AWS CDKを使ってCloudFrontとALBの環境構築をしてみた

はじめに

この記事はPlayground Advent Calender 2019の12/14の記事です。
初めまして。今年の春からPlaygroundでバックエンドをメインで勉強しているkikilsです。ここでは、ここ最近勉強しているAWS CDKというサービスについての知見を書いていきたいと思います。

行ったこと

CDKでユーザーがECSにアクセスするときにCloudFrontを経由する仕組みを作りました。
image.png

AWS CDK(Cloud Development Kit)とは

今年の7月にGAされたばかりのサービスで、AWSのインフラを TypeScript などの言語を使って定義します。プログラムはAppとStackとConstructという概念で構成します。CDKプログラムを実行することで CloudFormation テンプレートを生成してそのテンプレートを使ってデプロイします。

仕組み

CloudFrontを経由しないALBへのアクセスを拒否するには、ALBのリクエストルーティング機能を使います。ALBのlistenerのルールに「Cloud Frontからのアクセスであるかどうかの判定をしたらECSに転送する」というルールを追加したうえで「それ以外のアクセスは403を返す」というルールもデフォルトアクションにすることで実現します。CloudFrontからのアクセスかどうかの判定は、httpヘッダを読み取り、User-agentがCloudFrontのものであるかとCloudFront側でカスタムヘッダを追加してそれが一致するかどうかを判定します。

コードを書いてみる

まず、FargateとCloudFontを構築するStackを定義します。

const app = new cdk.App();

const fargateStack = new FargateStack(app, `fargate`);
new CloudFrontStack(app, `cloudfront`, {
    loadBalancer: fargateStack.loadBalancer,
});

次に、CloudFrontのStackを作成していきます。AWS CDKの公式APIレファレンスを参照するとCloudFrontWebDistributionクラスがあるのでこれを用います。カスタムオリジンにはfargateStackで渡してあげたALBのDNSを指定しています。また、originProtocolPolicyをhttpにしていますが本番環境ではhttpsに変更したほうがよいです。カスタムヘッダには、任意の文字列を設定します。

cloudFront-stack.ts
interface CloudFrontProps extends cdk.StackProps {
    loadBalancer: ApplicationLoadBalancer;
}

export class CloudFrontStack extends cdk.Stack {
    constructor(scope: cdk.Construct, id: string, props: CloudFrontProps) {
        super(scope, id, props);

     new CloudFrontWebDistribution(this, `distribution`, {
            defaultRootObject: "",
            originConfigs: [
                {
                    customOriginSource: {
                        domainName: props.loadBalancer.loadBalancerDnsName,
                        originProtocolPolicy: OriginProtocolPolicy.HTTP_ONLY
                    },
                    behaviors: [
                        {
                            isDefaultBehavior: true
                        }
                    ],
                    originHeaders: {
                        ["x-pre-shared-key"]: "PRESHAREDKEY"
                    }
                }
            ]
        });
    }
}

次に、fargateを構築します。clusterとtaskDefinitionは各自で当ててください。

fargate-stack.ts
export class FargateStack extends cdk.Stack {
    public readonly loadBalancer: ApplicationLoadBalancer;
    const fargate = new ApplicationLoadBalancedFargateService(
            this,
            "apps",
            {
                cluster: cluster,
                taskDefinition: taskDefinition
            }
        );

        const listener = fargate.listener.node.defaultChild as CfnListener;
        listener.defaultActions = [
            {
                type: "fixed-response",
                fixedResponseConfig: {
                    statusCode: "403",
                    contentType: "text/html",
                    messageBody: "<h1>403 Forbidden</h1>"
                }
            }
        ];

        new CfnListenerRule(this, `listener`, {
            actions: [
                {
                    type: `forward`,
                    targetGroupArn: fargate.targetGroup.targetGroupArn
                }
            ],
            conditions: [
                {
                    field: "http-header",
                    httpHeaderConfig: {
                        httpHeaderName: "User-agent",
                        values: ["Amazon CloudFront"]
                    }
                },
                {
                    field: "http-header",
                    httpHeaderConfig: {
                        httpHeaderName: "x-pre-shared-key",
                        values: ["PRESHAREDKEY"]
                    }
                }
            ],
            listenerArn: listener.ref,
            priority: 1
        });

説明

ApplicationLoadBalancedFargateServiceクラスはクラスターを作りALBとターゲットグループの接続をよしなにしてくれます。

  • listenerのデフォルトアクションを変更する

CDKにはlistenerのデフォルトアクションを設定するパラメータがまだないので、CloudFormationに直接上書きできるCfnListenerクラスを使います。実際に出力されるcloudformationのテンプレ―トは次のようになります。

~~~~~~~~~~
"appsLBPublicListenerEC0E93DB": {
      "Type": "AWS::ElasticLoadBalancingV2::Listener",
      "Properties": {
        "DefaultActions": [
          {
            "FixedResponseConfig": {
              "ContentType": "text/html",
              "MessageBody": "<h1>403 Forbidden</h1>",
              "StatusCode": "403"
            },
            "Type": "fixed-response"
          }
        ],
        "LoadBalancerArn": {
          "Ref": "appsLB2B3DA679"
        },
        "Port": 80,
        "Protocol": "HTTP"
      },
      "Metadata": {
        "aws:cdk:path": "fargate/apps/LB/PublicListener/Resource"
      }
    }
~~~~~~~~~~

このようにDefaultActionsが上書きされていることがわかります。

  • Cloud Frontからのアクセスであるかどうかの判定をしたらECSに転送する

listenerと同じくCDKにはApplicationListenerRuleというクラスが用意されていますがルールの条件にhttpヘッダがサポートされていないのでCloudformationを直に追加できるCfnListerRuleを使います。この時に出力されるテンプレートは次のようになります。


~~~~~~~~~~
"listener": {
      "Type": "AWS::ElasticLoadBalancingV2::ListenerRule",
      "Properties": {
        "Actions": [
          {
            "TargetGroupArn": {
              "Ref": "appsLBPublicListenerECSGroup7B961058"
            },
            "Type": "forward"
          }
        ],
        "Conditions": [
          {
            "Field": "http-header",
            "HttpHeaderConfig": {
              "HttpHeaderName": "User-agent",
              "Values": [
                "Amazon CloudFront"
              ]
            }
          },
          {
            "Field": "http-header",
            "HttpHeaderConfig": {
              "HttpHeaderName": "x-pre-shared-key",
              "Values": [
                "PRESHAREDKEY"
              ]
            }
          }
        ],
        "ListenerArn": {
          "Ref": "appsLBPublicListenerEC0E93DB"
        },
        "Priority": 1
      },
~~~~~~~~~~

このようにCloudFormationのテンプレートに追加されていることがわかります。

まとめ

このようにCDKはAWSサービスが抽象化されて非常にわかりやすくなっており、それだけではなく今回のようにパラメータにない場合でもAWSサービスを構築することができます。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした