HCP Terraform の AWS 連携: Dynamic provider credentials の設定

公開

HCP Terraform の run(plan や apply の実行)の中で AWS を操作するのは AWS provider。Terraform のプラグインで、.tf の aws_s3_bucket のような定義を AWS の API 呼び出しに変えるプログラム。

AWS の API を呼ぶには認証情報が要る。IAM ユーザーのアクセスキーを workspace の変数に置けば動く。

ただしアクセスキーには有効期限がない。漏れると、無効にするまで誰でも AWS を操作できる。その鍵を HCP Terraform に置いたままにはしたくない。

この記事では、その鍵を消して Dynamic provider credentials に置き換える。run のたびに HCP Terraform が AWS に身元を証明し、有効期限つきの認証情報を受け取る形。鍵はどこにも置かない。

確かめることは次の 3 つ。

  • アクセスキーを消した run がどう失敗するか
  • AWS 側に何を作り、HCP Terraform 側に何を置くか
  • 同じ .tf の run が、IAM ユーザーではなく IAM ロール(鍵を持たない)として AWS に入ること

前提

workspace tfc-study-stg の run が、IAM ユーザー tfc-study のアクセスキーで AWS の S3 バケットを 1 つ管理している状態(「HCP Terraform の変数: 2 種類の変数の置き場所と run での使われ方」を参照)から始める。

Dynamic provider credentials の仕組み

Dynamic provider credentials は、HCP Terraform が run のたびに provider 用の一時的な認証情報を外部から受け取る機能。

対応しているのは AWS、Google Cloud、Azure、Vault など。

AWS provider に認証情報を渡す形は 2 つある。

  • static credentials
    • IAM ユーザーのアクセスキーを workspace の Sensitive な Environment variable に置く
    • 鍵に有効期限はなく、無効にするまでずっと使える。定期的に入れ替える必要がある
  • Dynamic provider credentials
    • 鍵を置かない。run のたびに HCP Terraform が AWS に身元を証明し、有効期限つきの認証情報を受け取る
    • 公式ドキュメントが推奨する形

この記事では、static credentials から Dynamic provider credentials に置き換える。先に、使うものと run のたびに起きることを押さえておく。

設定するもの

AWS 側に 2 つ作り、HCP Terraform 側に 1 つ置く。

  • ID プロバイダー
    • AWS の IAM に作る。HCP Terraform が発行した token を AWS が信用するための設定
  • IAM ロールと信頼ポリシー
    • AWS の IAM に作る。ロールは、run が AWS を操作するときに IAM ユーザーの代わりに使うもので、鍵を持たない
    • 信頼ポリシーは、どの run がこのロールを使ってよいかをロールに書いたもの
  • Environment variable 2 つ
    • HCP Terraform の workspace に置く。この workspace の run はロールで AWS に入る、と HCP Terraform に伝える

このほかに AWS STS(Security Token Service)が出てくる。一時的な認証情報を発行する AWS のサービスで、作るものではない。

run のたびに起きること

HCP Terraform と AWS の間のやり取りは OpenID Connect(OIDC) という規格に従う。署名付きの token(文字列)で名乗り、受け取った側が公開鍵で署名を確かめる決まり。

plan と apply のそれぞれで、起きることは 7 つ。

sequenceDiagram
    participant hcp as HCP Terraform
    participant sts as AWS STS
    participant env as run の実行環境
    participant aws as AWS(S3 など)

    hcp->>hcp: 1. workload identity token を作って署名する
    hcp->>sts: 2. token でロールを引き受ける
    sts->>sts: 3. 署名と条件を確かめる
    sts-->>hcp: 4. 一時的な認証情報を返す
    hcp->>env: 5. 認証情報を置く
    env->>aws: 6. AWS provider が API を呼ぶ
    hcp->>env: 7. 終わったら実行環境ごと捨てる
  1. HCP Terraform が workload identity token を作る。run の身分証にあたる文字列
    • organization、project、workspace、run の段階(plan か apply)が書いてある
    • HCP Terraform の秘密鍵で署名してある
  2. plan か apply が始まるとき、HCP Terraform が token を AWS に送り、指定したロールを引き受けたいと頼む
  3. AWS が HCP Terraform の公開鍵で token の署名を確かめ、ロールの信頼ポリシーの条件と照合する
  4. 通れば AWS STS が、有効期限つきの認証情報を返す
  5. HCP Terraform がそれを run の実行環境に置く
  6. AWS provider がその認証情報で AWS の API を呼ぶ
  7. plan か apply が終わると実行環境ごと捨てられ、認証情報も消える

