Terraform シークレット管理を試してみた

# 背景・目的
以前、下記のとおりTerraformについて整理・試してきました。
今回は、[詳解 Terraform 第3版](https://www.oreilly.co.jp/books/9784814400522/)の、「第6章 シークレットを管理する」を整理、試します。

- [Terraformのモジュールを試してみた](https://qiita.com/zumax/items/01c7c78dfbf4a2335cf2)
- [Terraformのメモ](https://qiita.com/zumax/items/71184fc108b6894e5b27)
- [Terraform ステートを試してみた](https://qiita.com/zumax/items/74fad1270d3deab7379f)
- [Terraformのメモ](https://qiita.com/zumax/items/71184fc108b6894e5b27)

# まとめ
下記に特徴をまとめます

|特徴|説明|
|:--|:--|
|シークレットの種類|・個人<BR>・顧客<BR>・インフラ|
|個人シークレット|個人に紐づくもの。下記のようなものがある<BR>・Webサイトのユーザとパスワード<BR>・SSHキー<BR>・PGPキー|
|顧客シークレット|顧客に紐づくもの。下記のようなものがある<BR>・顧客が自社の製品にログインするユーザとパスワード<BR>・顧客の個人情報(PII)<BR>・顧客の顧客健康情報（PHI）|
|インフラシークレット|インフラに紐づくもの。下記のようなものがある<BR>・DBのパスワード<BR>・APIキー|
|シークレットが必要な場面|・プロバイダの認証<BR>・リソースとデータソース<BR>・ステートファイルとプランファイル|

# 概要
ソフトウェアでは、DBのパスワード、APIキー、TLS証明書、SSHキー、GPGキーなど様々なシークレットキーを使用します。
ここでは、このシークレット情報を安全に管理するために下記の内容を整理します。

- シークレット管理の基本
- シークレット管理ツール
- シークレット管理ツールとTerraform

## シークレット管理の基本
- プレーンテキストで保存せずに、シークレット管理ツールを使用すること

## シークレット管理ツール
### 保存するシークレットの種類
下記の3つの基本的種類がある

- 個人シークレット
    - 個人に紐づき、下記のようなものがある
        - Webサイトのユーザとパスワード
        - SSHキー
        - PGPキー
- 顧客シークレット
    - 顧客に紐づき、下記のようなものがある
        - 顧客が自社の製品にログインするユーザとパスワード
        - 顧客の個人情報(PII)
        - 顧客の顧客健康情報（PHI）
- インフラシークレット
    - インフラに紐づき、下記のようなものがある
        - DBのパスワード
        - APIキー

ほとんどのシークレット管理ツールは、上記のシークレットのどれか1つを保存されているように設計されている。
他の種類のシークレットを無理やり保存できるが、セキュリティやユーザビリティの観点から推奨しない。
例えば、下記のようなものがある。
- 顧客シークレット
    - ソルトを使ったハッシュアルゴリズムなどが使われる
    - 元のパスワードを得る必要はない
- インフラシークレット
    - AES256やワンタイムパスワードなどが使われる
    - シークレットを複合し、元のパスワードを戻せるようにする

### シークレットの保存方法
シークレットストアの保存方法として、よく使われるもの。
- ファイルベースのシークレットストア
- 集中型のシークレットストア

#### ファイルベースのシークレットストア
シークレットを暗号化されたファイルに保存する。この暗号化するには、暗号化キーが必要である。
そして、このキー自体がシークレットであり解決するには、キーマネージメントシステムがよく使われる。
- AWS KMS
- GCP KMS
- Azure Key Vault
または、PGPなどを利用する。

#### 集中型のシークレットストア
ネットワーク越しにアクセスするWebサービスであることが多く、シークレットを暗号化し、それらをMySQLなどのデータストアに保存する。集中型のシークレットストアでは、これらのシークレットを保存するのに暗号化キーを必要とする。
この暗号化キーは、通常サービス自身で管理するか、クラウドプロバイダーのKMSに保存する

### シークレットにアクセスするインターフェイス
API、CLI、UIなどがある。

## シークレット管理ツールとTerraform
### プロパイダ
AWSへの認証で、人が利用する場合は、`aws-vault`ツールがある。
CI/CDなどの人間が介在しない場合、下記のようなものがある。

- CIサーバとしてのCircleCI（シークレットを保存）
    - IAMユーザへのアクセスキーをCircleCIコンテキストに手動でコピーする
- JenkinsをCIサーバとして動かすEC2インスタンス（IAMロール）
    - IAMロールを割り当てる
- CIサーバとしてのGitHub Action（OIDC）
    - OIDCを使い、CIシステムとクラウドプロバイダーとの間に信頼された関係を作る

### リソースとデータソース
リソースとデータソースを使う場合に、シークレットを扱う。下記のような方法で取り扱うことが可能である。
- 環境変数
- 暗号化されたファイル
- シークレットストア

#### 環境変数
環境変数を通じて、シークレットを渡すことで、コードにプレーンテキストを保存しなくても良い。
環境変数を使う場合、セキュアに保存する方法として下記が挙げられる。
- 1Passwordなどのシークレットをマネージャーにシークレットを保存し、ターミナル上でシークレットを保存する事が可能
- 集中型のシークレットストア（Hashicorp Vaultなど）から、シークレットストアのAPIやCLIを使ってシークレットを読み出す

環境変数を使う場合の利点を下記に示す。
- コードやバージョン管理にプレーンテキストを保存する必要はない
- シークレットの保存が簡単
- どのようなプログラミング環境でも読み出し可能
欠点は下記の通り。
- コードの理解とメンテナンスが難しい
- シークレット管理方法を標準化が難しい。セキュリティ設定を強制できない
- 別の環境への追加を忘れる

下記に例を示す
- 変数をvariable.tfで宣言し、`sensitive = true`で宣言する
    ```
    variable "db_passward" {
        type      = string
        sensitive = true
    }
    ```
    
- リソースでの渡し方
    ```
    resource "aws_db_instance" "example" {
        identifier_prefix   = "terraform-up-and-running"
        engine              = "mysql"
        allocated_storage   = 10
        instance_class      = "db.t2.micro"
        skip_final_snapshot = var.db_name
        
        # シークレットをリソースに渡す
        username = var.db_username
        password = var.db_password
    }
    ```
- TF_VAR_から始まる環境変数を設定することで各変数に値を渡せる
    ```
    export TF_VAR_db_username = (DB_USERNAME)
    export TF_VAR_db_password = (DB_PASSWORD)
    ```

#### 暗号化されたファイル
シークレットを暗号化し、暗号文をファイルに保存し、バージョン管理システムに保存する方法について記載する。
ファイルに保存されたシークレットなどを保存するには、暗号化キーが必要である。
よくある解決策は、下記のとおりである。
- KMS
- PGP

利点と欠点は下記のとおりである。
- 利点
- 欠点

#### シークレットストア
シークレットを集中型シークレットストアに保存する方法を示す。
比較的ポビュラーなのは下記がある。
- AWS Secrets Manager
- Google Secret Manager
- Azure Key Vault
- HashiCorp Vault

利点と欠点は下記の通りである。
- 利点
    - コードやバージョン管理にプレーンテキストを保存する必要はない
    - すべてをコード内に記述できる
    - 通常はWebUIを使用できるので、シークレットの保存が簡単
    - シークレットが漏洩した際に、ローテートや無効化ができる（シークレットストアでサポートされている）
    - 監査ログをサポートしている
    - 暗号化、保存、アクセスパターンなどが強制されるので、取り扱いの標準化が簡単
- 欠点
    - バージョン管理、パッケージ化、コードと合わせたテストを行えない。新たな環境に追加の手間など煩雑である
    - コストがかかる
    - 自動テストとの統合が難しく、モックなど用意するか、テストデータを保存する
### ステートファイルとプランファイル
Terraformを使用する際に、シークレットにふれる場所は下記がある
- ステートファイル
- プランファイル

#### ステートファイル
Terraformリソースやデータソースに渡したどんなシークレットも、Terraformステートファイルにプレーンテキストで保存される。
環境変数、暗号化されたファイル、集中型シークレットストアなど、どこからデータベース認証情報を読んでも、リソースに認証情報を渡すと、プレーンテキストのterraform.tfstateファイルに保存してしまう。
これらを、回避するには下記を行う。

- 暗号化をサポートするバックエンドにTerraformステートを保存する
    - 暗号化をネイティブにサポートしているバックエンドを使用する
- Terraformバックエンドのアクセス制御を強化する
    - シークレット自体にアクセスできる人やリソースを制御する

#### プランファイル
terraform planコマンドでは、出力をファイルに保存できる。
このとき、Terraformリソースやデータソースに渡したシークレットがプレーンテキストで保存されてしまう。

回避するには、ステートファイルと同様に、「暗号化をサポートするバックエンドにTerraformステートを保存する」、「Terraformバックエンドのアクセス制御を強化する」

# 実践
## シークレットストアを使用する方法
### AWS Secret Managerで作成
1. AWSにサインインし、Secrets Managerに移動します
1. 「新しいシークレットを保存する」をクリックします
1. シークレットのタイプ「その他のシークレットのタイプ」、キー/値のペアを設定し、「次」をクリックします
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/ea847594-74da-bb85-424a-e7b945482f3d.png)
1. シークレットの名前を設定し、「次」をクリックします
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/e045cf98-8375-0d2e-7c69-1c4b4e1dc6ab.png)
1. 保存します
1. 作成されました
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/c7263937-0a42-f91b-b449-9c143fdcb4d8.png)

### moduleの作成
1. `modules/rdb`ディレクトリを作成し、その下にmain.tfを作成します
    ```
    locals {
      db_creds = jsondecode(
        data.aws_secretsmanager_secret_version.db_creds.secret_string
      )
    }
    
    data "aws_secretsmanager_secret_version" "db_creds" {
      secret_id = "db-creds"
    }
    
    resource "aws_db_instance" "example" {
      identifier_prefix   = "terraform-up-and-running"
      engine              = "mysql"
      allocated_storage   = 10
      instance_class      = "db.t３.micro"
      skip_final_snapshot = true
      db_name             = var.db_name
    
      username = local.db_creds.username
      password = local.db_creds.password
    }
    ```

### environments の作成 
1. `environments/rds`ディレクトリを作成後、main.tfを作成します
    ```
    module "rdb" {
      source = "../../../modules/rdb"
    
      db_name = local.db_name
    }
    ```
1. local.tfを作成します
    ```
    locals {
      db_name = "mydb"
    }
    ```
1. `terraform init --backend-config=backend.hcl`を実行します
1. terraform planとapplyを実行します
1. クラスタが作成されました。マスターユーザ名も設定された名前になっています
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/206276/0e9b681c-b55e-dabf-1287-47aed78138b4.png)


# 考察
今回、Terraformでシークレットを管理する方法を整理してみました。
今後は、こちらを活用し構築していきたいと思います。

# 参考
https://qiita.com/zumax/items/01c7c78dfbf4a2335cf2

https://qiita.com/zumax/items/71184fc108b6894e5b27

https://qiita.com/zumax/items/74fad1270d3deab7379f

https://qiita.com/zumax/items/71184fc108b6894e5b27

https://www.oreilly.co.jp/books/9784814400522/

https://registry.terraform.io/providers/hashicorp/local/latest/docs/data-sources/file?product_intent=terraform