AWS SSO を活用しているなら aws-sso-util を使おう

2021/04/30に公開

tl; dr

aws-sso-util を使うとコマンド一発で ~/.aws/config が生成できたりして便利なので使うべし。

AWS SSO とは

皆さん、AWS Single Sign-On (AWS SSO) というサービスを利用されていますか。

AWS SSO は 公式ページ によると下記のような記載がありますが、もっぱら前者の複数の AWS アカウントのアクセスに利用している方が多いでしょう。

複数の AWS アカウントとビジネスアプリケーションへのアクセスの一元的な管理を容易にし、割り当てられたアカウントとアプリケーションのすべてに対する 1 か所からのシングルサインオンアクセスをユーザーに提供できるようにする AWS のサービスです。

また、AWS CLI v2 からは ~/.aws/config に下記のような設定することで、CLI 作業でも AWS SSO による認証・認可が可能になりました[1]。ちょっと手で設定するのは面倒な記述量なのですが、一度設定するだけで安全に必要な環境に適切な権限でアクセスが出来るようになり、想像している以上に便利です。

[profile my-dev-profile]
sso_start_url = https://my-sso-portal.awsapps.com/start
sso_region = us-east-1
sso_account_id = 123456789012
sso_role_name = readOnly
region = us-west-2
output = json

問題点

実際に AWS SSO を運用し始めると様々な事情で AWS SSO の AWS アカウントとアクセス権限セットが増えていくことになります。もちろん、権限管理において適切な棚卸しが必要なのですが、複数プロジェクトを横断するようなロールやポジションにいる方は、どうしても AWS アカウントやアクセス権限セットが増えがちです。

  • 関わっているプロジェクトの開発フェーズ (開発・本番 etc.) 毎の AWS アカウント
  • 職務分掌による統制を効かせるための AWS アカウントやアクセス権限セット
  • 過去に関わったプロジェクトにおける有識者枠のアクセス権限セット (ReadOnlyAccess)

どうでしょう。AWS SSO を活用されている方は、1つくらいは心当たりがあるのではないでしょうか。また、前述の通り、ちょっと手で設定するのは面倒な記述量です。

37アカウント!

そして、各プログラミング言語の SDK での AWS SSO への対応状況はまちまちです。例えば、AWS CLI 自体が依存している AWS SDK for Python (boto3) が AWS SSO による認証・認可に対応したのは、2020 年 6 月の1.14.0です。私は主に Python 3 で開発することが多いので他言語の SDK の対応状況を把握していません。しかし、SDK レベルで AWS SSO による認証・認可に対応していないというのは、マルチアカウント運用下でちょっとしたツールを開発するなどといったニーズにおいて、なかなかしんどいものがありました。

解決策

  • 大量の AWS アカウントやアクセス権限セット
  • プロファイル設定が面倒
  • SDK やツールが AWS SSO による認証・認可に対応していない場合がある

こういった問題点を解決してくれるのが、aws-sso-util です。

https://github.com/benkehoe/aws-sso-util

インストール方法

README.md の Quickstart に書いてある通り、pipx でインストールするだけです。pipx を使ってない方は普通に pip で入れるとよいでしょう。

$ pipx install aws-sso-util

大量の AWS アカウントやアクセス権限セット & プロファイル設定が面倒

一行で解決できます。

(実行例)
$ aws-sso-util configure populate -u https://<example>.awsapps.com/start --sso-region us-east-1 --region us-east-1
Logging in to https://<example>.awsapps.com/start
Gathering accounts and roles
Writing N profiles to /home/<username>/.aws/config

プロファイル名は、デフォルトの動作では AWS アカウント名の A-Za-z0-9-._ ではない文字列は - に変更された上で、アクセス権限セット名と . で連結したものになります[2]。実際に生成されたプロファイルを見てみましょう。アカウント名に含まれている - に変更されていることが分かります。

