🚀

[やってみた]Cloudflareを新規に導入することになったのでTerraformで構築してみた

に公開

概要

弊社では、AWSを中心にインフラ環境を構築しており、メディア配信についてはCloudfront×S3バケットで提供しています。
あるサービスにおいて、動画配信サービスに注力し始めているのですが、想定よりもコストが増大しており、この最適化を目的にCloudflareの導入を検討することになりました。
AWSについては基本的にTerraformで管理しており、CloudflareについてもTerraformで管理できそうだったので、トライしてみたときの記録をまとめます。

Cloudflare Terraform Provider

Terraformのコード一覧については以下を参照しました。
https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs

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

初期設定~その1~

アカウント作成

まず、最初にアカウントを作成します。ここでいうアカウントとは組織単位のことを指しており、操作するユーザとは異なります。
このとき、まだAPIトークンが存在しないので、管理画面上でアカウントを作成します。

  1. https://www.cloudflare.com/ja-jp/ にアクセスし、画面右上に存在する「ログイン」ボタンを押下します。
  2. 画面下部に存在する「Sign up」リンクを押下します。
  3. メールアドレスとパスワードを入力し、CAPTCHA認証を行ったうえで「Sign up」ボタンを押下します。




初期設定~その2~

APIトークン作成

Terraformの利用にあたり、APIトークンを作成する必要があります。
一番最初は管理コンソール上で一時利用のAPIトークンを作成し、Terraformで操作できるようにしてから改めてAPIトークンを作成する方式を取っています。

  1. 「アカウントの管理」>「アカウントAPIトークン」を押下します。
  2. 画面上部に存在する「トークンを作成する」ボタンを押下します。
  3. (一時利用のため)”追加のトークンを作成”の「テンプレートを使用する」ボタンを押下します。
  4. 権限が”アカウント”・”APIトークン”・”編集”になっていることを確認し、必要に応じて「クライアントIPアドレスフィルタリング」と「TTL」のパラメータを指定し「概要に進む」ボタンを押下します。
  5. 内容を確認し、「トークンを作成する」ボタンを押下する。
  6. 表示されるAPIトークンの値を記録し、以下Terraformのproviderブロックに設定します。
provider "cloudflare" {
  api_token = "Hxxxxxxxxxxxxxxxo" // 手順6で確認したAPIトークンの値を指定
}





初期設定~その3~

Terraform設定

まずはTerraformでアカウントAPIトークンを作成するコードを記載します。

/*
 * アカウント情報
 */
data "cloudflare_account" "hogehoge" {
  filter = {
    name = "hogehoge"
  }
}

/*
 * APIトークンの権限グループ一覧取得
 */
data "cloudflare_account_api_token_permission_groups_list" "all" {
  account_id = data.cloudflare_account.hogehoge.account_id
}

locals {
  api_token_permission_group_map = {
    for group in data.cloudflare_account_api_token_permission_groups_list.all.result :
    group.name => group.id...
  }

  api_token_permission_groups = {
    // 使用する権限グループを指定
    account_api_tokens_write = local.api_token_permission_group_map["Account API Tokens Write"][0]
    account_settings_write   = local.api_token_permission_group_map["Account Settings Write"][0]
    account_rulesets_write   = local.api_token_permission_group_map["Account Rulesets Write"][0]
    workers_r2_storage_write = local.api_token_permission_group_map["Workers R2 Storage Write"][0]
    zone_write               = local.api_token_permission_group_map["Zone Write"][0]
  }
}

/*
 * アカウントAPIトークン
 */
resource "cloudflare_account_token" "for_terraform" {
  account_id = data.cloudflare_account.hogehoge.account_id
  name       = "Create Additional Tokens"
  policies = [
    {
      effect = "allow"
      permission_groups = [
        // ここに必要な権限を指定
        { id = local.api_token_permission_groups.account_api_tokens_write },
        { id = local.api_token_permission_groups.workers_r2_storage_write },
        { id = local.api_token_permission_groups.account_rulesets_write },
        { id = local.api_token_permission_groups.account_settings_write },
        { id = local.api_token_permission_groups.zone_write },
      ]
      resources = {
        // 権限の適用範囲を指定
        "com.cloudflare.api.account.${data.cloudflare_account.hogehoge.account_id}" = "*"
      }
    },
  ]
  condition = {
    request_ip = {
      in = [
        // アクセス許可を与えるIPアドレスを指定
        "123.45.67.89/32",
      ]
    }
  }

  lifecycle {
    ignore_changes = [
      // TTL周りは更新されるため、管理対象外としている。
      expires_on,
      not_before,
    ]
  }
}