以下、この 3 つを順に作る。

アクセスキーの役割を知るために消して run する

最初に、鍵が何をしていたかを消して確かめる。Variables で AWS_ACCESS_KEY_ID と AWS_SECRET_ACCESS_KEY を削除する。

そのうえで、.tf のディレクトリで terraform plan を実行する。

$ terraform plan
Running plan in HCP Terraform. Output will stream here. Pressing Ctrl-C
will stop streaming the logs, but will not stop the plan running remotely.

Preparing the remote plan...

To view this run in a browser, visit:
https://app.terraform.io/app/<organization>/tfc-study-stg/runs/run-ASRid7MUhJgQjjMG

Waiting for the plan to start...

Terraform v1.13.3
on linux_amd64
Initializing plugins and modules...
random_pet.name: Refreshing state... [id=national-starfish]
╷
│ Error: No valid credential sources found
│
│   with provider["registry.terraform.io/hashicorp/aws"],
│   on providers.tf line 1, in provider "aws":
│    1: provider "aws" {
│
│ Please see https://registry.terraform.io/providers/hashicorp/aws
│ for more information about providing credentials.
│
│ Error: failed to refresh cached credentials, no EC2 IMDS role found,
│ operation error ec2imds: GetMetadata, exceeded maximum number of attempts,
│ 3, request send failed, Get
│ "http://169.254.169.254/latest/meta-data/iam/security-credentials/": dial
│ tcp 169.254.169.254:80: connect: network is unreachable
╵
Operation failed: failed running terraform plan (exit 1)

エラーを出したのは AWS provider。No valid credential sources found は、認証情報を探す場所を全部試して見つからなかった、という意味。AWS の SDK が探す場所は次の 3 つ。

  • 環境変数
  • 設定ファイル(~/.aws/ のもの)
  • EC2 のメタデータ

run の実行環境には設定ファイルがなく、環境変数も消したので、どこにも認証情報がない。

run は Plan errored で終わり、run のページの Diagnostics に同じエラーが出る。

Plan errored で終わった run のページ。Diagnostics に Error: No valid credential sources found と、認証情報の探し方の案内が表示されている

ここから、鍵の代わりになるものを AWS と HCP Terraform に作っていく。

HCP Terraform の token を信用させるために ID プロバイダーを作る

AWS が HCP Terraform の token を本物だと確かめられるように、IAM に ID プロバイダーを 1 つ作る。

token は HCP Terraform の秘密鍵で署名してあるので、確かめるには HCP Terraform の公開鍵が要る。公開鍵は次の URL に置いてあり、誰でも取れる。

https://app.terraform.io/.well-known/jwks

ID プロバイダーは「この URL が発行した token は、この URL から公開鍵を取って確かめる」という AWS 側の設定。入力するのは 2 つだけ。

  • プロバイダーの URL
    • https://app.terraform.io。末尾に / を付けない
  • 対象者(Audience)
    • aws.workload.identity。token の中の「この token は誰に見せるためのものか」(aud)の値で、HCP Terraform が AWS 向けに作る token の既定値
    • これを確かめることで、ほかのサービス向けに作った token を AWS に使い回せないようにする

AWS のコンソールの IAM で ID プロバイダー → プロバイダーを追加 を開き、OpenID Connect を選んでこの 2 つを入力する。

作ると、ID プロバイダーの一覧に app.terraform.io が並ぶ。

IAM の ID プロバイダの一覧。プロバイダ名 app.terraform.io、タイプ OpenID Connect の 1 件が表示されている

鍵の代わりに run が使う IAM ロールを作る

run が AWS を操作するときに使う IAM ロールを作り、HCP Terraform のこの workspace の run だけが使えるように信頼ポリシーで絞る。

IAM ユーザーとロールの違い

IAM ユーザー tfc-study はアクセスキーを持っていた。鍵を持っているプログラムは、誰でもそのユーザーになれる。

IAM ロールは鍵を持たない。代わりに次の 2 つを持つ。

  • 信頼ポリシー
    • 誰がこのロールを引き受けてよいかを書いたもの
    • 条件を満たした相手が引き受けを頼むと、AWS STS が有効期限つきの認証情報を発行する
  • 許可ポリシー
    • 引き受けた相手が何をしてよいかを書いたもの。IAM ユーザーに付けるものと同じ
    • この記事では、IAM ユーザーに付いていたポリシー tfc-study をロールにも付ける

ロールの作成

AWS のコンソールの IAM で ロール → ロールを作成 を開き、信頼されたエンティティタイプ で ウェブアイデンティティ を選ぶ。

ID プロバイダーに app.terraform.io を選ぶと、HCP Terraform 専用の入力欄が出る。

欄 値
アイデンティティプロバイダー app.terraform.io
Audience aws.workload.identity
Workload Type Workspace Run
Organization <organization>
Project Default Project
Workspace tfc-study-stg
Run phase *(plan でも apply でも同じロールを使う)

次の画面で許可ポリシーに tfc-study を選び、ロール名を tfc-study にして作る。

ロール名は IAM ユーザーと同じにしてよい。ユーザーとロールは別のもので、ARN の形(user/ と role/)で見分けられる。

作ったロールの ARN は arn:aws:iam::<AWS ID>:role/tfc-study の形になる。

信頼ポリシーで使える run を絞る

ロールのページの 信頼関係 に、入力した値から AWS が作った信頼ポリシーがある。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Federated": "arn:aws:iam::<AWS ID>:oidc-provider/app.terraform.io"
            },
            "Action": "sts:AssumeRoleWithWebIdentity",
            "Condition": {
                "StringEquals": {
                    "app.terraform.io:aud": "aws.workload.identity"
                },
                "StringLike": {
                    "app.terraform.io:sub": [
                        "organization:<organization>:project:Default Project:workspace:tfc-study-stg:run_phase:*",
                        ""
                    ]
                }
            }
        }
    ]
}

