Antigravity CLI ネットワーク問題の整理

Antigravity CLI ネットワーク問題の整理

1. 背景

最初の Docker 開発環境では、主に Codex を利用していた。

この初期構成には、すでに Squid コンテナが含まれていた。

app-editor
  Codex / 開発作業用コンテナ

squid
  Docker network 内にある proxy コンテナ

git-bare
  restricted network 上の内部 Git サーバー

app-editor は複数の Docker network に接続されていた。

services:
  app-editor:
    networks:
      - restricted
      - ingress
      - outbound

  git-bare:
    networks:
      restricted:
        aliases:
          - git

内部 Git サーバーは restricted network 上にあり、app-editor から以下のように参照していた。

git://git:9418/app.git

この段階では、Antigravity CLI 固有の通信要件はまだ問題になっていなかった。


2. 構成図ビフォーアフター

2.1 Before: 初期構成

flowchart LR
    subgraph Docker["Docker Compose"]
        subgraph Restricted["restricted network"]
            App["app-editor<br/>Codex / 開発作業 / 後から agy 導入"]
            Git["git-bare<br/>git://git:9418/app.git"]
        end

        subgraph ProxyNet["proxy / outbound network"]
            SquidC["Squid container<br/>http://squid:3128"]
        end

        App -->|internal git| Git
        App -.->|HTTP_PROXY / HTTPS_PROXY| SquidC
        SquidC --> Internet["Internet"]
    end

    App -.->|Antigravity CLI が一部直行| Internet

    classDef problem fill:#ffe6e6,stroke:#cc0000,color:#000;
    class Internet,App problem;

初期構成では、Codex 利用を前提とした Docker 開発環境に、すでに Squid コンテナが含まれていた。
後から Antigravity CLI を導入したところ、Squid 経由ではなく外部へ直行しようとする通信があり、DNS / proxy / route の問題が表面化した。

2.2 After: 最終構成

flowchart LR
    subgraph Docker["Docker Compose"]
        subgraph Internal["internal / private networks"]
            App2["app-editor<br/>Codex / Antigravity / npm / Go"]
            Git2["git-bare<br/>git://git:9418/app.git"]
        end
    end

    App2 -->|private IP 宛ては許可| Git2

    App2 --> Bridge["Docker bridge<br/>docker0 / br-*"]
    Bridge --> DU["DOCKER-USER"]
    DU --> Filter["DOCKER-EGRESS-FILTER"]
    Filter --> Private["private IP<br/>10/8, 172.16/12, 192.168/16<br/>RETURN"]
    Filter --> IPSet["ipset docker_allowed_v4<br/>DNS解決結果の許可IP"]
    Filter --> Drop["LOG + DROP<br/>未許可通信"]

    IPSet -->|TCP 80/443 only| Internet2["Internet"]
    Drop -.-> Blocked["example.com など<br/>未許可宛先"]

    DockerNAT["Docker NAT / MASQUERADE<br/>Docker に任せる"] -.-> Bridge

    classDef ok fill:#e6ffed,stroke:#008000,color:#000;
    classDef block fill:#ffe6e6,stroke:#cc0000,color:#000;
    classDef control fill:#e6f0ff,stroke:#005fcc,color:#000;

    class Private,IPSet,Internet2 ok;
    class Drop,Blocked block;
    class DU,Filter,DockerNAT control;

最終構成では、Squid / transparent proxy / nftables 単体制御は使わず、Docker の DOCKER-USER chain と ipset を使って外向き通信を制御する。
Docker の NAT / MASQUERADE は Docker に任せ、自前では許可IPへの TCP 80/443 と private IP 宛てだけを通す。


3. 問題の発生

後から Antigravity CLI (agy) を導入しようとしたところ、外部通信まわりの問題が表面化した。

代表的なエラーは以下。

Eligibility check failed:
Post "https://daily-cloudcode-pa.googleapis.com/v1internal:loadCodeAssist":
dial tcp: lookup daily-cloudcode-pa.googleapis.com on 127.0.0.11:53:
server misbehaving

別の状況では以下のようなエラーも出た。

dial tcp 142.251.24.95:443: connect: network is unreachable

