Open12

Shadowsocks サーバー/クライアント構築検証

yunayuna

VPNの中間者攻撃 Port Shadow(ポートシャドウ)

VPN接続者に対する中間者攻撃手法Port Shadow (CVE-2021-3773)攻撃についての
この記事を見て、この脆弱性の対象にならないshadowsocksについて興味を持ちました。
https://x.com/__kokmt/status/1813935779875541176

エンドユーザーの場合, 最も確実な緩和策は、それらだけがアクセスできるプライベートVPNサーバーに接続するか、OpenVPNやWireGuardの代わりにShadowsocksやTorなどの脆弱でないプロトコルに切り替えることです。

そこで、自分でもShadowsocksを用いた暗号化通信を行ってみようと思います。
ゴールとして、Shadowsocksを経由させたリモートアクセスで安全性の確保を行うことも検証します。

yunayuna

公式はこちら
https://shadowsocks.org/

最新のdoc
https://github.com/shadowsocks/shadowsocks/wiki

概要

わかりやすい記事
https://bestvpn.jp/shadowsocks/

もともと、中国のグレートファイアーウォール(GFW)を通過するために開発された経緯がある(現在もその目的で利用されている)。
SOCKS5という、httpsでwrapされたプロトコルを用いることで、通過させることができる。
通信はいくつかの暗号化手法により秘匿できるので、この点でVPNのように通信暗号化を実現できます。

chatGPTによる説明はこちら

Shadowsocksは、インターネット上での検閲を回避するために使用されるオープンソースの暗号化プロトコルです。中国のプログラマーである"clowwindy"によって2012年に開発されました。主に検閲されたインターネット環境(例えば中国のグレートファイアウォールなど)から、アクセスが制限されているウェブサイトやサービスにアクセスするために使われます。

Shadowsocksは、Socks5プロキシプロトコルをベースにしており、クライアントとサーバー間の通信を暗号化して、インターネットのフィルタリングや監視を回避します。ユーザーは、Shadowsocksサーバーを自分で設定するか、他の提供者からサーバーアクセスを購入することができます。設定後、このサーバーがインターネット上のトラフィックを中継し、ユーザーの実際のIPアドレスやアクセスしている内容を隠すことができます。

ShadowsocksはVPN(Virtual Private Network)と似ていますが、いくつかの重要な違いがあります。例えば、Shadowsocksは特定のアプリケーションやブラウザだけをプロキシ経由で動かすことができるため、設定が柔軟であり、全てのインターネットトラフィックをリダイレクトする必要はありません。また、比較的軽量であり、速度が速く、設定が簡単であるという利点があります。

ただし、Shadowsocksは匿名性を提供するものではなく、主に検閲回避が目的であるため、匿名化されたインターネットアクセスが必要な場合は、Torなどの他のツールを使用することが推奨されます。

参考にした記事

https://hackernoon.com/installing-shadowsocks-rust-a-secure-open-source-proxy-server-better-than-vpn

yunayuna

サーバーの準備

AWS EC2にサーバーを建てる

CPUの種類に関わらず使えるので、比較的安めのAWSのAmazonLinux2023(AMD/t4g系)インスタンスを立ち上げて、ここに構築していきます。

yunayuna

サーバーのチューニング

1. 同時接続数を増やす

  • 数千の同時 TCP 接続を処理するために、開かれるファイル記述子の制限を増やす
sudo vi /etc/security/limits.conf

以下をファイルに追加(rootによるサーバー起動を想定)

# for server running in root:
root soft nofile 51200
root hard nofile 51200
  • Shadowsocks サーバーを起動する前に、最初に ulimit を設定する
ulimit -n 51200

2.kernel parameters​のチューニング

shadowsocksのチューニングパラメータの原則は以下の通りです。

  1. ポートと接続をできるだけ早く再利用する
  2. キューとバッファをできるだけ大きくする
  3. 待ち時間が長く、スループットが高いTCP輻輳アルゴリズムを選択す

公式サイトに記載されている本番サーバー /etc/sysctl.conf のサンプルはこちら。

sudo vi /etc/sysctl.conf
fs.file-max = 51200

net.core.rmem_max = 67108864
net.core.wmem_max = 67108864
net.core.netdev_max_backlog = 250000
net.core.somaxconn = 4096

net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_tw_recycle = 0
net.ipv4.tcp_fin_timeout = 30
net.ipv4.tcp_keepalive_time = 1200
net.ipv4.ip_local_port_range = 10000 65000
net.ipv4.tcp_max_syn_backlog = 8192
net.ipv4.tcp_max_tw_buckets = 5000
net.ipv4.tcp_fastopen = 3
net.ipv4.tcp_mem = 25600 51200 102400
net.ipv4.tcp_rmem = 4096 87380 67108864
net.ipv4.tcp_wmem = 4096 65536 67108864
net.ipv4.tcp_mtu_probing = 1
net.ipv4.tcp_congestion_control = hybla

このまま設定して、
即時反映のために以下を実行したところ、エラーが表示。

$ sudo sysctl -p
...省略...
sysctl: cannot stat /proc/sys/net/ipv4/tcp_tw_recycle: No such file or directory
...省略...

調査したところ、
Amazonlinux2023のような最新のlinuxカーネルでは、
tcp_tw_recycleオプションは、特にNATを経由する環境で問題を引き起こす可能性があり、そのため近年のLinuxカーネルではこのオプションが廃止されているとのこと。

なので、/proc/sys/net/ipv4/tcp_tw_recycleが存在しないため、
sysctlコマンドがこの設定を適用できないことが原因。
この部分だけコメントアウトして、再反映。

sysctl.conf
# net.ipv4.tcp_tw_recycle = 0
sudo sysctl -p

※解説(by chatGPTo)

