TerraformでAWS(EC2)のキーペアを作成する

## 前提

- Terraform 0.11系。

## 説明

- 以下のように「`ssh-keygen`」などで作成したものを利用しているのを、Terraformの方に作成までやらせてみる。

```bash
ssh-keygen -t rsa -b 4096 -C '' -N '' -f example.id_rsa
```

## tfファイル

### キーペア作成

- [「TLS」プロバイダ](https://www.terraform.io/docs/providers/tls/)の[「tls_private_key」リソース](https://www.terraform.io/docs/providers/tls/r/private_key.html)を利用して作成する。
    - https://www.terraform.io/docs/providers/tls/r/private_key.html

```hcl:keygen.tf
/**
 * require
 **/
terraform {
  required_version = ">= 0.11.0"
}

/**
 * variable
 **/
variable "key_name" {
  type        = "string"
  description = "keypair name"
  #default    = "example" # キー名を固定したかったらdefault指定。指定なしならインタラクティブにキー入力して決定。
}

# キーファイル
## 生成場所のPATH指定をしたければ、ここを変更するとよい。
locals {
  public_key_file  = "./${var.key_name}.id_rsa.pub"
  private_key_file = "./${var.key_name}.id_rsa"
}

/**
 * resource
 **/
# キーペアを作る
resource "tls_private_key" "keygen" {
  algorithm = "RSA"
  rsa_bits  = 4096
}

/**
 * file
 **/
# 秘密鍵ファイルを作る
resource "local_file" "private_key_pem" {
  filename = "${local.private_key_file}"
  content  = "${tls_private_key.keygen.private_key_pem}"

  # local_fileでファイルを作ると実行権限が付与されてしまうので、local-execでchmodしておく。
  provisioner "local-exec" {
    command = "chmod 600 ${local.private_key_file}"
  }
}

resource "local_file" "public_key_openssh" {
  filename = "${local.public_key_file}"
  content  = "${tls_private_key.keygen.public_key_openssh}"

  # local_fileでファイルを作ると実行権限が付与されてしまうので、local-execでchmodしておく。
  provisioner "local-exec" {
    command = "chmod 600 ${local.public_key_file}"
  }
}

/**
 * output
 **/
# キー名
output "key_name" {
  value = "${var.key_name}"
}

# 秘密鍵ファイルPATH（このファイルを利用してサーバへアクセスする。）
output "private_key_file" {
  value = "${local.private_key_file}"
}

# 秘密鍵内容
output "private_key_pem" {
  value = "${tls_private_key.keygen.private_key_pem}"
}

# 公開鍵ファイルPATH
output "public_key_file" {
  value = "${local.public_key_file}"
}

# 公開鍵内容（サーバの~/.ssh/authorized_keysに登録して利用する。）
output "public_key_openssh" {
  value = "${tls_private_key.keygen.public_key_openssh}"
}
```

### AWSアカウント情報

```hcl:aws.tf
provider "aws" {
  profile = "example"
  region  = "ap-northeast-1"
}
```
- 「`profile`」には[AWS CLI](https://aws.amazon.com/jp/cli/)のプロファイル名を指定。


### EC2のキーペアとして登録

- [「AWS」プロバイダ](https://www.terraform.io/docs/providers/aws/index.html)の[「aws_key_pair」リソース](https://www.terraform.io/docs/providers/aws/r/key_pair.html)を利用。
    - https://www.terraform.io/docs/providers/aws/r/key_pair.html

```hcl:aws_key_pair.tf
resource "aws_key_pair" "key_pair" {
  key_name   = "${var.key_name}"
  public_key = "${tls_private_key.keygen.public_key_openssh}"
}
```

## 使い方

- キーペア作成するだけなら、AWSプロバイダ関連の記述は不要（上記tfファイル群のうち「`keygen.tf`」だけで良い）。

### plan

```bash
terraform plan
```
```shell-session
var.key_name
  keypair name

  Enter a value: 
```
- キーペア名を聞かれるので、「`example`」と入力すると、以下のように表示される。

```shell-session
An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  + local_file.private_key_pem
      id:                         <computed>
      content:                    "${tls_private_key.keygen.private_key_pem}"
      filename:                   "./example.id_rsa"

  + local_file.public_key_openssh
      id:                         <computed>
      content:                    "${tls_private_key.keygen.public_key_openssh}"
      filename:                   "./example.id_rsa.pub"

  + tls_private_key.keygen
      id:                         <computed>
      algorithm:                  "RSA"
      ecdsa_curve:                "P224"
      private_key_pem:            <computed>
      public_key_fingerprint_md5: <computed>
      public_key_openssh:         <computed>
      public_key_pem:             <computed>
      rsa_bits:                   "4096"


Plan: 3 to add, 0 to change, 0 to destroy.
```

### apply

```bash
terraform plan
```
```shell-session
var.key_name
  keypair name

  Enter a value: 
```
- ここでもキーペア名を聞かれるので、「`example`」と入力すると、以下のように表示される。

```shell-session
An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  + local_file.private_key_pem
      id:                         <computed>
      content:                    "${tls_private_key.keygen.private_key_pem}"
      filename:                   "./example.id_rsa"

  + local_file.public_key_openssh
      id:                         <computed>
      content:                    "${tls_private_key.keygen.public_key_openssh}"
      filename:                   "./example.id_rsa.pub"

  + tls_private_key.keygen
      id:                         <computed>
      algorithm:                  "RSA"
      ecdsa_curve:                "P224"
      private_key_pem:            <computed>
      public_key_fingerprint_md5: <computed>
      public_key_openssh:         <computed>
      public_key_pem:             <computed>
      rsa_bits:                   "4096"


Plan: 3 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: 
```
- 最終確認が入るので、「yes」と入力すると、キーペアが作成される。
    - AWSへのEC2キーペア登録もするなら、同時に登録される。

#### 以下のように変数を指定してやっても良い。

```bash
terraform apply -var 'key_name=example'
```

## 注意点

- ssh-keygenと同じような感覚で、このまま「キーペア生成ツール」としては利用できない。
    - 「`key_name`」変数を変更すると、既に作成したキーペアファイルが削除（destroy）されてしまう。
    - lifecycleブロックで適切にignore_changes ~~指定してやればいける？（未検証）~~
        - [以下](#追記-ignore_changesについての検証結果)で検証した通り、単にignore_changes指定しただけでは「キーペア生成ツール」として利用することはできない、ということが分かった。

### [追記] `ignore_changes`についての検証結果

- 例えば以下のように、上記例「`keygen.tf`」のうち、秘密鍵（`private_key_pem`）、公開鍵（`public_key_openssh`）それぞれについて、lifecycleブロックで「filename」をignore_changes指定してやったところ、削除（destroy）はされなくなったものの、単にそれだけで、再生成自体が行われなくなった。

```hcl
resource "local_file" "private_key_pem" {
  filename = "${local.private_key_file}"
  content  = "${tls_private_key.keygen.private_key_pem}"

  provisioner "local-exec" {
    command = "chmod 600 ${local.private_key_file}"
  }

  lifecycle {
    ignore_changes = [ 
      "filename",
    ]   
  }
}
```

- 考えてみれば当たり前のことで、「`ignore_changes`」は変更を無視するということなんだから、ここに「`filename`」を指定すれば、「変数の値を変更してファイル名が変わったとしても、何もしない。」となるだけの、至って正常な挙動だった。
- 「`lifecycle`」ブロックには、「`ignore_changes`」も含め、以下のような項目がある。（※ [詳細はマニュアル参照](https://www.terraform.io/docs/configuration/resources.html#lifecycle-lifecycle-customizations)）
    - **`ignore_changes`**
        - 指定した属性の変更を無視して何もしない。
        - 属性名をlist指定
    - **`prevent_destroy`**
        - このリソースをdestroyしない。変更などの影響でdestroyが実行されるとエラーになる。
        - true/falseのbool指定
    - **`create_before_destroy`**
        - 同名リソースを再生成(create)する時に、先に削除(destroy)する。
            - false指定すると、createしてからdestroyするようになるという、処理順制御の項目。（削除はされる）
        - true/falseのbool指定
            - 暗黙のデフォルト値は「`true`」になっている。

よって、「**ssh-keygenコマンドのように、ただ鍵ペアを生成するだけ、というツール利用はできない。**」というのが結論になる。
逆に、鍵名を変数化せずにしておけば、削除や生成がtfファイルの記述内容と連動するので、「鍵管理ツール」として利用することはできるかも知れない。

----

## 参考情報など

### モジュールについて

- 外部モジュールとして指定してやれば、変数指定だけで利用できたりする。
- 「[Terraform Module Registry](https://registry.terraform.io/)」で探すと良い。


### 参考モジュール

- [cloudposse/terraform-aws-key-pair: Terraform Module to Automatically Generate SSH Key Pairs (Public/Private Keys)](https://github.com/cloudposse/terraform-aws-key-pair)
    - https://github.com/cloudposse/terraform-aws-key-pair
    - Terraform Module Registry URL
        - https://registry.terraform.io/modules/cloudposse/key-pair
    - 様々なTerraformモジュールを作っている「[Cloud Posse](https://cloudposse.com/)」謹製モジュール。色々指定できる。ガッツリ系。

- [cloudposse/terraform-tls-ssh-key-pair: Terraform module for generating an SSH public/private key file.](https://github.com/cloudposse/terraform-tls-ssh-key-pair)
    - https://github.com/cloudposse/terraform-tls-ssh-key-pair
    - Terraform Module Registry URL
        - https://registry.terraform.io/modules/cloudposse/ssh-key-pair
    - 上記モジュールからAWS部分を除外して、キーペア作成だけにしたもの。

- [mitchellh/terraform-aws-dynamic-keys: Terraform module that dynamically generates a public/private keypair.](https://github.com/mitchellh/terraform-aws-dynamic-keys)
    - https://github.com/mitchellh/terraform-aws-dynamic-keys
    - Terraform Module Registry URL
        - https://registry.terraform.io/modules/mitchellh/dynamic-keys
    - Terraformを作っているHashiCorp代表、ミッチェル・ハシモト氏（[mitchellh](https://github.com/mitchellh)）謹製モジュール。アッサリ系。分かりやすい。
        - 今回の記事は、ほぼこのモジュール内容のパクリ。