つまり、Codex 利用時には問題になっていなかった以下の要素が、Antigravity CLI の導入によって問題化した。

Docker DNS
外部 API への HTTPS 通信
proxy 設定
Docker network の default route
Antigravity CLI が proxy を尊重するかどうか

4. 試したこと 1: 既存の Squid コンテナを使う

4.1 目的

初期構成にすでに存在していた Squid コンテナを使って、Antigravity CLI の外部通信を proxy 経由にできないか確認した。

4.2 試した構成

app-editor から Docker network 内の Squid コンテナを参照した。

http://squid:3128

環境変数として以下を設定した。

HTTP_PROXY=http://squid:3128
HTTPS_PROXY=http://squid:3128
http_proxy=http://squid:3128
https_proxy=http://squid:3128

Python から Squid への TCP 接続や CONNECT は確認できた。

oauth2.googleapis.com:443
daily-cloudcode-pa.googleapis.com:443

への CONNECT が通ることも確認した。

4.3 結果

Squid コンテナ自体は動いていた。

しかし、Antigravity CLI は一部通信で proxy を完全には使っていないように見えた。

そのため、単純な HTTP_PROXY / HTTPS_PROXY 方式では解決しなかった。

4.4 分かったこと

Squid コンテナに到達できることと、
Antigravity CLI が常に Squid を使うことは別問題

5. 試したこと 2: Squid の hostname / IP 固定

5.1 目的

proxyconnect tcp: lookup squid ... no such host のようなエラーを避けたかった。

5.2 試したこと

squid という hostname の名前解決が不安定に見えたため、Compose 上で IP を固定したり、proxy の指定を hostname ではなく IP にしたりした。

例:

HTTPS_PROXY=http://172.30.0.52:3128

5.3 結果

Squid コンテナへの名前解決や到達性の問題はある程度整理できた。

しかし、Antigravity CLI の eligibility check で発生する直行通信の問題は残った。

5.4 分かったこと

Squid の名前解決問題を直しても、
Antigravity CLI が proxy を無視する通信は解決しない

6. 試したこと 3: CoreDNS を試す

6.1 位置づけ

CoreDNS は初期構成には存在しなかった。

Claude 関連の URL や、外部サービスの名前解決を制御・確認する流れの中で、試験的に試した。

これは本格導入ではなく、名前解決制御の検証だった。

6.2 目的

特定ドメインを任意の IP に向ければ、通信を制御できるのではないかと考えた。

例:

daily-cloudcode-pa.googleapis.com -> Squid の IP

6.3 結果

この方法は根本的な解決にならなかった。

理由は、DNS で Squid の IP に向けても、HTTPS クライアントは以下へ接続するだけだからである。

SquidのIP:443

しかし Squid は通常、HTTP proxy として 3128 で待ち受けている。

Squid:
  3128 で HTTP proxy として待ち受け

Antigravity CLI:
  443 に HTTPS 接続しようとする

そのため、DNS を書き換えても 443 -> 3128 の変換にはならなかった。

6.4 分かったこと

DNS は宛先IPを変えられるだけ
宛先ポートやプロトコルまでは proxy 用に変換しない

7. 試したこと 4: コンテナ内 iptables + redsocks

7.1 目的

Antigravity CLI が proxy を無視するなら、コンテナ内で TCP 80/443 を強制的に proxy へ流したかった。

7.2 試した構成

app-editor
  ↓ iptables OUTPUT
redsocks

Squid コンテナ

7.3 結果

app-editor が default route を持たない状態では、外部 IP への接続が routing 段階で失敗した。

network is unreachable

この場合、iptables の NAT OUTPUT に到達する前に失敗する。

つまり、redsocks に流す以前に、カーネルが「その宛先へ行く経路がない」と判断していた。

7.4 分かったこと

iptables OUTPUT で横取りするには、
まず対象宛先が route 可能である必要がある

default route が無い internal network だけでは、
NAT OUTPUT に到達する前に失敗する

8. 試したこと 5: ホスト側 Squid + transparent proxy

8.1 目的

コンテナ内で制御するのではなく、ホスト側で Docker bridge から出る通信を強制的に proxy へ流したかった。

