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. 終わったら実行環境ごと捨てる
- HCP Terraform が workload identity token を作る。run の身分証にあたる文字列
- organization、project、workspace、run の段階(plan か apply)が書いてある
- HCP Terraform の秘密鍵で署名してある
- plan か apply が始まるとき、HCP Terraform が token を AWS に送り、指定したロールを引き受けたいと頼む
- AWS が HCP Terraform の公開鍵で token の署名を確かめ、ロールの信頼ポリシーの条件と照合する
- 通れば AWS STS が、有効期限つきの認証情報を返す
- HCP Terraform がそれを run の実行環境に置く
- AWS provider がその認証情報で AWS の API を呼ぶ
- 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 に同じエラーが出る。

ここから、鍵の代わりになるものを 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 が並ぶ。

鍵の代わりに 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はないので、条件には効かない
- token の中の
sub の条件がないと、HCP Terraform 上のどの organization の run でもこのロールを引き受けられてしまう。AWS のユーザーガイドも、この条件を必須としている。
許可ポリシーは 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を置く
- 引き受けを頼むロール。plan と apply でロールを分けたいときは、代わりに
保存すると、Variables のページに次の 2 つが出る。
- 変数の一覧に Dynamic Credential Set の列が付き、2 つの変数の行に Default provider instance と表示される
- ページの下に Dynamic provider credential sets の欄が出て、Amazon Web Services の credential set が 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 = <owner>"]
state[state]
end
subgraph vm["run の実行環境(Linux)"]
token["workload identity token<br>sub = organization:<organization>:...: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を登録する
- HCP Terraform が発行した token を AWS が信用するための設定。URL に
- IAM ロール
- run が AWS を操作するときに使う。何をしてよいかは、このロールに付けた許可ポリシーで決まる
- ID プロバイダー
- ロールの信頼ポリシーで、このロールを使える run を organization、project、workspace で絞る
- HCP Terraform 側の設定は Environment variable 2 つ。
TFC_AWS_PROVIDER_AUTH = trueとTFC_AWS_RUN_ROLE_ARN
参考 URL
- [official] Dynamic provider credentials in HCP Terraform | Terraform | HashiCorp Developer
- [official] Workload identity in HCP Terraform | Terraform | HashiCorp Developer
- [official] Use dynamic credentials with the AWS provider in HCP Terraform | Terraform | HashiCorp Developer
- [official] AWS quick setup | Terraform | HashiCorp Developer
- [official] HCP Terraform's run environment | Terraform | HashiCorp Developer
- [official] IAM で OpenID Connect (OIDC) ID プロバイダーを作成する - AWS Identity and Access Management
- [official] OpenID Connect フェデレーション用のロールを作成する (コンソール) - AWS Identity and Access Management
- [official] AssumeRoleWithWebIdentity - AWS Security Token Service
- [official] Docs overview | hashicorp/aws | Terraform Registry
- [official] aws_caller_identity | Data Sources | hashicorp/aws | Terraform Registry
- GitHub - hashicorp/terraform-dynamic-credentials-setup-examples