AWS リソースの更新を Terraform からだけに限定する
はじめに
AWS リソースを Terraform (またはその他の IaC ツール) で実装していると、時々「あれ、思わぬ差分が」みたいなことありませんか?そう、マネコン (マネジメントコンソールのこと) からやっちゃってました、なんてことがままあると思います。
このような事態を発生させないために、(半ば強引に) Terraform からだけリソースを更新できるようにする方法 を実装しましたので、ご紹介したいと思います。
対象読者
- Terraform (またはその他の IaC ツール) で AWS リソースを管理したいインフラエンジニア
- ある程度は IaC 管理できているが、どうしてもマネコンからリソース更新されてしまうことにお悩みのインフラエンジニア
- 上記のようなガバナンスは効かせたいが利便性は損ないたくないと考えているインフラエンジニア
結論
IAM ポリシーの Condition
要素に aws:UserAgent
を指定して、一部アクションを Terraform からのみ許可する (Terraform から以外の操作を拒否する) ようにしました。
どうやったか
IAM ポリシーで実装するイメージはありましたが、どうやって実装するのかが分からず、あまり情報が無かったので、色々と調べてみました。
Chat-GPT に聞いてみた
まず、Chat-GPT (GPT-4o) に聞いてみました。
AWS で特定の操作を Terraform からのみ許可する IAM ポリシーはどのようになりますか?
Terraformでの操作のみを許可するIAMポリシーを作成するためには、Terraformが使用する特定のユーザーやロール、あるいはIPアドレス範囲などに基づいてアクセスを制限することが考えられます。
ここでは、Terraformが使用する特定のアクセスキーやIAMロールに基づいて操作を許可するポリシーの例を示します。
...
特定の操作をある IAM ロールからのみ許可するという方法を提案されました。これでもできなくはないですが、リソースに応じてロールを切り替えないといけません。また、仕事では役割に応じてロールを分けて使っているので、役割に応じて複数のロールを作成する必要があります。
なので、利便性とメンテナンス性の観点で却下としました。
Amazon Q に聞いてみた
次に AWS 純正の生成 AI アシスタントである Amazon Q に聞いてみました。
日本語には対応していないため、英語で質問します。
How do I allow actions by Terraform, but not Management console?
To allow certain actions in AWS only from Terraform and not from the AWS Management Console,
you can create an IAM policy that uses the aws:UserAgent condition key to check the user agent string of the request.
Terraform sets a specific user agent string that you can use to identify requests made by Terraform. [1]
Here's an example IAM policy that allows the ec2:StartInstances and ec2:StopInstances actions only when the request is made by Terraform:
...
[1] IAM Policy Condition - Allow CLI access only | AWS re:Post
要約すると aws:UserAgent
を使う方法を提案されました。以下がその例です。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["ec2:StartInstances", "ec2:StopInstances"],
"Resource": "*",
"Condition": {
"StringLike": {
"aws:UserAgent": "*Terraform*"
}
}
},
{
"Effect": "Deny",
"Action": ["ec2:StartInstances", "ec2:StopInstances"],
"Resource": "*",
"Condition": {
"StringNotLike": {
"aws:UserAgent": "*Terraform*"
}
}
}
]
}
参考 URL は以下です。
CloudTrail イベントログを調べてみた
Amazon Q に提案してもらった方法が筋が良さそうだったので、実際に UserAgent
を使ってマネコンからの操作と Terraform での操作とが区別できそうかを CloudTrail のイベントログで確認しました。
マネコンからの場合
"userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36",
Web ブラウザでよく見る Mozilla ...
が表示されています。
Terraform からの場合
"userAgent": "APN/1.0 HashiCorp/1.0 Terraform/1.3.1 (+https://www.terraform.io) terraform-provider-aws/5.42.0 (+https://registry.terraform.io/providers/hashicorp/aws) aws-sdk-go-v2/1.26.0 os/macos lang/go#1.21.8 md/GOOS#darwin md/GOARCH#arm64 api/secretsmanager#1.28.4",
Terraform
と表示されており使えそうなので、この UserAgent
を使うことにします。
ポリシーの実装
もちろん Terraform で実装します。今回は AdministratorAccess
権限を付与しつつ、Secrets Manager の一部アクションを制限するケースを考えます。
# 信頼関係のポリシー
data "aws_iam_policy_document" "assume_role" {
statement {
actions = ["sts:AssumeRole"]
principals {
type = "AWS"
identifiers = ["arn:aws:iam::************:root"]
}
}
}
# AdministratorAccess を data で参照
data "aws_iam_policy" "admin" {
name = "AdministratorAccess"
}
# Terraform からのみ許可するポリシー (今回は Secretes Manager の一部アクションを対象とする)
data "aws_iam_policy_document" "restricted_actions" {
statement {
effect = "Deny"
actions = [
"secretsmanager:Create*",
"secretsmanager:Update*",
"secretsmanager:Delete*",
"secretsmanager:Put*",
"secretsmanager:RotateSecret",
"secretsmanager:CancelRotateSecret",
"secretsmanager:ReplicateSecretToRegions",
"secretsmanager:RemoveRegionsFromReplication",
"secretsmanager:StopReplicationToReplica",
]
resources = ["*"]
# ユーザーエージェントが Terraform でないものを拒否
condition {
test = "StringNotLike"
variable = "aws:UserAgent"
values = ["*Terraform*"]
}
# AWS サービスからのアクションを除外
condition {
test = "Bool"
variable = "aws:ViaAWSService"
values = ["false"]
}
}
}
# IAM ロールを定義
resource "aws_iam_role" "default" {
name = "restricted-admin-role"
assume_role_policy = data.aws_iam_policy_document.assume_role.json
# AdministratorAccess をアタッチ
managed_policy_arns = [
data.aws_iam_policy.admin.arn,
]
# Terraform からのみ許可するポリシーをインラインポリシーとしてアタッチ
inline_policy {
name = "RestrictedActions"
policy = data.aws_iam_policy_document.restricted_actions.json
}
}
実装されたインラインポリシーは以下の通りです。
{
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"secretsmanager:Update*",
"secretsmanager:StopReplicationToReplica",
"secretsmanager:RotateSecret",
"secretsmanager:ReplicateSecretToRegions",
"secretsmanager:RemoveRegionsFromReplication",
"secretsmanager:Put*",
"secretsmanager:Delete*",
"secretsmanager:Create*",
"secretsmanager:CancelRotateSecret"
],
"Condition": {
"Bool": {
"aws:ViaAWSService": "false"
},
"StringNotLike": {
"aws:UserAgent": "*Terraform*"
}
},
"Effect": "Deny",
"Resource": "*"
}
]
}
aws:UserAgent
とあわせて aws:ViaAWSService: false
を指定するのがポイントです。これを指定しないといくつかの操作が失敗する場合があります。
確認
Secrets Manager シークレットに対するアクションについて、上記の制限を掛けてみました。シークレットはサービスにとって重要な情報を保管しており、統制無く更新されては困るためです。
マネコンからの場合
マネコンからシークレットを作成しようとすると、エラーになって作成することができません。
Terraform からの場合
Terraform からのシークレット作成は成功します。
[参考] デプロイコマンド & 結果
当社では Terragrunt を使っているため terragrunt apply
コマンドにてデプロイしています。
$ terragrunt apply
data.aws_availability_zones.available: Reading...
...
Terraform used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# aws_secretsmanager_secret.default will be created
+ resource "aws_secretsmanager_secret" "default" {
+ arn = (known after apply)
+ force_overwrite_replica_secret = false
+ id = (known after apply)
+ name = "secret_test_tf"
+ name_prefix = (known after apply)
+ policy = (known after apply)
+ recovery_window_in_days = 30
+ replica {
+ kms_key_id = (known after apply)
+ last_accessed_date = (known after apply)
+ region = (known after apply)
+ status = (known after apply)
+ status_message = (known after apply)
}
}
# aws_secretsmanager_secret_version.default will be created
+ resource "aws_secretsmanager_secret_version" "default" {
+ arn = (known after apply)
+ id = (known after apply)
+ secret_id = (known after apply)
+ secret_string = (sensitive value)
+ version_id = (known after apply)
+ version_stages = (known after apply)
}
Plan: 2 to add, 0 to change, 0 to destroy.
Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.
Enter a value: yes
aws_secretsmanager_secret.default: Creating...
...
Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
さいごに
任意のアクションを UserAgent
を使って Terraform からのみに制限する方法をご紹介しました。試してはいませんが、他の IaC ツールでも実現できるのではないかと思います。ただし、 UserAgent
は容易に偽装できるので絶対ではない点にご注意ください。
リアルタイム法人調査システム「SimpleCheck」を開発・運営するシンプルフォーム株式会社の開発チームのメンバーが、日々の開発で得た知見や試してみた技術などについて発信していきます。 Publication 運用への移行前の記事は zenn.dev/simpleform からご覧ください。
Discussion