この/etc/sysctl.confファイルには、Linuxサーバーのカーネルパラメータをチューニングする設定が記載されています。
総体的に見て、この/etc/sysctl.confファイルの設定は、ファイルディスクリプタの数やネットワークバッファのサイズ、
TCP接続のキューや制御アルゴリズムなど、様々な点でサーバーのパフォーマンスと効率を最大化することを目的としています。
特に高トラフィックや高遅延環境における性能向上を強く意識した調整が行われていることがわかります。以下、各設定項目について詳細に解説します。

  1. fs.file-max = 51200:

    • 説明: システムで同時に開くことができるファイルの最大数を設定します。
    • 目的: ファイルディスクリプタの上限を増やし、多数のファイルを扱う場合のリソース制限を緩和するため。
  2. net.core.rmem_max = 67108864net.core.wmem_max = 67108864:

    • 説明: 受信(rmem)および送信(wmem)バッファの最大サイズを設定します。
    • 目的: ネットワークパフォーマンスを向上させるため、大きなバッファサイズを使用することで高帯域幅の通信をサポートします。
  3. net.core.netdev_max_backlog = 250000:

    • 説明: パケットを処理する前にキューに保持できる最大パケット数を設定します。
    • 目的: 高負荷状態でもパケットのドロップを防ぎ、スループットを向上させるため。
  4. net.core.somaxconn = 4096:

    • 説明: TCP接続要求を保持するバックログキューの最大長を設定します。
    • 目的: 接続待ちのキューを増やし、高トラフィック時に接続のスループットを向上させるため。
  5. net.ipv4.tcp_syncookies = 1:

    • 説明: SYN flood攻撃に対抗するためのTCP SYN cookiesを有効にします。
    • 目的: SYN flood攻撃防御のため、リソースを効率的に管理します。
  6. net.ipv4.tcp_tw_reuse = 1:

    • 説明: TIME-WAIT状態のソケットを再利用する機能を有効にします。
    • 目的: 多数の短命な接続がある場合、ポート枯渇を防ぐため。
  7. net.ipv4.tcp_tw_recycle = 0:

    • 説明: TIME-WAIT状態のソケットの迅速な再利用を無効にします。
    • 目的: NAT環境下での接続問題を防ぐため。tcp_tw_recycleは一般的に問題を引き起こすことがあるため無効化されています。
  8. net.ipv4.tcp_fin_timeout = 30:

    • 説明: FIN-WAIT-2状態にあるソケットが待機する時間を設定します(デフォルト60秒)。
    • 目的: 接続のクリーンアップを迅速に行い、リソースを解放するため。
  9. net.ipv4.tcp_keepalive_time = 1200:

    • 説明: TCP keepaliveメッセージを送信するまでのアイドル時間を設定します。
    • 目的: 不活発な接続を検出し、無駄なリソース消費を防ぐため。
  10. net.ipv4.ip_local_port_range = 10000 65000:

    • 説明: ローカルポートの使用範囲を設定します。
    • 目的: 使用可能な動的ポート範囲を広げ、多数の短命接続をサポートするため。
  11. net.ipv4.tcp_max_syn_backlog = 8192:

    • 説明: SYNクエストのバックログキューの最大長を設定します。
    • 目的: 高トラフィック時により多くの接続要求を処理できるようにするため。
  12. net.ipv4.tcp_max_tw_buckets = 5000:

    • 説明: システムが保持できるTIME-WAITソケットの最大数を設定します。
    • 目的: 大量の短命接続がある場合に、不要なリソース消費を防ぐため。
  13. net.ipv4.tcp_fastopen = 3:

    • 説明: TCP Fast Openを有効にします。この番号「3」は、クライアントとサーバーの両方でTCP Fast Openを許可することを示しています。
    • 目的: TCP接続の初回ラウンドトリップを削減し、接続遅延を低減するため。
  14. net.ipv4.tcp_mem = 25600 51200 102400:

    • 説明: TCPスタックがメモリをどのように管理するかを設定します。3つの値は以下の意味を持ちます。
      • 最小値 (25600): TCPメモリの使用量が最低でもこれだけ確保される。
      • デフォルト値 (51200): 通常時のメモリ使用量。
      • 最大値 (102400): TCPスタックに許可される最大メモリ使用量。
    • 目的: TCPバッファメモリの管理をチューニングし、パフォーマンスを最適化するため。
  15. net.ipv4.tcp_rmem = 4096 87380 67108864:

    • 説明: TCP受信バッファの最小サイズ、デフォルトサイズ、および最大サイズを設定します。
    • 目的: 大きなデータ転送時に最適なバッファサイズを提供し、受信性能を向上させるため。
  16. net.ipv4.tcp_wmem = 4096 65536 67108864:

    • 説明: TCP送信バッファの最小サイズ、デフォルトサイズ、および最大サイズを設定します。
    • 目的: 大きなデータ転送時に最適なバッファサイズを提供し、送信性能を向上させるため。
  17. net.ipv4.tcp_mtu_probing = 1:

    • 説明: パスMTUディスカバリを有効にします。値「1」は自動で調整を行うことを示しています(「2」はさらに積極的に調整を行います)。
    • 目的: ネットワーク経路上のフラグメンテーションを回避し、パフォーマンスを最適化するため。
  18. net.ipv4.tcp_congestion_control = hybla:

    • 説明: TCPの輻輳制御アルゴリズムを「hybla」に設定します。「hybla」は高遅延環境(例えば衛星通信)でのパフォーマンス改善を目指したアルゴリズムです。
    • 目的: 高遅延環境でも効率良くデータ転送が行えるようにするため。
yunayuna

暗号化アルゴリズムについて

最も強力な選択肢はAEAD暗号である。 推奨されるのは "chacha20-ietf-poly1305 "か "aes-256-gcm "である。 他のストリーム暗号も実装されているが、完全性と真正性は提供されない。

AEAD暗号についての詳細

shadowsocksのこちらのページで解説されています。
https://shadowsocks.org/doc/aead.html

AEADとは、Authenticated Encryption with Associated Dataの略で、「関連データを含む認証された暗号化」のことである。 AEAD暗号は機密性、完全性、真正性を同時に提供する。 AEAD暗号は最新のハードウェア上で優れた性能と電力効率を発揮する。 ユーザーは可能な限りAEAD暗号を使用すべきである。

