AWSアカウントの認証をGsuiteに統合するために

## はじめに

現在私が勤めている[Sassor](https://sassor.com)では、システムのインフラを主にAWSで構成しています。

プロジェクトを進めるうちに、コスト管理やセキュリティの観点から、プロジェクト毎にAWSアカウントを分けて運用するようになりました。そしてインフラを管理するメンバーがそこまで多くないこともあり、AWSアカウントを作るたびに、利用メンバーのアカウントをIAMユーザとしてそれぞれのAWSアカウントに作成していました。

しかし、気付けばAWSアカウントが2桁近くまで増えてきていたため、人が出入りがあると、アカウントの追加、削除にそれなりの時間がかかるようになっていました。今後のことも考えこの際、ユーザ管理方法を見直すことにしました。

SassorではすべてのメンバーにGsuiteアカウントを用意することになっていたので、GsuiteをIDプロバイダ、AWSをサービスプロバイダとしたSAML認証の仕組みの導入を検討しました。なお、Sassorでは複数のAWSアカウントを AWS Organization を用いた組織アカウントとして管理しており、マスターアカウント、メンバーアカウントへのそれぞれのアクセスについてもそれぞれ検討しました。

## 達成したいゴール

以下の達成が今回のゴールです。

- SAML認証によりマスターアカウントにGsuiteアカウントでアクセスできること
- マスターアカウントにアクセス後、スイッチロールによりメンバーアカウントにアクセスできること
- 上記について、以下のアクセス手段が簡単に利用できること
  - AWSコンソール経由
  - AWSクライアント経由（AWS SDKを利用したもの）

これらを達成するために以下の流れで設定、検証を行います。

- マスターアカウントへのアクセス許可の設定手順（SAML認証利用）
- メンバーアカウントへのアクセス許可の設定手順（スイッチロール利用）
- AWS SDK経由の利用を簡略化する方法
- 様々なAWSクライアントにおけるAWSリソースへのアクセス方法の検証

## 設定・検証

### マスターアカウントへのアクセス許可の設定手順（SAML認証利用）

早速ですが、こちらの手順については説明を割愛します。

以下のAWSの公式ブログが非常に分かりやすいのでこちらを参照してください。

- https://aws.amazon.com/jp/blogs/startup/techblog-saml-gsuite/

より詳しく仕組みを知りたい場合は以下のドキュメントが参考になります。

- https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/id_roles_providers_enable-console-saml.html

Googleサイドのドキュメントにも以下に手順の説明がありますが、AWSサイドのドキュメントの方が分かりやすいです。

- https://support.google.com/a/answer/6194963

なお設定方法は詳しく書きませんが、ユーザ毎にある程度権限を限定するために以下の3つのAssumeロールを作成しています。

- GSadmin: AdministratorAccessポリシーを付与
- GSpoweruser: PowerUserAccessポリシーを付与
- GSreadonly: ReadOnlyAccessポリシーを付与

この資料ではGSadminしか使用していませんが、実際にはユーザごとに割り当てるロールを変更する運用を想定しています。

### メンバーアカウントへのアクセス許可の設定手順（スイッチロール利用）

こちらの手順についても説明を割愛します。

AWS Organization を利用して作成したメンバーアカウントには「OrganizationAccountAccessRole」が自動的に作成されますが、こちらを利用する場合は特別な設定は必要ありません。

以下のAWSの公式ドキュメントが非常に分かりやすいのでこちらを参照してください。

- https://docs.aws.amazon.com/ja_jp/organizations/latest/userguide/orgs_manage_accounts_access.html#orgs_manage_accounts_access-cross-account-role

スイッチロールする際の注意点としては、

- マスターアカウントへのSAML認証時に割り当てるAsumeロールがメンバーアカウントに用意したAsumeロールへのアクセス権(AssumeRole権限)を持つこと

ですが、マスターアカウントへのSAML認証時に管理者権限（AdministratorAccess権限）を持たせればAssumeRole権限も含まれているため、今回の検証では管理者権限をもつロール（GSadmin）を用意しました。

スイッチロールによってアクセス可能なメンバーアカウントを限定したいといった要件がある場合は、細かい制限を設けたAssumeロールを作成すればよいだけです。

なお、先に述べた「OrganizationAccountAccessRole」はメンバーアカウントの管理者権限（AdministratorAccess権限）をもつため、メンバーアカウントの権限を制限したい場合は、メンバーアカウント側にも新たにAssumeロールを作成する必要があります。

今回は手始めに以下の簡単な条件で検証しました。

- マスターアカウントへのアクセス
  - マスターアカウントに対してGsuiteをIdpとしたSAML認証によりアクセス
    - SAML認証時に割り当てられるロールが管理者権限（AdministratorAccess）を持つこと
      - GSadminを利用
- メンバーアカウントへのアクセス
  - (1) マスターアカウントに対してGsuiteをIdpとしたSAML認証によりアクセス
    - SAML認証時に割り当てられるロールが管理者権限（AdministratorAccess）を持つこと
      - GSadminを利用
  - (2) メンバーアカウントのAssumeロールへスイッチロール
    - スイッチしたAssumeロールが管理者権限（AdministratorAccess）を持つこと
      - AWS Organizationで自動作成された「OrganizationAccountAccessRole」を利用

### AWS SDK経由の利用を簡略化する方法

ここまでの設定を行うとAWSコンソールへのアクセスは実現できますが、AWS SDK経由でアクセスする場合はには以下の2ステップでの手順が必要です。（ここで AWS SDK と表現しているのはサードパーティのクライアントも含めるためです。）

- STSを利用した一時的セキュリティ認証情報の取得
- 一時的セキュリティ認証情報を利用したアクセス

一時的セキュリティ認証情報について詳しく知りたい場合は以下を参照してください。

- https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/id_credentials_temp.html

一時的セキュリティ認証情報については、一度取得するとデフォルト1h、最大で12h有効なものを取得できます。取得した認証情報をAPIに認識させる方法には環境変数を利用する方法やクレデンシャルファイルに登録する方法などがありますが、1〜12h毎にこの作業をツールを利用せずに行うのは非常に面倒です。（AWS CLI を利用した認証情報の取得方法に興味がある方は[こちら](https://docs.aws.amazon.com/ja_jp/cli/latest/reference/sts/assume-role.html)を参照）

そこで今回はこの一時的セキュリティ認証情報を取得し、クレデンシャルファイル(~/.aws/credentials)に登録するまでの流れを簡略化してくれるツール「[aws-google-auth](https://github.com/cevoaustralia/aws-google-auth)」を利用します。

詳しい利用方法は以下のサイトを参照してください。

- https://blog.kriation.com/2019/aws-cli-via-saml-02.html

なおこのツールでは一時的セキュリティ認証情報の有効期限を指定できますが、AWS側の制約で指定可能な期限はデフォルトが1時間です。この制限を延長したい場合は、以下の手順でロール毎に有効期限の最大値を変更する必要があります。

- https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/id_roles_use.html#id_roles_use_view-role-max-session

また設定ファイルに記載するIDはGoogleAdminのコンソール画面から以下の様に取得します。

- IDプロバイダID
  - アプリ > SAML アプリ > Amazon Web Services の設定画面 > サービスプロバイダの詳細 > 証明書を管理 > SSOのURLの「idpid=」に続く文字列
  - URL: https://accounts.google.com/o/saml2/idp?idpid=XXXXXXXX
- サービスプロバイダID
  - アプリ > SAML アプリ > Amazon Web Services の設定画面のURLの「service=」に続く数字
  - URL: https://admin.google.com/AdminHome?hl=ja#AppDetails:service=YYYYYYYY

### 様々なAWSクライアントにおけるAWSリソースへのアクセス方法の検証

AWSをコマンドラインから操作する際、実際の運用では公式のAWS CLI以外にも様々なクライアントを利用します。今回は社内で主に利用している以下のクライアントで利用方法を検証しました。

- [AWS CLI](https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/cli-chap-welcome.html)
- [AWS EB CLI](https://docs.aws.amazon.com/ja_jp/elasticbeanstalk/latest/dg/eb-cli3.html)
- [Serverless Framework](https://serverless.com/framework/docs/providers/aws/cli-reference/)
- [Terraform](https://www.terraform.io/docs/cli-index.html)
- [Roadworker](https://github.com/codenize-tools/roadworker)

検証には以下を設定例として利用します。

(セキュリティ上アカウントIDなどは適当なものに置き換えています)

- SAML認証
  - IDプロバイダのID: XXXXXXXX
  - サービスプロバイダのID: YYYYYYYY
  - セッションID名: kinoshita@sassor.com (Gsuiteのメインメールアドレス)
- マスターアカウント
  - AWSアカウントID: 111111111111
  - AWSコンフィグプロファイル名: sassor-saml
  - Assumeロール名: GSadmin
  - セッション有効期限: 43200
- メンバーアカウント
  - AWSアカウントID: 222222222222
  - AWSコンフィグプロファイル名: member-sw
  - Assumeロール名: OrganizationAccountAccessRole

上記の設定例をもとに設定とクレデンシャルを以下のようにそれぞれ用意します。

```console
$ vi ~/.aws/config
[default]

[profile sassor-saml]
region = ap-northeast-1
google_config.ask_role = False
google_config.google_idp_id = XXXXXXXX
google_config.role_arn = arn:aws:iam::111111111111:role/GSadmin
google_config.google_sp_id = YYYYYYYY
google_config.u2f_disabled = False
google_config.google_username = kinoshita@sassor.com
google_config.keyring = False
google_config.duration = 43200
google_config.bg_response = None

[profile member-sw]
region = ap-northeast-1
role_arn = arn:aws:iam::222222222222:role/OrganizationAccountAccessRole
source_profile = sassor-saml

※一時的セキュリティ認証情報の取得と登録（二段階認証あり）
$ aws-google-auth -p sassor-saml
Google Password:
MFA token: 123456
Assuming arn:aws:iam::111111111111:role/GSadmin
Credentials Expiration: 2020-04-08 07:30:00+09:00

$ cat ~/.aws/credentials
[default]

[sassor-saml]
aws_access_key_id = xxxx
aws_secret_access_key = xxxx
aws_security_token = xxxx
aws_session_expiration = 2020-04-08 07:30:00+09:00
aws_session_token = xxxx
```

以下、参考までに設定とクレデンシャルに関する公式ドキュメントです。

- https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/cli-chap-configure.html
- https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/cli-configure-role.html

以上を踏まえ、それぞれのクライアントでの利用方法を示します。

#### AWS CLI


`--profile`オプションにそれぞれプロファイル名を指定することで利用できます。

```console
[マスターアカウント]
$ aws --profile sassor-saml ec2 describe-instances

[メンバーアカウント]
$ aws --profile member-sw ec2 describe-instances
```

#### AWS EB CLI

AWS CLI 同様、 `--profile`オプションにそれぞれプロファイル名を指定することで利用できます。

```console
[マスターアカウント]
$ eb init --profile sassor-saml

[メンバーアカウント]
$ eb init --profile member-sw
```

#### ServerlessFramework

`--aws-profile`オプションにそれぞれプロファイル名を指定することで利用できます。

ただし、Asumeロールを利用するメンバーアカウントのプロファイルを指定する場合は`AWS_SDK_LOAD_CONFIG`環境変数を指定する必要があります。認証のためにクレデンシャルだけを見るのではなく、設定も見ますよという設定のようです。

ServerlesFrameworkはNode製なので、AWS SDKの以下の仕様に従っているようです。

- https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/loading-node-credentials-configured-credential-process.html

```console
[マスターアカウント]
$ sls deploy -s dev --aws-profile sassor-saml

[メンバーアカウント]
$ AWS_SDK_LOAD_CONFIG=1 sls deploy -s dev --aws-profile member-sw
```

#### Terraform

TerraformではAWSリソースを2つの目的で利用しています。

- deploy対象のリソースの状態管理
- deploy対象

リソースの状態管理については利用しなくても良いですが、SassorではS3を利用してるため確認を行いました。

##### deploy対象のリソースの状態管理

状態管理をS3で行う場合以下のような設定を用意します。

```console
$ cat backend.tf
terraform {
  backend "s3" {
    bucket         = "com.sassor.terraform"
    key            = "member/terraform.tfstate"
    profile        = "sassor-saml"
    region         = "ap-northeast-1"
    dynamodb_table = "TerraformStateLockTable"
  }
}
```

ここでbackendブロックの中に`profile`を直接記載すればデフォルトのプロファイル名として利用できます。ただし、プロファイル名は利用者毎に異なる運用をしている場合も多いので、この設定を上書きする方法も示します。

なお、初期化を行う前に排他制御に利用する(LockIDをHashキーに持つ)テーブルをDynamoDBに作成しておきます。

```console
$ aws --profile sassor-saml dynamodb create-table \
	--table-name TerraformStateLockTable \
	--attribute-definitions AttributeName=LockID,AttributeType=S \
	--key-schema AttributeName=LockID,KeyType=HASH \
	--provisioned-throughput ReadCapacityUnits=1,WriteCapacityUnits=1
```

以上を踏まえて、以下のようにプロジェクトディレクトリを初期化します。

指定方法に違いはありませんが、デフォルト値を利用したい場合オプションを省略します。

```console
[マスターアカウント]
$ terraform init

[メンバーアカウント]
$ terraform init -backend-config="profile=member-sw"
```

正しく設定されたかはローカルのtfstateファイルで確認できます。

```console
[マスターアカウント]
$ grep profile .terraform/terraform.tfstate
            "profile": "sassor-saml",
            
[メンバーアカウント]
$ grep profile .terraform/terraform.tfstate
            "profile": "member-sw",
```
  
##### deploy対象

deploy関連のコマンドについてはプロバイダの設定でプロファイルを指定します。

その際、プロファイル名は変数化することで任意の名前を指定できます。以下の例のようにデフォルトプロファイル名をすることも可能です。

```console
$ cat variables.tf
variable "profile" {
	type = string
	default = "sassor-saml"
}

$ cat provider.tf
provider "aws" {
  profile = var.profile
  region  = "ap-northeast-1"
}
```

指定方法に違いはありませんが、デフォルト値を利用したい場合オプションを省略します。

```console
[マスターアカウント]
$ terraform plan

[メンバーアカウント]
$ terraform plan -var "profile=member-sw"
```

なお、プロバイダの設定にprofileを記載しない場合、`AWS_PROFILE`環境変数を用いてプロファイル名を指定することができます。その場合は、Serverless Framework 同様に、Asumeロールを利用するメンバーアカウントのプロファイルを指定する場合は`AWS_SDK_LOAD_CONFIG`環境変数を指定する必要があります。TerraformはGo製なので以下の仕様に従っているようです。

- https://docs.aws.amazon.com/sdk-for-go/api/aws/session/
- https://www.terraform.io/docs/providers/aws/index.html#shared-credentials-file

変数で指定した場合になぜ必要がないのかはわかりません。。。

#### Roadwork

AWS CLI 同様、 `--profile`オプションにプロファイル名を指定することで利用できます。

ただしAssumeロールを利用するメンバーアカウントのプロファイルは指定することができません。

```console
[マスターアカウント]
$ eb init --profile sassor-saml
```

Sassorでは元々Route53をマスターアカウントでのみ利用していたため問題はありませんでしたが、メンバーアカウントでも利用したい場合は別の方法を考える必要がありそうです。

## おわりに

今回の検証で、SAML認証を利用しても工夫すれば比較的利用しやすい環境が構築できることが分かりました。SAML認証とスイッチロールを組み合わせた利用方法の説明がなかなか見つからなかったので、同じ悩みを抱えている方の参考になれば幸いです。

## 参考資料
- SAML認証
  - 利用できる連携項目
    - https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/id_roles_providers_create_saml_assertions.html
  - エラー対処
    - https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/troubleshoot_saml.html#troubleshoot_saml_invalid-response
- 一般的なスイッチロールの説明
  - https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/id_roles_use_switch-role-console.html
  - https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/id_roles_use_switch-role-cli.html
  - https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/id_roles_use_switch-role-api.html