I’m looking for a way to temporarily open a port in NixOS. I want to test a web app I develop on my mobile phone. The app is served from my laptop using miniserve
, like this:
miniserve --index=index.html dist
I can access it from the laptop at http://localhost:8080/
and the IP address of the WIFI, i.e. http://192.168.1.56:8080/
. But the same address on my phone doesn’t respond. I’m pretty sure it’s because the firewall settings on my NixOS laptop.
I could open the ports in /etc/nixos/configuration.nix
and run sudo nixos-rebuild switch
, but that’s not convenient and also not secure (I would most likely forget to close the port later). Is there a way to temporarily open a port from the command line? Preferably without super user access. Perfect solution would be to integrate it with Direnv, but I’ll appreciate anything more convenient than nixos-rebuild switch
.
6 Likes
Use iptables
to add or remove firewall rules imperitively. I’m fairly certain NixOS doesn’t automatically save/restore iptables rules, so any rules you create this way will not persist across reboots.
- List firewall rules:
sudo iptables -L
- Open port 8080:
sudo iptables -A INPUT -p tcp --dport 8080 -j ACCEPT
- Identify firewall rule number:
sudo iptables -L INPUT --line-numbers
- Remove firewall rule:
sudo iptables -D INPUT <RULE_NUM>
I know of no method to alter firewall rules without root privileges.
2 Likes
If you’re trying to avoid typing in a password just to enable or disable the firewall rule, you can use a script to temporarily enable the port.
# file: ~/bin/run-with-port
#!/usr/bin/env bash
# Usage: sudo run-with-port <port> <cmd> <args...>
set -ueo pipefail
open-port() {
local port=$1
iptables -A INPUT -p tcp --dport $port -j ACCEPT
}
close-port() {
local port=${1:-0}
iptables -D INPUT -p tcp --dport $port -j ACCEPT
}
if [[ -z "$1" ]]; then
echo "Port not given" >&2
exit 1
fi
PORT=$1
shift; # Drop port argument
if [[ 0 -eq $# ]]; then
echo "No command given" >&2
exit 1
fi
open-port $PORT
# Ensure port closes if error occurs.
trap "close-port $PORT" EXIT
# Run the command as user, not root.
runuser -u $SUDO_USER -- "$@"
# Trap will close port.
I just wrote this, tested that it opens and closes the port, but not much more thoroughly than that.
You can update your sudoer config to allow running the run-with-port
script without a password, and you now have:
- No need to type password to open port.
- Can run any single command while port is open.
- Port is closed after command stops, even if command errors.
4 Likes
The much easier option: pick a port range (10100 to 10110 as an example) and open that in the firewall from your configuration.nix with a nice comment explaining why. When you are working on a project that requires external access, have it listen on one (or more) of the ports in the range. There is no harm leaving a port open in the firewall if there is nothing listening.
3 Likes
Thanks. Thinking about it a little more I realized that important aspect is easy collaboration with other developers. I want the development environment to be automatically reproduced with with as much detail as possible. That’s one of the big advantages of using Nix. So asking people to change global system configuration is not appealing.
But I guess it’s a safe assumption that every developer will be a /sudoer/ on their development computer. So using sudo
in a script should be fine.
Is there a sudo
-less solution for other distros?
Just to be clear, http://192.168.1.56:8080/
is not accessible from your phone (which is on the same WiFi network)? This workflow works in Ubuntu and Windows at least…
AFAIK Ubuntu desktop simply has no firewall enabled by default. That’s why your workflow works out of the box.
I think your best bet for convenience is allowing all traffic from the local network.
3 Likes
Another option might be to set up tailscale, and set tailscale0 as a trusted interface. I haven’t tested this, but I think it should work. I just have certain common ports whitelisted in my firewall settings in configuration.nix
.
5 Likes
after opening port 8080, you need reload firewall service to take effect
sudo iptables -A INPUT -p tcp --dport 8080 -j ACCEPT
systemctl reload firewall
2 Likes
A little trick I use: you define a firewall rule that allows traffic to TCP ports listed in an ipset and similarly for UDP.
By default the sets are empty, but you can then use the ipset
command to dynamically add ports to either set as required without having to reload your full set of firewall rules.
2 Likes
So here do you mean that you just pass nixos a config like
networking.firewall.allowedTCPPorts = [];
…and then you use ipset
to set them dynamically?
That specific option only deals with ports.
You need to use networking.firewall.extraCommands
and set the rule in there. There’s a brief example here: https://serverfault.com/a/946207
2 Likes
Thanks I should have realized this
I could not make your script work on my NixOS. I have modified it like so:
#!/usr/bin/env bash
# Usage: sudo run-with-port <port> <cmd> <args...>
set -ueo pipefail
ip46tables() {
iptables "$@"
ip6tables "$@"
}
open-port() {
local port=$1
ip46tables -I nixos-fw 1 -p tcp --dport $port -j nixos-fw-accept
}
close-port() {
local port=${1:-0}
ip46tables -D nixos-fw -p tcp --dport $port -j nixos-fw-accept
}
if [[ -z "$1" ]]; then
echo "Port not given" >&2
exit 1
fi
PORT=$1
shift; # Drop port argument
if [[ 0 -eq $# ]]; then
echo "No command given" >&2
exit 1
fi
open-port $PORT
# Ensure port closes if error occurs.
trap "close-port $PORT" EXIT
# Run the command as user, not root.
runuser -u $SUDO_USER -- "$@"
# Trap will close port.
You need to show what your tables look like with iptables -L
after you’ve added a port for anyone to be able to comment on what’s going on. But the easiest workaround the issue if the port doesn’t matter, is just to pre-allow some random high range and then pick a port from there when you need something exposed.