Incusのコンテナ/仮想マシンの外向き通信をホスト上のSquidのACLで制御する

はじめに

AIエージェントのsandboxを作るのに、使い慣れたIncusを活用したいと考えました。 Incusのインスタンスはファイルシステムは元々ホストと分離されているので、あとは外向き通信です。 ChatGPTに聞いて、Incusのホスト上でSquidを動かす構成を試してみたのでメモです。

構成図

Incusインスタンス(コンテナまたは仮想マシン)ではhttps_proxyなどの環境変数を設定し、Incusホスト上のSquidでフォワードプロキシーしてインターネット上のホストにアクセスします。この際SquidのACLで許可したドメインのみにアクセスできるようにします。

  flowchart LR
    direction LR

    subgraph Host["Incus Host"]
        subgraph C["Incus Container"]
            APP1["Application"]
        end
        subgraph V["Incus VM"]
            APP2["Application"]
        end

        S["Squid"]
    end
    subgraph NET["Internet"]
        EXT1["allowed.example.jp"]
        EXT2["denied.example.jp"]
    end

    APP1 -->|"Proxy"| S
    APP2 -->|"Proxy"| S
    S --> EXT1
    S --x EXT2

SquidのインストールとACLの設定

SquidはUbuntuのパッケージでインストールします。

sudo apt-get install -y squid

ACLの設定ファイル /etc/squid/conf.d/acl.conf を以下の内容で作成します。 SquidのACLの詳細は https://wiki.squid-cache.org/SquidFaq/SquidAcl を参照してください。

acl SSL_ports port 443
acl Safe_ports port 80
acl Safe_ports port 443

http_access deny !Safe_ports
http_access deny CONNECT !SSL_ports

acl allowed_domains dstdomain "/etc/squid/allowed_domains.txt"

http_access allow allowed_domains
http_access deny all

上記で指定した/etc/squid/allowed_domains.txtファイルにアクセスを許可するドメインのリストを記載します。.github.comのようにドットで始めるとそのドメインとサブドメインを許可します。サブドメインの例としてあげていますが、github.comの場合はセキュリティー上は個々のサブドメインを設定したほうが良いと思います。

# ubuntu
archive.ubuntu.com
security.ubuntu.com
motd.ubuntu.com
esm.ubuntu.com
contracts.canonical.com
livepatch.canonical.com

# github
.github.com
.githubusercontent.com

# node.js
nodejs.org
registry.npmjs.org

# mise
mise-versions.jdx.dev

# python
pypi.org
files.pythonhosted.org

# uv
astral.sh
releases.astral.sh

# trafficserver
ci.trafficserver.apache.org

# lan
192.0.2.3

インスタンスでホストのSquidをフォワードプロキシーとして使う設定

cloud-initを使って、Incusのインスタンス(コンテナまたは仮想マシン)の作成時にhttp_proxyhttps_proxyなどの環境変数を設定し、http(s)通信にホストのSquidをフォワードプロキシーとして使うようにします。

以下のコマンドでdefaultプロファイルの設定を編集します。

incus profile edit default

表示された内容のうち

config; {}

の部分を以下のように書き換えます。 192.0.2.2はIncusホストのIPアドレスに置き換えてください。3128はSquidのデフォルトポートです。

config:
  cloud-init.user-data: |
    #cloud-config
    timezone: Asia/Tokyo
    package_upgrade: true
    packages:
      - rsync
    bootcmd:
      - mkdir -p /etc/systemd/system.conf.d
      - sed -i 's|^#Defaults:%sudo env_keep += "http_proxy https_proxy ftp_proxy all_proxy no_proxy"|Defaults:%sudo env_keep += "http_proxy https_proxy HTTP_PROXY HTTPS_PORXY ftp_proxy all_proxy no_proxy NO_PROXY"|' /etc/sudoers
    write_files:
      - path: /etc/profile.d/90-http_proxy.sh
        permissions: "0644"
        content: |
          export http_proxy=http://192.0.2.2:3128
          export https_proxy=http://192.0.2.2:3128
          export HTTP_PROXY=http://192.0.2.2:3128
          export HTTPS_PROXY=http://192.0.2.2:3128
          export no_proxy=localhost,127.0.0.1
          export NO_PROXY=localhost,127.0.0.1
      - path: /etc/apt/apt.conf.d/90proxy
        permissions: "0644"
        content: |
          Acquire::http::Proxy "http://192.0.2.2:3128/";
          Acquire::https::Proxy "http://192.0.2.2:3128/";
      - path: /etc/systemd/system.conf.d/proxy.conf
        permissions: "0644"
        content: |
          [Manager]
          DefaultEnvironment="http_proxy=http://192.0.2.2:3128"
          DefaultEnvironment="https_proxy=http://192.0.2.2:3128"
    runcmd:
      - systemctl daemon-reexec

インスタンスの作成時にcloud-init対応のイメージを使用する必要があります。 以下の例のように、イメージ名が/cloudで終わるものを指定するようにします。

Ubuntu 26.04のコンテナをc1という名前で作成する例。

incus launch iamges:ubuntu/26.04/cloud c1

Ubuntu 26.04の仮想マシン(Virtual Machine)をvm1という名前で作成する例。

incus launch iamges:ubuntu/26.04/cloud --vm vm1

