スタートアップにおけるAWSへのSSOや権限管理の整備
2024年10月からIVRyで働いている @abnoumaru です。
10といえば...TVアニメ「SHIROBAKO」10周年おめでとうございます!🍩😭🌙🎉🐻👏
はじめに
IVRyのメンバー数は、ここ1年で急激に増加しています。人数増加に伴い、コーポレートエンジニアによるEntra IDを利用したユーザ管理が迅速に進んでいます。また、エンジニアメンバーについても例外なく増えており、Q毎の人数の推移は以下のようになっています。
エンジニアが増えるということは、各種XaaSへアクセスするメンバーも増えてきていて、アカウントやユーザの管理、オンボーディング等が煩雑になります。このため、仕組みによる解決やルールの整備が求められます。
これらの課題を解決すべく自分はEntra IDとIAM Identity Centerを用いたAWSへのSSOや権限管理の整備を、Entra ID導入に並行して進めてきました。
この記事では以下の流れで、最近取り組んだことおよび今後取り組んでいきたいことを書いていきます。
- Entra IDとIAM Identity Centerを利用したSSOの全体感
- 整備の上で悩んだIAM Identity CenterやCustomer Managed PolicyのTerraform管理
- 今後の展望
Entra IDとIAM Identity Centerを利用したSSOを整備した
今回初めて提供する側としてEntra IDの概念に触れ、IAM Identity Centerを構築しました。しかし、初見では概念がよくわからなかったり、すんなりいかずに詰まったところもありました。実際に触ってみて「一度設定してしまうとあまり触らないので知識が溜まりづらい」もしくは「触る機会すらない」可能性が高いだろうな、と思いました。また「他社ではどのように管理しているか?具体的に知ることができない部分である」とも感じました。
このように実装時に悩んだ経験から「記事に書き起こすことで誰かの役に立てるのではないか?」という気持ちになったのが、記事にすることにしたきっかけです。
全体像
Entra IDとIAM Identity Centerの全体図は以下のとおりです。
IAM Identity CenterではSCIMがサポートされているので、Entra ID側で実施した変更(グループ作成、ユーザ追加・削除…)が自動でIAM Identity Centerにプロビジョニングされます。プロビジョニングされたグループに対してPermission SetsとAWSアカウントをどのように紐づけるか?が今回の主題です。
「そもそもIAM Identity Centerとは?」「Permission Sets(アクセス許可セット)の概念、AWSアカウントやグループとの関係」「AWS Managed PolicyとCustomer Managed Policyという種類がある」などのイメージを掴む段階で以下の記事に助けられました。
また先ほど紹介した図の中にある「誰が、どの権限で、どのアカウントで、アクセスするか設定する」は以下の記事に参考に記載したものです。実現したかったこと
IVRyのAWSの利用方法を考えたときに、今回は以下のように実現したいことがありました。(よくある話ではあると思いますが、言葉にしてみています。)
- メンバーの役割によってアクセスできるAWSアカウントや権限を柔軟に制御したい
- ex. 検証/テスト用AWSアカウントにはマネージドなポリシーをアタッチして普段なるべく自由に利用してほしい
- ex. 本番用AWSアカウントには「普段状況確認等に利用する読み取り用ポリシー」と「有事の際や作業時に利用する必要最低限の権限に絞られたポリシー」など利用シーンに応じた権限を割り当てる
- 役割をまたいで利用したいAWSリソースがある
- ex. セールスやエンジニアがCSVをアップロードすることで一括処理できるようなS3バケット
- ex. LPサイトのS3バケットとCloudFront(Invalidationのみ)
また、上記に加えて、今後グループやアカウントが増えていく可能性も見越しました。このため抽象化して変数部分を更新するだけで柔軟に「Permission Setsの作成」と「グループに対するAWSアカウントとPermission Setsの割り当て」ができるようにTerraformを書くことを意識しました。
IAM Identity CenterやCustomer Managed PolicyのTerraform管理
先に話しておかなければならないことがあるのですが、前章で宣言した通り元気にTerraformを書いていき、すべてが終わってからとても重要な事に気づきました。moduleがあります。
公式moduleを使いたい人はそちらを参考にしていただき、実際にTerraformを書く過程で考えたことに注目いただけると幸いです。IAM Identity CenterのTerraform
IAM Identity CenterのTerraformのコード量は多くなかったので、変数部分を仮の値にして1枚の .tfファイルとしてGistに公開してみました。あまりにも複雑なことをしていなければそのまま使えると思います。
Permission Setsのコード
まず、エンジニア向けのローカル変数の定義を例にPermission Setsのコードについて解説します。Permission Setsに複数のポリシーを定義しています。
50 custom_developers = {
51 name = "CustomDevelopersAccess"
52 description = "For Developers."
53 attachment_policy = [
54 { "type" = "managed", "policy_name" = "ReadOnlyAccess" },
55 { "type" = "customer_managed", "policy_name" = "CustomDevelopersAccess" },
56 ]
57 }
Customer Managed PolicyはマネージドなReadOnlyAccessに必要なポリシーを加えた権限にしたいです。このため「Data Source aws_iam_policy でマネージドなReadOnlyAccessのポリシーを取得し、Data source aws_iam_policy_document の source_policy_documents 内で取得したマネージドなポリシーと追加が必要なポリシーをマージするか?」と最初考えました。しかしながらIAMポリシーの文字数制限( LimitExceeded: Cannot exceed quota for PolicySize: 6144 )に引っかかりどうすればよいか悩みました(CustomerDevelopersAccessの作成方法については後述)。よく調べるとPermission Setsには複数のポリシーを紐づけることが出来るとわかったので、Permission Sets毎にpolicyのリストを作って動的にポリシーをアタッチすればよい、という考えにたどり着きました。元々自分はPermission Setsにはひとつのポリシーしか紐づけられないと勘違いしていたことがしくじりです。
また、AWS Managed PolicyとCustomer Managed PolicyではPermission SetsにポリシーをアタッチするTerraform Resourceが異なります。このためアタッチするポリシーを抽出できるようにtype(managed、customer_managed)も定義しました。
60 # typeがmanagedなものを抽出
61 managed_policies = {
62 for key, value in local.permission_sets : key => value if anytrue([for p in value.attachment_policy : p.type == "managed"])
63 }
64
65 # typeがcustomer_managedなものを抽出
66 customer_managed_policies = {
67 for key, value in local.permission_sets : key => value if anytrue([for p in value.attachment_policy : p.type == "customer_managed"])
68 }
151 resource "aws_ssoadmin_managed_policy_attachment" "permission_sets_with_managed_policy" {
152 for_each = local.managed_policies
153
154 instance_arn = tolist(data.aws_ssoadmin_instances.management.arns)[0]
155 permission_set_arn = aws_ssoadmin_permission_set.permission_sets[each.key].arn
156 managed_policy_arn = "arn:aws:iam::aws:policy/${each.value.attachment_policy[0].policy_name}"
157 }
158
159 resource "aws_ssoadmin_customer_managed_policy_attachment" "permission_sets_with_customer_managed_policy" {
160 for_each = local.customer_managed_policies
161
162 instance_arn = tolist(data.aws_ssoadmin_instances.management.arns)[0]
163 permission_set_arn = aws_ssoadmin_permission_set.permission_sets[each.key].arn
164 customer_managed_policy_reference {
165 name = [for p in each.value.attachment_policy : p.policy_name if p.type == "customer_managed"][0]
166 path = "/"
167 }
168 }
この書き方によりかなり柔軟にPermission Setsに権限を付与することが出来ました。
グループのコード
グループについてはローカル変数で以下のように定義しました。グループごとに紐づけたいAWSアカウントとPermission Setsを自由に組み合わせられるようになっています。
92 developers = {
93 group_id = "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
94 description = "For Developers"
95 permissions = [
96 { account_key = "production", permission_set_key = "custom_developers" },
97 { account_key = "production", permission_set_key = "read_only" },
98 { account_key = "staging", permission_set_key = "power_user" },
99 { account_key = "sandbox", permission_set_key = "power_user" },
100 ]
101 }
しかしながらTerraform Resourceからそのままではうまく扱えなかったので、以下の箇所でflattenしてからキーを追加しています。
112 # グループの権限情報をリストにする
113 permissions_list = [
114 for group_name, group in local.groups : [
115 for permission in group.permissions : {
116 group_name = group_name
117 group_id = group.group_id
118 account_key = permission.account_key
119 permission_set_key = permission.permission_set_key
120 }
121 ]
122 ]
123
124
125 # 権限を一意に識別するためのキーを作成する
126 assignments = {
127 for perm in flatten(local.permissions_list) :
128 "${perm.group_name}-${perm.account_key}-${perm.permission_set_key}" => perm
129 }
Customer Managed Policyを生成するTerraform
次はCustomer Managed PolicyのTerraformについても解説します。(こちらのコードはGistに公開していません。)
例えば、前述の「セールスやエンジニアがCSVをアップロードすることで一括処理できるようなS3バケット」があるとき、セールス用のポリシーとエンジニア用のポリシーの aws_iam_policy_document の中で同じ定義を繰り返し書きたくないです。このため「CSVをアップロードすることで一括処理できるようなS3バケット」といった単位でポリシーを定義してsource_policy_documentsでまとめるような書き方にしてみました。
具体的には、Permission Setsで紐づける各アカウント側で作成するCSV用のバケットにアクセスするポリシーは以下のように書きました。
data "aws_iam_policy_document" "management_csv_bucket" {
(割愛)
}
# LP関連のS3やCloudFrontにアクセスするポリシー
data "aws_iam_policy_document" "management_lp" {
(割愛)
}
# 緊急時にCode Deployを制御するためのポリシー
data "aws_iam_policy_document" "emergency_code_deploy" {
(割愛)
}
# エンジニア向けの権限として必要なポリシーを選択してマージ
data "aws_iam_policy_document" "developers" {
source_policy_documents = [
data.aws_iam_policy_document.management_csv_bucket.json,
data.aws_iam_policy_document.management_lp.json,
data.aws_iam_policy_document.emergency_code_deploy.json,
]
}
IAM Identity Centerで設定するCustomer Managed Policyの定義はこうです。
resource "aws_iam_policy" "developers" {
name = "CustomDevelopersAccess"
policy = data.aws_iam_policy_document.developers.json
}
これらのポリシーはPermission Setsで紐づけたいAWSアカウントごとに作成する必要がある点に気をつけてください。
なおこれらの整備のお陰で、このブログを書いてる公開2日前、マーケメンバー向けのLPサイト用の権限追加の依頼がありましたが、フロントメンバー向けに定義していたポリシーを利用することで迅速にグループとユーザ追加の対応できました。
将来的にはPRベースで権限を申請できるような文化を作っていきたいと考えています。(まだ生まれたばかりなので浸透はこれからですが、すでに権限追加してみましたというPRはあった。)
今後の展望
これまでAWSへのSSOや権限整備、またその管理方法について話してきました。
しかしながら課題はまだまだあります。
例えばインフラのコードはTerraformで管理されておりAtlantis経由でGitOpsで実行されているため、インフラを直接触る機会はかなり少ないですが、まだ一部緊急対応用の権限や業務上必要な権限を割り当てている部分があります。次はこれを一時的に付与するような仕組みや緊急時の対応方法の整備を実施していきます。既存の仕組みやクラウドで提供された機能を利用する方法も考えましたが、ChatOpsでシンプルに権限付与、承認、証跡の記録が残せる、組織の文化や働き方にあった仕組みの検討が始まっています。
なお、クラウドアクセスの成熟度については、以下の資料がシンプルにまとまっていて非常に参考になると個人的に思っていて、この資料を参考にしながら自分たちの現在地を確認したり、今後の打ち手を検討してみました。
最後に
この記事では技術的な話や方針について書きましたが、現状の整理や方針検討、切り替えなど社内のメンバーにたくさん協力いただく必要がありました。みなさん諸々対応してくれてありがとうございました。
また、目先ではZero Touch Productionの中にもあるような自動化をより推進したり、長期的には大きくなったサービスや組織をセキュリティを保つ仕組みを一緒に検討して創り上げていく仲間がもっと必要です。こういう方法がある、自分だったらこうする!という情報があればぜひ話してみたいです。
IVRyでは積極的にエンジニアメンバーを採用しています。是非一緒に働けたら嬉しいです。
Discussion