8.2 試した構成

途中で、Squid を Docker コンテナ内ではなくホスト側に導入する構成を試した。

Docker container

Docker bridge
  ↓ DNAT
redsocks / sing-box

host Squid

Internet

redsocks は古くメンテナンスが不安だったため、sing-box も試した。

8.3 結果

Docker bridge から出る TCP 80/443 を DNAT し、ホスト側 Squid まで流すこと自体はできた。

しかし、新しい問題が分かった。

Squid access.log には以下のように出た。

CONNECT 172.66.147.243:443
CONNECT 104.20.23.154:443

期待していた以下のようなログではなかった。

CONNECT daily-cloudcode-pa.googleapis.com:443
CONNECT api.openai.com:443

8.4 原因

transparent proxy では、アプリが先に DNS 解決して IP に接続しようとする。

その TCP 接続を後から横取りするため、Squid から見ると宛先は IP だけになる。

アプリ
  ↓ DNS 解決
domain -> IP

IP:443 に接続

transparent proxy が横取り

Squid には IP:443 として届く

8.5 分かったこと

明示 proxy:
  Squid にドメイン名が残る
  dstdomain ACL が使える

transparent proxy:
  Squid には IP:port しか見えない
  dstdomain ACL は期待通り使えない

9. 試したこと 6: Squid で IP allowlist 制御

9.1 目的

transparent proxy でドメインが見えないなら、DNS 解決結果の IP で制御しようとした。

9.2 試した構成

allowed domains
  ↓ DNS 解決
allowed IPs

Squid dst ACL

Squid 側では dstdomain ではなく dst ACL を使う方向にした。

9.3 結果

IP ベースでの許可/拒否はできる見込みが立った。

しかし、ここで設計上の疑問が出た。

IP でしか制御しないなら、
Squid を挟む必要が薄いのではないか

9.4 分かったこと

Squid を proxy として使う意味は、
ドメイン名 CONNECT が見える場合に大きい

transparent proxy で IP しか見えないなら、
Squid は単なる IP フィルタに近くなる

10. 試したこと 7: nftables による IP allowlist 制御

10.1 目的

Squid / sing-box を外し、Docker の外向き通信を nftables で直接制御したかった。

10.2 試した構成

Docker container

Docker bridge

nftables forward hook

allowed IP の TCP 80/443 だけ許可

10.3 結果

未許可の example.com は drop できた。

一方で、許可済みの api.openai.com は drop されていないにもかかわらず timeout した。

状況は以下。

example.com:
  drop ログあり
  通信失敗

api.openai.com:
  drop ログなし
  allowed_v4 に IP あり
  しかし timeout

原因は Docker の NAT / MASQUERADE が効いていなかったことだった。

手動で以下を入れると通信できた。

iptables -t nat -A POSTROUTING -s 172.18.0.0/16 -j MASQUERADE

10.4 分かったこと

nftables の filter で accept しても、
Docker の送信元NATが無ければ外部から戻り通信が返らない

Docker の iptables / NAT と、
自前 nftables を直接混ぜると事故りやすい

11. 最終案: DOCKER-USER + ipset

11.1 方針

Docker の NAT / MASQUERADE は Docker に任せる。

自前の通信制限は、Docker が用意している DOCKER-USER chain に入れる。

11.2 最終構成

Docker container

Docker bridge

DOCKER-USER

DOCKER-EGRESS-FILTER

ipset docker_allowed_v4

11.3 制御内容

private IP 宛て
  → RETURN

allowed IP + TCP 80/443
  → RETURN

UDP 443
  → DROP

その他
  → LOG + DROP

11.4 結果

Docker の NAT と競合しにくくなり、外向き通信制限を実現できた。

example.com のような未許可宛先は drop される。

api.openai.com など許可済みドメインの DNS 解決結果に含まれる IP は通る。


12. strict / relaxed モード

運用上、許可ドメインを2段階に分けた。

12.1 strict

Antigravity / OpenAI / Anthropic / Google API など、最低限に近い許可。

