bartekupartek

bartekupartek

Is -ssl_dist_optfile the way to secure cluster of Beam nodes? How to secure connections of publicly exposed Beam nodes and epmd?

I’ve multiple api instances installed on the on premise servers that are sometimes offline. I’d like to setup one master admin node that would allow me to connect to all of them when they are online. The further concern is that it would be nice to connect tenants only to admin node but not to each other, so that only admin node should know about all tenants.

To achieve this goal I’ve setup admin project with Elixir 1.9 with Dockerfile that exposes following ports:

ENV APP_PORT=4000 BEAM_PORT=9000 ERL_EPMD_PORT=4369
EXPOSE $APP_PORT $BEAM_PORT $ERL_EPMD_PORT

and configured release with limited range of ephemeral ports of Beam node:
env.sh.eex

case $RELEASE_COMMAND in
  start*|daemon*)
    ELIXIR_ERL_OPTIONS="-kernel inet_dist_listen_min $BEAM_PORT inet_dist_listen_max $BEAM_PORT"
    export ELIXIR_ERL_OPTIONS
    ;;
  *)
    ;;
esac
export RELEASE_DISTRIBUTION=name
export RELEASE_NODE=<%= @release.name %>@my_brodcast_domain.com

The dockerimage is deployed in the Kubernetes cluster and this setup is valid until I’m using this ports only inside the k8s cluster. As I’ve mentioned my most of tenant applications are outside k8s cluster on the on premise servers, so I’ve setup my_brodcast_domain.com domain that resolves Kubernetes LoadBalnacer Service witch 4369, 9000, 4000 ports publicly:

apiVersion: v1
kind: Service
metadata:
  name: admin-api
  namespace: admin-api
  labels:
    app.kubernetes.io/name: admin-api
    app.kubernetes.io/part-of: admin-api
spec:
  selector:
    app.kubernetes.io/name: admin-api
    app.kubernetes.io/part-of: admin-api
  type: LoadBalancer
  ports:
  - port: 4369
    targetPort: 4369
    name: epmd
  - port: 9000
    targetPort: 9000
    name: erlang
  - port: 80
    targetPort: 4000
    protocol: TCP
    name: app

I’m able to connect to admin node from tenant servers instances:

iex --name "myapp2@my_brodcast_domain.com" --cookie "super_super_secret"

Also I’m able to login by remote console “remsh” to admin node and I’ve got access to all connected nodes, hurray I was happy for a while:

iex(admin_api@my_brodcast_domain.com)2> Node.list
[:"myapp2@my_brodcast_domain.com", :"myapp1@my_brodcast_domain.com",
 :"myapp3@my_brodcast_domain.com", :"myapp4@my_brodcast_domain.com"]

but now my concern is about security, I’ve read that I shouldn’t never expose Beam port and epmd port in the public network.

Since Erlang/OTP 20.2 release there is ssl_dist_optfile option with following description:

A new command line option -ssl_dist_optfile has been
added to facilitate specifying the many options needed
when using SSL as the distribution protocol.

I couldn’t find any interesting resource that would answer my question if is it possible to use it to secure my described cluster? If yes how to configure it with Elixir 1.9 release?
I need to exclude using of SSH port tunnels because clients are installing this apps by docker-compose and with just run command and they don’t have technical skills to configure SSH to pass communication over bastion host etc.
Are there some other options to achieve my goal?

Most Liked

dch

dch

A few things:

  1. never ever ever expose epmd & your node to the internet. Opinions vary about whether your phoenix/cowboy/… app can be directly accessible, but personally I leave TLS to haproxy or similar tools, do basic route and denial-of-service handling (max connections/second etc) there, and then spread that out across my OTP instances. I also use a firewall to handle blocking really bad actors where haproxy is not sufficient - I then distribute that blocklist across all nodes.

  2. I restrict the range of inter-node ports that the VM can use, and via firewall restrict that range to only permitted nodes. This may require that non-trusted nodes need to bridge via ssh to get in, but I consider direct node access something that users should never get, have, nor need.

  3. erlang cookies are not a particularly big space, and can be brute-forced in parallel - there is no ratelimiting nor any other significant protection. By default the auto-created erlang cookie is a 20 byte sequence of upper-case ASCII characters so choose your own, and make it longer with a wider key space. Leave your epmd listening only on a VPN or private network if you must.

  4. In my specific case, all BEAMs are connected to an internal IPv6 only mesh vpn and this means that any user has to auth to the vpn first. My haproxy instances can bridge the real world to the vpn world, across any physical node.

  5. This makes connecting via remsh like this from a permitted node viz #2:

$ ERL_ZFLAGS='-proto_dist inet6_tcp -kernel inet_dist_listen_min 4370 -kernel inet_dist_listen_max 4400' \
    iex  --cookie (ssh koans@i09 cat .erlang.cookie) \
    --name console@wintermute.zt.koans \
    --remsh zen@i09.zt.koans
  1. TLS only encrypts the traffic between nodes unless you verify certificates. In my experience, TLS has been a huge pain in the butt to manage. I don’t like it, and use mesh vpn or spiped to avoid it. https://www.erlang-solutions.com/blog/erlang-distribution-over-tls.html

  2. As a final point, there is no particular need to use the same cookie on all nodes if your app doesn’t require the mesh. You can manually initiate remsh connections from your control node, using Node.set_cookie/2 and then Node.connect/1 will use a per-cookie node.

Finally, I rarely need console access once apps are deployed to production, so I’m curious what drives this requirement. Do you use distributed erlang layer at all? If not, don’t configure it, and just use ssh port forwarding to deal with any nodes that need some remsh love.

benwilson512

benwilson512

Author of Craft GraphQL APIs in Elixir with Absinthe

You can also solve this at the network layer by leveraging AWS VPCs, or the equivalent thereof in your cloud of choice. We don’t allow public internet traffic to hit our application servers at all. Public internet traffic hits load balancers, which route traffic on HTTP / HTTPs ports to the nodes.

@bartekupartek is using kubernetes so this is probably already how he’s doing things. For remsh look into kubectl exec.

NOTE: this does require that you trust your private network. If you’re a small to medium sized company this may be reasonable. If you’re large and doing multi-tenant stuff, you have to lose this assumption and do more complex stuff.

Where Next?

Popular in Questions Top

9mm
I am constructing a JSON object (map) and I need to conditionally set a field. I’m trying to write proper elixir-way code… and I’m at a l...
New
shahryarjb
Hello, I get Persian date from my client and convert it to normal calendar like this: def jalali_string_to_miladi_english_number(persi...
New
JorisKok
I have a server on AWS, and was running a load test using artillery. When looking at the Phoenix dashboard I see the Ports going to 100% ...
New
New
ovidiubadita
Hey all, I discovered Elixir and I love it. I always wanted to learn a functional programming and I intended to go for Haskell, but afte...
New
alice
Hey, Just curious what are the main benefits of Elixir compared to Clojure? When is Elixir more useful than Clojure and vice versa? Th...
New
bsollish-terakeet
Credo is smart enough to check for (something like) this: assert length(the_list) == 0 with this response: Checking if an enum is empt...
New
jason.o
In the code below, if the create action is not set to accept “extra_key” as an input, it errors out with a message shown above. Is there ...
New
joaquinalcerro
Hi there, I am working with Ecto-Postgresql and I need to call all of the records from a specific table but the table has 40,000 record...
New
marick
I had some trouble figuring out how to make many-to-many associations work. Once I got it working, I wrote a blog post. Because I'm a nov...
New

Other popular topics Top

johnnyicon
Hi all, I've just started learning Elixir and Phoenix Framework, so please pardon my n00bness at this stage. I'm trying to use Postg...
New
shahryarjb
Hello, I have map which I want to convert it to string like this: the map: %{last_name: "tavakkoli", name: "shahryar"} the string I ne...
New
fireproofsocks
Forgive me if this is obvious, but how does one delete a database record WITHOUT selecting it first? https://hexdocs.pm/ecto/Ecto.Repo.h...
New
aalberti333
As the title describes, I’m trying to run Enum.map() over a list of key/value pairs, where the value is a map. My data looks like this: ...
New
grych
Hi folks, Few months ago I have announced the proof-of-concept of the library to manipulate the browsers DOM objects directly from Elixi...
639 52238 488
New
bsollish-terakeet
Credo is smart enough to check for (something like) this: assert length(the_list) == 0 with this response: Checking if an enum is empt...
New
baxterw3b
Hi guys, i’m new in the Elixir world, and i have to say, that i love it! i’m having some problem to understand anonymous functions with ...
New
boundedvariable
I am going through the kafka architecture. All the features what the kafka is providing are already in Erlang. I would like hear your opi...
New
Qqwy
Update: How to use the Blogs &amp; Podcasts section You can post links to your blog posts or podcasts either in one of the Official Blog...
3271 126226 1237
New
lanycrost
Hi everyone! I need implement if…else if…else condition from my elixir code, and anymore of this control flow structures not work proper...
New

We're in Beta

About us Mission Statement