In Part 1 we turned our Monero daemon into a service.
Then in Part 2 we outlined how to troubleshoot a service that is failing to start.
Here in Part 3 we will look in more depth at security matters. Specifically firewalling with
firewalld, SELinux and securing your daemon’s API.
What is Firewalld?
Firewalld is the ‘new’ front-end to the firewall features of the Linux kernel.
It’s definitely a good idea to use a firewall on Linux as it’s one of the most simple and effective elements of securing your system. Further,
firewalld operates at the host level, whereas a network firewall only operates on traffic flowing between networks. So unlike a host firewall, it can’t protected from other systems on the same network – such as if the unlucky circumstance that another host on your network is compromised.
When to use Firewalld?
If your monero service is only listening on the internal loopback interface (localhost or 127.0.0.1), then often there is no firewall policy on that interface. That’s because only processes on the local system can talk to the loopback interface. By definition, the address
127.0.0.1 is not usable over a network.
But if your service is listening on any externally-facing interface – ethernet or wireless – then it is a good idea to make sure your firewall is on, and you are making an exception to the inbound filters for connections to
monerod. This is the interface we’ll use to accept connections from wallets on other systems.
Is Firewalld Running?
You can check this with:
systemctl status firewalld
If you do not have
firewalld installed or running, we won’t cover how to remedy that here, but it is definitely recommended.
A default firewall policy should already be installed on your Linux distribution. Usually these do two things:
- Allow all outbound connections – this system to other systems
- Block most inbound connections except
ssh– other systems to this one
There may be few other connections allowed in my default, but it is very likely your
rpc-port is blocked in the default firewall policy. So we are going to create a new rule for it below.
First let’s confirm that by taking a look at the current rules:
> firewall-cmd --list-all public (active) target: default icmp-block-inversion: no interfaces: eth0 sources: services: dhcpv6-client ssh ports: protocols: masquerade: no forward-ports: source-ports: icmp-blocks: rich rules:
This output tells us that the default zone is
public and the interface
eth0 is attached to it. We can also see that two services are allowed to talk into the system,
ssh. Note that the loopback interface (often
lo0) is not part of the default zone.
Determining the Firewall Rule to add
In Part 1 we configured
monerod to listen for wallet connections on a particular TCP port using the
rpc-bind-ip option. In this guide we actually used the default port for monero’s mainnet, which is
We now have two choices. We can allow any connection from other systems to
TCP/18081 on this system, or, we could only connections from certain IP address ranges to
TCP/18081. Everyone’s needs will vary, so I’m going to show you how to do either.
Allowing Wallet/RPC Connections From Any Source
Here we just allow the port via a port rule:
firewall-cmd --add-port 18081/tcp firewall-cmd --add-port 18081/tcp --permanent
To confirm the change is applied:
> firewall-cmd --list-all public (active) <..> services: dhcpv6-client ssh ports: 18081/tcp <..>
Allowing Wallet/RPC Connections From Specific Sources
To do this, we need to add what
firewalld calls a rich rule.
First let’s suppose that you only want to only allow from other systems on your home network, and your home network uses the IP network
192.168.0.0/16. Add the rich rules below:
firewall-cmd --add-rich-rule='rule family="ipv4" source address="192.168.0.0/16" port port="18081" protocol="tcp" accept' firewall-cmd --add-rich-rule='rule family="ipv4" source address="192.168.0.0/16" port port="18081" protocol="tcp" accept' --permanent
To confirm the change:
> firewall-cmd --list-all public (active) <..> rich rules: rule family="ipv4" source address="192.168.0.0/16" port port="18081" protocol="tcp" accept
That’s pretty much it. Even though Firewalld has been around for years, its logging capability is not great. Fortunately it’s quite reliable and you can be fairly sure the policy is doing what is says.
If you’re still not confident the right traffic is at least getting to your system’s network interface,
tcpdump is an ideal utility for this.
dnf install tcpdump tcpdump -i <interface-name> -n -nn -v 'tcp port 18081'
This will display a line with IP and port details for each packed that matches the filter
tcp port 18081.
For this section we are assuming you want to run SELinux in
Enforcing mode (you should want that!), but, SELinux is blocking
monerod from running or starting.
What is SELinux?
SELinux is a suite of tools and kernel features that provides security policy and enforcement over much of the activity happening internally on a Linux operating system. Whereas a firewall controls network traffic coming in and out of your system, SELinux acts like a firewall for internal system activity. For example, every time a process wants to read or write to a file, SELinux checks and enforces the policy for that specific combination of process and file.
SELinux used to be something most people turned off, but it’s become a lot more usable and no less essential in recent years. Nevertheless, anytime you modify your system – for example by adding new files to it – you are potentially going to have to contend with SELinux. That’s the price of good security.
How does SELinux work?
Very simply, SELinux uses the concept of labels in it’s policy definition. Labels can apply to files and processes and determine the right policy to apply. Further there are several types of labels.
We don’t need to know too many of the details, but let’s give a quick example. When a process wants to open a file, it makes a system call to the kernel. If SELinux is loaded the kernel triggers a policy check. The SELinux kernel module will look at the source labels (in this case of the process) and the destination labels (iin this case a file) and look for a policy rule matching that specific combination. If it doesn’t find one, the action will be blocked.
Why is SELinux Confusing?
The problem usually starts because a process that has an action blocked by SELinux doesn’t know that’s going on. The process will know something has gone wrong, because the system call will fail, but they may report an error that is misleading or unclear to the user. This is because many applications are not written with SELinux policy violations in mind.
Fortunately I am going to show you how to easily check for SELinux policy violations and also how to solve them to some extent. In short, the fix is usually quite simple – we just have to label the new files appropriately. The challenge is in detecting the problem and then working out the correct labels to use.
Checking Status of SELinux
We covered this briefly in Part 2, where we turned SELinux from
Permissive just in case it was causing any problems.
Before we proceed let’s just confirm we are back in in
Enforcing mode. In this mode SELinux is still going to both log and enforce policy violations:
> getenforce Enforcing
If you are
Permissive, switch to
Enforcing as follows:
> setenforce 1
Installing SELinux Tools
This package can help with solving any SELinux policy violations:
> dnf install setroubleshoot-server > dnf info setroubleshoot-server <..> Description : Provides tools to help diagnose SELinux problems. When AVC messages : are generated an alert can be generated that will give information : about the problem and help track its resolution. Alerts can be configured : to user preference. The same tools can be run on existing log files.
setroubleshoot basically goes through logs generated by the
auditd component of SELinux and packages them in a more human-readable way. It also makes a recommendation about how to fix the problem with an associated confidence level.
We definitely made some change to our filesystem in Part 1, so let’s see what those labels currently are:
ls -ZR /opt/crypto .: unconfined_u:object_r:unlabelled_t:s0 monero unconfined_u:object_r:unlabelled_t:s0 monero-data unconfined_u:object_r:unlabelled_t:s0 monero-linux-x64-v0.17.1.9.tar.bz2 unconfined_u:object_r:unlabelled_t:s0 monero-x86_64-linux-gnu-v0.17.1.9 ./monero-data: ./monero-x86_64-linux-gnu-v0.17.1.9: unconfined_u:object_r:unlabelled_t:s0 LICENSE unconfined_u:object_r:unlabelled_t:s0 monero-blockchain-ancestry unconfined_u:object_r:unlabelled_t:s0 monero-blockchain-depth unconfined_u:object_r:unlabelled_t:s0 monero-blockchain-export unconfined_u:object_r:unlabelled_t:s0 monero-blockchain-import unconfined_u:object_r:unlabelled_t:s0 monero-blockchain-mark-spent-outputs unconfined_u:object_r:unlabelled_t:s0 monero-blockchain-prune unconfined_u:object_r:unlabelled_t:s0 monero-blockchain-prune-known-spent-data unconfined_u:object_r:unlabelled_t:s0 monero-blockchain-stats unconfined_u:object_r:unlabelled_t:s0 monero-blockchain-usage unconfined_u:object_r:unlabelled_t:s0 monerod unconfined_u:object_r:unlabelled_t:s0 monero-gen-ssl-cert unconfined_u:object_r:unlabelled_t:s0 monero-gen-trusted-multisig unconfined_u:object_r:unlabelled_t:s0 monero-wallet-cli unconfined_u:object_r:unlabelled_t:s0 monero-wallet-rpc
As you can see the
-Z option to
ls shows us the current SELinux labels. On my system all files under
/opt/crypto happen to be labelled as follows:
- SELinux User: unconfined_u
- SELinux Role: object_r
- SELinux Type: unlabelled_t
These labels could be very different on your system. Defaults are very distribution dependant and when you copy files from other locations, the labels from those locations are often preserved.
Viewing SELinux Logs
Now let’s attempt to restart the service to generate some fresh logs.
systemctl restart monerod
Let’s go back to our monitoring terminal and type the following. This will display the last 100 logs generated by the
setroubleshoot package we installed earlier:
journalctl -t setroubleshoot -n 100
There can be many errors but I’m going to give just one example. Here is one of the errors original generated on my system before applying any fixes:
Jan 17 15:08:45 crypto.upaya.net.au setroubleshoot: SELinux is preventing /usr/lib/systemd/systemd from read access on the lnk_file /opt/crypto/monero. ***** Plugin catchall (100. confidence) suggests ************************** If you believe that systemd should be allowed read access on the monero lnk_file by default. Then you should report this as a bug. You can generate a local policy module to allow this access. Do allow this access for now by executing: # ausearch -c '(monerod)' --raw | audit2allow -M my-monerod # semodule -X 300 -i my-monerod.pp
Notice there is a recommended fix provided by
setroubleshoot. Now this fix results in a policy that I would not describe as ideal. In fact we don’t need a new policy rule, we just need to label our filesystem correctly. But the recommendations will usually work in a pinch, so it’s good to know they’re there, in case you fail to figure out a more appropriate fix.
Changing Filesystem Labels
So in my view the problem here is that the default policy supplied with my distribution (Centos 8) is not expecting
systemd to need access to files in the /
opt directory. That’s because
/opt is really outside the normal filesystem hierarchy for a base Linux install. So in principle we just need to label these new files and directories similar to directories that
systemd can access.
So here are the changes needed on my system to make the service to run successfully.
These commands add default labels, for the paths we created in Part 1, to the current policy:
semanage fcontext -a -s system_u -t default_t "/opt/crypto(/.*)?" semanage fcontext -a -s system_u -t bin_t "/opt/crypto/monero/monerod"
Essentially this allows all the matching paths to be read by
systemd, and the
monerod daemon to be executed. It’s a fairly safe change to make and is not too lax or permissive unlike the recommended fix from
This command restores that policy to the actual filesystem, and will update the labels accordingly:
restorecon -vR /opt/crypto/
Test and Test Again
At this point you can try restarting the monerod service and checking the status and logs again.
From here, this is very much a game of whack-a-mole. Each time you update the policy and restart the service, you may get a brand new error from SELinux. So repeat the process until you see no more errors when starting
In summary, if you are still getting SELinux errors after applying my recommended policy change, you can probably resolve the problem by using the recommendations from
setroubleshoot. This is still preferable to keeping SELinux in
Securing the Monero Daemon API
Wallets talk to monero daemons using an API, which monero calls RPC. The monero API allows you to do all sorts of things, but wallets only need very basic access and this is more secure.
We already restricted the type of calls that the
monerod would accept with this option:
RPC over TLS (SSL)
Another important security feature is to make sure your RPC communication channel is using TLS. This is the default configuration for
monerod so it should already be working. The daemon will generate its own certificate the first time, and wallets will start up TLS if requested (or demanded) by the server. This ensures that private information is not transmitted over your network in clear text – a good idea even for local area network communication.
To force TLS you can add this option to your
Of course there are also options to replace the self-generated certificate and key with your own.
You can also require wallets to authenticate themselves to the server. Otherwise any wallet that can reach the RPC port could connect to your server.
This username and optional password are stored in plain text in your
monerod.conf or passed to
monerod on the command-line. And the username at least is transmitted in clear text unless TLS is securing the communications. So this is not a particularly secure authentication mechanism, but it does add one more layer of protection and one more obstacle to any would-be attackers.
To use a login from a CLI wallet, add the
--daemon-login option to the command-line, for example:
/opt/crypto/monero/monero-wallet-cli --daemon-address <rpc-bind-ip>:<rpc-bind-port> --wallet-file <path-to-wallet> --daemon-login <username>
You will be prompted to enter your password afterwards. If you didn’t configure one, just hit enter at the prompt:
This is the command line monero wallet. It needs to connect to a monero daemon to work correctly. WARNING: Do not reuse your Monero keys on another fork, UNLESS this fork has key reuse mitigations built in. Doing so will harm your privacy. Monero 'Oxygen Orion' (v0.17.1.9-release) Logging to /opt/crypto/wallets/monero-wallet-cli.log Wallet password: XXXXXXXX Daemon client password:
In addition to listneing on a port for RPC communication, your daemon will also be listening on another port for accepting connections from other nodes/daemons. This is called Peer-to-peer (P2P) communication. The default port for monero
Unlike the RPC port,
monerod will be listening on the P2P port on all interfaces.
Displaying Open Ports & Connections
You can verify this as follows:
> ss -tapn | grep 1808 LISTEN 0 128 0.0.0.0:18080 0.0.0.0:* users:(("monerod",pid=1278290,fd=10)) LISTEN 0 128 0.0.0.0:18081 0.0.0.0:* users:(("monerod",pid=1278290,fd=14)) LISTEN 0 100 127.0.0.1:18082 0.0.0.0:* users:(("monerod",pid=1278290,fd=21)) ESTAB 0 0 10.0.33.7:58238 22.214.171.124:18080 users:(("monerod",pid=1278290,fd=31)) ESTAB 0 0 10.0.33.7:49816 126.96.36.199:18080 users:(("monerod",pid=1278290,fd=29)) ESTAB 0 0 10.0.33.7:34326 188.8.131.52:18080 users:(("monerod",pid=1278290,fd=34)) ESTAB 0 0 10.0.33.7:45206 184.108.40.206:18080 users:(("monerod",pid=1278290,fd=27)) ESTAB 0 0 10.0.33.7:40124 220.127.116.11:18080 users:(("monerod",pid=1278290,fd=22)) ESTAB 0 0 10.0.33.7:45540 18.104.22.168:18080 users:(("monerod",pid=1278290,fd=33)) ESTAB 0 0 10.0.33.7:54586 22.214.171.124:18080 users:(("monerod",pid=1278290,fd=30)) ESTAB 0 0 10.0.33.7:37150 126.96.36.199:18080 users:(("monerod",pid=1278290,fd=32)) ESTAB 0 0 10.0.33.7:37474 188.8.131.52:18080 users:(("monerod",pid=1278290,fd=37)) ESTAB 0 0 10.0.33.7:46602 184.108.40.206:18080 users:(("monerod",pid=1278290,fd=28)) ESTAB 0 0 10.0.33.7:52392 220.127.116.11:18080 users:(("monerod",pid=1278290,fd=35)) ESTAB 0 0 10.0.33.7:48160 18.104.22.168:18080 users:(("monerod",pid=1278290,fd=36)) ESTAB 0 0 10.0.33.7:18081 10.0.33.5:59656 users:(("monerod",pid=1278290,fd=38))
This command is listing any open ports or connections to or from the server on TCP ports containing ‘1808’. Let’s go through them. I’ve added line numbering for reference and IP addresses have been changed to protect the innocent.
- Lines 2-3 show the server is listening for new inbound connections over all interfaces on ports 18080 and 18081 as we expected (mainnet RPC and P2P respectively).
- Line 4 shows it’s also listening for local connections on a port we haven’t discussed yet – 18082. We’ll cover this in the next section.
- Lines 5-16 lists twelve outbound connections on port 18080. These were initiated by my daemon to other monero daemons, also referred to as peers or nodes. This is how my node is communicating with the monero network, getting new blocks etc. Each of these nodes will connect to twelve or more other nodes and so on.
- Finally, line 17 is an inbound connection on port 18081. This happens to be is from a wallet on another system in my network.
Notice there are no inbound connection to port 18080, even though my server is listening. That’s because my host and network firewalls don’t allow that. Essentially that makes my node a child node of the monero network. I am relying on other peers for my information, but I’m not providing the same service back to other peers that would like to get synced.
Allowing Connections from Other Peers
Is this a problem? Well, ideally I would be contributing more fully to the network by allowing inbound peers as well, but for security reasons I have chosen not to in this case. If you do want to allow peering connections, you can follow similar steps to enable this port in
firewalld (as well as any network firewall/router you have). There is no authentication for P2P connections because of course these could be from anywhere on the internet. But the number is limited for each node so that connections do not become concentrated on only a few nodes, so the overhead is not large. And no private information is available over the P2P channel so it’s actually not as essential to lock it down, as long as you trust the monero code itself.
Now since my firewalld is blocking the connection on port 18080, I don’t really care that the server is still listening on that port – nothing will get in either way. But if you do want to lock it down further, you can tell your daemon to only listen on localhost as follows:
Remember that line 4 was showing our system listening on the loopback interface on port 18082. Well, apparently this is an experimental or unfinished feature in monero. It’s not causing too much harm listening on localhost, but it can be disabled altogether with the following option:
Verify Your Binaries
Monero Security Summary
That’s about it for securing
monerod. There is a very interesting but still experimental feature in the works called I2P, so we won’t cover that here. I may add another section on that as it develops. In summary:
- Secure your network with a firewall
- Secure your Linux hosts with firewalld (or iptables)
- Do use SELinux
- Restrict access to your daemon, in particular the RPC channel
- Only enable features that are actually needed or that contribute to the community!