4

TL;DR

  • How to whitelist nodes that can Node.connect to Elixir?
  • Any other security recommendations?

Setup

I've begun tinkering with distributing Elixir across (for now) two different servers.

For example, let's say the server's two IP addresses are:

  1. 198.51.100.0
  2. 203.0.113.0

First, I added new rules to the iptables firewall on both servers, opening up port 4369 (EPMD) and a range of 10 ports between 9000-9010 for nodes. I'm also only allowing incoming connections from the other server's exact IP address.

Example config for 198.51.100.0:

-A INPUT -p tcp -m state --state NEW --dport 4369 -s 203.0.113.0 -j ACCEPT
-A INPUT -p tcp -m state --state NEW --dport 9000:9010 -s 203.0.113.0 -j ACCEPT

Example config for 203.0.113.0:

-A INPUT -p tcp -m state --state NEW --dport 4369 -s 198.51.100.0 -j ACCEPT
-A INPUT -p tcp -m state --state NEW --dport 9000:9010 -s 198.51.100.0 -j ACCEPT

Now I can open up iex shells on each machine:

198.51.100.0:

$ iex --name one@198.51.100.0 --cookie secret --erl '-kernel inet_dist_listen_min 9000' --erl '-kernel inedist_listen_max 9010'

203.0.113.0:

$ iex --name two@203.0.113.0 --cookie secret --erl '-kernel inet_dist_listen_min 9000' --erl '-kernel inedist_listen_max 9010'

I can successfully connect to node two from node one:

iex(one@198.51.100.0)> Node.connect(:'two@203.0.113.0')
true

And list nodes from node two:

iex(two@203.0.113.0)> Node.list
[:"one@198.51.100.0"]

My Question:

I've read that :net_kernel.allow/1 can be used to whitelist an exact list of allowed connections. But I can't seem to get it working:

iex(one@198.51.100.0)> :net_kernel.allow([])
:ok
iex(one@198.51.100.0)> Node.connect(:'two@203.0.113.0')
true

I would expect that since I've allowed a list of none, no connection would be allowed. Any tips?

Update:

I discovered that if I pass at least one value to :net_kernel.allow, it seems to work:

iex(one@198.51.100.0)> :net_kernel.allow([:'127.0.0.0'])
:ok
iex(one@198.51.100.0)> Node.connect(:'two@203.0.113.0')
false
23:38:27.702 [error] ** Connection attempt with disallowed node :"two@203.0.113.0" **
iex(one@198.51.100.0)> :net_kernel.allow([:'two@203.0.113.0'])
:ok
iex(one@198.51.100.0)> Node.connect(:'two@203.0.113.0')
true

Is that the trick?

Community
  • 1
  • 1
seanomlor
  • 973
  • 1
  • 10
  • 23
  • 2
    Be careful with whitelisting nodes. It ties your porgram to the infrastructure and makes scaling hard. If you trust your network, setting common cookie for allowed nodes should be enough. If you don't - think twice about using Erlang distributed. – tkowal Jan 19 '16 at 09:07

2 Answers2

7

Whitelisting is based on the VM Cookie, ~/.erlang.cookie. Then only authorized Nodes will be in possession of the good cookie to be able to connect.
For the security part, I've set up a Tinc mesh VPN between my servers and my laptop, and it's all the security I need while providing great flexibility.

Hécate
  • 1,005
  • 9
  • 14
  • The question is not about cookie or network security. It is about the behaviour of `net_kernel:allow/1` function. – Hamidreza Soleimani Jan 19 '16 at 08:05
  • 1
    The second part of my TL;DR at the top was more open ended with "Any other security recommendations?", so I appreciate this advice. Thank you. – seanomlor Jan 19 '16 at 17:34
3

net_kernel is a module that creates a gen_server process. In that process state it has some parameters such as allowed which holds a list of allowed nodes and at startup was initiated by an empty list.

There is an undocumented feature that if the given node for connection is not a member of allowed nodes but that list is empty, it lets the node to connect. This code snippet from net_kernel.erl module says this fact:

setup(Node,Type,From,State) ->
    Allowed = State#state.allowed,
    case lists:member(Node, Allowed) of
        false when Allowed =/= [] ->
            error_msg("** Connection attempt with "
                      "disallowed node ~w ** ~n", [Node]),
            {error, bad_node};
        _ ->
            %% set up connection to given node
    end.

Another important note is about net_kernel:allow/1 function which is an append-only function. You can check this fact in its source code when the new nodes are added to previous ones with ++ operator:

handle_call({allow, Nodes}, From, State) ->
    case all_atoms(Nodes) of
        true ->
            Allowed = State#state.allowed,
            async_reply({reply,ok,State#state{allowed = Allowed ++ Nodes}},
                        From);
        false ->
            async_reply({reply,error,State}, From)
    end;
Hamidreza Soleimani
  • 2,504
  • 16
  • 19