リモートワークを行う際に俎上に載る問題のひとつが「プライベートネットワークにある組織内リソースにどのようにアクセスするか」です。今回はそんな問題の解決策のひとつとなりうる、SSH経由でSOCKSプロキシを構築する方法を紹介しましょう。
HTTP/HTTPSが使えるVPN(のようなもの)がほしい
ここからいろいろ述べますが、簡単に言うと「SSHのDynamic Port ForwardingをSOCKSプロキシとして利用する」というだけの話です。
さて、リモートワークによる働き方は、会社の文化・セキュリティ要件によって多種多様に分かれます。その中でも最も「ゆるい」のが、「 個人の端末・インターネット回線を用いてあとは良しなにやってくれ」というものでしょう。作業がインターネット上で完結する、つまりパブリックなクラウドサービスを積極的に活用している業務であれば、それでもおおよそなんとかなります。
しかしながらそうでない場合は、何かにつけてプライベートネットワークである組織内LAN上のリソースにアクセスしなければなりません。そこで出てくるのがリモートデスクトップとVPNという概念です。リモートデスクトップはまず組織内LAN上に存在するPCに接続した上で、あとはそのPCから組織内リソースにアクセスします。つまりデータが社外ネットワークに出ることはないので比較的安全です[1] 。ただし「デスクトップ画面」をネットワーク上でやりとりするため、データ上の効率はあまりよくありません。社員の多くがVPNを利用することになって眠れない夜を過ごしているインフラエンジニアも多いことでしょう。また、組織内LAN上のPCにどのように接続するのか、経路をどう安全に保つのかという別の問題もあります。
リモートデスクトップに代わる案のひとつがVPN(Virtual Private Network)です。手元にあるPCをインターネット経由で組織内LANに接続する方式です。リモートデスクトップに対してネットワークに流れるデータは少なくなるかどうかは使い方に依存します。たとえばオフィス系ソフトウェアの編集がメインであれば、一度ファイルをダウンロードした上でローカルで編集すれば良いことになるので、流れるデータは大きく削減できるでしょう。ただし社外ネットワークの外にあるPCにデータをダウンロードするため、組織内のセキュリティルールとの整合性を取ることが難しくなります。もちろんVPN経由でリモートデスクトップを使うことも一般的です。
本連載ではこれまで次の記事で、リモートデスクトップ方式、VPN方式にそれぞれ使えるソフトウェアを紹介してきました。
今回はそれに対して、「 SSHを用いて組織内LAN内部のウェブリソースにアクセスする方法」を紹介しましょう。
組織内リソースが「HTTP/HTTPSでアクセスできるもので統一されている」というケースはそれなりに存在するかと思います。SMBに依存しているケースなど、100%そうなっていることはなかなかないのかもしれませんが、リモートワークを行う上で必要なものだけであればおおよそウェブブラウザーで完結するケースはそれなりに高そうです。本連載の読者であれば、組織内WikiやGitLab/Redmineをはじめとするチケット管理システムあたりさえ見られれば仕事ができることも多いのではないでしょうか[2] 。
モダンなウェブブラウザーであれば、SOCKS プロキシに対応しています。さらにSSHのDynamic Port Forwardingを用いれば、SSHコマンドをSOCKSサーバーにできます。つまりウェブブラウザー側で特定のアドレスのみSOCKSプロキシを使うように設定すれば、インターネット上のサイトと組織内LAN上のサイトの双方にその違いを意識することなくアクセスできるようになるわけです。
テスト用の隔離ネットワークサイトの作成
まずはテスト用に隔離されたネットワーク上のサイトを作成しましょう。方法はいくつか存在します。その中でも一番簡単なのはローカルからしかアクセスできないウェブサーバーが動いているマシンを作ることです。今回はLXDを使ってはいるものの、もちろんVirtualBoxでもmultipassでもかまいません。
$ lxc launch ubuntu:20.04 lts
$ lxc exec lts -- sh -c "apt update && apt full-upgrade -y && apt autoremove -y"
$ lxc exec lts -- sh -c "apt install nginx -y"
ホストから見たコンテナのアドレスが「10.93.9.120」だとすると、コンテナの外からNginxのテストサイトにアクセスできます。
$ curl -sL http://10.93.9.120/
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
(以下略)
これをコンテナの外からはアクセスできず、コンテナの中からのみアクセスするようにします。手っ取り早いのはufwでフィルタリングしてしまうことです。ここではすべてフィルタリングしてしまいましょう。
$ lxc exec lts -- ufw default deny
Default incoming policy changed to 'deny'
(be sure to update your rules accordingly)
$ lxc exec lts -- ufw enable
Firewall is active and enabled on system startup
$ lxc exec lts -- ufw status verbose
Status: active
Logging: on (low)
Default: deny (incoming), allow (outgoing), deny (routed)
New profiles: skip
これでコンテナの外からはアクセスできなくなりました。
$ curl -L http://10.93.9.120/
curl: (28) Failed to connect to 10.93.9.120 port 80: 接続がタイムアウトしました
このままだとコンテナ内部にSSHでログインできないので、それは許可しておきましょう。
$ lxc exec lts -- ufw allow ssh
Rule added
Rule added (v6)
SSHでコンテナ内部にログインすれば、サイトが閲覧できることも確認しておきます。
$ ssh ubuntu@10.93.9.120 curl -sL http://localhost/
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
(以下略)
このコンテナのネットワーク(10.93.9.0/24)を、今回は組織内LANとみなします。つまりコンテナの外にあるマシンから、コンテナ内部のサイトを閲覧することが目標となります。
SOCKSサーバーの立ち上げ
それでは、さっそくクライアントマシンからSSHコマンドでSOCKSサーバーを立ち上げてみましょう。
$ ssh -fNC -D 1080 -oExitOnForwardFailure=yes ubuntu@10.93.9.120
「10.93.9.120」はコンテナのIPアドレスです。実際に利用する場合は、組織内LAN上にあるSSHログイン可能なマシンのアドレスになるでしょう。踏み台を経由した多段接続が必要な場合は「-oProxyCommand='ssh -W %h:%p ユーザー名@ホスト名'
」なども組み合わせてください。より新しいOpenSSHなら「-J ユーザー名@ホスト名
」という記述(ProxyJump)も使えます。「 ubuntu@」とユーザー名を明示しているのはコンテナのユーザー名がクライアントマシンのそれと違うからです。もし同じなら指定しなくても大丈夫でしょう。
他のオプションは次のとおりです。
「-f
」 :sshコマンドをバックグラウンドで動かす。「 -oExitOnForwardFailure=yes
」がついているので、何かエラーが発生したらsshコマンドは終了します。
「-N
」 :単純にSSHのコネクションのみを作成します。SSH接続先で何かコマンドを実行することはありません。
「-C
」 :コネクションでやりとりするデータをすべて圧縮します。これは指定しなくても問題ありません。
「-D 1080
」 :SOCKSサーバーとしてポート1080で待ち受けます。
これでクライアントマシンから、コンテナの中のサイトが閲覧できるようになりました。curlコマンドにはSOCKSプロキシにも対応しているので実際に試してみます。
$ curl -sL --proxy socks5://localhost:1080/ http://10.93.9.120/
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
(以下略)
期待通り組織内LAN(コンテナネットワーク)内部のウェブサーバーにアクセスできました。ちなみにここまでの手順はWindowsでもPuTTYなどのSSHクライアントやWSL上のsshコマンドでも実現できるはずです。
たとえば組織内LANからのみアクセス可能な領域のgitリポジトリをcloneしたい場合は、次のように実行できます。
$ git -c http.proxy=socks5://localhost:1080 clone https://foo.example.co.jp/git/repo.git
設定が問題なさそうなら、「 ~/.ssh/config
」に書いてしまうと、再利用しやすくなります。
Host socks
HostName 10.93.9.120
User ubuntu
DynamicForward 1080
Compression yes
ExitOnForwardFailure yes
RequestTTY no
あとは「ssh -f socks
」で、さきほどのコマンドと同じ状態になります。
ウェブブラウザー側の設定
次にウェブブラウザーから、特定のアドレスにアクセスした際に指定したSOCKSサーバーを経由するようにします。まずは次のような内容の「socks.pac」ファイルを作成します。ファイル名はなんでもかまいません。
function FindProxyForURL(url, host) {
if (isInNet(host, "10.93.9.0", "255.255.255.0")) {
return "SOCKS5 localhost:1080";
}
return "DIRECT";
}
これは「Proxy Auto Configuration(PAC) 」と呼ばれるプロキシの自動構成スクリプトです。JavaScriptの関数としてロジックも書けるので、特定のURLなら特定のSOCKSサーバーに繋ぐといった判定も可能です。
今回はドメイン名の指定がないため、IPアドレスでマッチングを行います。isInNet()
は指定したhostが、第二引数のアドレスパターンと第三引数のネットワークマスクで指定したネットワークに存在する場合に真になります。第二引数はドメイン名で指定した場合、IPアドレスに変換した上で第三引数とかけ合わせます。
真になった場合はプロキシとして「SOCKS5 localhost:1080
」を使い、そうでない場合は「DIRECT
」つまりはプロキシを使わないという寸法です。
PACファイルの指定方法はウェブブラウザーごとで異なります。
まずFirefoxの場合は、設定の「一般」もしくは「about:preference#general 」からアクセスしたページの「接続設定」からネットワークの設定画面を開きます。
図1 Firefoxのインターネット接続設定
「自動プロキシ構成スクリプト」に先程作ったPACファイルのパスを指定してください。ちなみにURLとしてネットワークの先のファイルを指定することも可能です。
あとは普通に「http://10.93.9.120/」にアクセスすれば、本来クライアントから直接見れないウェブページが表示されるはずです。
Chrome/Chromiumに関しては、プロキシ関連の設定が削除されてしまったため、そのままではPACファイルを利用できません。たとえばSwitchyOmega などの拡張機能を入れる必要があります[3] 。
より一般的には特定の組織内LANには特定のドメイン名が付けられていることでしょう。そうすると次のような設定が考えられます。
function FindProxyForURL(url, host) {
if (dnsDomainIs(host, ".example.co.jp") && !isResolvable(host)) {
return "SOCKS5 localhost:1080";
}
return "DIRECT";
}
dnsDomainIs()
を使ってアクセスしようとしているサイトのホスト名が「.example.co.jp」にマッチしているかを確認します。さらにisResolvable()
でそのホスト名が名前解決できるかをチェックしています。「 名前解決できない=インターネットに公開されていないサイト」なのでSOCKSプロキシを経由させます。ちなみにFirefoxの場合、名前解決自体をSOCKS経由で行えます[4] 。つまりisResolvable()
がFALSEの状態でSOCKSに経由すれば名前解決が行えるようになるはずだ、ということです。
「Proxy Auto Configuration(PAC) 」には他にも「平日の特定の時間帯のみSOCKSを利用する」とか「ホスト名ではなくURLに特定の文字列が含まれていた場合のみSOCKSを利用する」などの設定が可能になる各種ユーテリティー関数が列挙されています。より細かく調整したい場合には参考になるでしょう。
ちなみに今回紹介した方法を使うと、本来は組織内にのみ公開が限定されていたウェブリソースに、公衆回線から簡単にアクセスできるようになります。仕事で使って良いかどうかについては、必ずあらかじめ組織内のセキュリティ担当部門に問い合わせましょう。問い合わせる際の質問の仕方が悪いと「ダメ」と言わざるを得なくなるので、よくよく考えてから問い合わせてください。