CloudFormation(&CDK)からCloudFlareリソースを作成する方法。
CloudFormationでCloudFlareリソースを作成する。
本ページで「ドキュメント」と表現する大半は以下の派生ページからとなります。(※引用元)
2023年9月30日時点で存在するCloudFormationレジストリのパブリックサードパーティ拡張機能として登録されているリソースタイプは以下4つのようです。
事前準備
①CloudFlare側でToken発行
手順はDNS::Recordを例にします。
※全ての権限をひとつのTokenとして生成して、後述するSetTypeConfigurationにての4つ全てに同じTokenを与える事も出来るかと思いますが、それぞれ細かく作成するのがベストであるのではと思います。
↓
↓
↓
↓
↓
↓
②ロール&有効化&TokenをSecrets Managerに格納
CloudFlareの全リソースタイプを一撃で有効化するテンプレートを作りました。
※前章でTokenを全て統一で作成された方は4つのパラメーター全てに同じTokenを入力ください。
※TypeListパラメーターは触らずで大丈夫です。
AWSTemplateFormatVersion: 2010-09-09
Transform: AWS::LanguageExtensions
# ------------------------------------------------------------#
# Metadata:
# ------------------------------------------------------------#
Metadata:
AWS::CloudFormation::Interface:
ParameterGroups:
- Label:
default: Stack
Parameters:
- RecordToken
- LoadBalancerToken
- MonitorToken
- PoolToken
- TypeList
# ------------------------------------------------------------#
# Parameters:
# ------------------------------------------------------------#
Parameters:
RecordToken:
Type: String
NoEcho: true
LoadBalancerToken:
Type: String
NoEcho: true
MonitorToken:
Type: String
NoEcho: true
PoolToken:
Type: String
NoEcho: true
TypeList:
Type: List<String>
Default: Record,LoadBalancer,Monitor,Pool
# ------------------------------------------------------------#
# Mappings:
# ------------------------------------------------------------#
Mappings:
# ------------------------------------------------------------#
# ResourcesMappings
# ------------------------------------------------------------#
ResourcesMappings:
Record:
2ndstr: Dns
LoadBalancer:
2ndstr: LoadBalancer
Monitor:
2ndstr: LoadBalancer
Pool:
2ndstr: LoadBalancer
# ------------------------------------------------------------#
# Resources
# ------------------------------------------------------------#
Resources:
# ------------------------------------------------------------#
# Secret
# ------------------------------------------------------------#
Secret:
Type: AWS::SecretsManager::Secret
Properties:
Name: CloudFlareToken
SecretString: !Sub '{"Record":"${RecordToken}","LoadBalancer":"${LoadBalancerToken}","Monitor":"${MonitorToken}","Pool":"${PoolToken}"}'
Fn::ForEach::ResourcesLoop:
- TypeItem
- !Ref TypeList
# ------------------------------------------------------------#
# TypeActivation
# ------------------------------------------------------------#
- ${TypeItem}TypeActivation:
Type: AWS::CloudFormation::TypeActivation
Properties:
AutoUpdate: true
ExecutionRoleArn: !GetAtt [!Sub '${TypeItem}ExecutionRole', Arn]
PublicTypeArn: !Sub
- arn:aws:cloudformation:${AWS::Region}::type/resource/c830e97710da0c9954d80ba8df021e5439e7134b/Cloudflare-${2ndstr}-${TypeItem}
- 2ndstr: !FindInMap [ResourcesMappings,!Ref TypeItem,2ndstr]
Type: RESOURCE
TypeName: !Sub
- Cloudflare::${2ndstr}::${TypeItem}
- 2ndstr: !FindInMap [ResourcesMappings,!Ref TypeItem,2ndstr]
# ------------------------------------------------------------#
# Role
# ------------------------------------------------------------#
${TypeItem}ExecutionRole:
Type: AWS::IAM::Role
Properties:
MaxSessionDuration: 8400
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: resources.cloudformation.amazonaws.com
Action: sts:AssumeRole
Condition:
StringEquals:
aws:SourceAccount:
Ref: AWS::AccountId
StringLike:
aws:SourceArn: !Sub
- arn:${AWS::Partition}:cloudformation:${AWS::Region}:${AWS::AccountId}:type/resource/Cloudflare-${2ndstr}-${TypeItem}/*
- 2ndstr: !FindInMap [ResourcesMappings,!Ref TypeItem,2ndstr]
Path: "/"
Policies:
- PolicyName: ResourceTypePolicy
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Deny
Action:
- "*"
Resource: "*"
③ターミナル(SetTypeConfiguration)操作
SetTypeConfigurationについてはこちら
以下をターミナルで実行してください。ちなみに私shell全然詳しくないです。
一生懸命作りましたがセンスのない書き方になっているであろう点はご容赦ください。
(書き換えてくださるのは大歓迎です。)
※①[awsのアカウントID]には12桁のアカウントIDをハイフン無しで入力ください。
※②[選択リージョン]は例えばus-east-1。
#!/bin/bash
awsaccountid="[awsのアカウントID]"
region="[選択リージョン]"
types=(
"Dns::Record"
"LoadBalancer::LoadBalancer"
"LoadBalancer::Monitor"
"LoadBalancer::Pool"
)
echo "Tokenを取得します。"
Secrets=`aws secretsmanager get-secret-value --region ${region} --secret-id CloudFlareToken | jq '.SecretString'| tr -d '{}" \\' `
Secret=(${Secrets//,/ })
echo "set-type-configurationを開始します。"
i=0
while [[ $i -lt 4 ]]
do
Token=`echo ${Secret[$i]} | awk '{print substr($0,index($0,":")+1)}'`
aws cloudformation set-type-configuration --region ${region} --type RESOURCE --type-name Cloudflare::${types[$i]} --configuration-alias default --configuration "{ \"CloudflareAccess\":{\"Url\":\"https://api.cloudflare.com/client/v4\",\"ApiKey\":\"${Token}\"}}"
i=`expr $i + 1`
done
echo "set-type-configurationの結果(batch-describe-type-configurations)を確認ください。"
for type in "${types[@]}" ; do
aws cloudformation batch-describe-type-configurations --region ${region} --type-configuration-identifiers TypeArn=arn:aws:cloudformation:${region}:${awsaccountid}:type/resource/Cloudflare-${type}
done
を
$bash SetTypeConfiguration.sh
してください。
※ちなみにTokenに付与する権限範囲の変更などがあった場合、SecretsManagerのValueを変更いただき、再度↑を走らせていただければ反映されると想定しています。
4つのリソースタイプ
Cloudflare::Dns::Record(←のみ実際の実行もしてみます)
以下は上記githubにサンプルとして置かれたものです。
今回はこのリソースタイプのみ作成を試してみたいと思います。
私は事前にドメインをひとつ取得してある状態です。
ドキュメント上のサンプル(↓)にはそれぞれのプロパティに対する値の末尾に","がありますが余分です。
AWSTemplateFormatVersion: '2010-09-09'
Description: Shows how to create a Dns Record in Cloudflare
Resources:
MySampleProject:
Type: Cloudflare::Dns::Record
Properties:
ZoneId: 0012345678123,
Type: A,
Name: example.app.com,
Content: 11.116.111.1,
Proxied: false,
Ttl: 600
↓
無事作成されたようです。
Cloudflare::LoadBalancer::LoadBalancer
[sample]
AWSTemplateFormatVersion: '2010-09-09'
Description: Shows how to create an LoadBalancer in Cloudflare
Resources:
Type: Cloudflare::LoadBalancer:LoadBalancer
MySampleProject:
ZoneId: !Ref ZoneId
Proxied: true
Name: !Ref DomainName
SessionAffinity: none
SessionAffinityAttributes:
ZeroDowntimeFailover: none
SteeringPolicy: off
RandomSteering:
DefaultWeight: 1
DefaultPools:
- !GetAtt Pool.Id
FallbackPool: !GetAtt Pool.Id
Enabled: true
Cloudflare::LoadBalancer::Monitor
[sample]
AWSTemplateFormatVersion: '2010-09-09'
Description: Shows how to set a LoadBalancer Monitor in Cloudflare.
Resources:
AgentConfigurationSample:
Type: Cloudflare::LoadBalancer::Monitor
Properties:
ExpectedCodes: 200
Method: GET
Timeout: 5
Path: /
Interval: 60
Retries: 2
Description: GET over HTTPS - expect 200
Type: http
Port: 80
AccountIdentifier: !Ref AccountId
FollowRedirects: false
AllowInsecure: false
ProbeZone: ""
Cloudflare::LoadBalancer::Pool
[sample]
AWSTemplateFormatVersion: '2010-09-09'
Description: Shows how to create a static LoadBalancer Pool in Cloudflare
Resources:
SampleNrqlCondition:
Type: Cloudflare::LoadBalancer::Pool
Properties:
AccountIdentifier: !Ref AccountId
MinimumOrigins: 1
CheckRegions:
- WEU
NotificationEmail: email@example.com
NotificationFilter:
Pool:
Healthy: false
Origins:
- Enabled: true
Address: <IP Address>
Name: server-1
Weight: 0.9
- Weight: 0.1
Enabled: true
Name: server-2
Address: <IP Address>
Name: Servers
Description: Region Pool
Monitor: !GetAtt Monitor.IdALL
Priority: CRITICAL
CDKの場合は
私もcdkはまだまだ触りはじめの為、もっと良い書き方があるかもしれません。
使っているのは両方ともL1コンストラクトです。
凄く初歩的な事かもしれませんがこちらに
for 文では一回のループごとに定数や変数が破棄されるため、同じ定数名でも宣言することができます。
のように書いてあった為、↓のようにしてみた所、cdksynth->deploy共に意図通りとなりました。
以下ではCFnとは違い機密情報であるTokenを埋め込みにはしていません。
SecretsManagerへのToken格納と、SetTypeConfiguration時の呼び出しだけ整合がつくよう行っていただければ同様となるかと思います。
import { Stack, StackProps } from 'aws-cdk-lib';
import { Construct } from 'constructs';
import { aws_cloudformation as cloudformation } from 'aws-cdk-lib';;
import { aws_iam as iam } from 'aws-cdk-lib';
const region = '[リージョン]';
const accountid ='[12桁のAWSアカウントIDをハイフンなしで]';
export class CdkCloudflareStack extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
const types : Array<[string,string]> = [
["Dns",'Record'],
['LoadBalancer','LoadBalancer'],
['LoadBalancer','Monitor'],
['LoadBalancer','Pool']
]
for(const type of types) {
const Role = new iam.CfnRole(this, 'CfnRole'+type[1], {
maxSessionDuration: 8400,
assumeRolePolicyDocument: {
Version: '2012-10-17',
Statement: [
{
Effect: 'Allow',
Principal: { Service:['resources.cloudformation.amazonaws.com'] },
Action: ['sts:AssumeRole'],
Condition: {
StringEquals:{
'aws:SourceAccount': accountid,
},
StringLike:{
'aws:SourceArn': 'arn:aws:cloudformation:'+region+':'+accountid+':type/resource/Cloudflare-'+type[0]+'-'+type[1]+'/*',
},
},
},
],
},
path: '/',
policies: [{
policyName: 'ResourceTypePolicy',
policyDocument: {
Version: '2012-10-17',
Statement:[
{
Effect: 'Deny',
Action: ['*'],
Resource: "*",
},
],
},
}],
});
new cloudformation.CfnTypeActivation(this, 'CfnTypeActivation'+type[1], {
autoUpdate: true,
executionRoleArn: Role.attrArn,
publicTypeArn: 'arn:aws:cloudformation:'+region+'::type/resource/c830e97710da0c9954d80ba8df021e5439e7134b/Cloudflare-'+type[0]+'-'+type[1],
type: 'RESOURCE',
typeName: 'Cloudflare::'+type[0]+'::'+type[1],
});
}
}
}
TypeActivation完了後のリソース利用については釈迦に説法とは思いますが、以下を参照いただければ幸いです。
Cloudflare MeetupのLT資料
以上でした。
有難うございました。
Discussion