ブログ

【AI活用】AIエージェントに SAP BTP を触らせない ― DevContainer と通信制御で作る「AI隔離」開発環境【前編】

この記事をSNSでシェア!

はじめに

こんにちは。BS事業部の上田です。

Claude Code をはじめとする AI コーディングエージェントは、コードを書くだけでなく、ターミナルでコマンドを実行し、ファイルを読み書きし、インターネットにアクセスできます。開発のスピードが大きく上がる一方で、私たちのように SAP BTP 上でお客様のシステムを開発・運用している立場からすると、気になることがあります。

開発端末には、BTP にログイン済みの cf コマンド、Git リポジトリに push できる SSH 鍵、社内やお客様の環境への経路がそろっています。つまり、AI エージェントが「その気になれば」本番環境を操作できてしまう状態です。

図1 よくある構成と、本記事の構成の違い

そこで私たちは、AI エージェントを使う開発環境を次の3つの原則で組み立て直しました。

【この記事の結論】「禁止する」のではなく「届かない」構造にする
 1. AI は DevContainer の中だけで動かす
 2. コンテナの通信は許可制にする(AI ON / AI OFF の2モード)
 3. 本番に届く鍵と道具はコンテナの外に置く(commit は AI、push とデプロイは人)

本記事は前編として、この3原則の考え方と、それを実現する仕組み(DevContainer・Squid・iptables)を紹介します。

後編では次回記事として、この環境を毎回手作業で作らずに済むよう自動構築するために作った社内ツールを紹介します。

対象読者

  • Claude Code などの AI コーディングエージェントを業務で使い始めたい方
  • SAP BTP などお客様の実行環境に接続する開発をしていて、AI の利用に不安がある方
  • チームの AI 利用ルールや開発環境の標準を決める立場の方

本記事で取り扱わないこと

  • 環境の具体的な構築手順と自動化(後編で扱います)
  • 仕組みを理解したうえで意図的に回避する行為への対策
  • DNS を使ったデータの持ち出しへの対策(「この仕組みで防げないこと」で触れます)

何が怖いのか ― AI エージェントの「手の届く範囲」

AI エージェントは、開発者と同じ権限でコマンドを実行します。開発者がログインしている CLI、読める SSH 鍵、つながるネットワークは、すべて AI からも使えます。

私たちが心配しているのは、AI の悪意ではありません。誤操作と暴走です。たとえば次のような場面です。

図2 誤操作が起きる場面の例

AI の提案を1つずつ確認すれば防げますが、何十回も承認を求められるうちに確認はどうしても甘くなります(いわゆる「承認疲れ」です)。経験の浅いメンバーほど、提案の危うさに気づきにくいという問題もあります。

「本番環境に触れる操作は禁止」というルールを周知したり、AI ツール側の許可設定で縛ったりする方法もあります。ただ、ルールは守られているかを確かめにくく、ツールの設定は人によってばらつきます。そこで発想を変え、AI がどこで動くかによって、そもそも何に届くかを決めてしまうことにしました。

AI が動く場所ごとの「手の届く範囲」を整理すると、次のようになります。

AI が動く場所SAP BTP へのデプロイ(cf 等)SSH 鍵を使った git push任意の Web サイトAI の API
Windows
(ローカルの VS Code)
端末に CLI があれば可能可能可能可能
WSL
(Remote-WSL 接続の VS Code)
WSL に CLI があれば可能可能可能可能
DevContainer
(本記事の構成)
不可(CLI を入れない)不可(鍵を置かない・22番ポートも遮断)AI ON:許可リストのみ
AI OFF:可能
AI ON:選んだ AI のみ
AI OFF:不可
表1 AI が動く場所と、届くもの

Windows や WSL で AI を動かすと、開発者ができることは AI にもすべてできます。DevContainer の中に閉じ込め、コンテナの外とのやり取りを絞ることで、初めて「届かない」状態を作れます。

全体像 ― どこに何を置くか

3原則に沿って組み立てた環境の全体像が次の図です。

図3 AI 隔離開発環境の全体構成