読むところは 3 つ。

  • Principal.Federated
    • 上で作った ID プロバイダー。この ID プロバイダーの token を持ってきた相手が対象、という意味
  • Action
    • sts:AssumeRoleWithWebIdentity。外部の token を見せてロールを引き受ける、AWS STS の操作
  • Condition
    • token の中の aud と sub がこの値のときだけ許す
    • sub は token の「誰か」を表す値で、organization、project、workspace、run の段階がこの順で並ぶ。run_phase:* は plan と apply の両方
    • sub の 2 つ目の空文字列はコンソールが入れたもの。空文字列に一致する sub はないので、条件には効かない

sub の条件がないと、HCP Terraform 上のどの organization の run でもこのロールを引き受けられてしまう。AWS のユーザーガイドも、この条件を必須としている。

許可ポリシーは tfc-study が 1 つ付いている。

ロール tfc-study の許可ポリシーの一覧。カスタマーマネージドの tfc-study が 1 件付いている

ロールで入ることを伝える環境変数を workspace に置く

HCP Terraform に、この workspace の run は AWS にこのロールで入る、と伝える。Variables に Environment variable を 2 つ追加する。

Key Value
TFC_AWS_PROVIDER_AUTH true
TFC_AWS_RUN_ROLE_ARN arn:aws:iam::<AWS ID>:role/tfc-study

どちらも鍵ではないので、Sensitive は付けなくてよい。

HCP Terraform が読む変数

この 2 つを読むのは HCP Terraform。run を始める前に読み、token を作って AWS に送るか、どのロールを頼むかを決める。

  • TFC_AWS_PROVIDER_AUTH
    • true のとき、HCP Terraform は run のたびに token を作って AWS に送る。これがないと何もしない
  • TFC_AWS_RUN_ROLE_ARN
    • 引き受けを頼むロール。plan と apply でロールを分けたいときは、代わりに TFC_AWS_PLAN_ROLE_ARN と TFC_AWS_APPLY_ROLE_ARN を置く