(余談)chatGPTによるAEADの解説

AEAD (Authenticated Encryption with Associated Data) は、暗号化と認証を同時に行うことができる暗号アルゴリズムの一種です。これにより、データの機密性(誰がそのデータを見ることができるか)と完全性(データが改ざんされていないこと)を一度に保証することができます。具体的には、以下の特徴を持っています。

構成要素

AEADアルゴリズムは主に以下の3つの部分から構成されます。

  1. 暗号化(Encryption): データを暗号化して、第三者が理解できない形式に変換します。
  2. 認証(Authentication): データに対して認証タグを付与し、データが改ざんされていないことを確認します。
  3. 関連データ(Associated Data): 認証には含まれるが、暗号化されない追加データです。これにより、ヘッダー情報などを保護しながら、暗号化されたデータ本体とは別に保存することができます。

代表的なAEADアルゴリズム

AEADアルゴリズムにはいくつかの代表的なものがあります。以下にいくつかの例を挙げます。

  1. GCM (Galois/Counter Mode):

    • 高速で並列化が可能なため、広く利用されています。
    • ブロック暗号(例: AES)を基盤にしており、暗号化と認証の両方を提供します。
  2. CCM (Counter with CBC-MAC):

    • AES CBCモードにカウンターモードとCBC-MACによる認証を組み合わせたものです。
    • 軽量でリソース制約のある環境(例: 組み込みシステム)で利用されます。
  3. Chacha20-Poly1305:

    • ストリーム暗号Chacha20と認証機構Poly1305を組み合わせたものです。
    • 高速で、特にモバイルや低資源環境での利用が推奨されています。

利用例

AEADは様々なプロトコルやアプリケーションで利用されています。例えば:

  • TLS(Transport Layer Security): ネットワーク通信の保護に利用されるプロトコルで、GCMやChacha20-Poly1305などのAEADアルゴリズムが使用されます。
  • VPN(Virtual Private Network): 安全な通信トンネルを提供するためにAEADが使用されます。
  • ストレージシステム: データの暗号化と認証を同時に行うことで、機密性と完全性を保ちます。

まとめ

AEADアルゴリズムは、セキュリティと効率を両立させるための強力な手法です。データの機密性と完全性を確保しつつ、高速で効率的な処理を実現するために、現代のさまざまなシステムやプロトコルで広く利用されています。

yunayuna

サーバーへのデプロイ

オリジナルのpython実装は、202407現在、オリジナル製作者がストップしているため、
現在活発に開発が進められているRust実装を使うことにします。

公式のgithubも参考にしつつ、
https://github.com/shadowsocks/shadowsocks/wiki
こちらの記事をベースに進めていきます。
https://hackernoon.com/installing-shadowsocks-rust-a-secure-open-source-proxy-server-better-than-vpn

バイナリを取得

最新のバージョンをチェックして、
環境に合わせたファイルを取得する。
https://github.com/shadowsocks/shadowsocks-rust/releases/latest

202407時点の、amd64最新バージョンはこちら。
shadowsocks-v1.20.2.aarch64-unknown-linux-gnu.tar.xz

#取得
$ sudo wget https://github.com/shadowsocks/shadowsocks-rust/releases/download/v1.20.2/shadowsocks-v1.20.2.aarch64-unknown-linux-gnu.tar.xz -P /opt/
#解凍して展開
$ sudo tar xf /opt/shadowsocks-*-linux-gnu.tar.xz -C /sbin/ --owner=root --group=root
$ sudo rm /opt/shadowsocks-*-unknown-linux-gnu.tar.xz

展開された各モジュールの説明

sslocal, ssserver, ssmanager, および ssurl ファイルは、いくつかの目的に役立つShadowsocksソフトウェアのコンポーネントです:

  • sslocal: これは、クライアントマシンで実行され、ローカルSOCKS5プロキシサーバーをセットアップするShadowsocksのクライアント側コンポーネントです。sslocal リモートのShadowsocksサーバーに接続し、暗号化された接続を介してネットワークトラフィックをルーティングします。

  • sserver: これは、リモートサーバーで実行されているShadowsocksのサーバー側コンポーネントです。ssserver から暗号化されたネットワークトラフィックを受信 sslocal クライアントはそれを解読し、ネットワーク上の適切なリソースに転送します。ネットワーク保護を提供し、検閲を回避します。

  • ssmanager: これはShadowsocksが提供する管理ツールです。これにより、ユーザー管理、接続、その他の構成の側面など、Shadowsocksサーバーインスタンスを管理および監視できます。

  • ssurl: これは、Shadowsocks URLリンクを作成および操作する便利な方法を提供するコマンドラインツールです。ssurlを使用すると、Shadowsocks接続パラメーターを含むURLリンクを作成して、簡単に共有したり、クライアントアプリケーションの自動構成に使用したりできます。

暗号の選択

いくつかの暗号が選択可能だが、
一般的なユーザーの場合 ChaCha20-IETF-Poly1305 または AES-256-GCM 暗号化アルゴリズムを
パフォーマンス、セキュリティ、互換性の点で選ぶのが良い。

使用されているデバイスとその特定のパフォーマンス特性に依存する場合があります。
ChaCha20-IETF-Poly1305 ハードウェアアクセラレーションなしのモバイルデバイスとIoTデバイスにとってより良い選択かもしれません AES, ながら AES-256-GCM ハードウェアをサポートするデバイスにより適している場合があります AES クロスプラットフォームの互換性の必要性。どちらの暗号化方法も安全であると見なされ、データを強力に保護します。

パフォーマンス

ChaCha20-IETF-Poly1305強力なプロセッサーの少ないデバイスやハードウェアサポートのないデバイスでは、多くの場合高速です AES。さまざまなプラットフォーム、特にモバイルデバイスとIoTデバイスで高性能を提供するように設計されています。AES-256-GCM AESのハードウェアアクセラレーションを備えた最新のデバイスでうまく機能しています。搭載デバイス AES-NI (Intel)またはARMv8-A暗号化拡張(ARM), AES-256-GCMよりも速いかもしれません ChaCha20-IETF-Poly1305。