https_proxyなどの環境変数の名前の大文字小文字について

We need to talk: Can we standardize NO_PROXY?によると、小文字のhttps_proxyか大文字のHTTPS_PROXYのどちらを参照するかがいろいろなソフトウェアーによって違うそうです。ということで、大文字と小文字の両方の環境変数を設定しています。

環境変数の指定方法についての補足

man environment.dによると/etc/environemnt.d/*.confで環境変数を指定することもできるようなのですが、不採用にしました。試してみたのですがincus shell インスタンス名でインスタンスに入ると設定が効いていなかったためです。

代わりに/etc/profile.d/90-http_proxy.shを作成して環境変数を設定しています。

また、sudoを実行する際に環境変数が引き継がれるように/etc/sudoersを編集しています。通常ならsudo visudoで編集すべきですが、cloud-initで初期化する時点では他に編集している人はいないのでsedで編集しています。

/etc/apt/apt.conf.d/90proxyaptapt-getでプロキシーを使うための設定です。

/etc/systemd/system.conf.d/proxy.confはsystemdのサービスでプロキシーを使うための設定です。bootcmd/etc/systemd/system.conf.d/ディレクトリーを作成し、write_filesでファイルを作った後、runcmdsystemctl daemon-reexecを実行して反映しています。

SquidのACLの許可対象のホストの追加手順

Squidで許可していないホストにアクセスしようとすると、403エラーになりSquidのアクセスログにTCP_DENIEDのログが出ます。以下のコマンドでSquidのアクセスログを確認します。

sudo tail /var/log/squid/access.log

ログの例を示します。198.51.100.1はアクセス元のIPアドレスでexample.jpがアクセスしようとしたドメインです。

1784483719.888     32 198.51.100.1 TCP_DENIED/403 3379 GET https://example.jp/ - HIER_NONE/- text/html

/etc/squid/allowed_domains.txtを編集して、許可するドメインを追加します。

sudo vim /etc/squid/allowed_domains.txt

Squidをリロードして変更を反映します。

sudo systemctl reload squid

IncusのACLでインスタンスからはホストのSquidへの通信のみ許可

上記のプロファイルでhttps_proxyなどの環境変数を設定していますが、インスタンス内でhttps_proxyなどの環境変数を削除されるかもしれません。すると、プロキシーを通さずに外向きの通信ができてしまいます。これを防ぐため、IncusのネットワークでACLを設定し、インスタンスからはホストのSquidへの通信のみ許可します。

以下のコマンドでacl設定を作成します。設定名のhttp-proxy-onlyはお好みで変更してください。

incus network acl create http-proxy-only

以下のコマンドで、上記で作成したacl設定にルールを追加します。192.0.2.2はホストのIPアドレスに置き換えてください。

incus network acl rule add http-proxy-only egress \
  action=allow destination=192.0.2.2 protocol=tcp destination_port=3128

以下のコマンドで作成したacl設定の内容を確認できます。

incus network acl show http-proxy-only

出力される内容の例を以下に示します。

name: http-proxy-only
description: ""
egress:
  - action: allow
    destination: 192.0.2.2
    protocol: tcp
    destination_port: "3128"
    state: enabled
ingress: []
config: {}
used_by:
  - /1.0/networks/incusbr0
project: default

Incusの初期セットアップ時に作られるブリッジネットワークのincusbr0に上記で作成したACLを適用するには以下のよう実行します。

incus network set incusbr0 security.acls="http-proxy-only"

ネットワークに設定されているACLの名前は以下のように確認できます。

$ incus network get incusbr0 security.acls
http-proxy-only

IncusのACL設定の詳細はネットワーク ACL を設定するには - Incus ドキュメントを参照してください。

(脱線) cloud-initの他の設定について

タイムゾーンの設定

timezone: Asia/Tokyoでタイムゾーンを日本時間にしています。

ホストとインスタンス間でrsyncを使うための設定

incus file pushincus file pullでも--recursiveオプションを使えばでホストとインスタンス間で指定のディレクトリー以下を再帰的にコピーできます。が、変更されたファイルだけをコピーしたいなどrsyncを使いたいケースもあります。

rsyncを使うにはコピー元とコピー先の両方にrsyncパッケージをインストールする必要があります。 そのため、以下の設定でパッケージをアップグレードしてrsyncをインストールしています。

    package_upgrade: true
    packages:
      - rsync

ホスト側では以下のエイリアスを定義します。

alias incusrsync='rsync -e fake-ssh'

fake-sshは以下の内容で作成し、実行パーミションをつけて$HOME/.local/binなどPATHの通った場所に置きます。

#!/bin/sh
set -eu

ctn="${1}"
shift

exec incus exec --project ${PROJECT:-default} "${ctn}" -- "$@"

これで以下のように実行するとホストからインスタンスにrsyncでディレクトリーを再帰的にコピーします。コピー元のディレクトリーの最後のスラッシュの有無での挙動はrsyncと同じで、以下のコマンドでは/path/to/dest/fooディレクトリーが作られることになります。

incusrsync -av /path/to/src/foo instance_name:/path/to/dest/

この後、/path/to/src/foo配下の一部のファイルを変更してそれをインスタンスにコピーしたい場合は以下のように実行します。

incusrsync -av /path/to/src/foo/ instance_name:/path/to/dest/foo/