Docker とWeb Proxy を使ったGate One を構築して、サーバにWeb ブラウザからSSH 接続する

# 実施内容の概要 
Web ブラウザからサーバにSSH 接続できるGate One を、Docker を使って構築してみます。
今回は実践することに重きを置き、Docker, Web サーバ(Apache), Gate One の基本的な機能のこと等については特に説明はしませんのでご了承ください。
主にGate One の起動と接続、Web サーバから内部のDocker へプロキシ(SSL プロキシあり/なし)及びプロキシ時の認証(SSH 接続時の認証も含めると2 段階認証)を設ける設定について実際に構築した時の手順を備忘録として記しておきます。

- Git Hub liftoff/GateOne
    - https://github.com/liftoff/GateOne

# 要件 
今回Gate One をインストールする環境としては次の通りです。


* 構成概要
<table><tbody>
  <tr>
    <th> OS</th>
    <td> Fedora 23</td>
  </tr>
  <tr>
    <th> Docker</th>
    <td> docker-1.9.1-6.git6ec29ef.fc23.x86_64</td>
  </tr>
  <tr>
    <th> Docker ホストのIP アドレス</th>
    <td> 192.168.1.112</td>
  </tr>
</tbody></table>

# gateone docker コンテナを起動する 
Gate One には既に`liftoff/gateone` イメージが用意されているので、を使ってGate One の簡単な使用感を確認しておきます。


```text:dockerrun
# docker run -t --name=gateone --add-host localhost:192.168.1.112 -p 8000:8000 liftoff/gateone
```


以上でgateone コンテナの起動は完了です。
動作確認するために指定したホストに対して`https` 接続をしてみましょう。


```text:適当なブラウザを開いて以下のurlにアクセス
https://192.168.112:8000/
# iptables は適宜解放しておいてください
```

デフォルトでGate One は自己署名された証明書を使用するので検証エラーが出るはずですが、それを無視して接続してください。
ブラウザからSSH 接続できることが確認できるはずです。

以降では、Apache を使用してweb proxy 経由でこのDocker コンテナ上のGate One を使用する環境を構築してみることにします。

# 内部proxy でgateone コンテナに接続する 
## docker コンテナの設定(SSL Proxy を使用しないで接続できるように設定) 
Apache のプロキシから接続できるようにDocker コンテナの設定を変更し、再ビルドしていきます。

Gate One の設定ファイルを編集し、デフォルトでON となっているSSL/TLS 接続をOFF (`disable_ssl` を`true`)にします。
また、Gate One の接続先をデフォルとで`dockerhost` としておきます。
デフォルとの接続先を変更するには、`command` の値に記載されているコマンドの引数に`--default_hosts='dockerhost'` と記載しておくようにします。
この`dockerhost` のIP アドレスは後ほどコンテナ上のhosts として定義し、デフォルトでDocker コンテナではなく、Docker ホストに接続できるようにします。


```text:99docker.conf
{
    "*": {
        "gateone": {
            "disable_ssl": true,
            "url_prefix": "/gateone",
            "origins": ["*"]
        },
        "terminal": { // These settings apply to the "terminal" application
            "commands": {"SSH": {"command": "/usr/local/lib/python2.7/dist-packages/gateone-1.2.0-py2.7.egg/gateone/applications/terminal/plugins/ssh/scripts/ssh_connect.py --default_host='dockerhost' -S '%SESSION_DIR%/%SESSION%/%SHORT_SOCKET%' --sshfp -a '-oUserKnownHostsFile=\\\"%USERDIR%/%USER%/.ssh/known_hosts\\\"'", "description": "Connect to hosts via SSH."}}
        }
    }
}
```


Dockerfile は以下のようにし、`99docker.conf` ファイルをDocker コンテナ上のGate One 設定ファイルディレクトリにコピーするようにします。

```text:Dockerfile
FROM liftoff/gateone
MAINTAINER Tsutomu Nakamura

ARG DOCKER_CONF=${DOCKER_CONF:-99docker.conf}
COPY ${DOCKER_CONF} /etc/gateone/conf.d/99docker.conf
```


