ウェブアクセスをドメイン名で任意のインターネット接続回線へ振分ける（Mac上の仮想マシンに色々試行できるUTM的な環境を作って遊んでみる）

いわゆるVPNサービスを利用していると、時々接続できないサイトかあります。全くダメなら良いのですが、サービスへ認証が成功した瞬間に不審なアクセスと見なされて、いきなりサービスへのアクセスをBANされることもあります。回復には電話したり･されたり等々で、大変手間がかかることが多いです。

- 都度手元端末のプロキシ除外リストへ登録したくない。
- 手元端末のVPNクライアントで同じようなことができない。
- どうせなら色々サイバーなツールを検証できる環境が欲しい。

ということで、「ウェブアクセスをドメイン名で任意のインターネット接続回線へ振分けたい、それならUTMっぽくしたMac上の仮想マシンで何とかしてみよう」と思い、色々やってみた次第です。以下、全体的に粗い記述で恐縮ですが「こんなやり方でVPN使う・使わないの制御ができたよ」ということで公開します。


#1. 環境
- 親機
    - MacOS 10.15.3 Catalina
- 子機
    - Debian 10 (Paralles Desktop 15.1.2 で稼働）


#2. 手順

##(1)親機Macの準備

#####pfを設定する（オプション: 実施はお好みで、以下同じ）
デフォルトで全て遮断した上、必要な通信のみ許可します（必要に応じて自発の通信も更に絞る）。

以下のリンク先をほぼそのまま参考にしていますが、私が使っている部分を載せておきます。
https://github.com/essandess/macOS-Fortress/blob/master/pf.conf

```/etc/pf.conf（当方環境から抜粋）
int_if = "en0"
scrub in all no-df
block log all #logは適宜オンオフする
pass on lo0 all
pass in quick proto udp from port bootps to port bootpc
pass proto udp from self to port domain keep state
pass out all keep state
pass on $int_if proto { udp, tcp } to { 224.0.0.2, 224.0.0.18, 224.0.0.251 } port mdns
```

#####snortを設定する（オプション）
あまり最近のMacOSへのsnort導入に関する情報が見つからなかったのですが、brewからインストールしました。公式から最新ルールの取得および反映、そしてルールファイルのパス調整等を実施したら、後は動かしながらチューニングです。

導入にあたっては、以下が参考になりました。
http://deepnode.us/apps/macosx_snort_install.pdf
https://gist.github.com/essandess/c1fb21d82aabf6138ef712195cbad03e
https://seclists.org/snort/2016/q1/269

```/usr/local/etc/snort/snort.conf（当方環境から抜粋）
# brew install で導入されたものをそのまま利用
# 以下の定義を確認の上、公式から入手のルールファイルを適宜反映
var RULE_PATH /usr/local/etc/snort/rules
var SO_RULE_PATH /usr/local/etc/snort/so_rules
var PREPROC_RULE_PATH /usr/local/etc/snort/preproc_rules

# 以下の定義を確認の上、ホワイトおよびブラックリスト用のファイルを作成
var WHITE_LIST_PATH /usr/local/etc/snort/rules
var BLACK_LIST_PATH /usr/local/etc/snort/rules

# ホワイトおよびブラックリスト用のファイルを作成（まずはtouchして空
ファイルでOK）
preprocessor reputation: \
   memcap 500, \
   priority whitelist, \
   nested_ip inner, \
   whitelist $WHITE_LIST_PATH/white_list.rules, \
   blacklist $BLACK_LIST_PATH/black_list.rules

# 出力するログ形式および項目を設定
output unified2: filename merged.log, limit 128, mpls_event_types, vlan_event_types
```


##(2)子機Debianの準備
#####Debianをインストールする（必須）
Debian公式の適当なイメージで、Paralles Desktopへ最小構成（GUI等無）でインストールします。

- Shared Network（共有ネットワーク）にする。
- その他の設定は、親機のリソースに応じて適当に。とりあえずCP1枚 & 512MBぐらいで始めればよいかと。
- あとバックグラウンド動作にしておく。作業はssh経由で。

#####squidを設定する（必須）
aptからsquidを導入の上、squid.confを設定します。

```/etc/squid/squid.conf（当方環境から抜粋）
# とりあえずaptから導入したものをほぼそのまま利用、今回設定したのはとりあえずvisible_hostnameぐらい
visible_hostname your_hostname
```

さらに振り分け設定をsquid.confに記述する。

```/etc/squid/squid.conf（当方環境から抜粋）
# VPNを利用しないドメイン名毎に、以下の2行を記載します。
# hogehoge.jpを実際のサイト名に置き換えてください。
acl domain_to_remote_proxy dstdomain .hogehoge.jp
acl ref_to_remote_proxy referer_regex [^.]*\.hogehoge.jp.*
# 上記で設定した以外のドメイン名へアクセスする場合（=VPNへ振り分ける場合）、パケットを3でマークします。
tcp_outgoing_mark 0x03 !ref_to_remote_proxy !domain_to_remote_proxy
```

#####VPN振分け用のルーティングテーブルを設定する（設定する）
VPN振分け用のルーティングテーブル(vpn_table)を追加します。

```:/etc/iproute2/rt_tables（当方環境から抜粋）
# 最終行にカスタムルール"201 vpn_table"を追加しました（番号は任意）
# reserved values
#
255     local
254     main
253     default
0       unspec
#
# local
#
#1      inr.ruhep
201     vpn_table
```
#####VPN振分け用のルートおよびルールを追加する（必須）
上記で設定したテーブルにVPN接続用のルートおよび振分けルールを設定するシェルを作成します。

```:/etc/openvpn/client/setup-routing（当方環境から抜粋）
#!/bin/bash
# VPN振分け用のルーティングテーブルを参照するパケット（=squidが3でマークした）はVPNへルーティングされる、という設定を追加します。
# 変数$route_vpn_gatewayはOpenVPNがVPN接続先のIPを割当てます。
ip route add 0.0.0.0/1 via $route_vpn_gateway dev $dev scope global table vpn_table
ip route add 128.0.0.0/1 via $route_vpn_gateway dev $dev scope global table vpn_table
sysctl -w net.ipv4.conf.$dev.rp_filter=2

# （Squidが）3でマークしたパケットはVPN振分け用のルーティングテーブルを参照するルールを加えます（結果として上記の通り、VPNへ振り分けられる）
ip rule del fwmark 3 table vpn_table &>/dev/null # This might fail but that's ok
ip rule add fwmark 3 table vpn_table
```

#####VPN接続を設定する（必須）
VPN接続用のシェルに以下の設定を加えます（VPN接続設定の詳細は利用中サービスから提供されている情報を参考にする）。

```/etc/openvpn/client.conf
route-noexec # VPN接続先から提供されるルート情報を反映しない
script-security 2 # ユーザ定義スクリプトを実行できるようにする
route-up /etc/openvpn/client/setup-routing # ユーザ定義スクリプトを実行する
```
以下のリンク先が大変参考になりました（ほとんどそのまま）。
https://superuser.com/questions/1185861/linux-routing-based-on-domain-names

#####その他（オプション）
各種ログのローテーション設定、親機子機のファイアーウォールのフィルタリング設定強化、snortのチューニング、SOCKSプロキシの導入、SSL Bumping等々、適当に実施します。

##(3)クライアントの準備
クライアントとなる親機のプロクシとして子機を設定します。一発でできたらおめでとう、私はそうじゃなかったので色々見直す良い機会になりました。同じ共有ネットワーク上のVMも子機をプロキシとして設定できますので、いろいろな試行･検証するにはこちらも良いのではないでしょうか。

#3. 最後に
- まずはさっと書いてみました、多分みんな分かるであろう点等々、色々と流してます。自分のための備忘と課題出し、そして一応こういうことができました、ということで。とはいえ適宜見直したいと思いますので、何かあればコメントいただければ。

- 今回以外の組み合わせでも子機がLinux系OS（UbuntuやCentOS）であれば動くと思います。また仮想マシンではなく、Dockerを使っても大丈夫ではないかと。

- なお本件、最初はFreeBSDで試行･運用しましたが、「ドメイン名による振分け」が出来ませんでした（今の私の知識だと）。今回debianで作成しましたが「こうすればFreeBSD環境でもできる」等あれば教えてください。

以上。。