Closed9

Elixirのノード間通信をTLSで行う

栗林健太郎栗林健太郎

Erlang/Elixirにおけるノード間通信は、特に何も指定しないと平文による通信になる。TLS通信もできるはずなのだが、どうやったらできるかを調べて、実際にやってみる。

やってみたいのは以下2つ。

  • 同一ホスト内にノードをふたつ立ち上げて、TLS通信する
  • Nerves内のノードと別のホストのノードとで、TLS通信する
栗林健太郎栗林健太郎

参考資料

Erlang/ElixirでTLSクライアント認証する

HTTPでのTLSの話だが、Elixirにおけるそのあたりの事情についてよくまとめられている。

Erlang -- Using TLS for Erlang Distribution

Erlangのドキュメント。これをElixir側からやってあげればいけるはず。

Some CLI commands break for application release with TLS encrypted distributed erlang node configuration · Issue #640 · bitwalker/distillery

Distilleryのリリースでパラメタを渡す方法。ワークアラウンドが必要(上記のイシューが解決するまでは)。NervesでTLSでのノード間通信をしたい場合は、上記が必要か?

AWS ECS上にErlang(Elixir) Clusterを組むためのツールを作った - PartyIX

Erlangノードを楽にクラスタ化するためのツール。Erlangのsys.configの話はErlang -- configにある。

Erlang ping node problem - Stack Overflow

nodeがつながらないときに。

栗林健太郎栗林健太郎

できた!

まずは、できてなかった理由から。

rel/vm.args.eex最後に以下のように書いた。

-proto_dist inet_tls -ssl_dist_optfile /etc/ssl.conf

ssl.confの内容は省略)

これでいけるはずなのに、ホスト側のIExからつながらない。

で、mix firmware; mix uploadをしてssh nerves.localして確かめてみたら、

> :init.get_plain_arguments()
['--no-halt', '--', '--dot-iex', '/etc/iex.exs', '-proto_dist', 'inet_tls',
 '-ssl_dist_optfile', '/etc/ssl.conf', '--']

となっていて、オプションがVMをブートするプログラムにちゃんと渡されてなかった!

ref/vm.args.eexをよくみてみると、

## Options added after -extra are interpreted as plain arguments and can be
## retrieved using :init.get_plain_arguments(). Options before the "--" are
## interpreted by Elixir and anything afterwards is left around for other IEx
## and user applications.

とある。ファイルの最後に書いちゃうと、--以降の引数になって、VMの引数にならないのであった。

栗林健太郎栗林健太郎

できた手順を書く。

まず、TLSを有効にしていない状態(生成されたファームウェアをデバイスにデプロイしただけの状態)。

iex(1)> :init.get_arguments()
[
  root: ['/srv/erlang'],
  progname: ['erlexec'],
  home: ['/root'],
  config: ['/srv/erlang/releases/0.1.0/sys.config'],
  boot: ['/srv/erlang/releases/0.1.0/shoehorn'],
  setcookie: ['nerves_tls_distribution_example_cookie'],
  mode: ['embedded'],
  kernel: ['shell_history', 'enabled'],
  heart: [],
  noshell: [],
  user: ['Elixir.IEx.CLI'],
  elixir: ['ansi_enabled', 'true'],
  boot_var: ['RELEASE_LIB', '/srv/erlang/lib']
]

この状態だとノード間通信はプレーンなTCP通信(inet_tcp_dist)で行われる。これをTLS通信(inet_tls_dist)で行えるようにする。

rel/vm.args.eexの、

## Options added after -extra are interpreted as plain arguments and can be

という行の前に、以下を追加する(他に証明書とか秘密鍵とか↓のファイルとかを追加するのだが、詳細はこのコミットを参照のこと)。

-proto_dist inet_tls -ssl_dist_optfile /etc/ssl.conf

この状態でアップロードしたら

iex(2)> :init.get_arguments()
[
  root: ['/srv/erlang'],
  progname: ['erlexec'],
  home: ['/root'],
  config: ['/srv/erlang/releases/0.1.0/sys.config'],
  boot: ['/srv/erlang/releases/0.1.0/shoehorn'],
  setcookie: ['nerves_tls_distribution_example_cookie'],
  mode: ['embedded'],
  kernel: ['shell_history', 'enabled'],
  heart: [],
  noshell: [],
  user: ['Elixir.IEx.CLI'],
  elixir: ['ansi_enabled', 'true'],
  proto_dist: ['inet_tls'],
  ssl_dist_optfile: ['/etc/ssl.conf'],
  boot_var: ['RELEASE_LIB', '/srv/erlang/lib']

ssl_dist_optfile: ['/etc/ssl.conf'],という行が現れた。

Nervesデバイス側でノードを起動する。

iex(9)> System.cmd("epmd", ["-daemon"])
{"", 0}
iex(10)> Node.start(:"device@nerves.local")
{:ok, #PID<0.1254.0>}

ホスト側で、以下のようにしてノードを起動する。

$  env RELEASE_COOKIE="nerves_tls_distribution_example_cookie" RELEASE_NODE=host@localhost RELEASE_DISTRIBUTION=name _build/rpi3_dev/rel/example/bin/example start_iex

で、IExからデバイス側につないでみると、うまくいった〜。

iex(host@localhost)1> Node.connect(:"device@nerves.local")
true
このスクラップは2021/07/03にクローズされました