ポイントは、AI がいる場所(DevContainer)と、いない場所(WSL・Windows)の境界をはっきりさせ、境界を越える通信を絞ることです。

  • Windows:VS Code の画面を表示するだけです。AI の拡張機能は入れません。
  • WSL2:人がターミナルで操作する場所です。デプロイ用の CLI と SSH 鍵はここに置きます。コンテナの通信を見張る Squid と iptables もここで動きます。
  • DevContainer:AI が作業する場所です。ソースコードと言語ランタイムはありますが、本番に届く鍵と道具はありません。

この構成を支えているのが、冒頭で挙げた3つの原則です。以降、1つずつ説明します。

図4 AI 隔離の3原則

原則① AI は DevContainer の中だけで動かす

DevContainer(Development Container)は、VS Code などのエディタからコンテナの中に接続して開発するための仕組みです。プロジェクトの .devcontainer/devcontainer.json に、使うイメージや拡張機能を書いておくと、VS Code がそのコンテナを作って接続してくれます。

AI エージェントの拡張機能は、Windows 側の VS Code には入れず、devcontainer.json の extensions にだけ書きます。

{
  "image": "mcr.microsoft.com/devcontainers/base:ubuntu",
  "customizations": {
    "vscode": {
      "extensions": [
        "anthropic.claude-code"   // AI はコンテナの中にだけ入れる
      ]
    }
  }
}

こうすると、コンテナに接続しているときだけ AI が使えるという分かりやすい境界ができます。Windows や Remote-WSL で AI を動かしてしまうと、後で説明する通信制御の対象外になってしまうため、この「置き場所」の徹底がすべての前提になります。

図5 AI 拡張機能を入れる場所

原則② コンテナの通信は許可制にする(AI ON / AI OFF)

仕組み:専用ネットワーク+Squid+iptables

DevContainer を、WSL 上に作った Docker の専用ネットワークに接続します。このネットワークから外に出る通信を、WSL 側の iptables と Squid(プロキシサーバー)で見張ります。

  1. iptables:専用ネットワークから出る 80番・443番の通信を Squid に転送し、それ以外のポートはすべて遮断します。SSH(22番)もここで止まります。
  2. Squid:転送されてきた通信の接続先ドメインを見て、許可するか遮断するかを決めます。

iptables による転送(透過プロキシ)にしているのは、プロキシの環境変数を無視するソフトウェアがあっても、制御から漏れないようにするためです。

# 専用ネットワークのブリッジ(例: dev-br0)から出る通信だけが対象
iptables -t nat -A PREROUTING -i dev-br0 -p tcp --dport 80  -j REDIRECT --to-port 3129
iptables -t nat -A PREROUTING -i dev-br0 -p tcp --dport 443 -j REDIRECT --to-port 3130
# 同じネットワーク内のコンテナ同士は許可し、それ以外に出ていく通信は遮断
iptables -I DOCKER-USER -i dev-br0 -o dev-br0 -j RETURN
iptables -A DOCKER-USER -i dev-br0 -j DROP

※ 説明用に簡略化しています。実際は専用のチェーンにまとめ、何度実行しても同じ状態になるようにしています。

HTTPS は復号しない(SNI だけを見る)

HTTPS の通信内容は暗号化されているため、プロキシで中身を見るには復号が必要です。ただ、復号するにはコンテナ側に独自の証明書を信頼させる必要があり、手間がかかるうえ、それ自体がリスクにもなります。

そこで、TLS の接続開始時に平文で送られる SNI(接続先のホスト名)だけを見て判定する方式にしました。Squid の ssl_bump peek で SNI を読み、許可するものは復号せずにそのまま通し(splice)、遮断するものは接続を切ります(terminate)。

図6 SNI だけを見て判定し、HTTPS は復号しない
acl step1 at_step SslBump1
ssl_bump peek step1                  # SNI を読むだけ(復号しない)
ssl_bump terminate ai_blocked_ssl    # 選んでいない AI は最初に遮断
ssl_bump splice    ai_allowed_ssl    # 使う AI は通す
ssl_bump splice    allowed_ssl       # 許可リストのサイトは通す
ssl_bump terminate all               # それ以外は遮断(AI ON の場合)

