Terraformでクレデンシャルを読み込む方法あれこれ

# これはなんですか

- TerraformでAWSクレデンシャル情報を読み込む方法について、簡単にまとめます。
- [ドキュメント](https://www.terraform.io/docs/providers/aws/index.html#authentication)を読めば全てわかる案件です。


# 前提

## IAMユーザーのクレデンシャル

AWSではIAMユーザーの認証情報として、アクセスキーが使えます。プログラムからAWSのリソースを操作する場合、このアクセスキーを使ってIAMユーザーとしての認証を行います。アクセスキーはアクセスキーIDとシークレットアクセスキーの組です。アクセスキーは一般にクレデンシャルと呼ばれます。

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


## IAMロール

AWSではIAMロールを使った認証も存在します。IAMロールは、IAMユーザーと同様にAWSリソースへのアクセス許可/拒否が設定できます。IAMユーザーと異なるのは、ロールをAWSのリソースにアタッチできる点です。IAMロールがアタッチされたリソースは、ロールの認証情報を利用して他のAWSリソースが操作できるようになります。

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

また、IAMロールは自らの権限をIAMユーザーに委譲できます。IAMユーザーは委譲された権限でAWSリソースを操作できます。

# Terraformでクレデンシャルを設定する

クレデンシャルの設定方法をいくつか挙げます。ほとんどTerraformドキュメントの日本語訳です。

https://www.terraform.io/docs/providers/aws/index.html#authentication


## .tf で `access_key` 属性と `secret_key` 属性を設定する

### 直接値を入力する

一番シンプルな方法は、`provider "aws" {}` に直接設定する方法です。次のように、 `access_key` 属性と `secret_key` 属性にそれぞれ、アクセスキーIDとシークレットアクセスキーを設定します。(この値はAWSドキュメントに書かれている例です。実際のクレデンシャルではありません)

```provider1.tf
provider "aws" {
  region     = "ap-northeast-1"
  access_key = "AKIAIOSFODNN7EXAMPLE"
  secret_key = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
}
```

### 変数を使う

アクセスキーをハードコードするのはクレデンシャル漏洩のリスクが高すぎるので、変数を利用して実行時に直接する方法もあります。

```provider2.tf
variable "access_key" {}

variable "secret_key" {}

provider "aws" {
  region     = "ap-northeast-1"
  access_key = var.access_key
  secret_key = var.secret_key
}
```

上記の設定をした場合、`terraform` コマンドの実行時に各変数の値を設定することになります。対話式のインターフェースで入力するか、`-var` オプションでコマンド入力時に指定します。

### `.tfvars` に値を埋め込む

コマンド入力のたびにクレデンシャルを投入するのは面倒です。これを回避する方法として、`.tfvars` ファイルを使う方法があります。`.tfvars` ファイルはTerraformの変数を設定するためのファイルです。デフォルトでは、`terraform.tfvars`、`terraform.tfvars.json`、`*.auto.tfvars`、`*.auto.tfvars.json` が自動的に読み込まれます。provider2.tf ファイル向けに `terraform.tfvars` を設定すると、次のようになります。

```terraform.tfvars
access_key = "AKIAIOSFODNN7EXAMPLE"
secret_key = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
```

Gitリポジトリでファイルを管理する際も、このように `.tfvars` にクレデンシャルを埋め込んで `.gitignore` で管理対象から除外することで、リモートリポジトリにクレデンシャルが載ることを防げます。

```:.gitignore
# terraform.tfvars を除外する
terraform.tfvars
```

## 環境変数を使う

以下の環境変数にクレデンシャルが設定されている場合、Terraformはそれを利用してIAMの認証を行います。

```provider3.tf
provider "aws" { }
```

```shell
$ export AWS_ACCESS_KEY_ID="AKIAIOSFODNN7EXAMPLE"
$ export AWS_SECRET_ACCESS_KEY="wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
$ export AWS_DEFAULT_REGION="ap-northeast-1"
$ terraform plan
```

AWS CLI 用に名前付きプロファイルが設定済みの場合は `AWS_PROFILE` も利用できます。

```shell
$ export AWS_PROFILE="your_project"
$ terraform plan
```

これら環境変数については、AWS CLIのドキュメントを確認してください。

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

名前付きプロファイルについては、以下ドキュメントを確認してください。

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

### direnvでディレクトリ単位で環境変数を設定する

上記の方法でクレデンシャル を渡す場合、実行の都度環境変数を設定するのは面倒です。また、異なるAWSアカウントを操作するような状況では、設定間違いの危険もあります。それを解決するツールとして、[direnv](https://github.com/direnv/direnv) があります。ディレクトリ内に `.envrc` というファイルを置いてdirenvを有効化することで、そのディレクトリ配下では `.envrc` 内で設定した環境変数が使えるようになります。設定方法についてはここでは省略します。

direnv環境下でGitリポジトリ管理をする場合は注意が必要です。 `.envrc` にアクセスキーIDとシークレットアクセスキーを直接設定する場合、`.envrc` を `.gitignore` で除外しないと、リモートリポジトリ上にクレデンシャルが公開されてしまいます。

```sh:.envrc
export AWS_ACCESS_KEY_ID="AKIAIOSFODNN7EXAMPLE"
export AWS_SECRET_ACCESS_KEY="wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
export AWS_DEFAULT_REGION="ap-northeast-1"
```

この場合は次のように `.gitignore` を設定しましょう。

```:.gitignore
.envrc
```

より良い方法は、AWS CLIで名前付きプロファイルを設定し、`.envrc` では `AWS_PROFILE` を指定するという方法です。これであれば最悪 `.envrc` を除外し忘れてもクレデンシャルの漏えいにはなりません。

```sh:.envrc
export AWS_PROFILE="your_project"
export AWS_DEFAULT_REGION="ap-northeast-1"
```

### 名前付きプロファイルを使う時の注意

[AWS CLI での IAM ロールの使用](https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/cli-configure-role.html) に書かれているように、AWSではIAMロールのアクセス情報で名前付きプロファイルが設定できます。このプロファイルをTerraformで使う場合は `AWS_SDK_LOAD_CONFIG` 変数に真値を設定する必要があります。direnvと併用する場合、.envrcは次のようになります。

```sh:.envrc
export AWS_PROFILE="your_project"
export AWS_DEFAULT_REGION="ap-northeast-1"
export AWS_SDK_LOAD_CONFIG=1
```

`AWS_SDK_LOAD_CONFIG` に真を表す値として `1` を設定しました。特別な制限がなければ、direnvでクレデンシャルを設定する場合はこの書き方をすれば良いと思います。

## クレデンシャルが書かれたファイルを指定する

AWS CLI では、デフォルトで `$HOME/.aws/credentials` にクレデンシャル情報を配置します。Terraformもデフォルトでこのファイルを参照して名前付きプロファイルを認識します。`provider "aws" {}` には `shared_credentials_file` 属性があり、これを使えばAWS CLIのデフォルトとは異なるクレデンシャル情報が読み込めます。

```provider4.tf
provider "aws" {
  region                  = "ap-northeast-1"
  shared_credentials_file = "/Users/tf_user/.aws/creds"
  profile                 = "your_project"
}
```

ここまで説明した方法には優先順位が設定されています。複数の設定がある場合は次の順で認証が行われます。

- `provider "aws" {}` の `access_key` 属性と `secret_key` 属性
- 環境変数
    - アクセスキーを指定する方が名前付きプロファイル指定より優先される※要検証
- `provider "aws" {}` の `shared_credentials_file` 属性

## ロールが設定されたEC2インスタンス上で実行する

TerraformをEC2インスタンス内で実行する場合は、インスタンスにアタッチされたIAMロールの認証情報が使われます。この場合、特にアクセスキーや名前付きプロファイルを指定する必要はありません。デプロイ用サーバ (Jenkinsなど?) を利用する場合が当てはまるかと思います。

## ECS/CodeBuild で実行する

TBD
https://www.terraform.io/docs/providers/aws/index.html#ecs-and-codebuild-task-roles

## AssumeRole する

IAMロールから委譲された権限を使ってTerraformを実行します。

TBD
https://www.terraform.io/docs/providers/aws/index.html#assume-role
https://dev.classmethod.jp/cloud/aws/iam-role-and-assumerole/
https://dev.classmethod.jp/etc/sugano-005-s3/
https://dev.classmethod.jp/cloud/aws/assume-role-deploy-iam-user-and-role/

# 所感

- ローカルで実行するなら環境変数を使うのが良い。
- CI/CD回すならCodeBuildに設定するのが良さそう。
    - 気が向いたら追記する(そもそも未確認......)