Docker イメージをビルドします。


```text:dockerbuild
# docker build -t="proxy_gateone" .
```


Docker コンテナを起動します。

```text:dockerrun
# docker run -t --name=proxy_gateone --add-host dockerhost:192.168.1.112 -p 8000:8000 proxy_gateone
```


### Gate One 設定ファイルの設定値の上書きについて 
Gate One の設定ファイルは、Gate One がインストールされたホストの`/etc/gateone/conf.d` ディレクトリの中に存在します。
ファイル名の形式は`*.conf` という形式のファイル名となっており、中身は例として次のようになっています。

```text:10server.conf
// This is Gate One's main settings file.
{
    // "gateone" server-wide settings fall under "*"
    "*": {
        "gateone": { // These settings apply to all of Gate One
            "address": "",
            "ca_certs": null,
            "cache_dir": "/tmp/gateone_cache",
            "certificate": "/etc/gateone/ssl/certificate.pem",
            "cookie_secret": "***************************************",
            "debug": false,
            "disable_ssl": false,
            "embedded": false,
            "enable_unix_socket": false,
            "gid": "0",
            "https_redirect": false,
            "js_init": "",
            "keyfile": "/etc/gateone/ssl/keyfile.pem",
            "locale": "en_US",
            "log_file_max_size": 100000000,
            "log_file_num_backups": 10,
            "log_file_prefix": "/gateone/logs/gateone.log",
            "log_rotate_interval": 1,
            "log_rotate_mode": "size",
            "log_rotate_when": "midnight",
            "log_to_stderr": null,
            "logging": "info",
            "multiprocessing_workers": null,
            "origins": ["localhost:8000", "127.0.0.1:8000", "************:8000", "172.17.20.218:8000"],
            "pid_file": "/tmp/gateone.pid",
            "port": 8000,
            "session_dir": "/tmp/gateone",
            "session_timeout": "5d",
            "syslog_facility": "daemon",
            "uid": "0",
            "unix_socket_mode": "0600",
            "unix_socket_path": "/tmp/gateone.sock",
            "url_prefix": "/",
            "user_dir": "/gateone/users",
            "user_logs_max_age": "30d"
        }
    }
}
```



```text:50terminal.conf
// This is Gate One's Terminal application settings file.
{
    // "*" means "apply to all users" or "default"
    "*": {
        "terminal": { // These settings apply to the "terminal" application
            "commands": {"SSH": {"command": "/usr/local/lib/python2.7/dist-packages/gateone-1.2.0-py2.7.egg/gateone/applications/terminal/plugins/ssh/scripts/ssh_connect.py -S '%SESSION_DIR%/%SESSION%/%SHORT_SOCKET%' --sshfp -a '-oUserKnownHostsFile=\\\"%USERDIR%/%USER%/.ssh/known_hosts\\\"'", "description": "Connect to hosts via SSH."}},
            "default_command": "SSH",
            "dtach": true,
            "enabled_filetypes": "all",
            "environment_vars": {"TERM": "xterm-256color"},
            "session_logging": true,
            "syslog_session_logging": false
        }
    }
}
```

今回`99docker.conf` というファイルを作成しましたが、上記の`10server.conf` ファイル内の設定値をオーバーライドするするには、ファイル名の先頭に付いている数字より大きい数値(今回は"99"としました)のファイルを用意して後から読み込まれるようにし、同じ値を再度定義してあげることでその値を上書きすることができます。
例えば先ほどの`99docker.conf` ファイルの例では`10server.conf` の`disable_ssl`, `url_prefix`, `origins` と`50terminal.conf` の`command` の値を上書きしています。

## 内部プロキシの設定 
既にhttpd サーバを使っていて入り口を1 箇所に集約したい場合、http サーバの内部プロキシを使用してGate One コンテナへのリクエストとレスポンスを中継させるができます。

