クラウドリソースのカバレッジを改善してインフラのドリフトを軽減する
Stephane Jourdan
2022年3月23日
0 分で読めます非推奨の通知: マネージドリソースのドリフト検出
マネージドリソースのドリフト検出は、snyk iac describe --only-managed and snyk iac describe --drift
を含めて非推奨となりました。マネージドリソースのドリフト検出は、2023 年 9 月 30 日に終了しました。
開発者としては、クラウド環境のセキュリティを確保するため、実際に何が動作しているのか可視性を最大限に高める必要があります。Infrastructure as Code (IaC) は、開発者がクラウドインフラを自動化できるよう支援します。クラウドに導入したツールなどは管理され、簡単に監査できます。ただし、インフラの IaC カバレッジ 100% を達成して維持するには、多くの課題が存在します。
クラウド環境に実際に導入され、実行されているツールなどにはセキュリティが確保されていますが、多くの場合、開発チームや他のチーム、認証済みサービスなどにより、多数の手動によるアクションが定期的に実行されています。これらの変更は IaC や監査の対象外となっており、設定ミスやセキュリティ上の懸念などの問題を引き起こす可能性があります。このような場合に、ドリフト管理が役立ちます。まだ IaC によって管理されていないリソースや、何らかの理由で変更されたリソースについての報告が必要になるためです。
この記事では、Snyk IaC を使用して、開発者が IaC によって管理されていないクラウドリソース (アンマネージドリソース) や、予想される状態から逸脱したクラウドリソース (マネージドリソース) を発見する方法について説明します。
環境をセットアップする
Snyk IaC は、検出したリソースを Terraform のリソースとしてリスト化するため、どのクラウドサービスのどの部分が検出対象であるかを簡単に把握できます。たとえば、1 つの Amazon API Gateway v2 サービスは、少なくとも 12 の Terraform リソースで構成されています。Snyk によって提供される検出情報に基づいて、修正を元に戻すか、新しいリソースをインポートするか、または単に変更を削除するかをすばやく判断できます。
以下の Terraform ファイルを使用すると、2 つの AWS リソースを作成できます。ランダムな接尾辞を持つ IAM ユーザー「user1」、アクセスキー、および読み取り専用アクセスのポリシーを作成します。
この記事の執筆時点では、Terraform v1.1.7 と AWS プロバイダー v3.74.2 を使用しています。
次の HCL 設定を再利用します。
main.tf
1resource "random_string" "prefix" {
2 length = 6
3 upper = false
4 special = false
5}
6
7resource "aws_iam_user" "user1" {
8 name = "user1-${random_string.prefix.result}"
9
10 tags = {
11 Name = "user1-${random_string.prefix.result}"
12 manual = "true"
13 }
14}
15
16resource "aws_iam_access_key" "user1" {
17 user = aws_iam_user.user1.name
18}
19
20resource "aws_iam_user_policy_attachment" "user1" {
21 user = aws_iam_user.user1.name
22 policy_arn = "arn:aws:iam::aws:policy/ReadOnlyAccess"
23}
その Terraform の設定を適用します。
1$ terraform init
2[...]
3$ terraform apply
4[...]
ディレクトリのルートにterraform.tfstate
が存在することを確認します。
1$ ls -al terraform.tfstate
2-rw-r--r-- 1 sjourdan staff 5049 Mar 16 18:31 terraform.tfstate
また、IAM ユーザーが AWS 上に正常に作成されていることも確認します。
初期状態から始める
まず、Terraform で管理されていないすべてのクラウドリソースをリストアップしましょう。
1$ snyk iac describe --only-unmanaged
おそらく、Terraform で管理されていないリソースの膨大なリストが表示されるはずです。有益な情報かもしれませんが、実用性に富んでいるとはいえません。Snyk IaC には、見つかったすべてのリソースを .snyk
ポリシーファイルに追加することで、一括でリソースを無視する機能があります。
完全に管理された環境で正確に作業するため、上記で作成した 2 つのリソースのみを使用し、既存のアンマネージドリソースはすべて無視しましょう。
1$ snyk iac describe --only-unmanaged --json | snyk iac update-exclude-policy
もう一度スキャンして、環境で検出されたドリフトが意図的に無視されていることを確認します (後からいつでもドリフトのインポートをスケジュールできます)。
1$ snyk iac describe --only-unmanaged
2
3Scanned states (1)
4Found 3 resource(s)
5 - 100% coverage
6Congrats! Your infrastructure is fully in sync.
これで初期状態から始める準備ができました。
IAM でドリフトを発生させる
ここで、実際の状況をシミュレートするために 3 種類のドリフトを作成します。
既存の IAM ユーザーに対する変更 (後で元に戻します)
新しい IAM ポリシーの手動アタッチ (後で削除します)
新しい IAM ユーザー (後で修正します)
この操作は、IAM の AWS コンソールに移動して行います。
タグを追加して既存の IAM ユーザーを変更する
IAM ユーザーページで、「user1」をクリックします
タグタブをクリックします
タグの編集ボタンをクリックします
新しいキー (「environment」) と新しい値 (「production」) を追加します
保存をクリックします
既存の IAM ユーザーに強力なポリシーをアタッチする
IAM ユーザーページで、「user1」をクリックします
権限タブをクリックします
権限を追加ボタンをクリックします
既存のポリシーを直接アタッチをクリックします
管理者アクセスを選択します
次へ: レビューをクリックします
権限を追加をクリックして確認します
別の IAM ユーザーを手動で作成する
IAM ユーザーページで、ユーザーを追加ボタンをクリックします
ユーザー名:フィールドで「user2」と入力します
アクセスキーを選択します
次へ: 権限ボタンをクリックします
権限やタグは設定しません
ユーザーを作成をクリックします (表示される認証情報は関係ないため、無視してもかまいません)。
これで、Snyk IaC のドリフト検出を使用して、手動による変更に対応する準備ができました。
マネージドインフラおよびアンマネージドインフラのドリフト
では、Snyk IaC で変更がどのように検出されるか、まずは Terraform でまったく管理されていないリソースから見ていきましょう。
1$ snyk iac describe --only-unmanaged
2
3Scanned states (1)
4Found resources not covered by IaC:
5 aws_iam_access_key:
6 - AKIASBXWQ3AYQETE6OFR
7 User: user2
8 aws_iam_policy_attachment:
9 - user1-84i30k-arn:aws:iam::aws:policy/AdministratorAccess
10 aws_iam_user:
11 - user2
12Found 6 resource(s)
13 - 50% coverage
14 - 3 resource(s) managed by Terraform
15 - 3 resource(s) not managed by Terraform
16 - 0 resource(s) found in a Terraform state but missing on the cloud provider
このスキャンは、Terraform のリソース用語を使用して報告されています。
手動で作成した IAM「user2」とその IAM アクセスキー
Terraform が管理する IAM ユーザー「user1」に手動でアタッチした IAM ポリシー
では、Terraform によって管理されているリソースのうち、さまざまな Terraform 状態で検出された変更について確認してみましょう。
1$ snyk iac describe –only-managed
2Scanned states (1)
3Found changed resources:
4 From tfstate://terraform.tfstate
5 - user1-84i30k (aws_iam_user.user1):
6 + tags.environment: <nil> => "production"
7Found 5 resource(s)
8 - 100% coverage
9 - 5 resource(s) managed by Terraform
10 - 1/5 resource(s) out of sync with Terraform state
11 - 0 resource(s) found in a Terraform state but missing on the cloud provider
このスキャンではアンマネージドモードのスキャンとはまったく異なる出力が報告され、大幅に時間がかかりました (「アンマネージド」スキャンモードの 9 秒に対して 36 秒)。
この出力からわかるように、IAM ユーザーの「user1-84i30k」は、「user1」という名前で HCL で (リソースとして) 検出でき、「environment」という名前のタグが「production」に設定されています。
アクションプラン
Snyk のドリフト検出ツールによって、予想と実態の間で 4 つの想定外の相違が発見されました。ここで、チームは以下のように判断しました。
IAM ユーザー「user2」は本番環境で使用されており、Terraform にインポートする必要がある。
セキュリティ上の理由から、IAM アクセスキー「user2」はローテーションする必要がある。
「user1」はどんな場合でも管理者には設定しない。
「user1」の新しいタグが一部の要件で必要になるため、Terraform にインポートする必要がある。
対象 | リソースタイプ | 会社名 | ドリフトタイプ | アクション |
---|---|---|---|---|
IAM ユーザー |
|
| アンマネージド | IMPORT |
IAM アクセスキー |
|
| アンマネージド | ROTATE |
アタッチ済み IAM ポリシー |
|
| アンマネージド | DELETE |
IAM ユーザーのタグ |
|
| マネージド | IMPORT |
導入パイプラインは対策にならない
Terraform の優れた導入パイプラインがある場合、次に terraform apply
がトリガーされるときに正常に戻ることを期待するかもしれません。
このような場合、Terraform で実行しているのは、導入ジョブです。
1$ terraform apply
2Terraform will perform the following actions:
3
4 # aws_iam_user.user1 will be updated in-place
5 ~ resource "aws_iam_user" "user1" {
6 id = "user1-84i30k"
7 name = "user1-84i30k"
8 ~ tags = {
9 - "environment" = "production" -> null
10 # (1 unchanged element hidden)
11 }
12[...]
13
14Plan: 0 to add, 1 to change, 0 to destroy.
Terraform は手動で作成またはアタッチされたリソースを検出することを想定してはおらず、変更されたリソースを元の状態に戻すだけです (この状況でこの動作は望ましくありません)。
対象 | リソースタイプ | 会社名 | ドリフトタイプ | アクション |
---|---|---|---|---|
IAM ユーザー |
|
| アンマネージド | NONE |
IAM アクセスキー |
|
| アンマネージド | NONE |
アタッチ済み IAM ポリシー |
|
| アンマネージド | NONE |
IAM ユーザーのタグ |
|
| マネージド | REVERT |
どのケースも、期待するほど有益ではありません。
手動で作成された IAM ユーザーとそのアクセスキーが報告されていない (無益)
マネージドユーザーに手動でアタッチされた管理者ポリシーが報告されていない (無益)
マネージドユーザーに手動で追加された重要なタグが元に戻される (有害)
このタイプの検出と作業には、別のタイプのツールが必要です。
カバレッジを改善する
アンマネージドリソースの 50% カバレッジからスタートします。
1$ snyk iac describe --only-unmanaged
2
3Scanned states (1)
4Found resources not covered by IaC:
5 aws_iam_access_key:
6 - AKIASBXWQ3AYQETE6OFR
7 User: user2
8 aws_iam_policy_attachment:
9 - user1-84i30k-arn:aws:iam::aws:policy/AdministratorAccess
10 aws_iam_user:
11 - user2
12Found 6 resource(s)
13 - 50% coverage
14 - 3 resource(s) managed by Terraform
15 - 3 resource(s) not managed by Terraform
16 - 0 resource(s) found in a Terraform state but missing on the cloud provider
チーム計画に基づいて改善していきましょう。
「user1」の IAM ポリシーを削除する
最も緊急で簡単な作業から始めましょう。マネージドリソース IAM「user1」の「管理者」ポリシーを削除します。
IAM > ユーザー > 「user1」の順にクリックします
権限 > 「AdministratorAccess」の削除をクリックします
1$ snyk iac describe --only-unmanaged
2Scanned states (1)
3Found resources not covered by IaC:
4 aws_iam_access_key:
5 - AKIASBXWQ3AYQETE6OFR
6 User: user2
7 aws_iam_user:
8 - user2
9Found 5 resource(s)
10 - 60% coverage
11 - 3 resource(s) managed by Terraform
12 - 2 resource(s) not managed by Terraform
これで、AWS リソースの 50% から上昇して 60% をカバーするようになりました。
対象 | リソースタイプ | 会社名 | ドリフトタイプ | アクション | ステータス: |
---|---|---|---|---|---|
IAM ユーザー |
|
| アンマネージド | IMPORT | |
IAM アクセスキー |
|
| アンマネージド | ROTATE | |
アタッチ済み IAM ポリシー |
|
| アンマネージド | DELETE | * |
IAM ユーザーのタグ |
|
| マネージド | ADD |
続けましょう。
Terraform 導入パイプラインのブロックを解除する
このパイプラインは、aws_iam_user.user1 のタグに対する手動の変更により現在ブロックされています。何かが導入されると、タグは HCL のものに戻ります。では、何が解決策になるでしょうか?Snyk IaC ドリフト出力を使用して、Terraform 設定を調整してみましょう。
以下の情報に基づいて調整します。
1Found changed resources:
2 From tfstate://terraform.tfstate
3 - user1-84i30k (aws_iam_user.user1):
4 + tags.environment: <nil> => "production"
出力から以下の点が判明しています。
「user1」という名前で
aws_iam_user
という名前のリソースを探しているこのリソースは terraform.tfstate に存在する (状態が数十または数百ある場合に非常に便利)
「production」という値を持つ、
environment
という名前の新しいタグキーが存在する
IAM ユーザーリソースで environment = "production"
のみ追加する更新を行ってみましょう。リソースは以下のとおりです。
1resource "aws_iam_user" "user1" {
2 name = "user1-${random_string.prefix.result}"
3
4 tags = {
5 Name = "user1-${random_string.prefix.result}"
6 environment = "production"
7 }
8}
これで、Terraform 導入パイプラインのブロックを安全に解除できるようになりました。
1$ terraform apply
2No changes. Your infrastructure matches the configuration.
3Apply complete! Resources: 0 added, 0 changed, 0 destroyed.
ここでは、「マネージド」ドリフトを修正しています。
1$ snyk iac describe --only-managed
2Scanned states (1)
3Found 3 resource(s)
4 - 100% coverage
5Congrats! Your infrastructure is fully in sync.
対象 | リソースタイプ | 会社名 | ドリフトタイプ | アクション | ステータス: |
---|---|---|---|---|---|
IAM ユーザー |
|
| アンマネージド | IMPORT | |
IAM アクセスキー |
|
| アンマネージド | ROTATE | |
アタッチ済み IAM ポリシー |
|
| アンマネージド | DELETE | * |
IAM ユーザーのタグ |
|
| マネージド | ADD | * |
IAM ユーザー 2 をインポートしてローテーションする
「user2」のケースに移りましょう。以下の操作を行います。
Terraform にインポートする
キーをローテーションする
まず、Terraform で IAM ユーザーをインポートしましょう。以下の方法で行うと簡単です。
まず、Snyk IaC から情報を収集します。
リソースタイプ | 会社名 |
---|---|
|
|
aws_iam_user resource
をインポートする方法は、Terraform の公式ドキュメントによると、 IAM ユーザーは name を使用してインポートできます。次のようにします。$ terraform import aws_iam_user.lb loadbalancer
.
また、必要な引数は name
だけです。ここで、この基本構造を HCL ファイルに追加しましょう。
1resource "aws_iam_user" "user2" {
2 name = "user2" # required
3}
このユーザーを Terraform にインポートします。
1$ terraform import aws_iam_user.user2 user2
2aws_iam_user.user2: Importing from ID "user2"...
3aws_iam_user.user2: Import prepared!
4 Prepared aws_iam_user for import
5aws_iam_user.user2: Refreshing state... [id=user2]
6
7Import successful!
カバレッジはどのように変化したでしょうか?調べてみましょう。
1$ snyk iac describe --only-unmanaged
2Scanned states (1)
3Found resources not covered by IaC:
4 aws_iam_access_key:
5 - AKIASBXWQ3AYQETE6OFR
6 User: user2
7Found 5 resource(s)
8 - 80% coverage
9 - 4 resource(s) managed by Terraform
10 - 1 resource(s) not managed by Terraform
現在、カバレッジは 60% から 80% に上昇し、リソースは 1 つだけ残っています。
対象 | リソースタイプ | 会社名 | ドリフトタイプ | アクション | ステータス: |
---|---|---|---|---|---|
IAM ユーザー |
|
| アンマネージド | IMPORT | * |
IAM アクセスキー |
|
| アンマネージド | ROTATE | |
アタッチ済み IAM ポリシー |
|
| アンマネージド | DELETE | * |
IAM ユーザーのタグ |
|
| マネージド | ADD | * |
キーをローテーションする
今すぐ実行してみましょう。キーを Terraform に追加するときには、ローテーションさせる必要があります。まず、新しいキーを HCL に追加して新しいキーを作成します (そして、たとえば関係するチームに渡します)。最後に、AWS から古いキーを削除します。
aws_iam_access_key の Terraform ドキュメントは非常に明快で、単に user2 の名前を引数として受け取る 1 つのリソースを作成できます。
1resource "aws_iam_access_key" "user2" {
2 user = aws_iam_user.user2.name
3}
導入パイプラインはすでにブロック解除されているため、Terraform を使用して新しいキーを作成し、安全に適用できます。
1$ terraform apply
2[...]
3Terraform will perform the following actions:
4
5 # aws_iam_access_key.user2 will be created
6 + resource "aws_iam_access_key" "user2" {
7 + create_date = (known after apply)
8 + encrypted_secret = (known after apply)
9 + id = (known after apply)
10 + key_fingerprint = (known after apply)
11 + secret = (sensitive value)
12 + ses_smtp_password_v4 = (sensitive value)
13 + status = "Active"
14 + user = "user2"
15 }
16
17Plan: 1 to add, 0 to change, 0 to destroy.
18
19aws_iam_access_key.user2: Creating...
20aws_iam_access_key.user2: Creation complete after 1s [id=AKIASBXWQ3AY4KPUNIHZ]
21
22Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
ここで古いキーを削除します。Snyk IaC 出力の情報を使用すると、キー名は AKIASBXWQ3AYQETE6OFR
ということがわかります。
このキーを削除する最も簡単な方法は以下のとおりです。
IAM > ユーザー > user2 > セキュリティ認証情報に移動します
Snyk IaC によって報告された
AKIASBXWQ3AYQETE6OFR
という名前のキーを非アクティブ化して削除します
カバレッジはどうなりましたか?
1$ snyk iac describe --only-unmanaged
2Scanned states (1)
3Found 5 resource(s)
4 - 100% coverage
5Congrats! Your infrastructure is fully in sync.
よくできました。Snyk IaC ドリフト検出のおかげで、すべてがマネージドの状態に戻りました。
対象 | リソースタイプ | 会社名 | ドリフトタイプ | アクション | ステータス: |
---|---|---|---|---|---|
IAM ユーザー |
|
| アンマネージド | IMPORT | * |
IAM アクセスキー |
|
| アンマネージド | ROTATE | * |
アタッチ済み IAM ポリシー |
|
| アンマネージド | DELETE | * |
IAM ユーザーのタグ |
|
| マネージド | ADD | * |
まとめ
この記事では、Snyk IaC ドリフト検出が手動で作成した AWS リソースの発見に役立つこと、開発者がそれらのリソースを Terraform HCL コードにインポートする上で役立つ正しい情報がすべて Terraform の用語で報告されることについて説明しました。また、変更を自動的に戻すことが必ずしも望ましい結果とは限らないこと、その導入パイプラインを整備することに加えて、軽量のドリフト検出アラートシステムが必要になることについても簡単に説明しました。
Snyk は、すべてのインフラをコードで管理すべきだと考えています。そうすれば、エンジニアはできるだけ早い段階でセキュリティフィードバックを取得し、問題を可視化できるからです。
このため、Snyk IaC は、チームが AWS アカウントで実際に稼働しているすべてのリソースを Terraform のコードにすばやく再統合し、IaC のカバレッジを高め、セキュリティ問題を全体的に低減できます。Snyk IaC は、クラウドセキュリティとエンジニアリングチームの間のフィードバックループを閉じ、エンジニアに対して具体的な修正案をエンジニアに理解しやすい形式で直接報告することで、すばやく修正作業を実施できるようにしています。