RFC: Erlang Dist Security Filtering Prototype #1
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Erlang Dist Security Filtering Prototype
The Erlang Distribution Protocol (erldist) is used extensively to connect service nodes for use with clustered message routing.
When it comes to security, however, we have found erldist to be lacking, albeit by design, as mentioned in the docs with the following warning:
What exactly is Erlang Dist?
Erlang Dist is a bidirectional and stateful protocol between Erlang nodes (technically, other languages are also supported and are referred to as C or Hidden Nodes).
What does that actually look like in practice? Let's start two nodes in two separate terminals:
Initially, the nodes are not connected to any other nodes.
Let's run the following on our
foo@127.0.0.1
node:Our call to
net_adm:ping('bar@127.0.0.1').
resulted in a new connection between the two nodes.What can we do with this new connection?
First, let's try manually copying a pid binary from one terminal to another.
Let's run the following on our
foo@127.0.0.1
node:Let's copy the binary and run the following on our
bar@127.0.0.1
node:Let's run
receive
on ourfoo@127.0.0.1
node and send something back (remember, erldist connections are bidirectional):Let's run
flush().
on ourbar@127.0.0.1
node:Great, message passing is working. Now what?
What else can we do?
As it turns out: we can do a lot of things right out of the box.
We can start arbitrary functions on another node:
We can call any function on another node and return the result to the caller:
We can reset the
group_leader
of any process on another node which will do things like redirect console output (or calls toio:format/2
) over the network to the calling node:We can send messages to any pid, registered process or port, or alias on another node:
How is Erlang Dist implemented?
As of Erlang/OTP 24, erldist is implemented in four main parts:
dist.c
:erlang:dist_ctrl_get_data/1
erlang:dist_ctrl_get_data_notification/1
erlang:dist_ctrl_get_opt/2
erlang:dist_ctrl_input_handler/2
erlang:dist_ctrl_put_data/2
erlang:dist_ctrl_set_opt/3
net_kernel
module for keeping track of connections and Dist Controller handles.dist_util
module which handles the handshake and net tick portions of the protocol.proto_dist
module, likeinet_tcp_dist.erl
, which handles the transport or "carrier" for the protocol.NOTE: There are also other parts specific to each
proto_dist
, like the customizations built into the Port Driver system leveraged byinet_drv.c
or thetls_sender.erl
andtls_socket.erl
which are leveraged byinet_tls_dist.erl
. In addition, some helper functions, likeerts_internal:dist_spawn_init/1
are used bydist.c
.What is Erlang Dist Security Filtering?
In short: it's a Firewall for Erlang Dist.
Filters (or rules) are configured per-node connection using either coarse-grained or fine-grained BIF calls that modify the
dist_entry
table withindist.c
for the given connection.Coarse-grained filters may be configured with either
accept
orreject
(withaccept
being the default):Fine-grained filters may be configured for
reg_send
andspawn_request
command types:Fine-grained filters may also be removed for
reg_send
andspawn_request
command types:Combined filtering may be tested to see whether a given command from a node will result in an
accept
orreject
:In addition, there is an option to enable Erlang-based filtering for
reg_send
,group_leader
,send
,spawn_request
, andalias_send
commands:The
my_dist_handler
module can be enabled per-node with:Any inbound control commands for these types, if accepted by the coarse-grained and fine-grained filters, will spawn a new process and execute the above callbacks based on the command type.
How is Erlang Dist Filtering implemented?
The implementation is still subject to change, but as of 2021H2:
dist_entry
table from thenet_kernel
module indist.c
:erlang:dist_add_filter/4
erlang:dist_del_filter/3
erlang:dist_set_filter/3
erlang:dist_set_handler/3
erlang:dist_test_filter/3
erts_internal
module to supportHandler:dist_spawn_init(Node, M, F, A)
callbacks for custom handlers.dist_util
andnet_kernel
to support calling BIF functions from (1):net_kernel:add_filter/4
net_kernel:del_filter/3
net_kernel:set_filter/3
net_kernel:set_handler/3
net_kernel:test_filter/3
dist_entry
struct anderl_alloc.types
to support a newdist_filter
hash table inerl_node_tables.h
.erl_create_process
inerl_process.c
so thatspawn_reply
is only enabled if thetag
on theErlSpawnOpts
is equal toam_spawn_reply
.