.aws/config
[profile Organizations-Management.AdministratorAccess]
sso_start_url = https://<example>.awsapps.com/start
sso_region = us-east-1
sso_account_name = Organizations Management
sso_account_id = 123456789012
sso_role_name = AdministratorAccess
region = us-east-1
credential_process = aws-sso-util credential-process --profile Organizations-Management.AdministratorAccess
sso_auto_populated = true

自動的に設定されるのは良いのですが、常用するにはプロファイル名が長すぎます。そこで、aws-sso-util では AWS アカウント名・ID、アクセス権限セット、リージョンといった情報からプロファイル名を生成するスクリプトを指定できるようになっています[3]。下記にサンプルスクリプトを示します。

profile_process.py
#!/usr/bin/env python3
import sys
import re

sep = "-"

(
    account_name,
    account_id,
    role_name,
    region_name,
    short_region_name,
) = sys.argv[1:6]

# サンプルスクリプトということもあり、辞書によるマッピングで。
# 実際には法則性のあるアカウント名を付けて機械的に処理すると良いでしょう。
# 動作に問題がないことを確認するため、一度だけ下記のパラメーターで起動されるので、
# これを適切に処理できるようにしておく必要があります。
# sys.argv[1:6] = ['foo', 'bar', 'baz', 'us-east-1', 'usea1', 0, 1]
account_map = {
    'foo': 'foo',
    'Organizations Management': 'mgmt',
    'Member Account 1': 'member1',
}
role_map = {
    'baz': 'baz',
    'AdministratorAccess': 'admin',
}

fields = [account_map[account_name], role_map[role_name]]

# aws-sso-util configure populate で --region, -r オプションを複数指定された場合、
# リージョン毎のプロファイル名に短縮形のリージョン名を追加する処理です。
# 短縮形のリージョン名は Availability Zone ID のプレフィックスと似ていますが、
# us-east-1 や us-west-1 では一致しないなど、aws-sso-util 独自形式なのが難点。
region_index, num_regions = (int(v) for v in sys.argv[6:8])
if region_index != 0:
    fields.append(short_region_name)

# プロファイル名を標準出力に表示する。
print(sep.join(fields))

一度、~/.aws/config を削除して、再生成してみます。私の環境は AWS SSO が東京リージョンでリリースされる前にバージニア北部でセットアップしたため、sso_regionus-east-1 です。一方で普段使いのリージョンは ap-northeast-1 となっています。こういった場合、--region オプションを付けると複数リージョンのプロファイルが生成されます。ではやってみましょう。

$ rm -f ~/.aws/config
$ chmod +x ./profile-process.py
$ aws-sso-util configure populate -u https://<example>.awsapps.com/start --sso-region us-east-1 \
    --region us-east-1 --region ap-northeast-1 \
    --raw-account-names --profile-name-process ./profile-process.py
.aws/config
[profile mgmt-admin]
sso_start_url = https://<example>.awsapps.com/start
sso_region = us-east-1
sso_account_name = Organizations Management
sso_account_id = 123456789012
sso_role_name = AdministratorAccess
region = us-east-1
credential_process = aws-sso-util credential-process --profile mgmt-admin
sso_auto_populated = true

[profile mgmt-admin-apne1]
sso_start_url = https://<example>.awsapps.com/start
sso_region = us-east-1
sso_account_name = Organizations Management
sso_account_id = 123456789012
sso_role_name = AdministratorAccess
region = ap-northeast-1
credential_process = aws-sso-util credential-process --profile mgmt-admin-apne1
sso_auto_populated = true
...(以下省略)

クラウド推進組織などが AWS アカウント名を一定のルールで付けているのであれば、人間が入力しやすいプロファイル名を自動生成できますね。更に、~/.aws/config が機械的に生成できるので、CLI ベースの運用手順書を利用している現場では、各人が利用しているプロファイル名を意識する必要がなくなります。

SDK やツールが AWS SSO による認証・認可に対応していない場合がある

