「開け、ごま！」で使えるようになるWebサーバーを作りたい！

## やりたいこと

最初は**どのページもアクセスできない**けど、
**秘密の順番でパスにアクセス**していくと、使えるようになる！　
ようなサーバーを作りたいです

一言でいえば、ポートノッキングのHTTP版みたいなものを作りたいです。

ポートノッキングで有名なツイートです。
<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">UDP53番、TCP443番、UDP123番とポートノッキングをするとTCP443番に10秒だけsshdが現れる、という中二病全開の設定をした。皆様にもお勧めしたい。</p>&mdash; hnw (@hnw) <a href="https://twitter.com/hnw/status/845856352052264961?ref_src=twsrc%5Etfw">2017年3月26日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

> ポートノッキングというのは、決められたポートを決められた順番で叩くことでファイアーウォールに穴を空けられるような仕組みのことです。
（引用：[ポートノッキングで10秒間だけsshdを公開する設定 - hnwの日記](http://d.hatena.ne.jp/hnw/20170327)）

## HTTP上でノッキングしたい理由

ポートノッキングだとHTTPSを使って暗号化していても、ポートの部分は暗号化されないはず[^port_encryption] なので、傍受しようと思えばできてしまうのかなと思いました。そこで、HTTPS上で、ノッキングすればHTTPヘッダもボディも暗号化されるため、ノッキングも暗号化できるのではと思い、セキュアになるかなと思ってやりたくなりました。また、HTTPを使うので、ブラウザからも簡単にノッキングできるので、ポートノッキングより容易にできるのではないかというのも一つ理由です。

[^port_encryption]: ネットワーク周りは特に自身がないので間違っているかもしれません

## 設計の方針

* **なるべくあらゆるサイトに適用**したいので、リバースプロキシとして作り、隠したいサーバーのURLを指定するようにする[^why_reverse_proxy]
* Dockerイメージを用意して、**Docker Composeで簡単にノッキングサーバを適用**できるようにする
* オプションで**自動でクローズしたりノッキングの間隔が空きすぎるとリセット**するなどセキュアにする
* HTTPSの対応は[HTTPS-PORTAL](https://github.com/SteveLTN/https-portal)やHerokuなどのようにHTTPをHTTPS化してくれるようなシステムの利用を想定しています

[^why_reverse_proxy]: ある特定のプログラミング言語のライブラリとかにすると、その言語で書かれているサーバーにしか適用できなさそうになるので、こういう方針にしました。

## http-knockingの使い方（npm）

以下のようにnpmで簡単にインストールができるようにしました。コマンドとして利用するので`-g`オプションでグローバルにインストールしてます。(Docker Composeでの使い方は次に書きます)

```bash:インストール
npm install -g http-knocking
```

以下が実行の方法です

* <http://localhost:8181>が隠したいサーバーです
* <http://localhost:8282>にノッキングサーバーが立ち上がります

```bash:実行方法
http-knocking --port=8282 --target-host=localhost --target-port=8181 --open-knocking="/hirake,/goma" --close-knocking="/tojiyo,/goma"
```

大雑把な構成は以下のようになります。
![http-knocking.png](https://qiita-image-store.s3.amazonaws.com/0/84451/a045ae80-109e-0ddf-3096-0636c4a905ed.png)
（PCの絵：[パソコンのイラスト（無料イラスト）フリー素材](http://www.wanpug.com/illust107.html)）


<http://localhost:8282>にアクセスすると最初は白紙のページです。以下の順番でアクセスすると、
1. <http://localhost:8282/hirake>
2. <http://localhost:8282/goma>
ノッキングが成功して、`localhost:8282`で`localhost:8181`の内容にアクセスできるようになり、ちゃんと隠していたサーバーが使えるようになりました！
（閉じるときは`/tojiyo` -> `/goma`の順番でアクセスすれば閉じます）

## http-knockingの使い方（Docker Compose）

Docker Composeを使う方法だと、npmなしで利用することができます。
Alpineのおかげで、http-knockingのDockerイメージのサイズが87MBと割と小さくなりました。そのためそこまで負担なく導入しやすいのではないか思います。

隠したいWebサーバーとして、[Ghost](https://ghost.org/)のDockerイメージ[ghost](https://hub.docker.com/_/ghost/)を使ってみました。
以下の起動方法は、以下の内容を`docker-compose.yml`として保存して、`docker-compose up`で起動します。起動後は<http://localhost:8282>にノッキングサーバーが立ち上がります。

```yaml:docker-compose.yml
version: '3.1'
services:
  http-knocking:
    image: nwtgck/http-knocking:v0.3.0
    ports:
      - '8282:8282'
    depends_on:
      - ghost
    restart: always
    command: --port=8282 --target-host=ghost --target-port=2368 --open-knocking="/alpha,/foxtrot,/lima"
  ghost:
    image: ghost
    restart: always
    expose:
      - "2368"
```

Docker Composeを使っているため、隠したいサーバー（今回のGhost）のポートにアクセスできないようにでき、よりわかりやすくてセキュアになっていると思います。あとは、これに[HTTPS-PORTAL](https://github.com/SteveLTN/https-portal)を組み合わせてHTTPS化するというのを想定しています。

以下が、実際のノッキングの動作です。'/alpha' -> '/foxtrot' -> 'lima'の順番でアクセスすると、ちゃんと開くのが確認できます。（`--close-knocking`を指定していなので、逆順でノッキングすることで閉じます）
![http-knocking animation](https://qiita-image-store.s3.amazonaws.com/0/84451/474c5f6a-197d-0bef-a75e-65873c49c200.gif)


## その他のオプション

セキュリティを高める目的などでいくつかオプションを用意しています。
バージョン0.3.0の時点で以下のオプションが利用可能です。

```
Options:
  --help                               Show help                       [boolean]
  --version                            Show version number             [boolean]
  --port                               Port of knocking server        [required]
  --target-host                        Target host to hide            [required]
  --target-port                        Target port to hide         [default: 80]
  --open-knocking                      Open-knocking sequence (e.g.
                                       "/alpha,/foxtrot,/lima")       [required]
  --close-knocking                     Close-knocking sequence (e.g.
                                       "/victor,/kilo")
  --enable-websocket                   Enable WebSocket proxy   [default: false]
  --auto-close-millis                  Time millis to close automatically
  --open-knocking-max-interval-millis  Time millis to reset open procedure
```

セキュリティを高めるには`--auto-close-millis`や`--open-knocking-max-interval-millis`が便利だと思います。

`--auto-close-millis`は開いたあと何ミリ秒たったら、自動でクローズするかを指定します。デフォルトだと自動でクローズしません。

`--open-knocking-max-interval-millis`はパスを指定する時間間隔の最大ミリ秒を指定します。例えば、10000と指定すれば、10秒以内に次のパスにアクセスする必要があります。その次のパスにアクセスするときもまた10秒以内にその次の次のパスにアクセスする必要があります。デフォルトだと無制限です。

## WebSocket対応
0.3.0からWebSocketもリバースプロキシ対応しました。
`--enable-websocket`オプションを指定することで、WebSocketの通信も通したり隠したりできるようになります。`--enable-websocket`オプションを指定しない場合は、WebSocketのリバースプロキシはされません。
（追記：2018/08/21）

## GitHub

TypeScriptで実装しています。
<https://github.com/nwtgck/http-knocking>

以下のような方法で、`install -g`せずに使ってもいいと思います。

```bash
git clone https://github.com/nwtgck/http-knocking.git
cd http-knocking
npm install
npm start -- --port=8282 --target-url=<ターゲットURL> --open-knocking="/alpha,/foxtrot,/lima"
```

## 作ろうと思ったきっかけ

あんまり関係のない話ですが、最後に作ろうと思ったきっかけについてです。
[`curl`コマンドとかでファイルを送信したり、受信したりするサーバー](https://github.com/nwtgck/trans-server-akka)を作っていて、そのサーバーの利用を制限する方法がないかなと思ったのがきっかけでした。利用を制限すると、悪意もった人が巨大なファイルを送るのを防いだり、その他悪意のあるアクセスも少しは防げるのではないかと思ってます。

あと幸運なことに、通信が確立した後にノッキングサーバーをクローズしても、ファイルの送受信は進み続けるので、確立さえすればHTTPボディが巨大なリクエストやレスポンスもhttp-knockingでクローズ後もアクセス可能でした。クローズ後は新しい送受信の始まりが確立することはないので、目指していたよりセキュアな状態が可能になったのかなと思っています。