0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

CloudflareのAPI TokenをTerraformで管理したい

Posted at

表題の通りです。
地味に苦戦したので書きます。
結論のコードだけ見たい方は、こちらから。

前提

CloudflareのDNSをTerraformで管理したいのですが、そのためのAPI TokenもTerraformで管理したいという状態です。
大まかな方針としては、TerraformでAPI Tokenを作成するためのAPI Tokenを手動で作り、それを使ってAPI TokenをTerraformから作成します。Terraformから作成するAPI TokenはDNSのWriteとAPI Tokenの意図せぬ変更を検知出来るようにAPI Tokenのreadを付与します。一番最初に手動で作ったtokenはrevokeします。(かなりガバガバですが許してください。)

API Tokenとは

Cloudflareには、APIを使う際に主に3種類のクレデンシャルが存在します。それぞれ、Account API Token, User API Token, (Global) API Keyです。(Global) API Keyは権限などの制限が出来ず非推奨化されています。User API Tokenはユーザーに紐づくトークンであるのに対して、Account API Tokenはアカウントに紐づきます。

便宜的に User API Token と読んでいますが、Cloudflareのドキュメント上では、User API Token は API Token と書かれています。ドキュメントを読むときは Account API Token と User API Token を混同しないように気を付けてください。

以下ではAccount API Tokenを管理する前提で進めますが、おそらくUser API Tokenでもそこまで大差はないと思います。

Account API Tokenを取得する

TerraformからAccount API Tokenを作成するための一時的なAccount API Tokenを取得します。一時的なものなのでこちらはWebUIから手動で。

ダッシュボードのトップページから、 「アカウントの管理」>「アカウントAPIトークン」を選択。
スクリーンショット 2026-01-05 175555.png

その後は「トークンを作成する」ボタンを押して、「追加のトークンを作成」というテンプレートを使うと楽だと思います。作成したらトークンが表示されるのでどっかにメモ。

プロフィールから発行出来るAPIトークンはUser API Tokenです。間違えないように注意してください。

Terraform の諸々の設定

下記ドキュメントを見ながらproviderとか諸々の設定します。
私は下記のようにしました。

terraform {
  required_providers {
    cloudflare = {
      source  = "cloudflare/cloudflare"
      version = "~> 5"
    }
  }
}

provider "cloudflare" {
}

variable "account_id" {
  description = "cloudflare account id"
  type        = string
  sensitive   = true
}

同時に下記の環境変数もセットします。

export CLOUDFLARE_API_TOKEN=hoge # 先ほどメモしたトークン
export TF_VAR_account_id=fuga # アカウントID

アカウントIDは下記の記事を参考にして取得したものをいれてください。

policyの設定

さて、ドキュメントによるとAccount API Tokenを作るには下記のような感じで出来るようです。下記はドキュメントの例を参考に簡略化したものです。

resource "cloudflare_account_token" "example_account_token" {
  account_id = "023e105f4ecef8ad9ca31a8372d0c353"
  name = "readonly token"
  policies = [{
    effect = "allow"
    permission_groups = [{
      id = "c8fed203ed3043cba015a93ad1616f1f"
    }, {
      id = "82e64a83756745bbbb1c9c2701bf816b"
    }]
    resources = {
      foo = "string"
    }
  }]
}

permission_groups の id の取得

permission_groupsのidを設定したいところなのですが、こちら文字列をべた書きが分かりにくいのでどうにかData Sourceから取得したいです。

私はcloudflare_account_api_token_permission_groups_listというものを使って下記のようにしました。

data "cloudflare_account_api_token_permission_groups_list" "dns_write" {
	account_id = var.account_id
	name = urlencode("DNS Write")
	scope = "com.cloudflare.api.account.zone"
}

data "cloudflare_account_api_token_permission_groups_list" "account_api_tokens_read" {
	account_id = var.account_id
	name = urlencode("Account API Tokens Read")
	scope = "com.cloudflare.api.account"
}

cloudflare_account_api_token_permission_groupsというData Sourceで出来そうに見えますが、なぜか出来ないので注意してください。ドキュメントについてはissueも上がっています。

