How to handle "sudo ufw" failing in a subshell when the parent shell is closed?

Viewed 71

I'm writing a shell script which will run in the background to control Syncthing and manage a UFW firewall in relation to Syncthing.

Here it is in simplified form:

#!/bin/bash

sync () {
    # Open the ports which Syncthing communicates on.
    sudo ufw allow syncthing &> /dev/null
    # Start Syncthing, and block script execution here until Syncthing is closed.
    syncthing &> /dev/null
    # Close the ports which Syncthing communicates on once it is closed.
    sudo ufw delete allow syncthing &> /dev/null
}

# Get sudo before the sync function is backgrounded.
sudo -v
# Run the sync function in the background.
sync &

This script works as intended when the terminal it is run from remains open.

If the terminal it is run from is closed while Syncthing is running though, then ports in the firewall are not closed when Syncthing is closed.

Is there a way to get this script to run properly -- closing the ports in the firewall after Syncthing is closed -- when the terminal it is started from is closed before Syncthing is closed?

Here is a script which you can use to experiment with this behaviour. It doesn't require Syncthing to be installed, and it outputs to syslog:

#!/bin/bash

test_function () {
    echo '-- Opening port' | logger -t TEST
    sudo ufw allow 80 | logger -t TEST
    echo '-- Close the terminal you started this script from in the next 10 seconds' | logger -t TEST
    sleep 10
    echo '-- Closing port' | logger -t TEST
    sudo ufw delete allow 80 | logger -t TEST
}

sudo -v
test_function &
2 Answers

I guess the sudo cache you create with sudo -v is tied to the terminal session, and goes away immediately when you log out.

The simple workaround then is to run the entire command using sudo.

#!/bin/sh

sync () {
    ufw allow syncthing
    su "$SUDO_USER" -c syncthing
    ufw delete allow syncthing
}

test "$SUDO_USER" && test -w / || {
  echo "${0##*/}: run this script using sudo" >&2
  exit 126
}

sync >/dev/null 2>&1 &

I also refactored the redirections to the caller; this should hopefully make it easier to change it to write diagnostics from ufw and syncthing to a log file, for example.

The su command runs syncthing as the invoking user; I am unfamiliar with its functionality, so this may be insufficient if it requires a login session or access to your desktop environment e.g. to display a GUI (and overkill if you don't mind running it as root).

Only the redirections were using Bash syntax, so replacing those with portable sh syntax allowed me to change the shebang to #!/bin/sh; on many systems, that should allow this script to consume significantly less resources.

Alternatively, update your sudoers privileges to allow you to run these specific ufw commands passwordless. If you have /etc/sudoers.d you can create a new file there to grant yourself these permissions.

you ALL=(root) NOPASSWD: /usr/bin/ufw allow syncthing
you ALL=(root) NOPASSWD: /usr/bin/ufw delete allow syncthing

(where obviously you'd replace you with your actual account name, and perhaps check the path to ufw which I simply guessed).

I used one of triplee's suggestions to handle this problem by enabling passwordless sudo for the ufw commands I wanted to run.

To do that I created a file called ufw in /etc/sudoers.d/ with this content:

%sudo ALL=(root) NOPASSWD:/usr/sbin/ufw allow syncthing
%sudo ALL=(root) NOPASSWD:/usr/sbin/ufw delete allow syncthing

Then with passwordless sudo in place, my script became:

#!/bin/bash

sync () {
    sudo ufw allow syncthing
    syncthing
    sudo ufw delete allow syncthing
}

sync &> /dev/null &

Now even when the terminal that it is run from is closed while syncthing is running, the ufw delete command still fires when syncthing is closed.

Related