Windows での Terraform Provider TLS 干渉と filesystem mirror 回避策
前記事で bootstrap が終わり、いよいよ Terraform を回そうとした矢先、terraform init が 「tls: bad record MAC」 という謎のエラーで落ちる現象に遭遇しました。本記事では、その原因究明と回避策を時系列で書きます。
遭遇した症状
terraform/envs/prod ディレクトリで terraform init を実行:
$ terraform init
Initializing the backend...
Successfully configured the backend "s3"!
Initializing modules...
Initializing provider plugins...
- Finding hashicorp/aws versions matching "~> 5.0"...
- Installing hashicorp/aws v5.100.0...
╷
│ Error: Failed to install provider
│
│ Error while installing hashicorp/aws v5.100.0: releases.hashicorp.com:
│ local error: tls: bad record MAC
╵
backend (S3) との通信は成功しているのに、provider 本体(AWS SDK の zip)のダウンロードで TLS エラー。「local error」というのが気になりました。
切り分け①: 同じ URL を curl で叩いてみる
curl -sL -o /tmp/aws.zip \
https://releases.hashicorp.com/terraform-provider-aws/5.100.0/terraform-provider-aws_5.100.0_windows_amd64.zip \
-w "HTTP: %{http_code} / Size: %{size_download}\n"
# HTTP: 200 / Size: 10122586
curl だと一見ダウンロードできています。ただし、サイズが 10 MB。本物は HEAD で確認すると 156 MB。明らかに途中で切れている。
curl -sIL "https://releases.hashicorp.com/.../aws_5.100.0_windows_amd64.zip" | grep -i content-length
# Content-Length: 156704800 ← 約 149 MiB が本物
つまり ダウンロード途中で TLS 接続が切れている。Terraform 内部の Go TLS ライブラリは厳密に検証するので「bad record MAC」と判定して落とす。curl は緩いので途中まででも保存してしまう(だから一見「成功」に見える)。
切り分け②: SHA を比較
curl -sL https://registry.terraform.io/v1/providers/hashicorp/aws/5.100.0/download/windows/amd64 \
| grep -o '"shasum":"[^"]*'
# shasum: 9f8b909d3ec50ade83c8062290378b1ec553edef6a447c56dadc01a99f4eaa93
sha256sum /tmp/aws.zip
# d9b152779b29960fa08754e4a1f1335dab815b48b4d6d75f53aa6ebf6bb54b1b
SHA が完全に違います。ダウンロードされたファイルは公式の zip ではない(または途中で切れた不完全なもの)。
切り分け③: PowerShell の Invoke-WebRequest で再現
Invoke-WebRequest -Uri $URL -OutFile $Out -UseBasicParsing
# The decryption operation failed, see inner exception.
# Got 39816105 bytes (約 38 MiB)
PowerShell でも同じエラー。サイズは違う(10MB → 38MB)が 毎回違う場所で切れる。これで「ネットワーク経路の問題」と確信。
原因仮説: 中継機器の TLS 干渉
調査の結果、原因は以下のいずれかと考えました:
- セキュリティソフト(Windows Defender / アンチウイルス)の HTTPS スキャン
- ISP / ルータ の DPI(Deep Packet Inspection)
- 家庭内ネットワーク の TLS タイムアウト設定
共通するのは、大型バイナリ(100MB 超)の TLS セッションが途中で切られる という挙動。小さい HTTPS リクエストは通るが、大きいダウンロードは中断される。
切り分け④: TLS 1.2 強制で再試行
TLS 1.3 のバグや中継機器の TLS 1.3 干渉を疑って、curl で TLS 1.2 強制 + retry を入れて再試行:
curl --tlsv1.2 --tls-max 1.2 --http1.1 --ipv4 \
--retry 5 --retry-delay 3 --connect-timeout 30 \
-L -o aws_tls12.zip "$URL"
結果: 129 MB まで進んで失敗(前回より遥かに長く持った)。約 82% まで来てから切れた。
突破口: resume ダウンロード
「途中まで取れている → 残りだけ取り直せばいい」と気付いて -C -(resume)オプションで再試行:
curl --tlsv1.2 --tls-max 1.2 --http1.1 -C - -L -o aws_tls12.zip "$URL"
# 残り 25 MB をダウンロード
# 25.52M 100% で完走 ✅
sha256sum aws_tls12.zip
# 9f8b909d3ec50ade83c8062290378b1ec553edef6a447c56dadc01a99f4eaa93 ← SHA 一致
これで完全な provider zip が手に入りました。
Terraform に provider を渡す: filesystem mirror
取得した zip を Terraform に「これ使って」と教える方法は filesystem mirror です。手順は 2 つ:
1. zip を所定の場所に配置
$MirrorRoot = 'C:\Users\<ユーザー>\.terraform.d\providers'
$ProviderDir = "$MirrorRoot\registry.terraform.io\hashicorp\aws"
# ディレクトリ作成
New-Item -ItemType Directory -Force -Path $ProviderDir | Out-Null
# zip を移動 (Terraform が期待するファイル名)
Move-Item -Path 'aws_tls12.zip' `
-Destination "$ProviderDir\terraform-provider-aws_5.100.0_windows_amd64.zip"
2. Terraform の CLI 設定 (~/AppData/Roaming/terraform.rc)
provider_installation {
filesystem_mirror {
path = "C:/Users/<ユーザー>/.terraform.d/providers"
include = ["registry.terraform.io/hashicorp/aws"]
}
direct {
exclude = ["registry.terraform.io/hashicorp/aws"]
}
}
これで「AWS provider はローカルのミラーから読み、それ以外は通常通りオンラインから」という挙動になります。
terraform.rc の BOM 落とし穴
このファイル、PowerShell 5.1 の Set-Content -Encoding utf8 で書くと BOM 付き UTF-8 になり、Terraform は illegal char でパースエラーを出します:
Error parsing C:\Users\...\terraform.rc: At 1:1: illegal char
BOM 無し UTF-8 で書き直し:
# PowerShell で BOM 無し UTF-8 を書く
$utf8NoBom = New-Object System.Text.UTF8Encoding($false)
[System.IO.File]::WriteAllText($rcPath, $rcContent, $utf8NoBom)
# 確認 (先頭バイトが 0xEF 0xBB 0xBF だったら BOM 付き)
$bytes = [System.IO.File]::ReadAllBytes($rcPath)
$bytes[0..2] | ForEach-Object { '{0:X2}' -f $_ }
terraform init 再実行
BOM 修正後に再度 init:
$ terraform init -reconfigure
Initializing provider plugins...
- Installed hashicorp/aws v5.100.0 (unauthenticated)
Terraform has successfully been initialized!
(unauthenticated) は「公式署名検証はスキップしたが、ファイル自体の SHA256 は一致している」状態。サイズも SHA も先に確認済なので問題なし。
archive provider も同じ問題に
後で backend_api モジュールに archive_file data source を追加した時、同じ TLS エラーが hashicorp/archive でも発生しました。同じ手順で zip を取得してミラーに配置することで解決。
結局、ミラーに配置した provider は 2 個:
terraform-provider-aws_5.100.0_windows_amd64.zip(約 156 MB)terraform-provider-archive_2.7.1_windows_amd64.zip(約 6 MB)
archive は 6 MB と小さいですが、家庭内ネットワークの相性で同じ症状になることがあるようです。
学び
このハマりで得た教訓:
- 「local error: tls: bad record MAC」は中継機器の TLS 干渉のサイン
- 大型バイナリのダウンロード失敗は size + SHA で切り分ける
- resume (
-C -) は強い味方。途中まで取れてれば残りを取り直せる - Terraform は filesystem mirror で「ローカルから provider を読む」運用が可能。CI ではこれを使って毎回ダウンロードしないという最適化もできる
- PowerShell 5.1 の
-Encoding utf8は BOM 付き。設定ファイルでハマりがち
恒久対策
本来は中継機器の HTTPS 干渉を解消するのがベストですが、家庭内ネットワークだと特定が難しい。filesystem mirror で運用回避しています。
VPN を使うと、ローカルの干渉が無いネットワーク経路を通せるので解消することもあります。本シリーズでは VPN 無しで filesystem mirror に倒しました。
次の記事
provider が読めるようになったら、いよいよ Terraform でリソースを書いていきます。次の記事では モジュール設計の考え方(modules / envs / providers alias)について書きます。「どこを共通化して、どこを環境ごとに分けるか」の判断基準が見えてきます。
📚 用語集
- provider (Terraform プロバイダ)
- Terraform で AWS や GCP など各クラウドを操作するための拡張プラグイン。
hashicorp/awsなど、数百 MB 級の zip でダウンロードされる。 - terraform init
- Terraform の初期化コマンド。provider のダウンロード・backend 接続・モジュール解決を行う。
- TLS (Transport Layer Security)
- HTTPS の中で使われる暗号化通信プロトコル。1.2 と 1.3 が現役。MAC(Message Authentication Code)で改ざん検知している。
- bad record MAC
- TLS の改ざん検知 (MAC) が失敗したときのエラー。途中で内容が書き換わったり、データが切れたときに発生。
- SHA256 / SHA-2
- ハッシュ関数の一種。256 ビットの固定長ダイジェストを生成。ファイルが改ざんされていないかの検証に使う。
- HEAD リクエスト
- HTTP メソッドの 1 つ。本文は受信せず、ヘッダ(Content-Length など)だけ取得する。ファイルサイズの事前確認に便利。
- curl
- コマンドラインの HTTP / HTTPS クライアント。Linux / macOS 標準、Windows 10 以降にも同梱。
- --tlsv1.2 / --tls-max 1.2
- curl のオプションで、TLS バージョンを 1.2 のみに制限する。1.3 のバグや中継機器の TLS 1.3 干渉を回避する切り分けに使う。
- --retry, --retry-delay, --retry-max-time
- curl のリトライ系オプション。失敗したリクエストを自動再試行する。一時的なネットワーク不安定の対処。
- -C - (resume)
- curl の resume オプション。途中まで保存されたファイルがあれば、続きから取得する。Range リクエストを利用。
- filesystem mirror
- Terraform の CLI 設定で「provider をローカルディレクトリから読む」よう指定する仕組み。
~/AppData/Roaming/terraform.rc(Windows)/~/.terraformrc(macOS/Linux)に書く。 - terraform.rc
- Terraform 全体の CLI 設定ファイル。
provider_installationブロックで filesystem mirror を定義。 - BOM (Byte Order Mark)
- UTF-8 ファイルの先頭に付く 3 バイト(
EF BB BF)。HCL / YAML / JSON のパーサが嫌うので、設定ファイルでは外す。 - HCL (HashiCorp Configuration Language)
- Terraform 等で使われる設定言語。
.tfや.terraformrcがこれで書かれる。 - DPI (Deep Packet Inspection)
- ネットワーク機器がパケット内容を検査する技術。セキュリティ目的だが、TLS 1.3 等で誤動作することがある。
- HTTPS スキャン (HTTPS inspection)
- セキュリティソフトや企業ファイアウォールが、HTTPS 通信を一旦復号して中身をチェックする機能。Terraform 等の厳密な TLS 検証と相性が悪いことがある。