SDK やツールが AWS SSO による認証・認可に対応していない場合にも、aws-sso-util 自身が credential_process としての機能を有しています。credential_process は外部プロセスによって認証情報(アクセスキー ID、シークレットアクセスキー、セッショントークン)を取得する設定です。古くからある設定なので、多くの SDK が対応しているというメリットがあります[4]。もう一度、生成されたプロファイルを見てみましょう。credential_processaws-sso-util 自身が設定されていることが分かります。

.aws/config
[profile mgmt-admin]
sso_start_url = https://<example>.awsapps.com/start
sso_region = us-east-1
sso_account_name = Organizations Management
sso_account_id = 123456789012
sso_role_name = AdministratorAccess
region = us-east-1
credential_process = aws-sso-util credential-process --profile mgmt-admin
sso_auto_populated = true

credential_proecss に指定されたコマンドを実際に実行してみると、ドキュメント[4]に記載された通り JSON 形式で認証情報が取得できます。そして、その認証情報を利用すると、AWS SSO で認証・認可が出来ていることが確認できるます。

(実行例)
$ aws-sso-util credential-process --profile mgmt-admin > auth.json
$ export AWS_ACCESS_KEY_ID=$(jq -r .AccessKeyId auth.json)
$ export AWS_SECRET_ACCESS_KEY=$(jq -r .SecretAccessKey auth.json)
$ export AWS_SESSION_TOKEN=$(jq -r .SessionToken auth.json)
$ rm auth.json
$ aws sts get-caller-identity
{
    "UserId": "AROASAMPLEIAMIDENTIFY:username@example.com",
    "Account": "123456789012",
    "Arn": "arn:aws:sts::123456789012:assumed-role/AWSReservedSSO_AdministratorAccess_maybefnv1a64hash/username@example.com"
}

その他の機能

AWS SSO サインイン時にプロファイル指定が不要に

AWS CLI v2 で AWS SSO にサインインする場合、下記のように何らかのプロファイルを指定する必要があります。

$ aws sso login --profile member1-admin

実際には AWS SSO のユーザーポータルにサインインするわけですが、オプションとして sso_start_url を指定するのは長すぎる。そこで、なんらかのプロファイルを指定してサインインするという方式に至ったのでしょうが、若干の違和感があります。

そこで、aws-sso-util はプロファイルを指定せずに AWS SSO のユーザーポータルにサインインする機能を提供しています。~/.aws/config 内で指定されている sso_start_url を自動的に取得してサインインが行われます。

$ aws-sso-util login

レアなユースケースかと思われますが、複数の AWS SSO にまたがった ~/.aws/config の場合でも、全てのユーザーポータルを開く --all オプションが利用できます。その場合、既に認証済みのユーザーポータルは開かないという気が利いた仕様となっていました。

AWS CloudFormation の出力

AWS SSO は AWS CloudFormation による設定をサポートしていますが[5]、他の CloudFormation リソースと同様、なかなか冗長な記述が必要になっています[6]。結構な AWS アカウント数を抱えている環境でも、AWS SSO の設定は手順書ベースで行っているケースも多いのではないでしょうか。

aws-sso-util では、CloudFormation マクロによりクライアントサイドでのテンプレート生成する機能を提供していています[7]。例えば、こんな感じのテンプレートファイルを用意します。

aws-sso-config.yaml
Transform: AWS-SSO-Util-2020-11-08

InstanceArn: arn:aws:sso:::instance/ssoins-samplessoinsid01

Groups:
  - 1234567890-550e8400-e29b-41d4-a716-446655440000

PermissionSet:
  - Name: ReadOnlyAccess
    ManagedPolicies:
      - ViewOnlyAccess

OUs:
  - ou-ouid-sample01

RecursiveOUs:
  - ou-ouid-sample02

Accounts:
  - 123456789012

