Speee DEVELOPER BLOG

Speee開発陣による技術情報発信ブログです。 メディア開発・運用、スマートフォンアプリ開発、Webマーケティング、アドテクなどで培った技術ノウハウを発信していきます!

AWS SSO のリソースを簡潔に記述するための Terraform モジュールを公開しました

はじめに

はじめまして。 開発基盤ユニット エンジニアの秋吉です。

AWS SSO におけるアクセス権限の割り当てを簡潔に記述するための Terraform モジュール speee/sso-assignment/aws を公開しました。

AWS SSO のリソースを Terraform で管理する場合、アクセス権限の割り当てを定義する aws_ssoadmin_account_assignment リソースを大量に記述する必要があります。 しかしながら、 aws_ssoadmin_account_assignment は指定する引数の数が多く、約半数が共通した値であるため、全体として非常に冗長なコードを記述せざるを得ませんでした。 そこで、Terraform モジュールを作成することで冗長さを吸収することにしました。 結果として、AWS SSO のリソースを Terraform で記述していた Speee の環境では、本 Terraform モジュールを使用することにより全体のコード量を約 1/4 に抑えることを実現しました。

Speee では、AWS SSO のリソースを Terraform で管理しています。 また、アクセス権限割り当てのリクエストを GitHub の PR で受け付けるという運用を行っています。 そのため、コード量を減少させることで以下のメリットを得られました。

  • PR を少ない差分で容易に作成することができるようになった
  • 既に割り当てられたアクセス権限の一覧性が増したため、アクセス権限の棚卸しが容易になった

Azure AD 等のサードパーティの Identity プロバイダと AWS SSO を連携して利用している組織は多いはずです。 AWS SSO のリソースを Terraform で管理している、もしくは、これからしようとしている方にとっては非常に便利な Terraform モジュールだと思います。

また、自動的に import を行うツール sso-importer も公開しました。 これにより、大量の terraform import コマンドを手動で実行することなく容易に導入できます。

本 Terraform モジュールおよび sso-importer は OSS として GitHub 上に公開しています。 PR や Issue をいただけましたら幸いです。

github.com github.com

Terraform モジュールを作成した背景

Speee では、約50個の AWS アカウントを管理しています。 この中規模マルチアカウント環境において、適切にアクセス権限を制御するために、ユーザおよびグループから各アカウントへのアクセス権限を AWS SSO で管理しています。

AWS SSO を導入した当初は、ユーザごとの各アカウントへのアクセス権限の割り当ては AWS マネジメントコンソールで管理していました。 しかしながら、AWS SSO の利用者が社内で増加していくにつれて、各アクセス権限の割り当てを AWS マネジメントコンソール上で手動運用するのが難しくなってきました。 これは、AWS SSO のコンソールではアクセス権限の割り当ての一覧性がよいとは言えず、必要以上に強い権限が割り当てられていたとしてもそれに気づくことができなくなってきたためです。 また、社内で管理している AWS アカウントの数が AWS SSO 導入時より増加したことも、手動運用が難しくなってきたことの要因の一つです。 さらに、AWS SSO の API が公開されていなかったため、この問題は我々だけではどうしようもありませんでした。

そのような事に悩んでいたところ、AWS SSO の API が公開されたという嬉しいニュースが飛び込んできました。

aws.amazon.com

これにより、AWS SSO の管理方法として、AWS マネジメントコンソール以外の選択肢を得ることができました。

また、ほどなくして、 Terraform Provider AWS でも AWS SSO がサポートされました。

github.com

そこで、AWS SSO に関するリソースを全て Terraform で記述し、GitHub のリポジトリで管理することにしました。 これは、以下の4つを目的としています。

  1. Git で管理することで、変更履歴を証跡として残すため
  2. 新たなアクセス権限付与の依頼を GitHub の PR で受け付けることで、PR の Description に変更理由を証跡として残すため
  3. コード化することで、既存のアクセス権限を可視化し、棚卸しを容易にするため
  4. 「アクセス権限の割り当て依頼」というコミュニケーションコストを軽減し、円滑に業務を行うため

上記の目的のうち、1つ目と2つ目の「証跡を残す」という点は達成することができました。 しかしながら、Terraform Provider AWS でサポートしているリソース aws_ssoadmin_account_assignment をそのまま使うとオプションの指定などが非常に冗長であったため、新たに「PR が面倒」という問題が発生しました。 また、コード量の肥大化により一覧性も損なわれ、棚卸しも引き続き困難な状況でした。 すなわち、Infrastructure as Code を実現することにより、新たな問題が発生しているという本末転倒な状況に陥ってしまいました。

