SSHを駆使して数々の試練（プロキシ、踏み台）を乗り越える話

# はじめに
この記事は[富士通クラウドテクノロジーズ Advent Calendar 2021](https://qiita.com/advent-calendar/2021/fjct)の24日目の記事です。

こんにちは新入社員の @u-koji です。
昨日の記事は @s_ave さんによる「[Apache BenchとVegetaを使ってLiteSpeed／Nginx／Apacheを性能比較](https://qiita.com/s_ave/items/e2d5d0a740c01d92ff05)」でした。
スループットや負荷への耐性がWebサーバによって有意に異なるということを知って大変おどろきました。
使い方によって最適なWebサーバを選択できるよう、ぜひ参考にさせていただきたいです。

## この記事の目標
以下の状況を想定します。

* 組織のWAN
    * 外部のサーバにアクセスでは必ずプロキシを経由する
* クラウド上に構築したシステム
    * クラウド上のVMへのsshには踏み台サーバを経由する
   
この記事では組織のWANに接続されたローカルPCから、sshを駆使してクラウド上の各種サーバにアクセスする方法を紹介します。
なおプロキシ、踏み台の設定によっては紹介した機能が制限されていることもありますのでご注意ください。
![プロキシ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/592663/83dea261-3380-e944-08ce-3850ec8ada5c.png)


# 踏み台サーバにログインする
## Linux/Macの場合
MacOSや多くのLinuxディストリビューションでは標準でOpenSSHクライアントがインストールされています。
OpenSSHはそれ自身でプロキシ経由でSSH接続することはできませんが、ProxyCommandオプションで外部のコマンドを呼び出すことでプロキシ経由でSSH接続が可能です。
connect-proxyコマンドとncコマンドを使って、HTTPプロキシ、SOCKS5プロキシ経由で踏み台にSSHする方法を以下に記します。
（コマンドはOS推奨のパッケージなどを利用してインストールしてください。）

```bash
# connect-proxyを使う場合
## http
ssh -o "ProxyCommand connect-proxy -H http_user@http_proxy.example.com:3128 %h %p" bation_user@bation.example.com
## socks5
ssh -o "ProxyCommand connect-proxy -S socks_user@socks_proxy.example.com:1080 %h %p" bation_user@bation.example.com
# ncを使う場合
## http
ssh -o "ProxyCommand nc -X connect -P http_user -x http_proxy.example.com:3128 %h %p" bation_user@bation.example.com
```
*ncコマンドを用いてSOCKS5経由でコネクションを張るには`-X 5`オプション（`-X`でプロトコルを選択しない場合はデフォルトでSOCKS5）を指定するとマニュアルには記載されていますが、検証環境では以下のエラーのため接続できませんでした；（求情報）

```bash
nc: authentication method negotiation failed
```

`$HOME/.ssh/config`に以下の設定を書き込むことで、コマンドをオプションを省略することができます。

```bash
Host Bation
    HostName bation.example.com
    User bation_user
    Port 22
    ProxyCommand connect-proxy -H http_user@http_proxy.example.com:3128 %h %p
    # 外部コマンドとオプションは任意のものを記載
    # 公開鍵を利用したい場合はよしなにIdentityfileを記載
```
この例では、`ssh Bation`とコマンドを入力することで踏み台サーバに接続することができます。

## Windowsの場合
WindowsでProxyを経由してSSHを行いたい場合、主に以下の3つの方法があると思います。
### ターミナルエミュレータを利用する
Tera TermやRloginなどのターミナルエミュレータでは、オプションを設定することでプロキシ経由で外部サーバにSSHすることができます。（説明は省きます）
### WSLを利用する
WSLの任意のディストリビューションを利用して、Linux/Macの場合と同様にSSHすることができます。
### Git Bash + OpenSSH
Git Bash同梱のMingw-w64にはWindowsで使えるconnectのバイナリが配布されており、これを利用してcmdからOpneSSHコマンドを利用してProxy経由でSSH接続ができます。
Git Bash（[Git for Windows](https://gitforwindows.org/)）をインストールして`%homepath%\.ssh\config`に以下の設定をしてください。

```config
Host Bation
    HostName bation.example.com
    User bation_user
    Port 22
    ProxyCommand C:\Program Files\Git\mingw64\bin\connect.exe -H http_user@http_proxy.example.com:3128 %h %p
    # connect.exeのパスはx86の場合とAMD64の場合で異なるため事前に確認してください
    # SOCKS Proxyの場合は `-S socks_user@socks_proxy.example.com:1080`
```
コマンドプロンプトやPowerShellで`ssh Bation`とすることで、踏み台サーバにログインできます。
*ちなみにMingw-w64単体にはconnect.exeは含まれておらず、Git Bashをインストールする必要がありました。

# 開発サーバにSSH
踏み台サーバにSSHした後に、踏み台サーバで再度sshコマンドを叩くことで開発サーバにログインすることができます。
-J（ProxyJump）オプションを利用すると、sshコマンドを2回実行する必要がなく、一発で踏み台サーバを経由して開発サーバにsshすることができます。
先程設定した`.ssh/config`を利用すると、以下のコマンドでログインできます。

```bash
ssh remote_user@ -J Bation
```

# DBへのポートフォワーディング
SSHにはポートフォワーディング機能が備わっており、これを活用することでローカルからDBにログインすることが可能です。
以下の例では`.ssh/config`に記載したBationを経由し、ローカルの3306ポートをDBサーバの3306ポートにフォワーディングしています。

```
ssh -L 3306:192.0.2.3:3306 Bation
```
このコマンドを実行するとプロンプトは踏み台サーバにログインした状態になります。
プロンプトのログインを保持したまま、CLIやGUIのDBクライアントでlocalhost(127.0.0.1):3306に接続することで、DBサーバにログインできます。

```bash
mysql -h 127.0.0.1 -uuser -p
```
*小ネタですが、mysqlでlocalhostを指定した場合はデフォルトでUNIXソケットを利用するため、127.0.0.1としています。

# SOCKSを利用したWebサーバへのアクセス
DBサーバにアクセスしたときと同様に、Webサーバの80番ポートをローカルの8080番ポートなどにフォワーディングすることで、ローカルからWebサーバにアクセスすることができます。
しかし複数のWebサーバが立ち上がっている場合などはポートの割当が大変になってきます。
sshの`-D`オプションを利用することで、sshをSOCKS5プロキシとして動作させることができ、プライベートLAN上のサーバへのアクセスが楽になります。

```bash
ssh -D 1080 Bation
```
ポートフォワーディングの時と同様にプロンプトは踏み台サーバにログインした状態になります。
その間はlocalhost:1080が踏み台へのSOCKS5プロキシとして動作します。

```bash
curl --proxy socks5://localhost:1080 192.0.2.4
```
またプロキシの設定をすることで、WebブラウザからもプライベートLAN上に構築したWebコンテンツの閲覧が可能になります。

# 終わりに
今回はSSHを活用して、幾多の試練（プロキシや踏み台）を乗り越え幸せな開発環境を構築する方法をご紹介しました。
みなさまの素敵なSSHライフに貢献できれば幸いです。
ここまで続いてきたアドベントカレンダーもついに明日で最後となります！
明日は @ConHumi さんの「FJCT エンジニアタスクフォース 2021 レポート」です。
それではみなさん、よいクリスマスを！

## おまけ
今回検証環境はDockerを利用して構築しました。
おそらくいないと思いますが、必要な人がいましたらご利用ください。

<details><summary>ソース</summary><div>

```yaml
version: '3'
services:
  socks5:
    image: serjs/go-socks5-proxy
    ports:
      - "1080:1080"
    environment:
      - PROXY_USER=socks_user
      - PROXY_PASSWORD=socks_password
    networks:
      - net1
  http-proxy:
    image: babim/squid
    ports:
      - "3128:3128"
    environment:
      - AUTH=true
      - USERNAME=http_user
      - PASSWORD=http_password
    networks:
      - net1
  bation_sshd:
    build:
      context: ./sshd
      dockerfile: ./Dockerfile
      args:
        - USER=bation_user
        - PASSWORD=bation_password
    networks:
      - net1
      - net2
  remote_sshd:
    build:
      context: ./sshd
      dockerfile: ./Dockerfile
      args:
        - USER=remote_user
        - PASSWORD=remote_password
    networks:
      - net2
  web:
    image: nginx
    networks:
      - net2
  db:
    image: mariadb
    environment:
      MARIADB_ROOT_PASSWORD: root_password
      MARIADB_USER: user
      MARIADB_PASSWORD: user_password
    networks:
      - net2
networks:
  net1:
  net2:
```
sshdのDokcerfileは[Dockerのドキュメント](https://docs.docker.jp/engine/examples/running_ssh_service.html)とほぼ同じです。

```dockerfile
FROM ubuntu:14.04
ARG USER
ARG PASSWORD
RUN apt-get update && apt-get install -y openssh-server
RUN mkdir /var/run/sshd
RUN useradd $USER
RUN echo ${USER}:${PASSWORD} | chpasswd
RUN sed -i 's/PermitRootLogin without-password/PermitRootLogin yes/' /etc/ssh/sshd_config

# SSH login fix. Otherwise user is kicked off after login
RUN sed 's@session\s*required\s*pam_loginuid.so@session optional pam_loginuid.so@g' -i /etc/pam.d/sshd

ENV NOTVISIBLE "in users profile"
RUN echo "export VISIBLE=now" >> /etc/profile

EXPOSE 22
CMD ["/usr/sbin/sshd", "-D"]
```
</div></details>