Claude CodeでOpenTofuを使う — validateとplanだけ許可する権限設計
Claude CodeにOpenTofuを触らせるとき、tofu validateとplanをallow、applyをdenyに置く設定と、tofu testやsaved planの落とし穴、state暗号化との関係を説明します。
Claude CodeにOpenTofuを任せるなら、線引きはtofu planまでです。tofu fmt、tofu validate、tofu planは自動で通し、tofu applyとtofu destroyは権限設定のdenyで止めます。差分を読むのはClaude、実行を決めるのは人、という分担です。
ただしOpenTofuには、この線をまたぐコマンドが2つあります。tofu testは実際にインフラを作り、tofu applyに保存済みのプランファイルを渡すと確認なしで実行されます。さらにOpenTofu独自のstate暗号化は、Claudeが扱うプランファイルの安全性を左右します。この記事では、Terraformの記事では出てこないこれらの差分を中心に、.claude/settings.jsonの書き方まで通して説明します。
Terraform側の検証ループ(fmtフック、plan差分のjq抽出)はClaude CodeでTerraformのコードを書く手順にまとめています。ここではtofuコマンドの許可設計に絞ります。
まず置く権限設定
結論から、プロジェクトの.claude/settings.jsonに入れる形を示します。
{
"permissions": {
"allow": [
"Bash(tofu fmt *)",
"Bash(tofu validate *)",
"Bash(tofu init -backend=false *)",
"Bash(tofu plan *)"
],
"ask": [
"Bash(tofu show *)"
],
"deny": [
"Bash(tofu apply *)",
"Bash(tofu destroy *)",
"Bash(tofu test *)",
"Bash(tofu refresh *)",
"Bash(tofu import *)",
"Bash(tofu state *)",
"Bash(tofu taint *)",
"Bash(tofu untaint *)",
"Bash(tofu force-unlock *)",
"Bash(tofu workspace *)"
]
}
}Claude Codeの権限は、deny、ask、allowの順に評価され、最初に一致したものが結果を決めます。バックエンドまで初期化する素のtofu initはどのルールにも載せていないので、実行のたびに確認が出ます。allowに入れたのは-backend=false付きだけです。ここでaskにBash(tofu init *)を足すと、先にaskへ一致して-backend=false付きまで毎回確認になるため、足しません。
*の位置にも注意が要ります。Bash(tofu plan *)はtofu planで始まるコマンドだけを通しますが、Bash(tofu *)と書くとapplyまで通ります。末尾の*の前にスペースがあるとき、引数なしのtofu planにも一致します。サブコマンドより前に*を置くと起動時に警告が出ます。この警告の読み方はwildcard before the restの警告と直し方で扱っています。
コマンド別の振り分け
OpenTofuのコマンドを、Claudeに任せてよい順に並べると次のようになります。
| コマンド | 実際に起きること | 設定 |
|---|---|---|
tofu fmt | 実際に起きることローカルのファイルを整形する | 設定allow |
tofu validate | 実際に起きること設定の構文と内部整合を調べる。リモートに触れない | 設定allow |
tofu init -backend=false | 実際に起きることプラグインとモジュールを取得し、バックエンドには接続しない | 設定allow |
tofu plan | 実際に起きること現在の状態を読み、差分を提案する。変更はしない | 設定allow |
tofu show | 実際に起きることプランやstateを表示する。-jsonだとsensitive値が平文 | 設定ask |
tofu init | 実際に起きることバックエンドの初期化まで行う | 設定未登録(毎回確認) |
tofu apply / tofu destroy | 実際に起きること実際のインフラを変更する | 設定deny |
tofu test | 実際に起きること実インフラを作って検証し、後で壊す | 設定deny |
tofu refresh | 実際に起きることstateをリモートの現状に書き換える | 設定deny |
tofu import / state / taint / force-unlock / workspace | 実際に起きることstateやロックを直接操作する | 設定deny |
tofu validateはリモートのstateもプロバイダーAPIも使わず、設定ファイルだけを見ます。ドキュメントには、エディターの保存時チェックやCIのテスト段として自動実行しても安全と書かれています。Claudeが編集のたびに回す用途に向いています。
ただしvalidateには初期化済みの作業ディレクトリが要ります。バックエンドに接続せずに初期化するにはtofu init -backend=falseを使います。このフラグについてinitのドキュメントは、他の初期化手順がバックエンドを必要とするため、すでに特定のバックエンドで初期化済みの作業ディレクトリにだけ使うよう勧めています。新規のディレクトリでは、最初のtofu initを人が実行してからClaudeに渡すのが素直です。
tofu planは変更を行いませんが、既定ではリモートの既存オブジェクトを読んでstateを最新化します。つまりクラウドの認証情報が環境にあることが前提で、読み取り権限だけのクレデンシャルを渡しておけば、Claudeに許可する操作の実害はさらに小さくなります。実環境への書き込み権限を持つ鍵をそのまま見せたままdenyだけに頼る構成は、次の節の抜け道を考えると心もとありません。
tofu plan -destroyは、全リソースを破棄する計画を作るモードです。実行されるのは計画の作成までで、plan *のallowで通っても破棄は起きません。ただしそのプランをapplyに渡せば破棄が走るので、applyのdenyが生命線になります。
tofu testはapplyを含む
tofu testは、変更を伴わない検証コマンドに見えます。しかしtofu testのドキュメントには、実際のインフラを作成してアサーションを確かめ、終わると作ったリソースを破棄すると書かれています。*.tftest.hclと*.tofutest.hcl、およびJSON版のファイルが、カレントディレクトリかtestsディレクトリから実行されます。
プロバイダーのモックを使わないテストなら、クラウドに本物のリソースが立ちます。課金も、命名の衝突も起こりえます。「テストだから安全」という直感でallowに入れないでください。上の設定ではdenyに置いています。ユニットテスト的に回したいなら、テストファイル側でモックを使う構成にしたうえで、人が実行します。
落とし穴
planを許可すると、保存済みプランの適用まで流れで進みやすい
tofu applyにプランファイルを渡すと、OpenTofuは確認なしでその内容を実行します。-auto-approveは保存済みプランでは無視され、ファイルを渡したこと自体が承認とみなされます。tofu plan -out=tfplanを許可しているなら、Bash(tofu apply *)のdenyが唯一の歯止めです。このdenyは外さないでください。
apply系を止める書き方の限界
denyルールが一致するのは、Claudeが書いたコマンドの文字列です。Claude Codeの権限ドキュメントには、Bash(git push *)がgit push origin mainを止めてもgit -C . push origin mainは止めないという例があります。同じ型がOpenTofuにもあります。
OpenTofuには、作業ディレクトリを指定するグローバルオプション-chdirがあります。tofu -chdir=infra applyは、先頭がtofu applyではないためBash(tofu apply *)に一致しません。またtofu -chdir=infra planもallowのtofu plan *に一致せず、毎回確認になります。モノレポで-chdirを使う運用なら、使うディレクトリごとにルールを足します。
{
"permissions": {
"allow": [
"Bash(tofu -chdir=infra plan *)",
"Bash(tofu -chdir=infra validate *)"
],
"deny": [
"Bash(tofu -chdir=infra apply *)",
"Bash(tofu -chdir=infra destroy *)"
]
}
}絶対パスでの起動(/usr/local/bin/tofu apply)やsh -c 'tofu apply'のような形も、文字列照合では別物です。権限ドキュメントは、denyやaskは「Claudeが普通に書く形」を止めるもので、セキュリティ境界ではないと述べています。より確実にしたい場合は、コマンド全文を検査するPreToolUseフックを使います。フックの作り方はTerraformの記事のフックの節の実装をそのままtofuに読み替えられます。複合コマンドの扱いはBashの複合コマンドと権限が参考になります。
もう一段強い手段は認証情報の側です。Claudeを動かすシェルには読み取り専用のクレデンシャルだけを渡し、適用用の鍵はCIにだけ置きます。これならコマンド文字列をすり抜けても、書き込みの権限がありません。
state暗号化とClaudeの扱うファイル
OpenTofuを選ぶ理由になりやすいのが、stateとプランの暗号化です。OpenTofuは、ローカル保存でもバックエンド経由でも、stateとプランファイルを保存時に暗号化できます。Claude Codeとの関係で重要なのは次の3点です。
プランファイルは平文になりうる。 tofu plan -out=FILEで書かれるファイルには、設定全体、計画された変更の値、入力変数が含まれます。ターミナル出力でマスクされた機密値も、プラン暗号化を有効にしない限り平文で入ることがあると、planのドキュメントは警告しています。Claudeに-out付きのplanを許すなら、プラン暗号化を有効にするか、ファイルをリポジトリに置かない運用にします。
tofu show -jsonはsensitive値を平文で返す。 showのドキュメントに、stateのsensitive値が-jsonでは平文で返ると注記があります。出力はそのままClaudeの会話に入るため、上の設定ではshowをaskにして、中身を見て判断できるようにしています。
暗号化の鍵はClaudeに見せない。パスフレーズ方式でも鍵管理システム方式でも、鍵の値をCLAUDE.mdやsettings.jsonに書かないでください。設定はterraform { encryption { ... } }ブロックに書くか、TF_ENCRYPTION環境変数で渡せます。両方を使うと環境変数側が優先して上書きされます。環境変数で渡す場合は、鍵を含むシェルの設定をClaudeのセッションから切り離したままにできるかを先に確認しておきます。
暗号化を有効にした後は、鍵プロバイダーやメソッドの名前を変えてはいけません。暗号化されたデータにそれぞれの名前のメタデータが入るためです。変更はfallbackブロックを使って段階的に行います。既存のstateに初めて暗号化を足すときは、unencryptedメソッドを一時的に置いて移行します。暗号化の設定ファイルをClaudeに編集させる場合は、この「名前を変えない」制約をCLAUDE.mdに書いておくと、リファクタリングの流れで名前を整える事故を防げます。
環境変数方式で暗号化を渡す構成では、変数が無い環境で平文が書かれないよう、次のようにenforced = trueを入れておく方法があります。
terraform {
encryption {
state {
enforced = true
}
plan {
enforced = true
}
}
}こうしておけば、ClaudeのシェルにTF_ENCRYPTIONが無い状態でplanを実行したとき、平文のプランやstateが作られずにエラーで止まります。「書けてしまうより止まる」ほうが、自動化の安全側です。
TerraformからOpenTofuに移るとき
既存のTerraformリポジトリを移行する場合、Claude Code側で変える点は多くありません。
- コマンド名が
terraformからtofuになるため、allowとdenyのルールをtofuで書き直す。両方のバイナリを残すなら、両方のルールを並べる CLAUDE.mdの検証手順(fmt、validate、planの順)をtofuのコマンドに置き換える- OpenTofuはTerraform構成との互換性を保つ方針で、多くのコードは変更なしで動くと説明されている。それでも、バックアップ、インストール、初期化と検証、小さな変更での確認という移行手順が公式に示されている。Claudeに任せる場合も、最初の
tofu initとtofu planの結果は人が見る terraform_remote_stateで連携する複数構成は、追加の注意が要るとして別の移行ガイドがある
テストファイルは、.tftest.hclと.tofutest.hclが同名で並ぶと.tofutest.hclが優先され、.tftest.hclは無視されます。両対応のモジュールを保つなら、Claudeにテストを書かせるときに拡張子の選び方をCLAUDE.mdで指定しておきます。
CLAUDE.mdに書く作業手順
権限設定は強制、CLAUDE.mdは方針の共有です。両方を揃えると、Claudeが許可されていないコマンドを試みて確認を求められる回数が減ります。
## OpenTofu
- コマンドは `tofu` を使う(`terraform` は使わない)
- 編集後の順序: `tofu fmt` → `tofu validate` → `tofu plan`
- `tofu apply` / `destroy` / `test` / `refresh` は実行しない。必要なら提案だけする
- `tofu plan -out` は使わない。差分は画面出力で報告する
- 暗号化ブロックの key_provider と method の名前は変更しない
- plan の結果は、追加・変更・削除の件数と、削除と再作成の対象を先に報告する「tofu plan -outは使わない」を入れているのは、プランファイルが平文で残る可能性を避けるためです。CIでプランを保存してから適用する運用なら、この行は不要です。
よくあるつまずき
validateが「初期化されていない」で失敗する。 tofu validateには、プラグインとモジュールが取得済みの作業ディレクトリが必要です。tofu init -backend=falseを先に実行します。
planが認証エラーになる。 tofu planは既定でリモートの状態を読みます。クレデンシャルがClaudeのシェルに届いているかを確認します。-refresh=falseを付ければ読み取りを省けますが、外部での変更を見落とすため、不完全なプランになりえます。
-lock=falseを勝手に付けられる。ロックを取らないと、同じワークスペースを別の人が同時に操作したとき危険です。必要がなければCLAUDE.mdで使わないよう書いておきます。
確認が毎回出る。許可ルールの先頭がコマンドの形(-chdir付きなど)と合っていません。権限ダイアログで「Yes, and don't ask again」を選ぶとルールが保存されますが、-chdirの違いごとに別ルールになります。/permissionsで整理すると、重複を減らせます。
まとめ
Claude CodeでOpenTofuを扱う設定の要点は、allowをfmt・validate・planに、denyをapply・destroy・test・refreshとstate操作に分けることです。tofu testと保存済みプランのapplyは見落としやすく、-chdirなどの別形式はルールの追加か、読み取り専用クレデンシャルで受けます。state暗号化は、Claudeが生成するプランファイルとshow -jsonの出力を守る別レイヤーです。
OpenTofu固有の機能に合わせて、enforced = trueと鍵の置き場所だけは先に決めておくと、権限ルールの抜けがあっても被害が小さく済みます。
関連する記事
Claude Code をもっと見る →Claude Codeとは — できること・料金・始め方と使い方の全体像
Claude CodeでScrapyのスクレイパーを作る — robots.txt遵守とcontracts検証
Claude CodeでROS 2パッケージを作る — colcon buildとlaunch_testの反復
Claude CodeでPySparkのジョブを書く — local[2]のpytestで検証する手順
Claude CodeでPower Platformのソリューションを編集する手順 — pac solution
Claude CodeでMQTT検証 — mosquittoでpublishとsubscribeを機械的に確かめる