f:id:rakiyoshi:20210817151617p:plain
社内で冗長であると問題視されていた

そこで、Terraform モジュールを作成して冗長さを吸収することで、アクセス権限の割り当てを簡潔に記述ができるようしようという事になりました。

Terraform モジュールの説明

ここでは、本 Terraform モジュールがどのようなものであるかを簡単に解説することで、得られるメリットについて説明します。 詳細な Terraform モジュールの使用方法や実装などは、Terraform Registry 上のドキュメントGitHub リポジトリを御覧ください。

本 Terraform モジュールの基本的な使い方は、speee/sso-assignment/aws モジュールの引数に以下の形式のオブジェクトを渡します。

{
  "<AWS アカウント名>" = {
    "groups" = {
      "<AWS SSO グループ名>" = [
        "<AWS SSO 権限セット名>",
      ]
    },
    "users" = {
      "<AWS SSO ユーザ名>" = [
        "<AWS SSO 権限セット名>",
      ]
    }
  },
}

例えば、以下のように権限を割り当てる場合について考えます。

  • account1 のアクセス権限
    • 管理者(AdministratorAccess)権限
      • SystemAdministratorGroup のメンバー
      • alice@example.com ユーザ
    • 読み取り専用(ReadOnlyAccess)権限
      • ManagerGroup のメンバー
  • account2 のアクセス権限
    • 読み取り専用(ReadOnlyAccess)権限
      • bob@example.com ユーザ

これを Terraform モジュールを使用せずに aws_ssoadmin_account_assignment でそのまま記述すると、以下のように書く必要がありました。

# assignments.tf

resource "aws_ssoadmin_account_assignment" "account1_administrator_systemadministrator" {
  instance_arn       = local.instance_arn
  permission_set_arn = aws_ssoadmin_permission_set["AdministratorAccess"].arn

  principal_id   = data.aws_identitystore_group["SystemAdministratorGroup"]id
  principal_type = "GROUP"

  target_id   = [for x in local.accounts : x.id if x.name == "account1"][0]
  target_type = "AWS_ACCOUNT"
}

resource "aws_ssoadmin_account_assignment" "account1_administrator_alice" {
  instance_arn       = local.instance_arn
  permission_set_arn = aws_ssoadmin_permission_set["AdministratorAccess"].arn

  principal_id   = data.aws_identitystore_user["alice@example.com"]id
  principal_type = "USER"

  target_id   = [for x in local.accounts : x.id if x.name == "account1"][0]
  target_type = "AWS_ACCOUNT"
}

resource "aws_ssoadmin_account_assignment" "account1_readonly_manager" {
  instance_arn       = local.instance_arn
  permission_set_arn = aws_ssoadmin_permission_set["ReadOnlyAccess"].arn

  principal_id   = data.aws_identitystore_group["ManagerGroup"]id
  principal_type = "GROUP"

  target_id   = [for x in local.accounts : x.id if x.name == "account1"][0]
  target_type = "AWS_ACCOUNT"
}

resource "aws_ssoadmin_account_assignment" "account2_readonly_bob" {
  instance_arn       = local.instance_arn
  permission_set_arn = aws_ssoadmin_permission_set["ReadOnlyAccess"].arn

  principal_id   = data.aws_identitystore_user["bob@example.com"]id
  principal_type = "USER"

  target_id   = [for x in local.accounts : x.id if x.name == "account2"][0]
  target_type = "AWS_ACCOUNT"
}

▶割り当て以外の記述 (クリックで展開)

# data.tf

data "aws_ssoadmin_instances" "instances" {}
data "aws_organizations_organization" "organization" {}
locals {
  instance_arn      = tolist(data.aws_ssoadmin_instances.instances.arns)[0]
  identity_store_id = tolist(data.aws_ssoadmin_instances.instances.identity_store_ids)[0]
  accounts          = data.aws_organizations_organization.organization.accounts
}
data "aws_identitystore_group" "system_administrator" {
  identity_store_id = local.identity_store_id
  filter {
    attribute_path  = "DisplayName"
    attribute_value = "SystemAdministratorGroup"
  }
}
data "aws_identitystore_group" "manager" {
  identity_store_id = local.identity_store_id
  filter {
    attribute_path  = "DisplayName"
    attribute_value = "ManagerGroup"
  }
}
data "aws_identitystore_user" "alice" {
  identity_store_id = local.identity_store_id

  filter {
    attribute_path  = "UserName"
    attribute_value = "alice@example.com"
  }
}
data "aws_identitystore_user" "bob" {
  identity_store_id = local.identity_store_id

  filter {
    attribute_path  = "UserName"
    attribute_value = "bob@example.com"
  }
}

