😸

SSHを使ってトンネリングしてみた

に公開

はじめに

VPNのIPからのみ実行許可しているAPIがあり、localでそのAPIを実行する方法を考えていたところSSH動的ポートフォワーディングによってSOCKSプロキシを起動しAPIを実行することができたので備忘録として残しておきます。

そもそもトンネリングとは

ネットワーク通信において、あるプロトコルのデータを別のプロトコルでカプセル化して転送する技術です。これにより、ネットワーク制限の回避、サポートされていないプロトコルの使用、セキュアな通信路の確保などが可能になります。
https://www.cloudflare.com/ja-jp/learning/network-layer/what-is-tunneling/

cloudflareにも記述はありますが、例えば受信するサーバーはIPv6しか対応していないけれど送信するサーバーはIPv4しか対応していない。その場合にトンネリングを使うことによってIPv4とIPv6で通信が可能になるとのことです。

下記はカプセル化のイメージです。
これは通常のパケット

┌──────────┬────────────┐
│ ヘッダ-   │ ペイロード   │
└──────────┴────────────┘

カプセル化されたパケットはこんなイメージ

┌──────────────┬─────────────────────────────────┐
│新しいヘッダー │     新しいペイロード              │
│              ├──────────┬────────────┤        │
│              │元ヘッダー│元ペイロード │        │
└──────────────┴──────────┴────────────┴────────┘

(右端揃わなかった・・・)

今回のケースでは、VPN制限されたAPIに対して、HTTPリクエストをSOCKS+SSHでカプセル化することで制限を回避しました。

SSH動的ポートフォワーディングとSOCKSプロキシとは

まず、どのようなコマンドでSSHを使ってトンネリングを行ったかを記載します

$ ssh -D 1080 -N -f -i "sha256.pem" ec2-user@ec2-xx-xx-xx-xx.your-reagion.compute.amazonaws.com

このコマンドではSSH動的ポートフォワーディングSOCKSプロキシという技術が使用されています

  • SSH動的ポートフォワーディング: SOCKSプロキシサーバーを作成する手段
  • SOCKSプロキシ: リクエストを中継・解釈する仕組み
  • SSH: 実際のトンネリング(暗号化・転送)を担当

つまり、SSH動的ポートフォワーディングによってSOCKSプロキシが作成され、
そのプロキシがSSHトンネルを使って通信を中継します。

SSH動的ポートフォワーディング

SSHの機能の一つで、-Dオプションを使ってローカルマシン上にSOCKSプロキシサーバーを作成する仕組みです。
https://man7.org/linux/man-pages/man1/ssh.1.html

具体的には下記のことが起こります

  • SSHクライアントがローカルの1080番ポートでSOCKSプロキシサーバーを起動
  • このプロキシに送られたリクエストを、SSHトンネル経由でリモートサーバー(EC2インスタンス)に転送
  • リモートサーバーが実際の通信を代行

SOCKSプロキシ

SOCKS(SOCKet Secure)は、クライアントとサーバー間でネットワークパケットを中継するプロキシプロトコルです。
今回のケースでは、HTTPリクエストを受け取って「どこに送るか」を解釈し、その指示をSSHトンネル経由でリモートサーバーに転送します。実際のトンネリング(暗号化・カプセル化)はSSHが行います。

もっと端的にいうと、そのAPIに対してリクエストを送るとSOCKSがプロキシとなってSSHで通信を行ってくれます。この役割を担っているのがSOCKSプロキシです。

実際のコード

nodeで実行する前提で記載します

$ npm install node-fetch socks-proxy-agent
$ ssh -D 1080 -N -f -i "sha256.pem" ec2-user@ec2-xx-xx-xx-xx.your-reagion.compute.amazonaws.com
import fetch from 'node-fetch'
import { SocksProxyAgent } from 'socks-proxy-agent'

const agent = new SocksProxyAgent('socks5://127.0.0.1:1080')
fetch('https://example-api.com/some/api-route', {
    agent,
  })
    .then((res) => res.json())
    .then((data) => {
      console.log(data)
    })

おわりに

AIを使えばいくらでも手段は出てきますが、使用して終わりにするのかはたまたその手段を深ぼって自分の知識にするのかで今後のエンジニアとしての賞味期限も違ってくるのかなーなんて思っています。
時間が許す限りは後者でいたいので今後も自己研鑽に励もうと思います。

参照

https://www.cloudflare.com/ja-jp/learning/network-layer/what-is-tunneling/

https://man7.org/linux/man-pages/man1/ssh.1.html

Discussion