CloudFormation Hookが利用可能になったので、試してみる。
やりたいこと
IaCに予防的統制を組み込みたい。
これまで、例えばS3で暗号化を強制したい場合、以下のような手段はあった。
- SCPでS3暗号化設定の変更を禁止する。
- Config RulesでS3暗号化が設定されていないバケットを検知する。
- CFn GuardでS3暗号化設定が入っているかどうかを事前にチェックする。
いずれも有益な統制ではあるが、より直截的に「S3暗号化設定が入ってなければスタックの作成を禁止」したい場合、これらの方法では難しかった。
フックならそれが実現できそうだ。
準備
CFnテンプレートを二つ用意する。
一つ目は何も設定されていないテンプレート。
AWSTemplateFormatVersion: "2010-09-09"
Description: "Hook Test. Encryption settings is not present."
Resources:
S3HookTestBucket:
Type: "AWS::S3::Bucket"
Properties:
BucketName: !Sub "hooktest-ng-${AWS::AccountId}-${AWS::Region}-bucket"
二つ目は、暗号化が設定されているテンプレート。
AWSTemplateFormatVersion: "2010-09-09"
Description: "Hook Test. Encrypytion is enabled."
Resources:
S3HookTestBucket:
Type: "AWS::S3::Bucket"
Properties:
BucketName: !Sub "hooktest-ok-${AWS::AccountId}-${AWS::Region}-bucket"
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: 'aws:kms'
KMSMasterKeyID: '12345678-abcd-efgh-1234-1234567890ab'
BucketKeyEnabled: true
ステップバイステップ
1. サンプルHookを検索する
フックはCloudFormation→レジストリ→パブリック拡張機能から検索できる。
AWSSamples
という名前でいくつか登録されているので、ここから検索する。以下二点に注意。
- パブリッシャーはAWSではなくサードパーティー。
- 前方一致。
AWSSamples::S3
はマッチするが、S3
はマッチしない。
今回は上から二つ目のAWSSamples::S3BucketEncrypt::Hook
を有効化する。
2. サンプルHookを有効化する
フックを選択すると詳細が表示されるので、右上の「アクティブ化」ボタンをクリックする。
アクティブ化の詳細設定画面に遷移するが、どうやらフックを登録するためのIAMロールのARNが必要らしい。
3. IAMロールを準備する
マニュアルを参考に、信頼関係を以下の通り設定したロールを作成する(ちなみにresources.cloudformation.amazonaws.com
はフックではなく拡張リソースタイプを登録するための設定)。
ロール名はCFnHookTestRole
とし、権限そのものはいったん'AdministratorAccess'とした。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": [
"hooks.cloudformation.amazonaws.com",
"resources.cloudformation.amazonaws.com"
]
},
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {
"aws:SourceAccount": "123456789012"
}
}
}
]
}
4. アクティベート
IAMロールとログ設定を入れ、他の設定はブランクのまま、「拡張機能のアクティブ化」をクリックする。
エイリアスと設定JSONの設定入力画面が出るので、それぞれ入力する。
エイリアスはリージョンごと・アカウントごとに一意なものを設定する必要がある模様。
別リージョン・別アカウントならかぶってもよいということになるが、マニュアルの記載を見る限り、なるべく一意にしておくことが望ましいようだ。ただ、その割にハイフンやアンスコが使えないのは不便としか言えない。
...と、色々調べてはみたものの、試した限りでは、このフックはdefault
というエイリアスしか受け付けないらしい。何なんだ。。。
Invalid configuration alias S3BucketEncrypt12345678901tokyo for HOOK type AWSSamples::S3BucketEncrypt::Hook. Only 'default' alias is supported for HOOK types
設定JSON(HookConfiguration)は以下のような感じになる。詳細はこちらに記載がある。
{
"CloudFormationConfiguration": {
"HookConfiguration": {
"TargetStacks": "ALL",
"FailureMode": "FAIL",
"Properties": {
"minBuckets":"1",
"encryptionAlgorithm": "aws:kms"
}
}
}
}
項目 | 内容 | 取りうる値 |
---|---|---|
TargetStacks | 対象スタックぽく見えるが、実質、有効/無効の設定。 | ALL, NONE |
FailureMode | フックのルールに違反した際の挙動。FAILにするとスタック作成が中止される。WARNだと警告止まり。 | FAIL, WARN |
Properties | 各フックの設定スキーマごとに定義された独自設定。 | フックによる |
S3暗号化のフックの場合、PropertiesはminBuckets(準拠している最小バケット数。これは意図がよくわからない)とencryptionAlgorithm(暗号化アルゴリズム)の二つ。
これはフックの設定スキーマに定義されているので、少し長いが引用しておく。
{
"typeName": "AWSSamples::S3BucketEncrypt::Hook",
"description": "Example resource hook that check Amazon S3 bucket encryption properties",
"sourceUrl": "https://github.com/aws-cloudformation/aws-cloudformation-samples/tree/main/hooks/python-hooks/s3-bucket-encryption",
"documentationUrl": "https://github.com/aws-cloudformation/aws-cloudformation-samples/blob/main/hooks/python-hooks/s3-bucket-encryption/README.md",
"typeConfiguration": {
"properties": {
"minBuckets": {
"description": "Minimum number of compliant buckets",
"type": "string"
},
"encryptionAlgorithm": {
"description": "Encryption algorithm for SSE",
"default": "AES256",
"type": "string"
}
},
"required": [],
"additionalProperties": false
},
"required": [],
"handlers": {
"preCreate": {
"targetNames": [
"AWS::S3::Bucket"
],
"permissions": []
},
"preUpdate": {
"targetNames": [
"AWS::S3::Bucket"
],
"permissions": []
}
},
"additionalProperties": false
}
5. 設定確認
アクティブ化が完了したら、レジストリ→アクティブ化済みの拡張機能から設定を確認してみる。
デフォルトでは一つも出てこないのでアレ?となるが、これはフィルターが「非公開登録」になっているためで、「アクティブ化済みのサードパーティー」に変更すると表示される。
アクティブ化され、失敗モード(FailureMode)がちゃんと「失敗(Fail)」になっていることがわかる。
6. 挙動確認
用意したCFnテンプレートを使ってテストする。
まずは、暗号化設定のないhooktest-ng.yml
でスタックを作成してみる。
S3暗号化のフックで評価に失敗した旨を表示して、CREATE_FAILEDとなった。
CloudWatch Logs(log-group-hook-test
)を見ると、以下のように詳細が記録されている。
次に暗号化を有効化したhook-test-ok.yml
でテスト。
無事成功した。
CloudWatch Logsの記録は以下の通り。
余談だが、AES256ではBucket key not enabled for bucket
とのエラーが出て上手くいかなかった。
サンプルフックのコードに何か問題があるのかも知れないが、本題から逸れるのでここではスルー。
まとめ
フックによって、従来より強い水準での予防的統制が実現出来るようになった。
ただ、他の大多数のセキュリティ施策と同じく、やり過ぎれば開発自由度を阻害し、運用を煩雑化することに繋がりかねない点は注意が必要かも知れない。
諸刃の剣ではあるが、うまく使えばIaCにもいい感じのガードレール(自動ブレーキと言った方が近いかも)を敷くことができそうな気はする。
おまけ
フックの開発について詳細はこちら。
こっちもいずれトライしてみたい。