nameやscopeは下記から確認できます。nameはURLエンコードをする必要があるのですが、直接書くと読みにくいためurlencode()を使用しています。

上記のページに記載がないものもある(実際にAccount API Tokens Readは載っていませんでした)ので注意してください。
確実なのは、CloudflareのAPIを叩いて確認することです。前述の環境変数が入っている状態なら、下記コマンドで取得できます。

curl https://api.cloudflare.com/client/v4/accounts/$TF_VAR_account_id/tokens/permission_groups -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN"

idの取得はone(data.cloudflare_account_api_token_permission_groups_list.dns_write.result).idのように行う想定です。

resources の設定

さて、いまのところ下記のような感じですが、この状態だと resources の設定がなく怒られてしまいます。

data "cloudflare_account_api_token_permission_groups_list" "dns_write" {
	account_id = var.account_id
	name = urlencode("DNS Write")
	scope = "com.cloudflare.api.account.zone"
}

data "cloudflare_account_api_token_permission_groups_list" "account_api_tokens_read" {
	account_id = var.account_id
	name = urlencode("Account API Tokens Read")
	scope = "com.cloudflare.api.account"
}

resource "cloudflare_account_token" "account_api_token" {
	account_id = var.account_id
	name       = "test"
	policies = [ {
	  effect = "allow"
	  permission_groups = [ {
		id = one(data.cloudflare_account_api_token_permission_groups_list.dns_write.result).id
	  }]
	},{
		effect = "allow"
		permission_groups = [ {
		  id = one(data.cloudflare_account_api_token_permission_groups_list.account_api_tokens_read.result).id
		} ]
	} ]
}

resourcesはpolicyを適用する先を指定するものです。詳しくは下記のドキュメントを参考にしてみてください。

結果的に下記のようになりました。

resource "cloudflare_account_token" "account_api_token" {
	account_id = var.account_id
	name       = "test"
	policies = [ {
	  effect = "allow"
	  permission_groups = [ {
		id = one(data.cloudflare_account_api_token_permission_groups_list.dns_write.result).id
	  }]
	  resources = jsonencode({
		"com.cloudflare.api.account.${var.account_id}" = {
		  "com.cloudflare.api.account.zone.*" = "*"
		}
	  })
	},{
		effect = "allow"
		permission_groups = [ {
		  id = one(data.cloudflare_account_api_token_permission_groups_list.account_api_tokens_read.result).id
		} ]
		resources = jsonencode({
		  "com.cloudflare.api.account.${var.account_id}" = "*"
		})
	} ]
}

terraform-provider-cloudflareのv5.13.0で破壊的な変更が入り、resourcesにはjsonencodeが必要になりました。

完成版

一応まとめて置いておきます

terraform {
  required_providers {
    cloudflare = {
      source  = "cloudflare/cloudflare"
      version = "~> 5"
    }
  }
}

provider "cloudflare" {
}

variable "account_id" {
  description = "cloudflare account id"
  type        = string
  sensitive   = true
}

data "cloudflare_account_api_token_permission_groups_list" "dns_write" {
	account_id = var.account_id
	name = urlencode("DNS Write")
	scope = "com.cloudflare.api.account.zone"
}

data "cloudflare_account_api_token_permission_groups_list" "account_api_tokens_read" {
	account_id = var.account_id
	name = urlencode("Account API Tokens Read")
	scope = "com.cloudflare.api.account"
}

resource "cloudflare_account_token" "account_api_token" {
	account_id = var.account_id
	name       = "test"
	policies = [ {
	  effect = "allow"
	  permission_groups = [ {
		id = one(data.cloudflare_account_api_token_permission_groups_list.dns_write.result).id
	  }]
	  resources = jsonencode({
		"com.cloudflare.api.account.${var.account_id}" = {
		  "com.cloudflare.api.account.zone.*" = "*"
		}
	  })
	},{
		effect = "allow"
		permission_groups = [ {
		  id = one(data.cloudflare_account_api_token_permission_groups_list.account_api_tokens_read.result).id
		} ]
		resources = jsonencode({
		  "com.cloudflare.api.account.${var.account_id}" = "*"
		})
	} ]
}

参考リンク

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?