自ホスト以外からのTCPリクエストを拒否する

WebサーバーなどTCP接続を受け付けるアプリケーションを動かす場合、`server.listen(3000, '0.0.0.0');` のように、リクエストを受け付けるIPアドレス(`0.0.0.0`)とPort番号(`3000`)を 指定することがある。

`0.0.0.0` は任意のIPアドレスで受け付けられる ワイルドカード の意味で、
自ホスト以外からのリクエストを受け付ける必要がある場合には `0.0.0.0` が使用されることが多いと思われる。

ただ、 `0.0.0.0` で任意のIPアドレスからのリクエストを受け付けられるようにすると、セキュリティ的に **望まないリクエストも受け付けてしまう**ので、制限したいことがある。

この記事では、その方法を紹介する。

# 自ホスト以外からのリクエストを拒否する方法
以下の3通りがある

- [`0.0.0.0` の代わりに `127.0.0.1` で listen する](#0000-の代わりに-127001-で-listen-する)
    - メリット: シンプル・確実
    - デメリット: `127.0.0.1` 以外の自ホストのIPアドレスでのリクエストが拒否される
        - `192.168.0.13` のような ローカル エリア接続のIP
        - `127.0.0.2` や `127.100.150.200` のような `127.0.0.1` 以外のループバックアドレス (`127.0.0.0/8`)
- [`0.0.0.0` で listen しつつ OSの Firewall で弾く](#0000-で-listen-しつつ-osの-firewall-で弾く)
    - メリット: 確実
    - デメリット: Firewall 設定が必要
- [`0.0.0.0` で listen しつつ アプリケーション側で弾く](#0000-で-listen-しつつ-アプリケーション側で弾く)
    - メリット: 柔軟
        - `127.0.0.0/8` 宛のリクエストをすべて許可できる
        - （特定の外部IPを許可することも可能）
    - デメリット: アプリケーションコードが増える・実装ミスのリスク


## `0.0.0.0` の代わりに `127.0.0.1` で listen する
### アプリケーションコード

```js
const net = require('net');

const server = net.createServer();

server.on('listening', () => {
    const {
        address,
        port,
    } = server.address();
    console.info(`Server started. (IP:port = ${address}:${port})`);
});

server.on('connection', connection => {
    connection.end('HTTP/1.0 200 OK\r\n\r\nok');
});

server.listen(3000, '127.0.0.1');
```

※ 動作確認を簡単にするためHTTPサーバーの機能をTCPレベルAPIで実装した
（雑に作ったので本番への流用はNG）

### 起動ログ
```bash
$ node index.js
Server started. (IP:port = 127.0.0.1:3000)
```


### lissten状況（抜粋）
`127.0.0.1:3000` で受け付けている

```powershell
> netstat -an

アクティブな接続

  プロトコル  ローカル アドレス      外部アドレス           状態
  TCP         127.0.0.1:3000         0.0.0.0:0              LISTENING
```

※ 外部アドレス は 状態が `LISTENING` の場合 `0.0.0.0:0` と表示される

### リクエストの受付可否
#### ループバックアドレス (`127.0.0.1`)
:o: つながる

```bash
$ curl --silent --show-error http://127.0.0.1:3000/
ok
```

#### ループバックアドレス2 (`127.0.0.2`)
:x: つながらない

```bash
$ curl --silent --show-error http://127.0.0.2:3000/
curl: (7) Failed to connect to 127.0.0.2 port 3000: Connection refused
```

#### ローカル エリア接続のIP (`192.168.0.13`)
:x: つながらない

```bash
$ curl --silent --show-error http://192.168.0.13:3000/
curl: (7) Failed to connect to 192.168.0.13 port 3000: Connection refused
```

## `0.0.0.0` で listen しつつ OSの Firewall で弾く
省略（各OSの手順に沿って設定してください）

## `0.0.0.0` で listen しつつ アプリケーション側で弾く

### 方法
TCP接続(`connection `) の 接続先IPアドレス, 接続元IPアドレス の情報を使って、自ホスト外かどうか判定する

- 接続先IPアドレス: `connection.localAddress`
    - クライアントから見てリクエスト先は サーバーから見て local
- 接続元IPアドレス: `connection.remoteAddress`
    - クライアントからは サーバーから見て remote

#### 自ホストからリクエストの条件
以下のいずれか

- remoteAddress が `127.0.0.1`
    - `127.0.0.0/8` (`127.0.0.1` や `127.1.2.3` など) 宛にリクエストした場合
- remoteAddress と localAddress が等しい
    - 自 IPアドレス宛の通信 (`192.168.0.13` や  `192.168.99.1` など)

#### ソースコード
```js
const {
    remoteAddress,
    localAddress,
} = connection;

// 自ホストからのリクエスト
// 1. 127.0.0.0/8 (127.0.0.1 や 127.1.2.3 など) 宛にリクエストした場合
// 2. 自 IPアドレス宛の通信 (192.168.0.13 や  192.168.99.1 など)
const fromSelfHost = remoteAddress === '127.0.0.1' || remoteAddress === localAddress;
```

### アプリケーションコード
- 自ホストからのリクエストか判定
    - 自ホストからのリクエスト: OKレスポンスを返却
    - ホスト外からのリクエスト: NGレスポンスを返却
        - ※ **許可しないリクエストには NGレスポンスを返すようにした**（確認しやすくするため）
- （リクエスト元・先などの情報をログ出力）
    - remoteAddress: 接続元IPアドレス（クライアント）
    - localAddress: 接続先IPアドレス（サーバー側）
    - requestLine: HTTP リクエストの1行目
    - fromSelfHost: 自ホストからのリクエストかどうか

```js
const net = require('net');

const server = net.createServer();

server.on('listening', () => {
    const {
        address,
        port,
    } = server.address();
    console.info(`Server started. (IP:port = ${address}:${port})`);
});

const okHttpResponse = `HTTP/1.0 200 OK

ok
`;

const ngHttpResponse = `HTTP/1.0 403 Forbidden

ng
`;

server.on('connection', connection => {
    const {
        remoteAddress,
        localAddress,
    } = connection;

    // 自ホストからのリクエスト
    // 1. 127.0.0.0/8 (127.0.0.1 や 127.1.2.3 など) 宛にリクエストした場合
    // 2. 自 IPアドレス宛の通信 (192.168.0.13 や  192.168.99.1 など)
    const fromSelfHost = remoteAddress === '127.0.0.1' || remoteAddress === localAddress;


    connection.once('data', data => {
        const requestLine = data.toString().split(/[\r\n]/)[0];
        console.info({
            remoteAddress,
            localAddress,
            requestLine,
            fromSelfHost,
        });
    });

    connection.end(fromSelfHost ? okHttpResponse : ngHttpResponse);
});

server.listen(3000, '0.0.0.0');

```


※ 動作確認を簡単にするためHTTPサーバーの機能をTCPレベルAPIで実装した
（雑に作ったので本番への流用はNG）

### 起動ログ
```bash
$ node index.js
Server started. (IP:port = 0.0.0.0:3000)
```


### lissten状況（抜粋）
`0.0.0.0:3000` で受け付けている

```powershell
> netstat -an

アクティブな接続

  プロトコル  ローカル アドレス      外部アドレス           状態
  TCP         0.0.0.0:3000           0.0.0.0:0              LISTENING
```

※ 外部アドレス は 状態が `LISTENING` の場合 `0.0.0.0:0` と表示される

### リクエストの受付可否
#### ループバックアドレス (`127.0.0.1`)
:o: つながる

```bash
$ curl --silent --show-error http://127.0.0.1:3000/
ok
```

##### アプリケーションログ
remoteAddress(`127.0.0.1`) が `127.0.0.1` と等しいためホスト内からのリクエストと判定されている(`fromSelfHost: true`) 

```js
{ remoteAddress: '127.0.0.1',
  localAddress: '127.0.0.1',
  requestLine: 'GET / HTTP/1.1',
  fromSelfHost: true }
```

#### ループバックアドレス2 (`127.0.0.2`)
:o: つながる

```bash
$ curl --silent --show-error http://127.0.0.2:3000/
ok

```

##### アプリケーションログ
remoteAddress(`127.0.0.1`) が `127.0.0.1` と等しいためホスト内からのリクエストと判定されている(`fromSelfHost: true`) 

```js
{ remoteAddress: '127.0.0.1',
  localAddress: '127.0.0.2',
  requestLine: 'GET / HTTP/1.1',
  fromSelfHost: true }
```

#### ローカル エリア接続のIP (`192.168.0.13`)
:o: つながる

```bash
$ curl --silent --show-error http://192.168.0.13:3000/
ok
```

##### アプリケーションログ
remoteAddress(`192.168.0.13`) が localAddress(`192.168.0.13`) と等しいためホスト内からのリクエストと判定されている(`fromSelfHost: true`) 

```js
{ remoteAddress: '192.168.0.13',
  localAddress: '192.168.0.13',
  requestLine: 'GET / HTTP/1.1',
  fromSelfHost: true }
```

#### 外部からのリクエスト
同一ネットワークに接続中のスマートフォンのブラウザからリクエスト

:x: （つながるが）アプリケーションに拒否された（NGレスポンスが返る）

##### レスポンス

```
ng
```

##### アプリケーションログ
remoteAddress(`192.168.0.128`) が localAddress(`192.168.0.13`) と異なるためホスト外からのリクエストと判定されている(`fromSelfHost: false`) → NGレスポンスを返却

```js
{ remoteAddress: '192.168.0.128',
  localAddress: '192.168.0.13',
  requestLine: 'GET / HTTP/1.1',
  fromSelfHost: false }
{ remoteAddress: '192.168.0.128',
  localAddress: '192.168.0.13',
  requestLine: 'GET /favicon.ico HTTP/1.1',
  fromSelfHost: false }
```

※ ブラウザからアクセスしたため、 `/favicon.ico` 宛のリクエストも存在する