そして、下記のコマンドを実行すると、デフォルトでは ./templates ディレクトリ配下にテンプレートが生成されます。
# 今のところ、aws-sso-util admin 系のコマンドに --region オプションがないため、プロファイルの regionsso_region が異なると使い勝手が悪いです。
# それを避けるため、以降、mgmt-admin プロファイルを使用していきます。

$ export AWS_PROFILE=mgmt-admin
$ aws-sso-util admin cfn aws-sso-config.yaml

サンプルで上手く伝わらないのが残念ですが、多段の OU 配下にある AWS アカウントに対しても RecursiveOUs によってアクセス権限セットを割り当てられています。

templates/aws-sso-config.yaml
AWSTemplateFormatVersion: '2010-09-09'
Resources:
  PermSetReadOnlyAccess:
    Type: AWS::SSO::PermissionSet
    Properties:
      InstanceArn: arn:aws:sso:::instance/ssoins-samplessoinsid01
      Name: ReadOnlyAccess
      ManagedPolicies:
      - arn:aws:iam::aws:policy/ViewOnlyAccess
  Assignment0ED97B:
    Type: AWS::SSO::Assignment
    Metadata:
      SSO:
        AccountSourceOU: ou-ouid-sample01
        TargetName: Main Account
    Properties:
      InstanceArn: arn:aws:sso:::instance/ssoins-samplessoinsid01
      PrincipalType: GROUP
      PrincipalId: 1234567890-550e8400-e29b-41d4-a716-446655440000
      PermissionSetArn: !GetAtt 'PermSetReadOnlyAccess.PermissionSetArn'
      TargetType: AWS_ACCOUNT
      TargetId: '123456789012'
  AssignmentD97B0E:
... (以下省略)

InstanceArn は省略するとサインインしている AWS SSO ユーザーポータルから自動取得されます。コマンドで確認したい場合は下記を入力します。

$ aws sso-admin list-instances

OUsRecursiveOUs で指定する Organization Unit の ID は下記のように取得します。

$ ROOT_ID=$(aws organizations list-roots --query 'Roots[].Id' --output text)
$ aws organizations list-children --parent-id $ROOT_ID --child-type ORGANIZATIONAL_UNIT

ネスト構造にある OU では、--parent-id に指定する ID を親 OU のものにして、下位の OU を辿っていく必要があります。RecursiveOUs がいかに便利かが分かるでしょう。

GroupsUsersaws identitystore です。これは AWS CLI v2 側の問題ですが、aws identitystore 系のコマンドは使い勝手がイマイチでした。--filters でユーザーやグループの出力する際にフィルターが指定できます。しかし、現時点 (aws-cli/2.2.1) では list-usersAttributeName=UserName,AttributeValue=... しか指定できません。--list-groups も同様に、AttributeName=DisplayName,AttributeValue=... しか指定できません。そのため、list-userslist-groups を使っても、フィルターの都合でユーザーやグループが1つしか取得できないのです。AWS SSO 側はアクセスコントロールの属性を有効にしていますが[9]、これらの属性は現時点では利用できないようです。AWS CLI v2 側の仕様ではありますが、残念ですね。

(実行例)
$ aws identitystore list-users --identity-store-id d-1234567890 --filters 'AttributePath=email,AttributeValue=username@example.com'

An error occurred (ValidationException) when calling the ListUsers operation: Only filter with value 'UserName' is allowed for this operation

また、aws-sso-util で現在の割り当て状況を CSV 形式で出力できるようになっています。こちらは権限管理の棚卸しに役立つことでしょう。

$ aws-sso-util admin assignments

最後に

いかがだったでしょうか。
// 一度書いてみたかった。

AWS Control Tower が東京リージョンで利用できるようになったこともあり[8]、今後のマルチアカウント環境において AWS SSO は欠かせないサービスになっています。これを機会に aws-sso-util を導入して、ズバッと ~/.aws/config を生成しましょう。

Discussion