api.openai.com
auth.openai.com
chatgpt.com
ab.chatgpt.com
platform.openai.com
oauth2.googleapis.com
antigravity-unleash.goog
www.googleapis.com
playwright.azureedge.net
antigravity-cli-auto-updater-974169037036.us-central1.run.app
storage.googleapis.com
api.anthropic.com
platform.claude.com
daily-cloudcode-pa.googleapis.com

12.2 relaxed

npm / GitHub / Go module / Playwright など、開発作業向けに許可を広げたもの。

api.openai.com
auth.openai.com
chatgpt.com
ab.chatgpt.com
platform.openai.com
oauth2.googleapis.com
antigravity-unleash.goog
www.googleapis.com
playwright.azureedge.net
api.github.com
github.com
codeload.github.com
objects.githubusercontent.com
raw.githubusercontent.com
proxy.golang.org
sum.golang.org
storage.googleapis.com
registry.npmjs.org
npmjs.com
www.npmjs.com
cdn.playwright.dev
api.anthropic.com
platform.claude.com

切り替え用に以下を用意した。

docker-egress-strict
docker-egress-relaxed

13. 内部 Docker 通信への対応

Compose 内には git-bare もあり、app-editor から以下でアクセスしていた。

git://git:9418/app.git

外向き制限を Docker bridge 全体にかけると、内部通信も巻き込む可能性がある。

そのため、private IP 宛ては制限対象から外した。

10.0.0.0/8
172.16.0.0/12
192.168.0.0/16

これにより、Docker 内部の Git サーバーなどへの通信は許可し、外部向け通信だけを allowlist 制御する形にした。


14. Docker restart 後の再適用

iptables / ipset は永続化しない。

また、Docker daemon restart 時には Docker の iptables ルールが再構成される可能性がある。

そのため、Docker 起動後に自前ルールを再適用するため、systemd drop-in を使った。

[Service]
ExecStartPost=/usr/local/sbin/update-docker-egress-ipset.sh

これにより、Docker daemon 起動後・再起動後に DOCKER-USER + ipset ルールを再適用する。


15. 最終的な妥協点

最終的には、厳密な FQDN 制御は諦めた。

理由は以下。

Antigravity CLI が proxy を完全には尊重しない
transparent proxy では Squid にドメイン名が残らない
SNI 制御は偽装や実装コストの問題がある
nftables を直接使うと Docker NAT と競合しやすい

そのため、以下に妥協した。

DNS 解決結果ベースの IP allowlist 制御
Docker NAT は Docker に任せる
通信制限は DOCKER-USER + ipset で行う
用途ごとに strict / relaxed を切り替える

この方式は、厳密なドメイン制御ではない。

CDN や共有 IP の場合、許可した IP 上の別ドメインにも到達できる可能性がある。

ただし、今回の目的には実用上かなり合っていた。


16. 最終構成

app-editor container

Docker bridge

DOCKER-USER

DOCKER-EGRESS-FILTER

ipset docker_allowed_v4
  ├─ private IP → RETURN
  ├─ allowed IP + TCP 80/443 → RETURN
  ├─ UDP 443 → DROP
  └─ others → LOG + DROP

Docker NAT / MASQUERADE は Docker 管理に任せる。

Docker NAT:
  Docker 管理

Egress 制限:
  DOCKER-USER + ipset 管理

17. 結論

最初は、Codex 利用を前提とした Docker 開発環境だった。

この初期構成には Squid コンテナも含まれていたが、Antigravity CLI 固有の通信問題はまだ表面化していなかった。

後から Antigravity CLI を導入したことで、外部通信、DNS、proxy、Docker network の問題が表面化した。

既存の Squid コンテナ、CoreDNS の試行、コンテナ内 redsocks、ホスト側 Squid + transparent proxy、Squid の IP allowlist、nftables などを順に試した。

しかし、最終的には Squid も transparent proxy も不要と判断し、Docker と競合しにくい DOCKER-USER + ipset 方式に落ち着いた。

これは FQDN 単位の厳密な制御ではなく、DNS 解決結果ベースの IP 制御という妥協である。

ただし、Antigravity CLI のように proxy を無視する可能性があるアプリに対しても、Docker コンテナ単位で外向き通信を制限できる、現実的な解決策になった。