通信をプロキシするWeb サーバの要件として、Web Socket のプロキシに対応している必要があります。
Apache の場合、`proxy_wstunnel_module` モジュールがそれを実現するためのモジュールになるので、それがロードされていることを確認します。


```text:proxy_wstunnel_moduleが有効になっていることの確認
# grep proxy_wstunnel_module /etc/httpd/ -r
/etc/httpd/conf.modules.d/00-proxy.conf:LoadModule proxy_wstunnel_module modules/mod_proxy_wstunnel.so
```


上記のようにLoadModule で`proxy_wstunnel_module` が定義されていればOK です。

次に、httpd.conf にプロキシの設定を追加しますが、Docker コンテナ上のGate One に`http`, `Web Socket` の通信をプロキシするように設定を追加します。
今回は、`/etc/httpd/conf.d/00_gateone_proxy.conf` ファイルを新規に作成して次のようなproxy 設定を書き込みます。


```text:/etc/httpd/conf.d/00_gateone_proxy.conf
<VirtualHost *:443>
    ServerName foo.example.com
    ServerAdmin webmaster@foo.example.com
    ServerSignature Off

    SSLEngine On
    SSLProxyEngine On
    SSLProtocol all -SSLv3
    SSLProxyProtocol all -SSLv3
    SSLHonorCipherOrder on
    SSLCipherSuite "ALL !aNULL !eNULL !LOW !3DES !MD5 !EXP !PSK !SRP !DSS !RC4"

    SSLCertificateFile /etc/letsencrypt/live/foo.example.com/fullchain.pem
    SSLCertificateKeyFile /etc/letsencrypt/live/foo.example.com/privkey.pem

    ## Proxy authentication
    # <Proxy *://bar.example.com:8000/*>
    #     AuthType Digest
    #     AuthName "Some Digest Auth"
    #     AuthDigestProvider file
    #     AuthUserFile /etc/httpd/.htdigest
    #     Require valid-user
    # </Proxy>

    ProxyPass /gateone/ws ws://127.0.0.1:8000/gateone/ws
    ProxyPassReverse /gateone/ws ws://127.0.0.1:8000/gateone/ws
    ProxyPass /gateone http://127.0.0.1:8000/gateone
    ProxyPassReverse /gateone http://127.0.0.1:8000/gateone
</VirtualHost>
```


設定を追加したらhttpd をreload します。


```text:reload
# systemctl reload httpd
```


reload したらweb ブラウザを開き、アクセスしてみましょう。


```text
# https://foo.example.com/gateone
```


Gate One のトップページが表示され、SSH 接続できれば成功です。

