Instead of completely opening up RPC ports to the whole internet, I would do something quite different. Here, you're relying on the strength of your RPC auth credentials and the implementation being bugs free. Bitcoind itself strongly discourages to do what was suggested in the other answer:
-rpcbind=<addr>[:port]
Bind to given address to listen for JSON-RPC connections. Do not expose
the RPC server to untrusted networks such as the public internet!
This option is ignored unless -rpcallowip is also passed. Port is
optional and overrides -rpcport. Use [host]:port notation for
IPv6. This option can be specified multiple times (default:
127.0.0.1 and ::1 i.e., localhost)
I solved it on a network level: Since I have SSH port open anyway (though on a non-common port just for that extra little bit of obscurity on top of the obviously non-obscurity-reliant security), I opted for SSH tunneling.
LN Node
Basically I have a systemd service on the LN node that starts after boot and creates an SSH connection to my Bitcoin node. Through that tunnel, it can access the node's RPC port 18332 (testnet). The port is not exposed directly to the internet this way, instead only reachable via SSH.
The service is mostly stolen from https://avizard.blogspot.com/2021/01/aggressive-yet-sane-persistent-ssh-with.html.
$ cat /etc/systemd/system/autossh_systemd_unit.service
[Unit]
Description=AutoSSH service to remotely access RPC of Bitcoin node
#After=network-online.target
# Use this instead if autossh will interact with the local SSH server
After=network-online.target sshd.service
[Service]
User=[NONPRIV_USER]
Environment="AUTOSSH_GATETIME=30"
Environment="AUTOSSH_POLL=30"
Environment="AUTOSSH_FIRST_POLL=30"
ExecStart=/usr/bin/autossh -M 0 -N -q \
-o "ServerAliveInterval 10" \
-o "ServerAliveCountMax 3" \
-o "ExitOnForwardFailure yes" \
-p [SSH_PORT] -l [USER] [HOST] \
-i [SSH_KEY] \
-L 18332:127.0.0.1:18332 #sleep 10
# We set 'sleep 10' to make ssh exit in case no TCP connections are forwarded in 10 seconds.
# Useful to get remote shell exit codes.
ExecStop=/usr/bin/kill $MAINPID
ExecReload=/usr/bin/kill -HUP $MAINPID
Restart=always
# On Linux TCP_TIMEWAIT_LEN is not tunable and set to (60*HZ), about 60 seconds. TCP_FIN_TIMEOUT also defauls to 60 seconds.
RestartSec=60
# See systemd.kill(5)
KillMode=process
[Install]
WantedBy=default.target
Bitcoin Node
Additionally, the ufw firewall on my Bitcoin node only allows SSH access from 2 specific IPs which are the one which my Lightning node runs on and another one from which I manage my stuff.
$ sudo ufw status
Status: active
To Action From
-- ------ ----
8333 ALLOW Anywhere # Bitcoin Mainnet
18333 ALLOW Anywhere # Bitcoin Testnet
[SSH_PORT]/tcp ALLOW [LN_NODE_IP] # SSH
[SSH_PORT]/tcp ALLOW [MGMT_IP] # SSH
8333 (v6) ALLOW Anywhere (v6) # Bitcoin Mainnet
18333 (v6) ALLOW Anywhere (v6) # Bitcoin Testnet
Lastly, all users can only be logged into with autorized SSH keys and the LN node doesn't hold a key to a superuser account, instead the only user that machine (server) can always access is a non-privileged user. This means in case the LN node gets taken control of, my Bitcoin node is still fine.
$ cat /etc/ssh/sshd_config
[...]
Port [SSH_PORT]
[...]
PubkeyAuthentication yes
[...]
PasswordAuthentication no
[...]
Additionally, an IDS would be advisable on both machines, but the setup for that exceeds the scopes of this question.
I would be interested to hear what other options people use!