インターネット越しでもセキュアなErlang Distributionを実現する
インターネット越しでもセキュアなErlang Distributionを実現したい。問題と対応策について整理する。
Erlang Distributionとは
Erlang Distributionは、ノードと呼ばれるErlangランタイムが相互に通信することで実現される。
epmd
Erlang Port Mapper Daemonの略。他のノードに接続する際に、接続先のノード名から接続先のポートを解決するために用いられる。
:foo@example.com
という名前のノードに接続しようとする場合、
- DNSを用いて
example.com
を名前解決する -
example.com:4369
で待ち受けているepmdに、そのノードの用いているポート番号を問い合わせる - 接続対象のノードがlistenしているそのポート番号に接続する
という流れになる。
プロトコルの詳細については、Distribution Protocolに記載されている。
セキュリティ上の問題
以下3つの問題があるため、デフォルトの設定ではインターネット越しにErlang Distributionを実現することは実際問題としてできない。
1. epmdへの接続
epmdへの接続に対しては特に認証はないため、接続されてしまうと登録しているノードの一覧を見られてしまう。
別のホストにあるノードと通信する場合は、epmdのlistenするポート(デフォルトでは4369番)への通信を許可しておく必要があるが、上述の問題があるためインターネット越しのノード間通信にepmdを使うことはできない。
2. 通信経路
デフォルトの通信モジュールinet_tcp_dist
による通信では、平文のTCPによる通信が行われる。
3. 認証
ノードの接続可否を決定するのに、magic cookie(以下、cookie)と呼ばれる文字列(Erlangの内部的にはatom)が用いられる。あるノードが別のノードに接続しようとした際、それぞれに設定されているcookieが同一であるかを比較することで認証する。
この方式は、ブルートフォースアタックに対して脆弱である。
セキュアなノード間通信
セキュリティを担保しつつインターネット越しでノード間通信をしたい場合、以下の3つの条件を満たす必要がある。
- epmdを使わない
- 通信経路を暗号化する
- セキュアな認証をする
1. epmdを使わない
epmdを使うと、前述の通りインターネット越しでのノード間通信をセキュアにはできない。以下のerl
コマンドオプションを用いることで、epmdを使わないようにできる。
-
-start_epmd false
:-name/-sname
オプションを用いてノードが起動された際、デフォルトでは自動的にepmdが起動される。epmdを必要としない場合、-start_epmd false
を設定する。 -
-erl_epmd_port ポート番号
: ノード間通信を行うerl_epmd
というプロセスがlistenし、また、他のノードにつなぐ際に使うポート番号を設定する。つまり、通信に使うポートを指定してやることで、epmdによるポート番号の解決を不要にする。
これらのオプションは、Elixirのiex
コマンドの引数では、 --erl "-start_epmd false" --erl "-erl_epmd_port 14369"
という形で渡すことができる。
また、 vm.args
でも指定することができる。
2. 通信経路を暗号化する
Erlang -- Using TLS for Erlang Distributionを参考に、 inet_tls_dist
によるTLSを用いるようにする。Elixirでの具体的な手順については、以下のメモに記載しているので参照されたい。
3. セキュアな認証をする
inet_tls_dist
によるTLS通信に加えて、クライアント証明書認証を用いることで、セキュアな認証を実現する。その際、サーバでは以下の項目を必ず設定しなければならない。
{verify, verify_peer}
{fail_if_no_peer_cert, true}
こちらについても、上述の「Vaultで発行した証明書を使ってElixirでmTLS通信する」で例を示しているので参照されたい。
補遺
-connect_all
オプションについて
ノードAからノードBに接続したとき、ノードBがノードCへすでに接続している場合、ノードAは自動的にノードCにも接続を試みる。それを避けたい場合、 -connect_all false
を設定する。
-no_epmd
オプションについて
-start_epmd false
を設定してepmdを起動しないようにしたとしても、他で起動されるとepmdとの通信が発生し、ノードが登録されてしまう。それを防ぐためには -no_epmd
を設定する。
……ということがErlang Questions - Questions about "erl -epmd_start false"で議論されているのだが、 -erl_epmd_port
と同時には使えないようだ。これだとノード間通信ができなくなってしまう。
⟩ iex --sname foo --erl "-erl_epmd_port 14693" --erl "-no_epmd"
erlexec: Missing -proto_dist option, expected when using -no_epmd.
⟩ iex --sname foo --erl "-erl_epmd_port 14693" --erl "-no_epmd" --erl "-proto_dist inet_tcp"
Protocol 'inet_tcp': register/listen error: {noproc,{gen_server,call,[erl_epmd,{register,foo,14693,inet},infinity]}}
-no_epmd
については、Document the -no_epmd option by JeromeDeBretagne · Pull Request #2945 · erlang/otpで議論されている。その結果として、以下のドキュメントが追加された。
-no_epmd
Specifies that the distributed node does not need epmd at all.
This option ensures that the Erlang runtime system does not start epmd and does not start the erl_epmd process for distribution either.
This option only works if Erlang is started as a distributed node with the -proto_dist option using an alternative protocol for Erlang distribution which does not rely on epmd for node registration and discovery. For more information, see How to implement an Alternative Carrier for the Erlang Distribution.
デフォルトのもの以外のプロトコルを用いて通信したい場合に、epmdを使わないようにするためのものということのようだ。このオプションが有効になっていると erl_epmd
が開始されないため、既存のプロトコルだと通信そのものができなくなってしまう。
Discussion