CentOSでautosshをsystemd serviceとして動かす

yumでインストールするとsystemd設定ファイルも配置されるので楽。


# （そもそも）SSH Port転送について

SSHの接続元ホスト、接続先ホストの間で通信を転送する一般的な技術です。

例えば、以下の環境を考えます。
検証ネットワーク内の機器にはインターネットから接続できず、一度踏み台にSSHでログインする必要があるとします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547524/3c4e3b65-f524-bc1f-ef8f-12d5252741c4.png)
ここで、操作端末から検証ネットワーク内のWebサーバにHTTPでアクセスしたい、となったらSSH Port転送が利用できます。

```bash:SSH
ssh -L 127.0.0.1:10080:<Webサーバのアドレス>:80 <踏み台サーバのアドレス>
```
操作端末から踏み台へのSSHログイン時に`-L`オプションを追加します。
すると、SSHが繋がっている間は、操作端末の`127.0.0.1:10080`への通信が（SSH接続先から見て）`<Webサーバのアドレス>:80` へ転送されます。

この時、Webブラウザで`http://127.0.0.1:10080`と入力すれば、Webサーバにアクセスすることができます。

また、通信の転送はSSHのチャンネルを通して行われるため、インターネット上では通信が暗号化されます。

# autosshとは

このようにSSH Port転送は便利なため、恒久的に利用したいことがあります。

そんなときに便利なのがautosshです。

```bash:AUTOSSH(1)より抜粋
     autossh is a program to start a copy of ssh and monitor it, restarting it as necessary should it die or
     stop passing traffic.
```
autosshはsshをモニターし、問題があればsshをリスタートします。
以下のように起動します。

```bash:autossh
AUTOSSH_GATETIME=0 autossh -M 0 -N -L <転送オプション> <SSH接続先>
```
ここで`-M`は監視用ポートの設定ですが、利用しないため`0`とします。
`-N`以降はSSHのオプションです。`-N`はSSH接続後にコマンドを実行しないオプションで、これが無いとシェルを起動しようとして失敗します。

それ以降は通常のSSH Port転送と同様です。

環境変数`AUTOSSH_GATETIME`は0にしないとSSH切断時にautosshが終了するようです。

autossh実行中は、SSHが切断されると再接続を試みます。

```journal
Sep 02 14:24:14 localhost.localdomain autossh[9968]: Connection to 192.168.122.61 closed by remote host.
Sep 02 14:24:14 localhost.localdomain autossh[9968]: ssh exited with error status 255; restarting ssh
Sep 02 14:24:14 localhost.localdomain autossh[9968]: starting ssh (count 2)
Sep 02 14:24:14 localhost.localdomain autossh[9968]: ssh child pid is 10189
Sep 02 14:24:14 localhost.localdomain autossh[9968]: ssh: connect to host 192.168.122.61 port 22: Connection refused
Sep 02 14:24:14 localhost.localdomain autossh[9968]: ssh exited with error status 255; restarting ssh
Sep 02 14:24:14 localhost.localdomain autossh[9968]: starting ssh (count 3)
Sep 02 14:24:14 localhost.localdomain autossh[9968]: ssh child pid is 10190
Sep 02 14:24:14 localhost.localdomain autossh[9968]: ssh: connect to host 192.168.122.61 port 22: Connection refused
Sep 02 14:24:14 localhost.localdomain autossh[9968]: ssh exited with error status 255; restarting ssh
Sep 02 14:24:14 localhost.localdomain autossh[9968]: starting ssh (count 4)
Sep 02 14:24:14 localhost.localdomain autossh[9968]: ssh child pid is 10191
Sep 02 14:24:14 localhost.localdomain autossh[9968]: ssh: connect to host 192.168.122.61 port 22: Connection refused
Sep 02 14:24:14 localhost.localdomain autossh[9968]: ssh exited with error status 255; restarting ssh
Sep 02 14:24:14 localhost.localdomain autossh[9968]: starting ssh (count 5)
Sep 02 14:24:14 localhost.localdomain autossh[9968]: ssh child pid is 10192
Sep 02 14:24:14 localhost.localdomain autossh[9968]: ssh: connect to host 192.168.122.61 port 22: Connection refused
Sep 02 14:24:14 localhost.localdomain autossh[9968]: ssh exited with error status 255; restarting ssh
Sep 02 14:24:14 localhost.localdomain autossh[9968]: starting ssh (count 6)
Sep 02 14:24:14 localhost.localdomain autossh[9968]: ssh child pid is 10193
Sep 02 14:24:14 localhost.localdomain autossh[9968]: ssh: connect to host 192.168.122.61 port 22: Connection refused
Sep 02 14:24:14 localhost.localdomain autossh[9968]: ssh exited with error status 255; restarting ssh
Sep 02 14:24:14 localhost.localdomain autossh[9968]: starting ssh (count 7)
Sep 02 14:24:14 localhost.localdomain autossh[9968]: ssh child pid is 10194
Sep 02 14:24:14 localhost.localdomain autossh[9968]: ssh: connect to host 192.168.122.61 port 22: Connection refused
Sep 02 14:24:14 localhost.localdomain autossh[9968]: ssh exited with error status 255; restarting ssh
Sep 02 14:24:16 localhost.localdomain autossh[9968]: starting ssh (count 8)
Sep 02 14:24:16 localhost.localdomain autossh[9968]: ssh child pid is 10195
Sep 02 14:24:26 localhost.localdomain autossh[9968]: ssh: connect to host 192.168.122.61 port 22: No route to host
Sep 02 14:24:26 localhost.localdomain autossh[9968]: ssh exited with error status 255; restarting ssh
Sep 02 14:24:34 localhost.localdomain autossh[9968]: starting ssh (count 9)
Sep 02 14:24:34 localhost.localdomain autossh[9968]: ssh child pid is 10196
```