AI ON と AI OFF の2つのモード

通信の許可ルールは、次の2つのモードを切り替えて使います。

図7 AI ON と AI OFF の2つのモード

AI ON では、許可リストに書いたドメインと使う AI のドメイン以外はすべて遮断します(default-deny)。SAP BTP を含め、リストにないドメインには届きません。

AI OFF では逆に、AI のドメインをすべて遮断し、それ以外は自由に通信できます。AI が使えない状態なので、通信を広げても AI が勝手に外部へアクセスする心配はありません。

どちらのモードでも、80番・443番以外のポートは遮断されたままです。

図8 コンテナからの通信が許可・遮断される流れ

AI のドメインは「ベンダーごとのリスト」で管理する

どのドメインが AI のものかは、AI ベンダーごとにまとめたリストで管理しています(Anthropic、GitHub Copilot、OpenAI など)。このリストを作るときに気をつけたことが2つあります。

  • AI 専用のドメインだけを載せる:npm のレジストリや GitHub など、AI 以外の用途でも使うドメインは載せません。載せると、AI OFF のときに通常の開発作業まで止まってしまいます。
  • AI の遮断ルールを、許可ルールより先に置く:許可リストに AI のドメインをうっかり書いてしまっても、使うと選んでいない AI には届きません。

今どちらのモードかを、プロンプトに表示する

モードを切り替えて使う以上、「今どちらの状態か」が分からないと事故のもとになります。そこで、コンテナのターミナルのプロンプトの先頭に [AI ON] / [AI OFF] を表示するようにしました。

WSL 側が現在のモードを状態ファイルに書き、コンテナはそれを読み取り専用でマウントして表示します。モードを切り替えるとコンテナを作り直さなくても表示が変わります。また、状態ファイルが読めないときは [AI OFF] と表示し、AI が使えると誤解させないようにしています。

図9 プロンプトに現在のモードを表示する(イメージ)

原則③ 本番に届く鍵と道具はコンテナの外に置く

通信を絞っても、コンテナの中にデプロイ用の CLI と認証情報があれば、許可されたドメイン経由で何かが起きる余地が残ります。そこで、本番環境に届く「鍵」と「道具」はそもそもコンテナに入れないことにしました。

  • デプロイ用の CLI(Cloud Foundry CLI の cf、SAP Cloud MTA Build Tool の mbt など)は WSL にだけ入れます。devcontainer.json の features やセットアップ用のコマンドには入れません。
  • SSH 鍵はコンテナにマウントしません。そのため、コンテナの中で git commit はできますが、git push / fetch / pull はできません。加えて 22番ポートが遮断されているので、二重に止まります。

「push できないと不便では?」と思われるかもしれませんが、実際はそうでもありません。DevContainer は WSL 上のリポジトリのフォルダをそのままマウントしているだけなので、コンテナの中で AI が行った commit は、そのまま WSL 側でも見えています。人は WSL のターミナルで差分を確認し、問題がなければ push する。この分担が、同期の手間なしで成り立ちます。

図10 DevContainer と WSL の作業分担

結果として、AI の作業結果が外に出るのは、必ず人が確認した後という流れになりました。レビューの機会が自然に確保できるのも、この構成の良いところです。

【注意】VS Code が認証情報をコンテナに引き継ぐことがある
VS Code の Dev Containers 拡張機能は、接続元(この構成では WSL)で SSH エージェントや Git の認証ヘルパーが動いていると、それを自動的にコンテナへ引き継ぎます。WSL 側で ssh-agent を常駐させると、コンテナから鍵が使える状態になるため注意してください(この構成では 22番ポートの遮断が残るので、SSH での push は止まります)。

運用してみてわかったこと

AI ON では、正当な通信も1つずつ許可していく必要がある

