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 する- メリット: シンプル・確実
- デメリット:
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 で弾く- メリット: 確実
- デメリット: Firewall 設定が必要
-
0.0.0.0
で listen しつつ アプリケーション側で弾く- メリット: 柔軟
-
127.0.0.0/8
宛のリクエストをすべて許可できる - (特定の外部IPを許可することも可能)
-
- デメリット: アプリケーションコードが増える・実装ミスのリスク
- メリット: 柔軟
0.0.0.0
の代わりに 127.0.0.1
で listen する
アプリケーションコード
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)
起動ログ
$ node index.js
Server started. (IP:port = 127.0.0.1:3000)
lissten状況(抜粋)
127.0.0.1:3000
で受け付けている
> netstat -an
アクティブな接続
プロトコル ローカル アドレス 外部アドレス 状態
TCP 127.0.0.1:3000 0.0.0.0:0 LISTENING
※ 外部アドレス は 状態が LISTENING
の場合 0.0.0.0:0
と表示される
リクエストの受付可否
ループバックアドレス (127.0.0.1
)
つながる
$ curl --silent --show-error http://127.0.0.1:3000/
ok
ループバックアドレス2 (127.0.0.2
)
つながらない
$ 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
)
つながらない
$ 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
など)
- 自 IPアドレス宛の通信 (
ソースコード
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: 自ホストからのリクエストかどうか
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)
起動ログ
$ node index.js
Server started. (IP:port = 0.0.0.0:3000)
lissten状況(抜粋)
0.0.0.0:3000
で受け付けている
> netstat -an
アクティブな接続
プロトコル ローカル アドレス 外部アドレス 状態
TCP 0.0.0.0:3000 0.0.0.0:0 LISTENING
※ 外部アドレス は 状態が LISTENING
の場合 0.0.0.0:0
と表示される
リクエストの受付可否
ループバックアドレス (127.0.0.1
)
つながる
$ curl --silent --show-error http://127.0.0.1:3000/
ok
アプリケーションログ
remoteAddress(127.0.0.1
) が 127.0.0.1
と等しいためホスト内からのリクエストと判定されている(fromSelfHost: true
)
{ remoteAddress: '127.0.0.1',
localAddress: '127.0.0.1',
requestLine: 'GET / HTTP/1.1',
fromSelfHost: true }
ループバックアドレス2 (127.0.0.2
)
つながる
$ curl --silent --show-error http://127.0.0.2:3000/
ok
アプリケーションログ
remoteAddress(127.0.0.1
) が 127.0.0.1
と等しいためホスト内からのリクエストと判定されている(fromSelfHost: true
)
{ remoteAddress: '127.0.0.1',
localAddress: '127.0.0.2',
requestLine: 'GET / HTTP/1.1',
fromSelfHost: true }
ローカル エリア接続のIP (192.168.0.13
)
つながる
$ curl --silent --show-error http://192.168.0.13:3000/
ok
アプリケーションログ
remoteAddress(192.168.0.13
) が localAddress(192.168.0.13
) と等しいためホスト内からのリクエストと判定されている(fromSelfHost: true
)
{ remoteAddress: '192.168.0.13',
localAddress: '192.168.0.13',
requestLine: 'GET / HTTP/1.1',
fromSelfHost: true }
外部からのリクエスト
同一ネットワークに接続中のスマートフォンのブラウザからリクエスト
(つながるが)アプリケーションに拒否された(NGレスポンスが返る)
レスポンス
ng
アプリケーションログ
remoteAddress(192.168.0.128
) が localAddress(192.168.0.13
) と異なるためホスト外からのリクエストと判定されている(fromSelfHost: false
) → NGレスポンスを返却
{ 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
宛のリクエストも存在する