📡

AWS リソースの更新を Terraform からだけに限定する

2024/07/10に公開

はじめに

AWS リソースを Terraform (またはその他の IaC ツール) で実装していると、時々「あれ、思わぬ差分が」みたいなことありませんか?そう、マネコン (マネジメントコンソールのこと) からやっちゃってました、なんてことがままあると思います。

このような事態を発生させないために、(半ば強引に) Terraform からだけリソースを更新できるようにする方法 を実装しましたので、ご紹介したいと思います。

対象読者

  • Terraform (またはその他の IaC ツール) で AWS リソースを管理したいインフラエンジニア
  • ある程度は IaC 管理できているが、どうしてもマネコンからリソース更新されてしまうことにお悩みのインフラエンジニア
  • 上記のようなガバナンスは効かせたいが利便性は損ないたくないと考えているインフラエンジニア

結論

IAM ポリシーの Condition 要素に aws:UserAgent を指定して、一部アクションを Terraform からのみ許可する (Terraform から以外の操作を拒否する) ようにしました。

どうやったか

IAM ポリシーで実装するイメージはありましたが、どうやって実装するのかが分からず、あまり情報が無かったので、色々と調べてみました。

Chat-GPT に聞いてみた

まず、Chat-GPT (GPT-4o) に聞いてみました。

Question
AWS で特定の操作を Terraform からのみ許可する IAM ポリシーはどのようになりますか?
Answer
Terraformでの操作のみを許可するIAMポリシーを作成するためには、Terraformが使用する特定のユーザーやロール、あるいはIPアドレス範囲などに基づいてアクセスを制限することが考えられます。
ここでは、Terraformが使用する特定のアクセスキーやIAMロールに基づいて操作を許可するポリシーの例を示します。

  ...

特定の操作をある IAM ロールからのみ許可するという方法を提案されました。これでもできなくはないですが、リソースに応じてロールを切り替えないといけません。また、仕事では役割に応じてロールを分けて使っているので、役割に応じて複数のロールを作成する必要があります。

なので、利便性とメンテナンス性の観点で却下としました。

Amazon Q に聞いてみた

次に AWS 純正の生成 AI アシスタントである Amazon Q に聞いてみました。
日本語には対応していないため、英語で質問します。

Question
How do I allow actions by Terraform, but not Management console?
Answer
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 を使う方法を提案されました。以下がその例です。

Example
{
  "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 は以下です。

https://repost.aws/questions/QUeU1yFgr1Sdu6UpEiquomYg/iam-policy-condition-allow-cli-access-only

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 の一部アクションを制限するケースを考えます。

main.tf
# 信頼関係のポリシー
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
  }
}

実装されたインラインポリシーは以下の通りです。

RestrictedActions
{
  "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 を指定するのがポイントです。これを指定しないといくつかの操作が失敗する場合があります。

https://dev.classmethod.jp/articles/tsnote-iam-allow-aws-service/

確認

Secrets Manager シークレットに対するアクションについて、上記の制限を掛けてみました。シークレットはサービスにとって重要な情報を保管しており、統制無く更新されては困るためです。

マネコンからの場合

マネコンからシークレットを作成しようとすると、エラーになって作成することができません。

Create secret from Management console

Terraform からの場合

Terraform からのシークレット作成は成功します。

Create secret from 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 は容易に偽装できるので絶対ではない点にご注意ください。

SimpleForm Tech Blog

Discussion