保存すると、Variables のページに次の 2 つが出る。

  • 変数の一覧に Dynamic Credential Set の列が付き、2 つの変数の行に Default provider instance と表示される
  • ページの下に Dynamic provider credential sets の欄が出て、Amazon Web Services の credential set が Active になる
Variables のページ。Workspace variables に environment、owner、TFC_AWS_PROVIDER_AUTH、TFC_AWS_RUN_ROLE_ARN(Sensitive)の 4 つが並び、TFC_AWS_ の 2 つに Dynamic Credential Set として Default provider instance が表示されている。下に Dynamic provider credential sets の欄があり、Amazon Web Services の Default provider instance が Active

設定が効いたことを同じ plan で確かめる

設定ができたので、最初に失敗した terraform plan をもう一度実行する。.tf もコマンドも変えていない。

$ terraform plan
Running plan in HCP Terraform. Output will stream here. Pressing Ctrl-C
will stop streaming the logs, but will not stop the plan running remotely.

Preparing the remote plan...

To view this run in a browser, visit:
https://app.terraform.io/app/<organization>/tfc-study-stg/runs/run-yf7keRBHhjrA6MKJ

Waiting for the plan to start...

Terraform v1.13.3
on linux_amd64
Initializing plugins and modules...
random_pet.name: Refreshing state... [id=national-starfish]
data.aws_caller_identity.current: Refreshing...
aws_s3_bucket.study: Refreshing state... [id=tfc-study-stg-national-starfish]
data.aws_caller_identity.current: Refresh complete after 0s [id=<AWS ID>]

Changes to Outputs:
  ~ caller_arn  = "arn:aws:iam::<AWS ID>:user/tfc-study" -> "arn:aws:sts::<AWS ID>:assumed-role/tfc-study/terraform-run-yf7keRBHhjrA6MKJ"

You can apply this plan to save these new output values to the Terraform state, without changing any real infrastructure.

今度は plan が通った。見るところは 2 つ。

  • data.aws_caller_identity.current: Refresh complete が出た。最初に失敗した場所(run から AWS への最初の問い合わせ)を通っている
  • caller_arn の値が変わった。この output は、AWS が「API を呼んできたのは誰か」として認識した相手の ARN。IAM ユーザー(user/tfc-study)から、ロールを引き受けた状態(assumed-role/tfc-study/...)になった

つまり run は、アクセスキーではなくロール tfc-study で AWS を操作している。バケットには差分がなく、変わったのはこの output だけ。

terraform apply を実行して、新しい値を state に保存する。

$ terraform apply

(plan の内容)

Do you want to perform these actions in workspace "tfc-study-stg"?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

Apply complete! Resources: 0 added, 0 changed, 0 destroyed.

Outputs:

bucket_name = "tfc-study-stg-national-starfish"
caller_arn = "arn:aws:sts::<AWS ID>:assumed-role/tfc-study/terraform-run-C9jkT8sAp4wasbCA"
pet_name = "national-starfish"

caller_arn の末尾は terraform-run- に run の ID を付けたもので、run ごとに変わる。plan のときと apply のときで値が違うのはそのため。この output は確認用なので、確認が済んだら外してよい。

不要になった IAM ユーザーとアクセスキーを消す

run はロールで AWS に入るようになったので、IAM ユーザー tfc-study を削除する。アクセスキーはユーザーと一緒に消える。ポリシー tfc-study はロールが使っているので残る。

$ aws iam get-user --user-name tfc-study
aws: [ERROR]: An error occurred (NoSuchEntity) when calling the GetUser operation: The user with name tfc-study cannot be found.

$ aws iam list-attached-role-policies --role-name tfc-study
{
    "AttachedPolicies": [
        {
            "PolicyName": "tfc-study",
            "PolicyArn": "arn:aws:iam::<AWS ID>:policy/tfc-study"
        }
    ]
}

消したあとに HCP Terraform の画面の + New run から run を 1 つ始めると、plan は通る。鍵がどこにもなくても、run はロール tfc-study として AWS に入っている。

全体像

アクセスキーを置く形と、Dynamic provider credentials の形を並べる。