# インストールしてsystemdで動かす

今回はCentOS7にインストールしてみます。
以下環境

- CentOS 7.8.2003
- autossh 1.4g


epelリポジトリを有効にしてインストールします。

```bash
sudo yum install epel-release
sudo yum install autossh
```
ここでsystemd設定ファイルが追加されます。

```bash
$ rpm -ql autossh
/etc/autossh
/usr/bin/autossh
/usr/lib/systemd/system/autossh@.service # <- これ
/usr/share/doc/autossh-1.4g
/usr/share/doc/autossh-1.4g/CHANGES
/usr/share/doc/autossh-1.4g/README
/usr/share/doc/autossh-1.4g/README.service
/usr/share/doc/autossh-1.4g/examples
/usr/share/doc/autossh-1.4g/examples/autossh.host
/usr/share/doc/autossh-1.4g/examples/rscreen
/usr/share/man/man1/autossh.1.gz

$ cat /usr/lib/systemd/system/autossh@.service
[Unit]
Description=autossh: %I
Documentation=file:///usr/share/doc/autossh/README.service man:autossh(1)
After=network.target

[Service]
User=autossh
Environment="AUTOSSH_GATETIME=0"
EnvironmentFile=/etc/autossh/%i.conf
ExecStart=/usr/bin/autossh $OPTIONS

[Install]
WantedBy=multi-user.target
```
`/usr/share/doc/autossh-1.4g/README.service`に説明があるので、そのとおりに設定ファイルを作ってみます。

```bash:/etc/autossh/web.conf
OPTIONS=-M 0 -N -L 127.0.0.1:10080:192.168.0.2:80 -i /etc/autossh/keys/testing.rsa -l testing 192.168.122.61
```
`OPTIONS`変数に指定したものがautosshに渡され実行されます。

systemdから起動する場合、autosshはユーザ`autossh`により実行されます。
そのため今回はSSHの`-l`オプションでログイン用のユーザを、`-i`オプションで認証用の秘密鍵を明示的に指定しています。

秘密鍵はautosshユーザがアクセスできるどこかに置いておきます。

```bash
# このマシン上のtestingユーザが、接続先に公開鍵認証でログインできる場合。
sudo -u autossh mkdir -m 700 /etc/autossh/keys
sudo cp /home/testing/.ssh/id_rsa /etc/autossh/keys/testing.rsa
sudo chown autossh:autossh /etc/autossh/keys/testing.rsa
```
SSHの初回ログイン時は接続先の確認が必要なため、一度SSHコマンドでログインします。

```bash:
$ sudo -u autossh ssh -i /etc/autossh/keys/testing.rsa testing@192.168.122.61
The authenticity of host '192.168.122.61 (192.168.122.61)' can't be established.
ECDSA key fingerprint is SHA256:gYYyvZ9++CwtcU3zlpfEJHgo509G7KaqSDpaqBDte8o.
ECDSA key fingerprint is MD5:de:c9:1c:80:0d:60:8c:33:9f:c8:22:48:8f:dc:6b:b1.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '192.168.122.61' (ECDSA) to the list of known hosts.
Last login: Wed Sep  2 12:18:35 2020 from 192.168.122.1
[testing@localhost ~]$
```

ここまでで準備は完了です。

サービスを起動します。
サービス名は`autossh@<設定ファイルの.conf前>` になります。

```bash
sudo systemctl start autossh@web
```
他のサービスと同様にステータスの確認や、リスタート・停止ができます。
マシンの起動時に開始するように設定することもできます。

