はじめに
この記事はPlayground Advent Calender 2019の12/14の記事です。
初めまして。今年の春からPlaygroundでバックエンドをメインで勉強しているkikilsです。ここでは、ここ最近勉強しているAWS CDKというサービスについての知見を書いていきたいと思います。
行ったこと
CDKでユーザーがECSにアクセスするときにCloudFrontを経由する仕組みを作りました。
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に変更したほうがよいです。カスタムヘッダには、任意の文字列を設定します。
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は各自で当ててください。
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サービスを構築することができます。