こちらでterraform applyを実行すると、APIトークンが作成されます。
その後、Cloudflareの管理画面にアクセスし、作成されたAPIトークンを確認します。

  1. 「アカウントの管理」>「アカウントAPIトークン」を押下します。
  2. terraformで作成したトークン名(今回は"Create Additional Tokens")の三点リーダーを押下し、「ロール」を押下する。
  3. トークン Create Additional Tokens をロールしてもよろしいですか?と表示されるので、「ロール」ボタンを押下する。
  4. 管理画面上でAPIトークンを作成したときと同様に、APIトークンの値を記録し、先ほど設定したproviderブロックのapi_tokenの値の差し替えます。


以上で、Terraformを使ってCloudflareのリソースを操作する準備が整いました。
次は実際にリソースを作成していきます。

R2 初期設定

AWSでいうS3バケットに相当するストレージサービスのR2をTerraformで構築してみます。

/*
 * アカウント情報
 */
data "cloudflare_account" "hogehoge" {
  filter = {
    name = "hogehoge"
  }
}

/*
 * ゾーン(ドメイン)情報
 */
data "cloudflare_zone" "hogehoge_com" {
  filter = {
    name = "hogehoge.com"
  }
}

# ===========================================
# R2
# ===========================================
resource "cloudflare_r2_bucket" "hogehoge" {
  account_id    = data.cloudflare_account.hogehoge.account_id
  name          = "hogehoge"
  location      = "APAC"
  storage_class = "Standard"
}

resource "cloudflare_r2_custom_domain" "test_hogehoge" {
  account_id  = data.cloudflare_account.hogehoge.account_id
  bucket_name = cloudflare_r2_bucket.hogehoge.name
  domain      = "test.hogehoge.com"
  enabled     = true
  zone_id     = data.cloudflare_zone.hogehoge_com.zone_id
  min_tls     = "1.3"
}

resource "cloudflare_r2_bucket_cors" "hogehoge" {
  account_id  = data.cloudflare_account.hogehoge.account_id
  bucket_name = cloudflare_r2_bucket.hogehoge.name
  rules = [{
    allowed = {
      methods = ["GET"]
      origins = [
        "http://localhost:3000",
        "https://test.hogehoge.com",
      ]
    }
  }]
}

特定ドメインのアクセス制限

AWSで使用していた際、特定環境へのアクセスはWAFなどで制限をかけており、Cloudflareでも同様の設定を行います。

/*
 * ゾーン(ドメイン)情報
 */
data "cloudflare_zone" "hogehoge_com" {
  filter = {
    name = "hogehoge.com"
  }
}

/*
 * 許可するIPアドレス一覧
 */
locals {
  office_ip_list = {
    tokyo_ip     = "123.45.67.89"
    osaka_ip     = "23.45.67.89"
  }
  allowed_ips = values(local.office_ip_list)

  # CloudflareのExpression用にフォーマット
  allowed_ips_expression = "{${join(" ", local.allowed_ips)}}"
}

# ===========================================
# セキュリティルール
# ===========================================
resource "cloudflare_ruleset" "custom_security" {
  zone_id = data.cloudflare_zone.hogehoge_com.zone_id
  name    = "default"
  kind    = "zone"
  phase   = "http_request_firewall_custom"
  rules = [
    {
      action = "block"
      expression = <<-EOT
        (
          not ip.src in ${local.allowed_ips_expression} and
          http.host eq "test.hogehoge.com"
        ) or 
        (
          not ip.src in ${local.allowed_ips_expression} and
          http.host eq "test-2nd.hogehoge.com"
        )
      EOT
      description = "TESTアクセス"
      enabled     = true
    }
  ]
}

最後に

普段触っているAWSと比較すると、細かいところでエラーが発生しており、100%Terraformで管理はできていませんが、当初の目的はほぼTerraformで完結するところまで到達できました。
特に、アクセス制限におけるIP制限等については、管理画面からの設定だと煩雑になってしまうため、コードで管理できるのは非常に助かっています。
当記事がCloudflareをTerraformで管理したい方の一助になれば幸いです。

nextbeat Tech Blog

Discussion