```bash
# ステータス確認
$ sudo systemctl status autossh@web
● autossh@web.service - autossh: web
   Loaded: loaded (/usr/lib/systemd/system/autossh@.service; disabled; vendor preset: disabled)
   Active: active (running) since Wed 2020-09-02 13:58:32 EDT; 6s ago
     Docs: file:///usr/share/doc/autossh/README.service
           man:autossh(1)
 Main PID: 9954 (autossh)
   CGroup: /system.slice/system-autossh.slice/autossh@web.service
           ├─9954 /usr/bin/autossh -M 0 -N -L 127.0.0.1:10080:192.168.1.2:80 -i /etc/autossh/keys/testing.rsa t...
           └─9955 /usr/bin/ssh -N -L 127.0.0.1:10080:192.168.1.2:80 -i /etc/autossh/keys/testing.rsa testing@19...

Sep 02 13:58:32 localhost.localdomain systemd[1]: Started autossh: web.
Sep 02 13:58:32 localhost.localdomain autossh[9954]: port set to 0, monitoring disabled
Sep 02 13:58:32 localhost.localdomain autossh[9954]: starting ssh (count 1)
Sep 02 13:58:32 localhost.localdomain autossh[9954]: ssh child pid is 9955

# リスタート
$ sudo systemctl restart autossh@web

# マシン起動時に開始
$ sudo systemctl enable autossh@web
```

# 使用例

いくつか利用できそうなケースを示します。

## web
Nginx等でTLS化するのも良いですが、用途によってはそこまでしなくても良いことがあります。

冒頭の例とはちょっと変わって、
Webサーバ（`192.168.122.61`）がファイアウォールで他ホストからのHTTPアクセスを許可していない場合を考えます。
このサーバは管理用に操作端末からSSH接続が可能だとします。

```bash
[testing@localhost ~]$ curl 192.168.122.61:80
curl: (7) Failed connect to 192.168.122.61:80; No route to host
# 接続できない。

[testing@localhost ~]$ ssh 192.168.122.61
Last login: Thu Sep  3 03:48:08 2020 from 192.168.122.1
# SSHは接続できる。
 
[testing@web ~]$ curl -i 127.0.0.1:80
HTTP/1.0 200 OK
Server: SimpleHTTP/0.6 Python/2.7.5
Date: Thu, 03 Sep 2020 07:59:31 GMT
Content-type: text/html; charset=UTF-8
Content-Length: 414

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"><html>
<title>Directory listing for /</title>
<body>
<h2>Directory listing for /</h2>
# 以下略。ローカルホストからはアクセス可。
```

ポート転送して操作端末からHTTP接続できるようにしてみます。
autosshの設定は以下です。

```bash:/etc/autossh/web.conf
OPTIONS=-M 0 -N -L 127.0.0.1:10080:127.0.0.1:80 -i /etc/autossh/keys/testing.rsa testing@192.168.122.61
```
使ってみます。

```bash
# 起動する。
[testing@localhost ~]$ sudo systemctl start autossh@web

[testing@localhost ~]$ curl 127.0.0.1:10080
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"><html>
<title>Directory listing for /</title>
<body>
<h2>Directory listing for /</h2>
# 以下略。
```

## VNC

LinuxのGUIをネットワーク越しに確認できるやつです。
ハイパーバイザから仮想マシンのディスプレイにアクセスする場合にも利用されることがあります。

今回は、

- ローカルからのVNCを受け付けるサーバに、
- WebサーバからSSHでログインし、
- WebサーバでnoVNCをホストして、
- ブラウザで操作ができるようにしてみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547524/4310d5c0-6190-fc20-116e-60d096973419.png)

autosshの設定ファイルです。
`192.168.122.1`の`127.0.0.1:5903`でVNCが待ち受けているとします。

```bash:/etc/autossh/vnc.conf
OPTIONS=-M 0 -N -L 127.0.0.1:15903:127.0.0.1:5903 -l testing -i /etc/autossh/keys/testing.rsa 192.168.122.1
```
ローカルホストの15903にポートを転送しました。
noVNCで利用するため、WebsockifyでこのポートをWebsocketに変換します。

```bash:websocketify
# これで起動しておく。
websockify 25903 localhost:15903
```
25903でWebSocketを受け付けます。
今回はNginxでnoVNCをホストしてみます。
(参考 https://github.com/novnc/noVNC/wiki/Proxying-with-nginx)

```bash:/etc/nginx/conf.d/novnc.conf
server {
    listen 80;
    server_name 192.168.122.80;
    location /websockify {
        proxy_pass http://127.0.0.1:25903/;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
    location / {
        index index.html;
        alias /usr/share/novnc;
    }
}
```
サービスを起動して、アクセスするとこんな感じです。
（Webページを開いて、パラメータを入力してConnectした後。）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547524/b3392eb9-c2d9-f643-1d49-8eb606ac0ba3.png)

ここで、Nginxをsystemdで起動するなら依存関係の設定ができます。

```bash
$ sudo systemctl edit nginx
# 以下を記載。
[Unit]
Wants=autossh@vnc.service
```

このようにしておくと、Nginxを起動する際にautosshも起動されるようになります。
扱いやすくて良いのではないでしょうか。

# まとめ

便利は便利だと思う。

ちなみにUbuntuだとsystemd設定ファイルは用意されていないので、自分で書く必要があるみたいです。