割り当てに関する記述だけでもこれだけ長くなってしまいます。 また、コードが構造化されていないため非常に読みづらいです。 この問題は、当然ながら割り当ての数が増えるほど大きくなっていきます。

そこで、同様のリソースを本 Terraform モジュールを使用して記述すると以下のようになります。

# terraform.tfvars

assignments = {
  "account1" = {
    "groups" = {
      "SystemAdministratorGroup" = [
        "AdministratorAccess",
      ],
      "ManagerGroup" = [
        "ReadOnlyAccess",
      ],
    },
    "users" = {
      "alice@example.com" = [
        "AdministratorAccess",
      ],
    },
  },
  "account2" = {
    "users" = {
      "bob@example.com" = [
        "ReadOnlyAccess",
      ],
    },
  },
}

▶割り当て以外の記述 (クリックで展開)

# main.tf

data "aws_ssoadmin_instances" "instances" {}
data "aws_organizations_organization" "organization" {}
locals {
  instance_arn      = tolist(data.aws_ssoadmin_instances.instances.arns)[0]
  identity_store_id = tolist(data.aws_ssoadmin_instances.instances.identity_store_ids)[0]
  accounts          = data.aws_organizations_organization.organization.accounts
}
variable "assignments" {
  type        = map(map(map(list(string))))
  description = "terraform.tfvars で値を設定する変数"
}

module "assignments" {
  source  = "speee/sso_assignments/aws"
  version = "1.0.0"

  instance_arn      = local.instance_arn
  identity_store_id = local.identity_store_id

  organization_accounts = local.accounts

  assignments = var.assignments
}

ユーザおよびグループから各 AWS アカウントに割り当てるアクセス権限の対応が簡潔に表現できていますね。 また、冗長な引数の指定を何度も記述する必要が無いため、行数も削減できています。

AWS アカウントの規模が大きくなるほど、この Terraform モジュールによる恩恵も大きくなっていきます。 Speee では、アカウントへのアクセス権限の割り当てに関するコード量だけで約7000行もありましたが、これを約1500行まで減らすことができました。 約1/4までの削減を実現しています。

導入方法と sso-importer の解説

AWS SSO のリソースを Terraform で管理するにあたって、既に AWS SSO を導入済みの環境においては大量の terraform import は避けられません。 そこで、半自動で terraform import を行うための CLI ツール sso-importer も公開しました。

github.com

こちらの CLI ツールは以下のコマンドでインストールします。

git clone https://github.com/speee/terraform-aws-sso-assignment-importer
npm install
npm run build
npm install -g .

sso-importer は、リポジトリ内にある example ディレクトリ内の例を使用して簡単に動作確認を行うことができます。 まず、example/custom を適当なディレクトリにコピーします。 そして、以下のコマンドを実行すると、本 Terraform モジュールを使用した AWS SSO のリソースの Terraform 化が可能です。 ※ 実行時は、 ${AWS_SSO_REGION} に AWS SSO を利用開始したリージョン名を指定してください。

sso-importer import \
  --sso-region ${AWS_SSO_REGION} \
  --assignment-name sample \
  --accounts ${AWS_ACCOUNT_ID_1} ${AWS_ACCOUNT_ID_2} ...

sso-importer は、実際のリソースに対応する sample.auto.tfvars (variables.tf 内の変数 assignments_sample の値) を生成し、terraform import を実行します。
実行後は同じディレクトリに terraform.tfstate が生成されます。 terraform.tfstate が生成された状態で terraform plan を実行し、差分が出ていないことを確認することで、正常に Import できていることを確認できます。

まとめ

AWS SSO の権限割り当てに関するリソースを簡潔に記述するために開発した Terraform モジュールについて説明しました。

Terraform で AWS SSO のリソースを管理することで、変更履歴を Git で管理できるようになるため、アクセス権限の棚卸しが容易になりました。 また、社内でのアクセス権限の割り当てリクエストを PR で受け付けることで、変更履歴だけでなく変更理由もDescription に証跡として残すことができます。

今回公開した Terraform モジュール speee/sso-assignment/awsterraform import のための sso-importer は OSS として公開しております。 Issue でのご意見や PR をお待ちしております。