セキュリティ

どちらも ChaCha20-IETF-Poly1305そして AES-256-GCM高レベルのセキュリティを提供します。ChaCha20と組み合わせたストリーム暗号です Poly1305 認証機関は、関連するデータを使用して認証された暗号化を形成します (AEAD) 建設。AES-256-GCM も AEAD 組み合わせる暗号 AES で暗号をブロックする ガロア/カウンターモード(GCM) 操作。どちらの暗号も暗号化コミュニティによって広範囲に分析されており、安全と見なされています。ただし、それらの選択は、実装に対する自信とハードウェアアクセラレーションの潜在的な脆弱性に依存する場合があります。

互換性

AES-256-GCMより広く採用されており、デバイスとプラットフォーム間の互換性が向上しています。これは、多くの最新のプロセッサーに組み込みのハードウェアアクセラレーションを利用することで、より長く、メリットを得るための標準となっています。ChaCha20-IETF-Poly1305, ますます人気が高まっている一方で、すべてのプラットフォームまたはすべてのソフトウェアでサポートされていない可能性があります。

選択可能な暗号の例

AEAD 2022暗号

  • 2022-blake3-aes-128-gcm
  • 2022-blake3-aes-256-gcm
  • 2022-blake3-chacha20-poly1305
  • 2022-blake3-chacha8-poly1305

AEAD暗号

  • chacha20-ietf-poly1305
  • aes-128-gcm
  • aes-256-gcm-

ストリーム暗号

plain または none (暗号化なし、デバッグまたはトランスポートのセキュリティを保証するプラグインでのみ使用)

パスワード生成

暗号には "password" のBase64キー文字列になる まったく同じ長さ 暗号のキーサイズとして
安全なキーを生成するために、このコマンドを使用することをお勧め。

