🔒

Fastly Terraform Provider を使ったオリジン ACL/Firewall Rule の自動更新

2022/02/05に公開

Fastly Terraform Provider について

認知度はまだそれほど高くないのですが、Fastly からは専用の Terraform Provider が開発・公開されており、地道にアップデートが行われています。
https://registry.terraform.io/providers/fastly/fastly/latest/docs

私自身、直近でこの Provider の開発に携わらせてもらい、それなりの数のバグを直したり細かな機能追加を行ってきたので、1・2年前の品質と比べると、現在のバージョンはかなり安定して利用できる状態になっていると思います。ぜひ試してみてください。

Fastly の VCL/Wasm サービス設定の変更をコード管理できるのはもちろん (IaC)、WAF や TLS 証明書の管理にも幅広く対応しています。

Fastly が公開している Public IP list

こちらも同様にあまり知られていないのですが、Fastly は自社インフラの Public IP list を公開しており、最新のリストはいつでも API から取得できます。

https://developer.fastly.com/reference/api/utils/public-ip-list/

これは何かというと、クライアント -> Fastly POP -> オリジン というリクエストフローにおいて (i.e., cache MISS/PASS)、Fastly POP がオリジンサーバーにアクセスする際に利用する IP アドレスを意味しています。つまり、オリジンからみた Fastly の src IP です。

また、Fastly のリアルタイムログ送信時にもデータはこの IP アドレス範囲内から送られてきます。

このリストを元にオリジンサーバー側で ACL/Firewall を適切に行うことで、Fastly ネットワーク以外からのオリジンへの直アクセスを禁止し、不要なセキュリティリスクを排除することができます。

突然のサービスダウン

「何も変更していないのに突然 503 エラーが頻発している!オリジンサーバーには全く問題はない!」
「急にログが送られてこなくなった!」

ある日突然サービスダウンに見舞われる事になります。
調べていくと、Fastly -> オリジンへの接続が確立できません。

そうです、上述の Public IP list を元に作成した ACL が作ったっきり放置され、きちんと最新のリストと同期・更新する運用がされていなかったのです。

これは実際に Fastly で働いていた経験からも、非常によくあるケースの1つでした。

その原因の一つは…

Public IP list の更新頻度

そこにはっきりと決まったスケジュールは存在しておらず、Fastly のインフラ増強や他の様々な理由で不定期に Public IP list が更新されることになります。この頻度は一般に非常に低く、多くても1年に1~2回というのがこれまでの実績だと思います。
この頻度故に更新が見逃されやすく、障害が起きて初めて気づく、という流れが起きてしまいます。CDN のように、全てのリクエストを吸収するフロントレイヤーにおいて、こうしたミスは致命的です。

Terraform を使った自動化

ではようやく本題に入りましょう。
Fastly Terraform Provider が提供している fastly_ip_ranges data source を使う事ができます。

https://registry.terraform.io/providers/fastly/fastly/latest/docs/data-sources/ip_ranges

まずは data source をクエリして動作に問題が無いことを確認します。

main.tf
terraform {
  required_providers {
    fastly = {
      source = "fastly/fastly"
      version = ">= 0.41.0"
    }
  }
}

provider "fastly" {
  no_auth = true
  alias  = "no_auth"
}

data "fastly_ip_ranges" "fastly" {
    provider = fastly.no_auth
}

output "fastly_ip_ranges" {
    value = data.fastly_ip_ranges.fastly
}
$ terraform plan

Changes to Outputs:
  + fastly_ip_ranges = {
      + cidr_blocks      = [
          + "103.244.50.0/24",
          + "103.245.222.0/23",
          + "103.245.224.0/24",
          + "104.156.80.0/20",
          + "140.248.128.0/17",
          + "140.248.64.0/18",
          + "146.75.0.0/17",
          + "151.101.0.0/16",
          + "157.52.64.0/18",
          + "167.82.0.0/17",
          + "167.82.128.0/20",
          + "167.82.160.0/20",
          + "167.82.224.0/20",
          + "172.111.64.0/18",
          + "185.31.16.0/22",
          + "199.232.0.0/16",
          + "199.27.72.0/21",
          + "23.235.32.0/20",
          + "43.249.72.0/22",
        ]
      + id               = "1355856011"
      + ipv6_cidr_blocks = [
          + "2a04:4e40::/32",
          + "2a04:4e42::/32",
        ]
    }

[...]

上記のように正しくデータが表示されていれば OK です。

AWS Security Group への適用

ここでは簡単な例として、特定の AWS Security Group へルールを追加してみます。

main.tf
terraform {
  required_providers {
    fastly = {
      source  = "fastly/fastly"
      version = ">= 0.41.0"
    }
    aws = {
      source  = "hashicorp/aws"
      version = ">= 3.74.0"
    }
  }
}

provider "fastly" {
  no_auth = true
  alias   = "no_auth"
}

data "fastly_ip_ranges" "fastly" {
  provider = fastly.no_auth
}

provider "aws" {
  # assume configurations are supplied via env variables
}

data "aws_security_groups" "fastly" {
  filter {
    name   = "group-name"
    values = ["fastly-src-ip"]
  }
}

resource "aws_security_group_rule" "fastly-http" {
  type              = "ingress"
  from_port         = 80
  to_port           = 80
  protocol          = "tcp"
  cidr_blocks       = data.fastly_ip_ranges.fastly.cidr_blocks
  ipv6_cidr_blocks  = data.fastly_ip_ranges.fastly.ipv6_cidr_blocks
  security_group_id = data.aws_security_groups.fastly.ids[0]
}

resource "aws_security_group_rule" "fastly-https" {
  type              = "ingress"
  from_port         = 443
  to_port           = 443
  protocol          = "tcp"
  cidr_blocks       = data.fastly_ip_ranges.fastly.cidr_blocks
  ipv6_cidr_blocks  = data.fastly_ip_ranges.fastly.ipv6_cidr_blocks
  security_group_id = data.aws_security_groups.fastly.ids[0]
}

上記の例では、まず aws_security_groups data resource を使って対象となる fastly-src-ip の Security Group id を取得し、aws_security_group_rule resource でルールの追加を行っています。

今回はデモ目的で filter によって取得される id が1つだけと分かっているので security_group_id = data.aws_security_groups.fastly.ids[0] にようにリストの最初の要素だけを処理していますが、VPC が複数あり 同名の Security Group が複数存在するような場合には、結果に複数の id が含まれるため、aws_security_group_rule resource で for_each を使うなどし、適宜対処できると思います。

Terraform の醍醐味は、なんといってもそれを取り巻くエコシステムの充実にあります。
上記では AWS を例に挙げましたが、GCP や Azure にも専用の Provider は提供されており、同様の手段が取れることは言うまでもありません。オンプレミス環境にも活用できるでしょう。

apply 自動化に向けて

ここまで、Terrafrom を使って簡単に Fastly の Public IP list を取得し、任意のリソースに渡せることが分かりました。ここから先の実際の自動化の実運用は、現在の組織で運用されている CI/CD パイプラインによって使いやすい選択肢を取れると思います。

ACL の更新、という最小単位での処理に切り出しているのであれば、apply を cron 的に実行し、オペレーターの介入なしにそのままシンプルに自動化してしまうのも1つですし (--auto-approve)、安全バーを設けたい場合には plan の差分によって任意のワークフローをトリガーし、Slack の通知によってオペレーターの承認を促す、という一般的な方法も取れると思います。

Discussion