VaultをTerraformに統合してAWSサービスを構築する方法について

# 概要
[Megazone Japan Advent Calendar 2023](https://qiita.com/advent-calendar/2023/megazonejapan)の14日目に掲載するTech記事を執筆しました。🫡

この記事では、VaultをTerraformに統合して、AWSリソースを構築する方法について説明します。    
一般的にはAWSからIAMを発行すると思いますが、VaultとTerraformを統合する場合、次のような違いがあるでしょう。

| カテゴリ | AWS IAM発行（一般的） | Vault IAM発行(Terraform統合) |
| :---- | :---- | :---- |
| 管理者の役割 | AWSコンソールにおけるIAM RoleとPolicyの生成及び管理 | Terraformを使用したVault経由のIAM RoleとPolicyの自動生成及び管理 |
| 開発者の役割 | 管理者によるIAM生成を通じたAWSリソースの構築 | Vaultによる動的IAM発行を使用したAWSリソースの構築 |
| セキュリティ | 手動作業に伴うミス発生の可能性 | 資格証明の動的生成及び破棄を通じたセキュリティの強化とリスク低減 |
| 自動化 | 手動管理の必要性 | 資格証明のライフサイクル自動化による効率向上 |

通常、AWSでIAMを発行した場合、そのIAM情報を開発者に直接渡し、ローカルで管理することが多いです。しかし、何らかの理由でアクセスキーが損傷したり、情報が漏洩するなどのミスが発生するリスクがあります。これらのリスクを軽減するために、VaultをTerraformに統合して資格情報を動的に生成し、コードを通じて管理することで、より効率的かつ安全に管理できると考えられます。

実際に以下のように役割を分けて、VaultをTerraformに統合して、AWSリソースを構築してみます。
```
管理者はVaultをTerraformに統合し、AWS Secrets Engineを生成する。
開発者はVaultのSecretへのアクセスせずに、Terraformだけで、AWSリソースを構築する。
```


# 環境について

本設定環境は以下の通りです。
- macOS
- [Vault on Docker](https://qiita.com/jeayoon/items/db6c4946a7a34ea40f1a)(※既に環境にVaultが設置されていれば問題ありません。Vaultがない、またはUIでの管理を試してみたい場合は、こちらのリンクを参考にしてください。)
- Terraform v1.5.0系
- AWS CLI

# VaultのTerraform作成について

Terraform作成する内容は以下の通りです。まずは、`/creds`の管理者部分から説明します。

```
.
├── creds # 管理者のTerraform
│   ├── backend.tf
│   ├── data.tf
│   ├── main.tf
│   ├── provider.tf
│   └── vars.tf
└── dev 　# 開発者のTerraform
    ├── backend.tf
    ├── data.tf
    ├── provider.tf
    ├── vars.tf
    └── vpc.tf
```

## SSM Parameter store作成（管理者の役割）

管理者のTerraformで、`vault_aws_secret_backend`のリソースを利用します。これは、HashiCorp Vaultの中でAWSの認証情報（アクセスキーとシークレットキー）を動的に生成するための機能です。 

VaultがAWSの認証情報を必要とする際、AWS Access KeyとSecret Keyが必要ですので、SSM Parameter Storeからこれらの情報を読み取るようにします。

![vault-terraform01](https://github.com/jeayoon/myWork/assets/17561411/f68a37c8-3d76-48e3-88f0-42382a8f701d)

```
- /dev/vault/token:　Vault実行用のVAULT_DEV_ROOT_TOKEN_ID
- /dev/vault/aws_secret_access_key: Vault用のAWS Secret key
- /dev/vault/aws_access_key_id:　Vault用のAWS Access key
```

## Vault Provider（管理者の役割）

次は、`Vault Provider`を使用してVault内で`AdministratorAccess`のRole持つAWS Secret Engineを有効化します。Terraformのコードは以下の通りです。

`/creds/backend.tf`は、
- Terraformのバージョンは1.5.0以上かつ1.6.0未満のバージョンが要求されています。
- tfstateをS3に管理するようにします。

```tf
terraform {
  required_version = "~> 1.5.0"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 4.0"
    }
  }

  backend "s3" {
    bucket  = "" # s3 bucket name
    region  = "ap-northeast-1"
    key     = "vault/creds/terraform.tfstate"
    encrypt = true
  }
}
```

`/creds/data.tf`は
- SSM Parameter Storeから特定のパラメータを取得します。
- IAMポリシーを定義するために使用されるデータソースの宣言を含んでいます。

```tf
data "aws_ssm_parameter" "token" {
  name = "/dev/vault/token"
}

data "aws_ssm_parameter" "access_key" {
  name = "/dev/vault/aws_access_key_id"
}

data "aws_ssm_parameter" "secret_key" {
  name = "/dev/vault/aws_secret_access_key"
}

data "aws_iam_policy_document" "policy" {
  statement {
    actions = ["sts:AssumeRole"]
    principals {
      type        = "AWS"
      identifiers = ["arn:aws:iam::${data.aws_caller_identity.this.account_id}:root"]
    }
  }
}

data "aws_caller_identity" "this" {}

data "aws_iam_policy" "policy" {
  name = "AdministratorAccess"
}

```

`/creds/main.tf`は
- VaultのAWS secret backendを設定します。
- AWS IAMロールを作成します。
- そのロールにポリシーをアタッチし、最後にVault内でAWS secret backend Roleを作成するためのリソース定義を含んでいます。
```tf
resource "vault_aws_secret_backend" "aws" {
  access_key                = data.aws_ssm_parameter.access_key.value
  secret_key                = data.aws_ssm_parameter.secret_key.value
  path                      = "aws"
  region                    = var.region
  default_lease_ttl_seconds = 3600
  max_lease_ttl_seconds     = 7200
}

resource "aws_iam_role" "role" {
  name               = "dev-vault-role"
  assume_role_policy = data.aws_iam_policy_document.policy.json
  tags               = merge(var.tags, {})
}

resource "aws_iam_role_policy_attachment" "attachment" {
  role       = aws_iam_role.role.name
  policy_arn = data.aws_iam_policy.policy.arn
}

resource "vault_aws_secret_backend_role" "role" {
  backend         = vault_aws_secret_backend.aws.path
  name            = "dev-vault-role"
  credential_type = "assumed_role"
  role_arns       = [aws_iam_role.role.arn]
}

```

`/creds/provider.tf`は、
- HashiCorp VaultとAWSのプロバイダーを設定するためのものです。
```tf
provider "vault" {
  address = var.vault_address
  token   = data.aws_ssm_parameter.token.value
}

provider "aws" {
  region = var.region
}
```

`/creds/vars.tf`は、
- Terraformで使用される変数を定義するためのものです。

```tf
variable "region" {
  type        = string
  default     = "ap-northeast-1"
  description = "AWS tokyo region"
}

variable "vault_address" {
  type        = string
  default     = "http://127.0.0.1:8200"
  description = "URL address for vault endpoint"
}

variable "tags" {
  type = map(string)
  default = {
    env = "dev_vault"
  }
  description = "dev vault tags"
}
```

コード作成ができたら、デプロイしてみましょう。
1. `terraform init`
2. `terraform plan`
3. `terraform apply`

結果としては、次のリソースが作成できたと思います。
```
# aws_iam_role.role
# aws_iam_role_policy_attachment.attachment
# vault_aws_secret_backend.aws
# vault_aws_secret_backend.aws
Apply complete! Resources: 4 added, 0 changed, 0 destroyed.
```
 
これで、管理者がVault内に`AdministratorAccess`のRole持つAWS Secret Engineを作成し、開発者にAWS IAM情報を渡す準備が整いました。

## AWS Secret Engineの確認（管理者の役割）

[Vault on Docker](https://qiita.com/jeayoon/items/db6c4946a7a34ea40f1a)のVault UIで管理しているため、Web上で確認してみます。    
`aws/`にAWS Secret Engineの作成ができているようです。
![vault-terraform02](https://github.com/jeayoon/myWork/assets/17561411/930deba8-1007-4c74-86c3-8f1c625fb062)

次は、開発者が使うVault Tokenを生成してみます。    
`aws/`に移動すると、先ほどTerraformのAWS secret backend Roleで作成した`dev-vault-role`があります。そのRoleの`Generate credentials`に移動します。
![vault-terraform03](https://github.com/jeayoon/myWork/assets/17561411/866ed4ec-2a72-4d18-b02c-25de78622c0d)

`Credential type`を`Assumed Role`で選択し、`Generate`をクリックします。
![vault-terraform04](https://github.com/jeayoon/myWork/assets/17561411/727b07f6-b060-46b9-a151-bcf752ed7881)

これにより、開発者用のSecret KeyとSecurity Tokenが生成されたことが確認できます。
<img width="674" alt="vault-terraform05-1" src="https://github.com/jeayoon/myWork/assets/17561411/2a5f56ab-30ad-458d-bed8-47bcb6c122d4">


## AWS VPC構築（開発者の役割）

AWS Secret Engineを有効にしたので、動作確認をしてみたいと思います。開発者がAWS Secret Engineから取得したAWS Access Credentialsを利用して簡単なAWS VPCをデプロイします。

`/dev/backend.tf`は、
- Terraformのバージョンは1.5.0以上かつ1.6.0未満のバージョンが要求されています。
- tfstateをS3に管理するようにします。
```tf
terraform {
  required_version = "~> 1.5.0"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 4.0"
    }
  }

  backend "s3" {
    bucket  = "" # s3 bucket name
    region  = "ap-northeast-1"
    key     = "vault/dev/terraform.tfstate"
    encrypt = true
  }
}
```

`/dev/data.tf`は、
- Vaultに対してAWS access credentialsを取得します。
```tf
data "vault_aws_access_credentials" "aws" {
  backend = "aws"
  role    = "dev-vault-role"　// vault_aws_secret_backend_role名
  type    = "sts" // AWS Security Token Service
}

```

`/dev/provider.tf`は
- HashiCorp VaultとAWSのプロバイダーを設定するためのものです。
```tf
provider "vault" {
  address = var.vault_address

}

provider "aws" {
  region     = var.region
  access_key = data.vault_aws_access_credentials.aws.access_key　//　下図のAccess Keyを表す
  secret_key = data.vault_aws_access_credentials.aws.secret_key　//　下図のSecret Keyを表す
  token      = data.vault_aws_access_credentials.aws.security_token　//　下図のSecurity tokenを表す
}
```
<img width="674" alt="vault-terraform05-1" src="https://github.com/jeayoon/myWork/assets/17561411/2a5f56ab-30ad-458d-bed8-47bcb6c122d4">

`/dev/vars.tf`は、
- Terraformで使用される変数を定義するためのものです。
```tf
variable "region" {
  type        = string
  default     = "ap-northeast-1"
  description = "AWS tokyo region"
}

variable "vault_address" {
  type        = string
  default     = "http://127.0.0.1:8200"
  description = "URL address for vault endpoint"
}

variable "tags" {
  type = map(string)
  default = {
    env = "dev_vault"
  }
  description = "dev vault tags"
}

```

`/dev/vpc.tf`は、
- TerraformのAWS VPCリソースです。
```tf
resource "aws_vpc" "main" {
  cidr_block       = "10.0.0.0/16"
  instance_tenancy = "default"
  tags             = merge(var.tags, { "Name" = "dev-vault-vpc" })
}
```
Terraformコードが準備できたら、コマンドを叩く前に`VAULT_TOKEN`の環境変数を設定する必要があります。    
`VAULT_TOKEN`は、Vaultへの認証および認可に使用されるトークンです。Vaultはセキュアなアクセスとシークレットの管理を提供するツールであり、トークンはVaultへのアクセスを許可するための認証手段として機能します。    
なお、以下の設定を行います。
```
- AWS_PROFILEの削除：unset AWS_PROFILE // Terraform上にVault AWS Access Credentialsを利用するために、Localにある aws profileを削除しておく

- VAULT_TOKENの設定：export VAULT_TOKEN=Vault実行用のVAULT_DEV_ROOT_TOKEN_ID

```

コード作成と環境変数の設定ができたら、デプロイしてみましょう。
1. `terraform init`
2. `terraform plan`
3. `terraform apply`

`dev-vault-vpc`というAWS VPC名でデプロイができました。

![vault-terraform06](https://github.com/jeayoon/myWork/assets/17561411/75ce57b3-b1ec-47b9-bf97-f7fc44b29d0c)

# 最後に

IAM情報をVaultとTerraformで統合することにより、開発者がTerraformコードだけで効率的に環境を構築でき、AWS Secrets keyを安全に管理できることがメリットだと思います。    
以上、VaultをTerraformに統合してAWSサービスを構築する方法についての記事でした。ありがとうございました！