アクセスキーを置く Dynamic provider credentials
run が誰として AWS を操作するか IAM ユーザー tfc-study IAM ロール tfc-study
それを証明するもの アクセスキー。有効期限なし run のたびに HCP Terraform が作る token。run の段階が終わると失効
HCP Terraform に置くもの AWS_ACCESS_KEY_ID、AWS_SECRET_ACCESS_KEY TFC_AWS_PROVIDER_AUTH、TFC_AWS_RUN_ROLE_ARN
その変数を読むもの AWS provider HCP Terraform
AWS に置くもの ユーザー、アクセスキー、許可ポリシー ID プロバイダー、ロール、信頼ポリシー、許可ポリシー
誰が使えるか 鍵を持っている者なら誰でも 信頼ポリシーの条件に合う run だけ。organization、project、workspace、run の段階で絞る
AWS provider が受け取る認証情報 環境変数の鍵そのもの AWS STS が返した一時的な認証情報
caller_arn arn:aws:iam::...:user/tfc-study arn:aws:sts::...:assumed-role/tfc-study/terraform-run-...
.tf 変えない 変えない

plan と apply で動いたものは次のとおり。

flowchart LR
    me[自分]
    tfcmd[["terraform コマンド"]]

    subgraph hcp[HCP Terraform]
        subgraph ws["workspace: tfc-study-stg"]
            tfcvars["Environment variables<br>TFC_AWS_PROVIDER_AUTH = true<br>TFC_AWS_RUN_ROLE_ARN = ロールの ARN"]
            tfvars["Terraform variables<br>environment = stg<br>owner = &lt;owner&gt;"]
            state[state]
        end
        subgraph vm["run の実行環境(Linux)"]
            token["workload identity token<br>sub = organization:&lt;organization&gt;:...:workspace:tfc-study-stg:run_phase:plan"]
            creds["一時的な認証情報"]
            terraform[["terraform v1.13.3"]]
            provider[["AWS provider"]]
        end
    end

    subgraph aws[AWS]
        idp["IAM ID プロバイダー<br>app.terraform.io"]
        role["IAM ロール tfc-study<br>信頼ポリシー(aud、sub の条件)<br>+ 許可ポリシー tfc-study"]
        sts[["AWS STS"]]
        bucket[("S3 バケット<br>tfc-study-stg-national-starfish")]
    end

    me -- "plan / apply を実行" --> tfcmd
    tfcmd -- ".tf 一式を送る" --> hcp
    tfcvars -- "設定を読んで token を作る" --> token
    token -- "AssumeRoleWithWebIdentity" --> sts
    idp -- "公開鍵で署名を確かめる" --> sts
    role -- "信頼ポリシーの条件に合うか" --> sts
    sts -- "返す" --> creds
    creds -- "読む" --> provider
    tfvars -- "var.〜 として渡る" --> terraform
    terraform -- "指示" --> provider
    provider -- "ロールの許可の範囲で API を呼ぶ" --> bucket
    terraform -- "読む・書く" --> state
  • HCP Terraform の workspace に置いてあるのはロールの ARN だけで、漏れて困るものはない
  • 鍵の代わりになる token と一時的な認証情報は、run の実行環境の中にだけあり、run が終わると消える
  • このロールを使える run は、AWS 側の信頼ポリシーで決まる

まとめ

  • Dynamic provider credentials は、run のたびに HCP Terraform が AWS から有効期限つきの認証情報を受け取る仕組み。アクセスキーを workspace に置かなくてよい
  • AWS 側の設定は 2 つ
    • ID プロバイダー
      • HCP Terraform が発行した token を AWS が信用するための設定。URL に https://app.terraform.io、対象者に aws.workload.identity を登録する
    • IAM ロール
      • run が AWS を操作するときに使う。何をしてよいかは、このロールに付けた許可ポリシーで決まる
  • ロールの信頼ポリシーで、このロールを使える run を organization、project、workspace で絞る
  • HCP Terraform 側の設定は Environment variable 2 つ。TFC_AWS_PROVIDER_AUTH = true と TFC_AWS_RUN_ROLE_ARN

参考 URL

Ritolabo

100%