![GateOneProxy_0000.png](https://qiita-image-store.s3.amazonaws.com/0/70152/7a290fde-3ff0-7184-3f0c-a690791fcede.png)

![GateOneProxy_0001.png](https://qiita-image-store.s3.amazonaws.com/0/70152/42973d90-0f48-6e5b-7cf0-5228ef9cf4ec.png)


# 内部proxy でgateone コンテナにSSL/TLS で接続する 
先ほどのサンプルでは内部プロキシに、暗号化されていない`Web Socket`, `http` で行っていましたが、その内部プロキシをSSL/TLS を使った方法で実施してみます。
内部プロキシの先のGate One に使用する証明書は、今回はLet's Encrypt で取得済みのものとします。
なお、今回は証明書の取得や準備については説明を割愛します。

SSL/TLS で内部プロキシを行うに当たり、環境としては以下のとおりです。


* 内部プロキシ先のSSL/TLS 設定
<table><tbody>
  </tr>
  <tr>
    <th> 内部プロキシ先のFQDN</th>
    <td> bar.example.com</td>
    <td> 今回はApache と同じホスト上にGate One のDocker コンテナがあるものとします</td>
  </tr>
  <tr>
    <th> 証明書ファイル</th>
    <td> /etc/letsencrypt/live/bar.example.com/fullchain.pem</td>
    <td> 中間証明書とサーバ証明書。letsencrypt で取得することを想定。Docker ホストOS 上に既にあるものを再利用</td>
  </tr>
  <tr>
    <th> 秘密鍵ファイル</th>
    <td> /etc/letsencrypt/live/bar.example.com/privkey.pem</td>
    <td> letsencrypt で取得することを想定。ホストOS 上に既にあるものを再利用</td>
  </tr>
</tbody></table>

## docker コンテナの設定(SSL/TLS でListen するように設定) 
Dockerfile については、先ほどの内部プロキシ設定時のDockerfile を流用します。
Gate One の設定ファイルを新規に作成(`99dockerssl.conf`)し、以下のように`disable_ssl` を`false` に変更し、`certificate` と`keyfile` を新たに定義します。


```text:99dockerssl.conf
{
    "*": {
        "gateone": {
            "disable_ssl": false,
            "certificate": "/etc/letsencrypt/live/bar.example.com/fullchain.pem",
            "keyfile": "/etc/letsencrypt/live/bar.example.com/privkey.pem",
            "url_prefix": "/gateone",
            "origins": ["*"]
        },
        "terminal": { // These settings apply to the "terminal" application
            "commands": {"SSH": {"command": "/usr/local/lib/python2.7/dist-packages/gateone-1.2.0-py2.7.egg/gateone/applications/terminal/plugins/ssh/scripts/ssh_connect.py --default_host='dockerhost' -S '%SESSION_DIR%/%SESSION%/%SHORT_SOCKET%' --sshfp -a '-oUserKnownHostsFile=\\\"%USERDIR%/%USER%/.ssh/known_hosts\\\"'", "description": "Connect to hosts via SSH."}}
        }
    }
}
```


`--build-arg` を指定してイメージをビルドします。


```text:dockerbuild
# docker build --build-arg DOCKER_CONF="99dockerssl.conf" -t="sslproxy_gateone" .
```


コンテナを起動します。

```text:dockerrun
# docker run -t --name=gateone --add-host dockerhost:192.168.1.112 -v /etc/letsencrypt:/etc/letsencrypt -p 8000:8000 sslproxy_gateone
```


## proxy の設定(SSL/TLS でproxy するように設定) 
Apache のproxy でssl を有効とするには、`SSLProxyEngine` の値を`On` とする必要があります。
定義例としては以下の通りです。


```text:/etc/httpd/conf.d/00_gateone_proxy.conf
<VirtualHost *:443>
    ServerName foo.example.com
    ServerAdmin webmaster@foo.example.com
    ServerSignature Off

    SSLEngine On
    SSLProxyEngine On
    SSLProtocol all -SSLv3
    SSLProxyProtocol all -SSLv3
    SSLHonorCipherOrder on
    SSLCipherSuite "ALL !aNULL !eNULL !LOW !3DES !MD5 !EXP !PSK !SRP !DSS !RC4"

    SSLCertificateFile /etc/letsencrypt/live/foo.example.com/fullchain.pem
    SSLCertificateKeyFile /etc/letsencrypt/live/foo.example.com/privkey.pem

    ## Proxy authentication
    # <Proxy *://bar.example.com:8000/*>
    #     AuthType Digest
    #     AuthName "Some Digest Auth"
    #     AuthDigestProvider file
    #     AuthUserFile /etc/httpd/.htdigest
    #     Require valid-user
    # </Proxy>

    ProxyPass /gateone/ws wss://bar.example.com:8000/gateone/ws
    ProxyPassReverse /gateone/ws wss://bar.example.com:8000/gateone/ws
    ProxyPass /gateone https://bar.example.com:8000/gateone
    ProxyPassReverse /gateone https://bar.example.com:8000/gateone
</VirtualHost>
```


設定を記載したら、httpd をreload します。


```text:reload
# systemctl reload httpd
```


Web ブラウザを開き、先ほどと同様にGate One のページが見えることを確認してください。

```text:Webブラウザからアクセス
# https://foo.example.com/gateone
```


## 補足: SSH ログイン時認証に公開鍵ペアを使用する 
サーバが公開鍵認証を要求するように設定されている場合、Gate One のSSH クライアント側で公開鍵ペアを作成し、公開鍵を予めサーバ側に設置しておくようにしてください。
公開鍵を作成するには、SSH 接続画面から、右側のモニタアイコンをクリックし、`Manage Identities` をクリックします。
![GateOneProxy_0002.png](https://qiita-image-store.s3.amazonaws.com/0/70152/4539e8f0-9972-27c1-2577-7eff867ab5b6.png)


SSH Identity Manager ウィンドウが出てきたら、`New Identity` をクリックします。
![GateOneProxy_0003.png](https://qiita-image-store.s3.amazonaws.com/0/70152/6fdad802-53f5-943a-4c9d-06f5f1fd5188.png)


嘉木常法入力ウィンドウが出てくるので、画面の指示に従って入力していきます。
![GateOneProxy_0004.png](https://qiita-image-store.s3.amazonaws.com/0/70152/2c23b97e-6b4f-ccc6-31e1-f244f170edc6.png)


鍵が作成されるとSSH Identity Manager ウィンドウに戻り、右側に公開鍵情報が表示されるので、それを接続先のサーバに設置しておいてください(以降手順割愛)。

![GateOneProxy_0005.png](https://qiita-image-store.s3.amazonaws.com/0/70152/8e790fb8-6f54-0ddf-20b2-f8ffa8df17c7.png)


設置したら、Gate One から接続してみて、鍵認証が通ることを確認してください。

## Gate One 側の機能で実装できる認証について 
今回実装したProxy 認証とSSH のログイン認証以外にGate One 側で認証機能を設けることができるようになっています。
今回設定手順については割愛しますが、`kerberos`, `google`, `pam`, `none(認証なし)` が指定できるようになっています。

- Authentication Classes
    - https://liftoff.github.io/GateOne/Developer/auth.html

## origin を修正しなかった場合のエラーについて (作業備忘録)
origin の指定が明示的にあると次のようなエラーが出て、Gate One に接続できない事象が発生します。
そのため、Gate One の`origin` の設定は`*` として設定しました。


```text:99dockerssl.conf
{
    "*": {
        "gateone": {
            ......
            "origins": ["*"]
        },
        "terminal": { // These settings apply to the "terminal" application
            "commands": {"SSH": {"command": "......", "description": "Connect to hosts via SSH."}}
        }
    }
}
```



```text:originを指定しなかった時のエラー
[E 160320 06:05:16 server:1565] Origin check failed for: https://bar.example.com
[W 160320 06:05:16 web:1946] 403 GET /gateone/ws (172.17.0.1) 8.59ms
[I 160320 06:05:18 server:1317] Settings have been modified.  Reloading from /etc/gateone/conf.d
```


# 参考 
- Install GateOne – an HTML5 ssh client
    - https://www.disk91.com/2014/technology/systems/install-gateone-an-html5-ssh-client/

- EC2インスタンスでhttp proxyサーバーを利用する
    - https://github.com/mechamogera/MyTips/wiki/EC2%E3%82%A4%E3%83%B3%E3%82%B9%E3%82%BF%E3%83%B3%E3%82%B9%E3%81%A7http-proxy%E3%82%B5%E3%83%BC%E3%83%90%E3%83%BC%E3%82%92%E5%88%A9%E7%94%A8%E3%81%99%E3%82%8B

- How To SSH into Your VPS from the Browser with GateOne
    - https://www.digitalocean.com/community/tutorials/how-to-ssh-into-your-vps-from-the-browser-with-gateone

- Backporting Apache support for websockets reverse proxy (aka getting GateOne to work behind Apache)
    - http://blog.cafarelli.fr/2013/04/backporting-apache-support-for-websockets-reverse-proxy-aka-getting-gateone-to-work-behind-apache/

- iptables failed - No chain/target/match by that name #16816
    - https://github.com/docker/docker/issues/16816

- tunneling secure websocket connections with apache
    - http://stackoverflow.com/questions/11468154/tunneling-secure-websocket-connections-with-apache

- Request - Allow change of default host #94 
    - https://github.com/liftoff/GateOne/issues/94