OpenSSHと踏み台運用

OpenSSHは好きですか？
我々エンジニアにとって切り離せないssh。
"ssh 踏み台"というと`ProxyCommand`設定を書いて踏み台でsshコマンドを実行させるqiita記事が多い中、システム管理者がDMZなど特別なIPセグメントに置くサーバーにアクセスするためにネットワーク経路を迂回してアクセスことに限定した `LocalForwarding` による運用について書きます。
リモートホストに対して認証可能な状態(秘密鍵とかがあること)が前提となっております。ご了承ください。

---

私が使用しているMac OSのOpenSSHのバージョンはこちらです。これをベースに話をします。

```console:version
% ssh -V
OpenSSH_7.6p1, LibreSSL 2.6.2
```

## forwardingの基本操作

```console:port_forwarding
% ssh -L 10022:リモートホスト:22 fumidai
```

ssh コマンドの `-L` オプションを使えばローカルportをforwardingできます。
目的のリモートホストへは、ローカルportを通じて接続できるようになります。

sshのman pageには、このようにも書かれております。

```console:man_ssh
     -L [bind_address:]port:host:hostport
     -L [bind_address:]port:remote_socket
     -L local_socket:host:hostport
     -L local_socket:remote_socket
             Specifies that connections to the given TCP port or Unix socket on the local (client) host are to be
             forwarded to the given host and port, or Unix socket, on the remote side.  This works by allocating a
             socket to listen to either a TCP port on the local side, optionally bound to the specified bind_address,
             or to a Unix socket.  Whenever a connection is made to the local port or socket, the connection is for-
             warded over the secure channel, and a connection is made to either host port hostport, or the Unix
             socket remote_socket, from the remote machine.

             Port forwardings can also be specified in the configuration file.  Only the superuser can forward privi-
             leged ports.  IPv6 addresses can be specified by enclosing the address in square brackets.

             By default, the local port is bound in accordance with the GatewayPorts setting.  However, an explicit
             bind_address may be used to bind the connection to a specific address.  The bind_address of
             ``localhost'' indicates that the listening port be bound for local use only, while an empty address or
             `*' indicates that the port should be available from all interfaces.
```

local socketはinet socketだけでなく。unix domain socketへも同時にbindできます。

## INETとUNIX domain socketのforwarding

```console:ssh_forward
% ssh -fN \
-L /tmp/d192.168.x.a-22.sock:192.168.x.a:22 \
-L /tmp/d192.168.x.b-22.sock:192.168.x.b:22 \
-L 10001:192.168.x.a:22 \
-L 10002:192.168.x.b:22 \
fumidai
```

このように1つの踏み台接続の中でINET、UNIX socketの両刀が可能です。
またforwarding先も複数指定できます。

しかし、いちいち `-L` を書くのも面倒なので、ssh_configに必要なforwardを記述します。

```config:~/.ssh/config
Host fumidai
User username
Hostname 192.168.x.x
LocalForward 10001 192.168.x.A:22
LocalForward 10002 192.168.x.B:22
LocalForward /tmp/d192.168.x.C-3306.sock 192.168.x.C:3306

```

踏み台への接続を維持した状態でtargetホストに接続する例です。

```console:ssh
% ssh -fN fumidai

% ssh -p 10001 -l root localhost "uname -ar"
Linux x.x.x.x 2.6.18-308.11.1.el5 #1 SMP Tue Jul 10 08:48:43 EDT 2012 x86_64 x86_64 x86_64 GNU/Linux

% cat /tmp/.my.cnf
[client]
user=ユーザー
password=パスワード
socket=/tmp/d192.168.x.C-3306.sock

% mysql --defaults-file=/tmp/.my.cnf << a
select current_timestamp;
a
setsockopt TCP_NODELAY: Operation not supported on socket
current_timestamp
2018-12-18 19:21:09
```

踏み台を利用して、リモートホストに接続できました。

> `setsockopt TCP_NODELAY: Operation not supported on socket` というwarningが出力されますが、割愛します。無視してください。
> ここはあまりエレガントではないですね

## forwardingの応用

リモートホストのMySQLが 127.0.0.1 からしか接続を許可していないケースを考えてみます。

たとえば

```
% mysql --defaults-file=~/.my.cnf
setsockopt TCP_NODELAY: Operation not supported on socket
ERROR 1045 (28000): Access denied for user 'admin'@'192.168.x.x' (using password: NO)
```

リモートホスト上で自分自身(localhost)に接続した場合のみ許可されているケースです。多段でforwardingします。

forward1 
`自分PC:tcp/10001 -> 踏み台:tcp/22 -> リモートホスト:tcp/22`
forward2
`自分PC:3306.sock -> forward1 -> リモートホスト:tcp/3306`

ssh_configにDB接続の設定を追加します

```config:~/.ssh/config
Host fumidai
User username
Hostname 192.168.x.x
LocalForward 10001 192.168.x.A:22

Host db
User username
Hostname localhost
Port 10001
LocalForward /tmp/d192.168.x.A-3306.sock 127.0.0.1:3306
```

`forward1` = `リモートホスト:tcp/22` へのセッションに対して接続することで、リモートホストから見た時に、あたかもlocalhostから接続されたかのように接続されます。

### 実行例

```console:fording
% cat /tmp/.my.cnf
[client]
user=admin
password=パスワード
socket=/tmp/d192.168.x.A-3306.sock

% ssh -fN fumidai
% ssh -fN db

% mysql --defaults-file=/tmp/.my.cnf << a
select current_timestamp;
a
setsockopt TCP_NODELAY: Operation not supported on socket
current_timestamp
2018-12-18 19:25:12
```

多段forwardingにすることで、接続できました。

## 最後に注意事項
言うまでもないことですが、closeを忘れずに。

```console:kill
% ps axww | grep ssh\ -fN | grep -v grep | awk '{print $1}' | xargs kill
```