📖

インターネット越しでもセキュアなErlang Distributionを実現する

2021/07/27に公開

インターネット越しでもセキュアなErlang Distributionを実現したい。問題と対応策について整理する。

Erlang Distributionとは

Erlang Distributionは、ノードと呼ばれるErlangランタイムが相互に通信することで実現される。

epmd

Erlang Port Mapper Daemonの略。他のノードに接続する際に、接続先のノード名から接続先のポートを解決するために用いられる。

:foo@example.com という名前のノードに接続しようとする場合、

  1. DNSを用いて example.com を名前解決する
  2. example.com:4369 で待ち受けているepmdに、そのノードの用いているポート番号を問い合わせる
  3. 接続対象のノードが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つの条件を満たす必要がある。

  1. epmdを使わない
  2. 通信経路を暗号化する
  3. セキュアな認証をする

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