1
votes

Background:

I am trying to write a program in Elixir to test distributed algorithms by running them on a set of processes and recording certain statistics. To begin with I will be running these processes on the same machine, but the intention is eventually to have them running on separate machines/VMs.

Problem:

One of the requirements for algorithms I wish to implement is that messages include authentication. That is, whenever a process sends a message to another process, the receiver should be able to verify that this message did indeed come from the sender, and wasn't forged by another process. The following snippets should help to illustrate the idea:

# Sender
a = authenticate(self, receiver, msg)
send(receiver, {msg, self, a})
# Receiver
if verify(msg, sender, a) do
  deliver(msg)
end

Thoughts so far:

I have searched far and wide for any documentation of authenticated communication between Elixir processes, and haven't been able to find anything. Perhaps in some way this is already done for me behind the scenes, but so far I haven't been able to verify this. If it were the case, I wonder if it would still be correct when the processes aren't running on the same machine.

I have looked into the possibility of using SSL/TLS functions provided by Erlang, but with my limited knowledge in this area, I'm not sure how this would apply to my situation of running a set of processes as opposed to the more standard use in client-server systems and HTTPS. If I went down this route, I believe I would have to set up all the keys and signatures myself beforehand, which I believe could possible using the X509 Elixir package, though I'm not sure if this is appropriate and may be more work than is necessary.

In summary:

  1. Is there a standard/pre-existing way to achieve authenticated communication between processes in Elixir?
  2. If yes, will it be suitable for processes communicating between separate machines/VMs?
  3. If no to either of the above, what is the simplest way I could achieve this myself?
2
Why would not you trust messages from other erlang processes running by the same ErlangVM/cluster in the first place? - Aleksei Matiushkin
@AlekseiMatiushkin's point is right, I think. Once you have ErlangVMs connected into a cluster, they have a lot of access to each other. In particular it's possible to execute arbitrary code on one from the other. I'd say you should make sure that the connection between the VMs itself is done securely (for example over an encrypted channel), not try to enforce it on the level of messages between processes. - Paweł Obrok
Thanks both for the responses. @PawełObrok The answer to Aleksei's question is that I may deliberately set up some processes to misbehave. It's part of the tests that I want to do. I know it's a bit weird. The possibility of executing arbitrary code won't actually be a problem in my case - I just need to be sure that messages are authenticated. Are you suggesting that authentication can be achieved simply by the way the VMs are connected? If so, I might have to look into that. - Chris Dueck
may set up processes which are supposed to act nefariously as part of my testing How does supposed authentication help with avoiding a nefarious process? Normally this would be handled by whatever proxy/load balancer you put in front of your system, i.e. the inside is trusted, the outside isn't, and the clients normally authenticate e.g. using certificates. This has no effect on algorithms, since in any real world scenario it's handled at the boundary, potentially with dedicated hardware/network applications. So whatever you're trying to model is a fantasy, nobody will run it that way, ever. - Kuba hasn't forgotten Monica
@UnslanderMonica As I showed in the question the idea is that if a process did send a message with the name of a different one, the verification step would fail and it would not be accepted. I understand that this is not standard and I never claimed that it was. However, the correctness of the algorithms I wish to test is entirely reliant on this. Saying "this has no effect on algorithms" is simply not true in my case. I'm not doing anything with load balancers or clients from the outside. I feel you may have misunderstood my situation and calling it a fantasy is frankly not helpful. - Chris Dueck

2 Answers

3
votes

As Aleksei and Paweł point out, if something is in your cluster, it is already trusted. It's not quite like authenticating random web requests that could have originated virtually anywhere, you are talking about messages originating from inside your local network of trusted machines. If some nefarious actor is running on one of your servers, you have far bigger problems to worry about than just authenticating messages.

There are very few limitations put on Elixir/Erlang processes running inside a cluster with respect to security: their states can be inspected by any other process, for example. Some of this transparency is by-design and necessary in order to have a fault-tolerant system capable of doing hot-code reloads, but the conversation about the specific how's and why's is too nuanced for me to do it justice.

If you really need to do some logging to have an auditable "paper trail" to verify which process sent which message, I think you'll have to roll your own solution which could rely on a number of common techniques (such as keys + signatures, block-chains, etc.). But keep in mind: these are concerns that would come up if you were dealing with web requests between different servers anyhow! And there are already protocols for establishing secure connections between computers, so I would not recommend re-inventing those network protocols in your application.

Your time may be better spent working on the algorithms themselves and not trying to re-invent the wheel on security. Your app should focus on the unique stuff that nobody else is doing (algorithms in your case). If you have multiple interconnected VMs passing messages to each other, all the "security" requirements there come with defining the proper access to each machine/subnet, and that requirement holds no matter what application/language you're running on them.

2
votes

The more I read what are you trying to achieve, the more I am sure all you need is the footprint of the calling process.

For synchronous calls GenServer.handle_call/3 you already have the second parameter as a footprint.

For asynchronous messages, you might add the caller information to the messages themselves. Like, instead of sending a plain :foo message, send {:foo, pid()} or somewhat even more sophisticated like {:foo, {pid(), timestamp(), ip(), ...} and make callee to verify those.

That would be safe by all means: cluster would ensure these messages are coming from trusted sources, and your internal validation might ensure that the source is valid within your internal rules.