sservice genkey -m "METHOD_NAME"`

今回はchacha20-ietf-poly1305を採用して、パスワード生成します。

PASSWORD_0=$(ssservice genkey -m "chacha20-ietf-poly1305")
PASSWORD_1=$(ssservice genkey -m "chacha20-ietf-poly1305")
PASSWORD_2=$(ssservice genkey -m "chacha20-ietf-poly1305")

# password for main config
$ echo $PASSWORD_0
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

# password for guest1
$ echo $PASSWORD_1
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

# password for guest2
$ echo $PASSWORD_2
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

設定

設定を置くディレクトリを生成

sudo mkdir /etc/shadowsocks-rust

chacha20-ietf-poly1305を採用した場合の、
Shadowsocksの設定はこちら

/etc/shadowsocks-rust/ssserver-guest.json
{
    "server": "::",
    "outbound-bind-interface": "eth0",
    "ipv6_first": false,
    "ipv6_only": false,
    "server_port": 12345,
    "password": "<PASSWORD_0>",
    "mode": "tcp_and_udp",
    "method": "chacha20-ietf-poly1305",
    "timeout": 300,
    "udp_timeout": 300,
    "udp_max_associations": 512
}

multiuserサポートをshadowsocks-rustに追加するには、それに応じて構成ファイルを変更する必要があります。
(usersのフィールドを追加)

/etc/shadowsocks-rust/ssserver-guest.json
 {
    "server": "::",
    "outbound-bind-interface": "eth0",
    "ipv6_first": false,
    "ipv6_only": false,
    "server_port": 12345,
    "password": "<PASSWORD_0>",
    "mode": "tcp_and_udp",
    "method": "chacha20-ietf-poly1305",
    "timeout": 300,
    "udp_timeout": 300,
    "udp_max_associations": 512,
    "users": {
        "guest1": "<PASSWORD_1>",
        "guest2": "<PASSWORD_2>"
    }
}

Systemd の設定

サービスファイルに@を使って、ファイル内でファイル名で指定した変数を使えるようにします。

sudo vi /etc/systemd/system/ssserver@.service

例えば、sudo systemctl start ssserver@guest とすると、
"guest"の文字を、ファイル内の
%i %I で指定した箇所に変数として挿入できるようになります。(%Iは、urlencodeでデコードされた変数をセット)

/etc/systemd/system/ssserver@.service
[Unit]
Description=Shadowsocks-rust ssserver on %I
Documentation=https://github.com/shadowsocks/shadowsocks-rust
After=network.target
Wants=network.target

[Service]
ExecStart=/sbin/ssserver -c /etc/shadowsocks-rust/ssserver-%i.json
Restart=on-failure
User=nobody
Group=nobody
LimitNOFILE=4096

[Install]
WantedBy=multi-user.target

サービス有効化

sudo systemctl daemon-reload
# %Iを guest で作成します。
sudo systemctl start ssserver@guest
sudo systemctl enable ssserver@guest
sudo systemctl status ssserver@guest

#停止・再起動
sudo systemctl stop ssserver@guest
sudo systemctl restart ssserver@guest

これでサービスの起動は完了。

yunayuna

クライアントからの接続テスト(windows)

client の稼働

windows clientをwindowsにインストールして、
設定、firefoxを使って接続してみる。

windows clientの最新はこちらからダウンロードできます。
https://github.com/shadowsocks/shadowsocks-windows/releases

Shadowsocks.exe を実行し、
上記サーバーで設定したポート、パスワード、暗号化種別を設定。
プロキシポートは、windowsのローカルでshadowsocks clientに接続するためのポート
(デフォルトで1080になってます)。

firefox で、localのshadowsocksをプロキシとして設定

shadowsocksの有効にします

システムトレイで起動しているshadowsocksを左クリックして、起動させます。

※グローバルプロキシ:全ての接続についてプロキシを経由させます。
※PACモード:「Proxy Auto-Configuration」の略で、
プロキシサーバの設定を自動的に行うためのモードです。
PACファイルは特定のURLに対するネットワーク接続がプロキシーサーバーを経由するべきか直接行うべきかなどを制御できます。

設定後、ipをチェックすると、
EC2で建てたshadowsocksのサーバーのIPになっており、Proxyが成功している事が分かります。

https://www.cman.jp/network/support/go_access.cgi

これで、クライアントからの接続確認は完了ですが、念のためパケットを確認。

まず、ローカルのDNSキャッシュをクリア

C:\WINDOWS\system32>ipconfig /flushdns

Windows IP 構成

DNS リゾルバー キャッシュは正常にフラッシュされました。

クライアントのプロキシを無効にした場合、
ブラウザで対象ドメインのDNSパケットが発生します。

クライアントのプロキシを有効にした場合、
今回はshadowsocksのサーバーをドメイン指定しているため、
shadowsocksサーバーのドメインについてDNSパケットが発生し、その後で取得したipアドレスにhttpsアクセスが発生しています。

これで接続先のドメイン情報は秘匿され、DNSサーバーへの問い合わせは行われませんし、
パケットはshadowsocksで設定した暗号化がされた状態なので、仮にshadowsocksサーバーのDNSがポイズニング(DNS Spoofing)などの中間者攻撃が行われたとしても、
正しく通信できなくなるだけで監視・改ざんはされないはずです。

yunayuna

クライアントからの接続テスト(ubuntu)

client の稼働

ubuntu用のclientをダウンロードし、
設定、firefoxを使って接続してみる。

サーバーで使ったshadowsocks-rustには、クライアントのバイナリも含まれているので、
サーバーと同様に、こちらの最新を利用していきます。
https://github.com/shadowsocks/shadowsocks-rust/releases/latest

今回検証するクライアント機はamd64なので、こちらを利用。
shadowsocks-v1.20.2.x86_64-unknown-linux-gnu.tar.xz
解凍した中にある、sslocalがクライアント用バイナリです。

sslocal
ssmanager
ssserver
ssservice
ssurl

#取得
$ sudo wget https://github.com/shadowsocks/shadowsocks-rust/releases/download/v1.20.2/shadowsocks-v1.20.2.x86_64-unknown-linux-gnu.tar.xz -P /opt/
#解凍して展開
$ sudo tar xf /opt/shadowsocks-*-linux-gnu.tar.xz -C /sbin/ --owner=root --group=root
$ sudo rm /opt/shadowsocks-*-unknown-linux-gnu.tar.xz

ubuntuクライアントの設定手順は、基本的にはserverと同じです。
ゆくゆくはdebインストーラを作成すると便利そうですが、今回は手動で設定を進めていきます。

設定ファイルを置くディレクトリを作成

sudo mkdir /etc/shadowsocks-rust

設定ファイルは、サーバーとほぼ同じですが、ローカルで動作させるための設定を追加しています

/etc/shadowsocks-rust/sslocal-guest.json
{
    "server": "<ip or domain>",
    "server_port": 12345,
    "password": "<PASSWORD_0>",
    "method": "chacha20-ietf-poly1305",
    "local_address": "127.0.0.1",
    "local_port": 1080
}

Systemd の設定

serverと同様。

sudo vi /etc/systemd/system/sslocal@.service
/etc/systemd/system/sslocal@.service
[Unit]
Description=Shadowsocks-rust sslocal on %I
Documentation=https://github.com/shadowsocks/shadowsocks-rust
After=network.target
Wants=network.target

[Service]
ExecStart=/sbin/sslocal -c /etc/shadowsocks-rust/sslocal-%i.json
Restart=on-failure
User=nobody
#ubuntuのnobodyユーザーが所属するグループがnogroup だったので、修正(OSによって異なるので要注意)
#Group=nobody
Group=nogroup
LimitNOFILE=4096

[Install]
WantedBy=multi-user.target

サービス有効化

sudo systemctl daemon-reload
# %Iを guest で作成します。
sudo systemctl start sslocal@guest
sudo systemctl enable sslocal@guest
sudo systemctl status sslocal@guest

#停止・再起動
sudo systemctl stop sslocal@guest
sudo systemctl restart sslocal@guest

firefoxでの動作テスト

windows clientと同様に、プロキシの設定で、上記ローカルで稼働しているsslocalの
url(127.0.0.1)、port(1080)を指定すると、きちんとshadowsocksサーバー経由でwebの閲覧ができました。

nobodyユーザーのgroupを考慮したdebインストーラ生成(chatGPTメモ)

インストーラ側でdebパッケージを作成する際に、システムの設定(具体的にはnobodyの所属グループ)に合わせてシステムサービスの設定ファイルを動的に生成する方法としては、以下のような手順が考えられます。

  1. インストーラスクリプトを作成します。このスクリプトでは、nobodyユーザの所属グループを確認し、それに基づいてSystemdの設定ファイルを生成します。

具体的には、以下のようなbashスクリプトを考えることができます。

#!/bin/bash
GROUP=$(id -gn nobody)
SERVICE_CONFIG="/etc/systemd/system/sslocal@.service"
cp sslocal@.service.template $SERVICE_CONFIG
sed -i "s/NOBODY_GROUP/$GROUP/g" $SERVICE_CONFIG

このスクリプトでは、まずidコマンドでnobodyの所属グループを取得します。次に、sslocal@.service.template(このファイルは事前に作成しておく必要があります)をsslocal@.serviceとしてコピーし、sedコマンドでテキストの置換を行っています。このスクリプトの前提として、Systemdの設定ファイルテンプレートにおいて、nobodyの所属グループを指定する部分を"NOBODY_GROUP"とし、それを動的に置換することで適切な設定ファイルを生成しています。

  1. 作成したスクリプトをdebパッケージのpostinst(インストール後に実行されるスクリプト)に含めます。これにより、debパッケージのインストール時にスクリプトが実行され、適切なSystemdの設定ファイルが生成されるようになります。
yunayuna

この後やりたいこと

  • Rustdeskのサーバーを立て、ローカルからRustdeskへのアクセスをshadowsocks Proxyを通して行う検証。

  • shadowsocks以外のsocks5クライアントから、shadowsocks serverへの通信が可能か?を検証する。
    Rust製の実装がいくつかありそうなので、できれば自作のRustアプリケーションに組み込みたい

https://github.com/dizda/fast-socks5
https://github.com/ajmwagar/merino

yunayuna

shadowsocksを通してRustdeskサーバーにアクセス(準備)

Rustdeskをself-hostedで立てて、そのサーバーへのアクセスをshadowsocksを通して行うことを検証してみます。
Rustdeskサーバーは、privateなネットワーク内に配置し、shadowsocksプロトコル経由以外ではアクセスできないようにします。
これにより、よりセキュアな通信を実現することを目的とします。

前提

shadowsocks側で、dns設定を行うこともできそうなのですが(?)、これは後ほど試すことにして、
▼optional features
https://github.com/shadowsocks/shadowsocks-rust?tab=readme-ov-file#optional-features

今回は、shadowsocksではdnsの処理を設定しない方法で検証します。

hostsで対応可能?

最初に、shadowsocks内のhostsで設定変更可能か確認してみました。
例として、example.comドメインを、Rustdeskのサーバーのprivate ipに設定してみます。

/etc/hosts
127.0.0.1   localhost localhost.localdomain localhost4 localhost4.localdomain4
::1         localhost6 localhost6.localdomain6
192.168.xxx.xxx  example.com

しかし、shadowsocksで受信したリクエストは、hostsを無視して通信されてしまいました。
この方法は失敗です。

DNSサーバーを動作させる

shadowsocksサーバーと同じサーバー上に、
DNSサーバーを立てて、Rustdeskサーバーに接続できるかを検証します。

DNSサーバーはBINDが有名ですが、今回はRust製のhickory-dnsを使ってみます。
https://github.com/hickory-dns/hickory-dns

バイナリが公式サイトで用意されていない(2024/7/23時点)ので、
Rustが入ってる自環境でビルド。今回はサーバーがARM64なので、これ用にビルドします。

#Rustが入ってなければ入れる
#rust install
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source ~/.bashrc

#git install
sudo apt install git -y

#hickory-dnsをダウンロード(gitでcloneする)
git clone https://github.com/hickory-dns/hickory-dns.git

cd hickory-dns
# mainブランチは安定版ではないので、最新バージョンのコミットIDまで戻す(0.24.1の例)
git checkout -b latest_release e56a18e

#通常のビルドであれば、これでOKだが・・・
cargo build --release -p hickory-dns

# shadowsocksサーバーがarm64だった場合、arm64用ビルドが必要なので、環境準備
sudo apt install -y gcc-aarch64-linux-gnu
rustup target add aarch64-unknown-linux-gnu

# cargo/configファイルに以下を追加(なければファイル新規作成)
vi ~/.cargo/config

[target.aarch64-unknown-linux-gnu]
linker = "aarch64-linux-gnu-gcc"

#arm64用ビルド
cargo build  --target aarch64-unknown-linux-gnu --release -p hickory-dns

作成したバイナリをサーバーにアップ。
また、設定ファイル、zoneファイルを記述

example.toml
#example.comについてはexample.com.zone に記述されたzone情報に基づいて処理する
[[zones]]
zone = "example.com"

zone_type = "Primary"

file = "example.com.zone"

#それ以外は、8.8.8.8 のdnsサーバーにforwardする
[[zones]]
zone = "."

## zone_type: Primary, Secondary, Hint, Forward
zone_type = "Forward"

stores = { type = "forward", name_servers = [
    { socket_addr = "8.8.8.8:53", protocol = "udp", trust_nx_responses = false },
    { socket_addr = "8.8.8.8:53", protocol = "tcp", trust_nx_responses = false },
] }
example.com.zone
; example.comのipv4ドメインは、192.168.xxx.xxx で解決させる
@   IN          SOA     ns.example.com. admin.example.com. (
                                199609203 ; Serial
                                8h        ; Refresh
                                120m      ; Retry
                                7d        ; Expire
                                24h)      ; Minimum TTL

                NS      bbb
                MX      1 alias
                A       192.168.xxx.xxx
                AAAA    ::1

bbb             A       127.0.0.1
no-service 86400 IN MX 0 .

上記設定のもとで、ファイルを配置

./hickory-dns (binary)
./example.toml
./zone/example.com.zone

hickory-dnsを実行し、dnsサーバーを立ち上げる。ポート指定も-pオプションでできますが、
今回はデフォルトportの53で立ち上げ。

#デフォルトポート53で立ち上げる場合、管理者権限が必要。
#example.tomlを読み込み、zoneファイルが配置されているディレクトリを ./zone に指定
$ sudo ./hickory-dns -c ./example.toml -z ./zone/

1721723969:INFO:hickory_dns:336:Hickory DNS 0.24.1 starting                                                                                                                                                                                                                    
1721723969:INFO:hickory_dns:341:loading configuration from: "./example.toml"                                                                                                                                                                     
1721723969:INFO:hickory_dns:268:zone successfully loaded: example.com.                                                                                                                                                                                                        
1721723969:INFO:hickory_dns:268:zone successfully loaded: .                                                                                                                                                                                                                    
1721723969:INFO:hickory_dns:401:binding UDP to 0.0.0.0:53                                                                                                                                                                                                                      
1721723969:INFO:hickory_dns:406:listening for UDP on 0.0.0.0:53                                                                                                                                                                                                                
1721723969:INFO:hickory_dns:419:binding TCP to 0.0.0.0:53                                                                                                                                                                                                                      
1721723969:INFO:hickory_dns:424:listening for TCP on 0.0.0.0:53                                                                                                                                                                                                                
1721723969:INFO:hickory_dns:695:                                                                                                                                                                                                                                               
1721723969:INFO:hickory_dns:697:                                   ------                                                                                                                                                                                                      
1721723969:INFO:hickory_dns:697:                                ---------                                                                                                                                                                                                      
1721723969:INFO:hickory_dns:697:                              +----------                                                                                                                                                                                                      
1721723969:INFO:hickory_dns:697:                            ++++++-------                                                                                                                                                                                                      
1721723969:INFO:hickory_dns:697:                           ++++++++++++++                                                                                                                                                                                                      
1721723969:INFO:hickory_dns:697:                          +++++++++++++++                                                                                                                                                                                                      
1721723969:INFO:hickory_dns:697:                          +++++++++++++++                                                                                                                                                                                                      
1721723969:INFO:hickory_dns:697:                         ++++++++++++++++                                                                                                                                                                                                      
1721723969:INFO:hickory_dns:697:                         ++++++++++++++++ ####                                                                                                                                                                                                 
1721723969:INFO:hickory_dns:697:                         ++++++++++++++++ ########                                                                                                                                                                                             
1721723969:INFO:hickory_dns:697:                         ####++++++++++++ ###########
1721723969:INFO:hickory_dns:697:                         ################ ############                           
1721723969:INFO:hickory_dns:697:                         ################ ##############                         
1721723969:INFO:hickory_dns:697:                         ################ ##############                         
1721723969:INFO:hickory_dns:697:                         ################ ###############                        
1721723969:INFO:hickory_dns:697:                         ################ ###########++++                        
1721723969:INFO:hickory_dns:697:                         ################ ######++++++++++                       
1721723969:INFO:hickory_dns:697:                         ################ ++++++++++++++++                       
1721723969:INFO:hickory_dns:697:                         ###############  ++++++++++++++++                       
1721723969:INFO:hickory_dns:697:                         ###############   +++++++++++++++                       
1721723969:INFO:hickory_dns:697:                         ##############     +++++++++-----                       
1721723969:INFO:hickory_dns:697:                         #############       +++++--------                       
1721723969:INFO:hickory_dns:697:                         ++++++#####           +----------                       
1721723969:INFO:hickory_dns:697:                         +++++++++               ---------                       
1721723969:INFO:hickory_dns:697:                         +++++                       -----                       
1721723969:INFO:hickory_dns:697:                                                                                 
1721723969:INFO:hickory_dns:697:                  ##               ###                                           
1721723969:INFO:hickory_dns:697: ###         ### ####              ###                                           
1721723969:INFO:hickory_dns:697: ###         ###                   ###                                           
1721723969:INFO:hickory_dns:697: ###         ###                   ###                                           
1721723969:INFO:hickory_dns:697: ###         ### ####  ##########  ###    #### ##########   ####### ####     ####
1721723969:INFO:hickory_dns:697: ############### #### ####    #### ###  ####  ####    ####  ###      ###     ### 
1721723969:INFO:hickory_dns:697: ###         ### #### ###          #######   ####      #### ###       ###   ###  
1721723969:INFO:hickory_dns:697: ###         ### #### ###          ########  ###       #### ###       #### ###   
1721723969:INFO:hickory_dns:697: ###         ### #### ###      ### ###   ###  ###      ###  ###        #######   
1721723969:INFO:hickory_dns:697: ###         ### ####  ##########  ###    #### ##########   ###         #####    
1721723969:INFO:hickory_dns:697: ###         ### ####    ######    ###     ####  ######     ###          ###     
1721723969:INFO:hickory_dns:697:                                                                        ###      
1721723969:INFO:hickory_dns:697:                                                +++++  +   + ++++   #######      
1721723969:INFO:hickory_dns:697:                                                ++   + +++ +  ++    #####        
1721723969:INFO:hickory_dns:697:                                                ++   + + +++   ++                
1721723969:INFO:hickory_dns:697:                                                +++++  +   + +++++               
1721723969:INFO:hickory_dns:699:
1721723969:INFO:hickory_dns:479:awaiting connections...
1721723969:INFO:hickory_dns:484:Server starting up

動作確認。設定したとおりにドメインが解決できています。

[ec2-user@ip-172-31-3-74 ~]$  dig example.com

; <<>> DiG 9.16.48-RH <<>> example.com
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 27805
;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; WARNING: recursion requested but not available

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags: do; udp: 4096
;; QUESTION SECTION:
;example.com.                  IN      A

;; ANSWER SECTION:
example.com.           86400   IN      A       192.168.xxx.xxx

;; Query time: 0 msec
;; SERVER: 172.31.xx.xx#53(172.31.xx.xx)
;; WHEN: Tue Jul 23 09:07:49 UTC 2024
;; MSG SIZE  rcvd: 57

ec2のDNSサーバー指定を、ローカルで立ち上げたdnsサーバーに向ける

通常のサーバーであれば、DHCPでなく固定でdnsサーバーを指定すれば良いのですが、
ec2はDHCPの設定のまま、VPCで設定を変更する形になるようです。

▼参考にした記事
https://note.com/peternwse/n/n7897ecdb336c

これに基づいて、対象のEC2インスタンスが所属するVPCのDHCP設定(AWSのDHCPオプションセット)を変更します。

新規でDHCPオプションセットを作成

ドメインネームサーバーのところを、該当EC2インスタンスのプライベートIP(172.31.xxx.xxx)を指定。
ドメイン名はデフォルトのものをコピー、それ以外は空白です。

VPCのDHCPオプションセットを、上記作成したものに切り替え、
即時反映されるよう、EC2インスタンスを再起動します。

クライアントからの検証

shadowsocksサーバー、dnsサーバーを起動させた状態で、socks5による通信を行ってみると、
example.com以外へのアクセスはそのまま、
example.comへのアクセスは、上記で設定したIP(192.168.xxx.xxx)に飛ぶようになりました。

yunayuna

Rustdeskの設定

Rustdeskの各サーバーを、上記hickory DNSで設定したドメインに指定

Socks5/HTTP(s)プロキシの設定で、クライアントのローカルで起動させているshadowsocksを指定
(ユーザー・パスワード不要)

これで、ubuntu→windows問題なく接続できました。
接続経路として、以下のようになっています。

ubuntu側(リモートアクセスする方)
[ RustdeskClient(ubuntu) ]
|
v
[ ローカルのShadowsocks(ubuntu shadowsocks-rust sslocal) ]
(暗号化データ)
|
v
[ shadowsocksサーバー ]
(復号化データ)
|
v
[ Hickory DNSサーバー ]
(IP問い合わせ)
|
v
[ shadowsocksサーバー ]
(IPレスポンス)
|
v
[ Rustdeskサーバー ]

windows側(アクセス先のPC)
[ RustdeskClient(windows) ]
|
v
[ ローカルのShadowsocks(ubuntu shadowsocks-windows システムプロキシモード) ]
(暗号化データ)
|
v
[ shadowsocksサーバー ]
(復号化データ)
|
v
[ Hickory DNSサーバー ]
(IP問い合わせ)
|
v
[ shadowsocksサーバー ]
(IPレスポンス)
|
v
[ Rustdeskサーバー ]

問題発生

ubuntu(shadowsocks-rust proxy経由)→windows(shadowsocks-windows proxy経由) 接続OK🔵
windows(shadowsocks-windows proxy経由)→ubuntu(shadowsocks-rust proxy経由) 接続NG❌(ubuntuがオフライン)

となってしまいました。
ubuntu側のRustdeskのステータスバーが、以下のようにネットワーク接続中のままで、
なぜか「準備完了」にならないため、windows側からも見つけられないようです。

shadowsocksに問題がありそうなので、
プロキシからダイレクトにRustserverを見るように変更を入れてみました。
(※各設定変更後、一度サービスを停止してから開始しないと反映されないことがあるので注意)

windows側だけ、shadowsocksを止めてダイレクトにRust接続させる

ubuntu(shadowsocks-rust proxy経由)→windows(direct接続) 接続OK🔵
windows(direct接続)→ubuntu(shadowsocks-rust proxy経由) 接続NG❌(ubuntuがオフライン)
両方をProxyした時と全く同じ挙動。

ubuntu側だけ、shadowsocksを止めてダイレクトにRust接続させる

ubuntu(no proxy)→windows(shadowsocks-windows proxy経由) 接続NG❌(立ち上がっているように見えるが、「中継サーバーに接続できませんでした」エラー)
windows(shadowsocks-windows proxy経由)→ubuntu(no proxy) 接続OK🔵

最後に、両者のクライアントともshadowsocksをオフにして、ダイレクトにrustdeskサーバーにアクセスさせた場合
ubuntu(no proxy)→windows(no proxy) 接続OK🔵
windows(no proxy)→ubuntu(no proxy) 接続OK🔵

問題なし。

以上から言えることは、

  • ubuntuでshadowsocks-rust クライアント(sslocal)を起動させてRustdeskからプロキシさせたとき、
    自マシンは他マシンに認識されない
  • windowsのshadowsocks-windows経由のRustdesk端末に対し、Direct接続のRustdeskからはリモートアクセスできない

です。
ubuntu上のshadowsocks-rustのクライアントの挙動に、何らかrustdeskと相性が悪いことがあるのか?

windowsのshadowsocks クライアントについて
今度はshadowsocks-windowsじゃなくて、shadowsocks-rustに切り替えて再度テストしてみます。

windowsもshadowsocks-rustのクライアントに変更

windowsバイナリをダウンロードして、ローカルで動かしたところ、
上記ubuntuでShadowsocks-rustを動かした時と同様に、Rustdeskネットワークに接続中・・
となってしまい、自端末が認識されない状況に。

ubuntu、windowsともにshadowsocks-rust localのログを確認したところ、同じログが出ている事が出続けていることが判明。

 [2m2024-07-30T23:49:35.3720129+09:00[0m [33m WARN[0m socks5 udp is disabled
[2m2024-07-30T23:49:35.696748+09:00[0m [33m WARN[0m socks5 udp is disabled
[2m2024-07-30T23:49:52.7167621+09:00[0m [33m WARN[0m socks5 udp is disabled
[2m2024-07-30T23:50:09.7250946+09:00[0m [33m WARN[0m socks5 udp is disabled
[2m2024-07-30T23:50:26.7550628+09:00[0m [33m WARN[0m socks5 udp is disabled
[2m2024-07-30T23:50:43.7675854+09:00[0m [33m WARN[0m socks5 udp is disabled
[2m2024-07-30T23:51:00.7736434+09:00[0m [33m WARN[0m socks5 udp is disabled

何か設定が不足しているのでは?
issueを検索してみると、
https://github.com/shadowsocks/shadowsocks-rust/issues/1317
これが足りないとのことで、追加して動かしてみる。

"mode": "tcp_and_udp"

動いた!

シンプルに設定不足でした。

もう一度、上記2パターンもテスト

windows側だけ、shadowsocksを止めてダイレクトにRust接続させる

ubuntu(shadowsocks-rust proxy経由)→windows(direct接続) 接続OK🔵
windows(direct接続)→ubuntu(shadowsocks-rust proxy経由) 接続NG❌(立ち上がっているように見えるが、「中継サーバーに接続できませんでした」エラー)

ubuntu側だけ、shadowsocksを止めてダイレクトにRust接続させる

ubuntu(no proxy)→windows(shadowsocks-windows proxy経由) 接続NG❌(立ち上がっているように見えるが、「中継サーバーに接続できませんでした」エラー)
windows(shadowsocks-windows proxy経由)→ubuntu(no proxy) 接続OK🔵

上記から、
shadowsocks経由の端末に、shadowsocks無しから接続はできないという事が分かりました。
shadowsocks無し端末は、peer接続がshadowsocks経由でなく直接接続しようとするので、対象端末が見つからないということ(?)

理由については一旦置いておき、動作について把握できたので検証終了とします。

yunayuna

windowsにshadowsocks-rustをサービスとして登録

公式に記載がある通り、
https://github.com/shadowsocks/shadowsocks-rust?tab=readme-ov-file#local-client-for-windows-service

windows用のバイナリに含まれる「sswinservice.exe」を使って、
こちらで登録されます。

New-Service -Name "shadowsocks-local-service" `
            -DisplayName "Shadowsocks Local Service" `
            -BinaryPathName "\"<Path\to>\sswinservice.exe\" local -c \"<Path\to>\\config.json\""