AI ON は default-deny なので、npm install や Maven の依存ライブラリの取得も、取得先を許可リストに書かない限り失敗します。最初は戸惑いますが、Squid のアクセスログ(/var/log/squid/access.log)を見れば、どのドメインが拒否されたかがすぐに分かります。拒否されたものを確認して、必要なものだけを許可リストに足していく運用にしました。

図11 拒否ログを見て許可リストを育てる(ログはイメージ)

DevContainer を初めて作るときに必要な通信(イメージや拡張機能の取得など)は、遮断した状態で一度作ってみて、ログから必要なドメインを洗い出し、最初から許可リストに入れておくようにしました。

許可リストは、.sap.com のようなサブドメインをまとめて許可する書き方はせず、ホスト名を1つずつ書くルールにしています。広く許可すると、BTP の API など届いてほしくない先まで含まれてしまうためです。

一時的に通信を開けたいときは AI OFF に切り替える

新しいライブラリを調べるときなど、あちこちのサイトにアクセスしたい場面では、AI OFF に切り替えます。AI OFF では AI 自体が使えないので、「通信を開けたまま AI が動く」状態にはなりません。作業が終わったら AI ON に戻します。

複数の環境を同時に起動すると、遮断が外れることがある

WSL2 では、すべてのディストリビューションが同じネットワーク環境を共有しています。プロジェクトごとに別のディストリビューションで同じ仕組みを動かしたところ、2つを同時に起動すると Squid のポートや iptables のルール、Docker のネットワークがぶつかり、遮断が効かなくなる場合があることが分かりました。

そのため、同時に起動できるのは1つの環境だけと決め、VS Code を開く前に他の環境が起動していないかを確認し、起動していれば停止するようにしています。

図12 同時に起動できるのは1つの環境だけ

この仕組みで防げないこと

この仕組みは万能ではありません。防げないものを明確にしておきます。

図13 この仕組みで防げないこと
  • DNS を使った持ち出し:コンテナは名前解決ができるため、ドメイン名にデータを埋め込めば、低速ながら外部の DNS サーバーまで届きます。意図的に仕組みを組まない限り起きない経路として、対象外にしています。
  • WSL のシェルや Windows で直接動かした AI:通信制御はコンテナの専用ネットワークにしか効きません(原則①が前提になる理由です)。
  • 意図的な回避:Squid の設定を書き換える、sudo で iptables のルールを消す、といった操作は防げません。
  • 許可した取得先を経由する通信:npm や Maven のリポジトリは AI ON でも許可しているため、AI が書いた依存定義(インストール時に実行されるスクリプトなど)がそこを経由して動く余地は残ります。

私たちはこの仕組みを、悪意への対策ではなく、誤操作と暴走を構造的に防ぐものと位置づけています。端末の管理や、BTP 側の権限設計(開発者アカウントに本番環境の権限を持たせない、など)と組み合わせて使うことが前提です。

まとめ

AI コーディングエージェントを安全に使うために、「禁止する」のではなく「届かない」構造を作りました。

  1. AI は DevContainer の中だけで動かすことで、AI のいる場所といない場所の境界をはっきりさせる
  2. コンテナの通信を許可制にすることで、AI ON のときは許可したサイトと選んだ AI にしか届かないようにする
  3. 本番に届く鍵と道具をコンテナの外に置くことで、AI の作業結果が外に出るのは必ず人が確認した後にする

AI を導入するときは、まず「自分の AI がどこで動いていて、そこから何に届くのか」を棚卸ししてみることをおすすめします。表1のように整理するだけでも、思っていた以上に多くのものに届いていることに気づくはずです。

ただし、この構成は WSL・Docker・Squid・iptables・DevContainer の設定が1つでも抜けると、遮断が外れてしまいます。メンバー全員の端末で、毎回同じ状態を手作業でそろえるのは現実的ではありません。後編では、この環境を設定ファイル1枚から自動で構築するために作った社内ツールを紹介します。

参考文献

投稿者プロフィール

上田 弘明
WEBエンジニアをやってます。 高度の柔軟性を維持しつつ、臨機応変に対処出来るようになりたいです。
この記事をSNSでシェア!