0

I am attempting to clear out all existing services configured in firewalld via a bash script.

# produces {cockpit,dhcpv6-client,ssh} as an example
local EXISTING_SERVICES="{$(firewall-cmd --permanent --list-service | sed -e 's/ /,/g')}"
# firewall-cmd --permanent --remove-service={cockpit,dhcpv6-client,ssh}
firewall-cmd --permanent --remove-service="${EXISTING_SERVICES}"

When this is run, firewall-cmd returns:

Warning: NOT_ENABLED: {cockpit,dhcpv6-client,ssh}
success

The problem seems to be firewall-cmd interprets the list of services to disable as a single service name, instead of a list. When I run the command manually from the shell, the same exact (copy/pasted) command works like expected.

Example script to replicate:

EXISTING_SERVICES="{$(firewall-cmd --permanent --list-service | sed -e 's/ /,/g')}"
echo "firewall-cmd --permanent --remove-service=${EXISTING_SERVICES}"
firewall-cmd --permanent --remove-service="${EXISTING_SERVICES}"

Results:

Shell results

What is the difference between running this via script and via direct shell commands?


Update: Tried running the script with set -x as suggested by @fra-san, which produced the following results when run from the script:

Results

And the following results when run from the shell:

enter image description here

It seems the shell (and/or firewalld) behaves differently when run interactively and expands the list of services into 3 separate --remove-service= flags. This is very unexpected behavior.

SnakeDoc
  • 492
  • The two firewall-cmd commands you are running are not equivalent to each other; using set -x to look at what is actually executed will likely be revealing. By the way, firewall-cmd --permanent --remove-service={cockpit,dhcpv6-client,ssh} (the commented line) would work fine in your script, right? – fra-san May 12 '21 at 20:29
  • @fra-san the existing services are unknown, so I cannot hard-code that line in as it's written in the comment... but yes, if hard-coded into the script it executes fine. I was suspecting an issue with bash variable expansion more than some issue with firewalld... I'll try set -x and see the results, didn't think of that actually! – SnakeDoc May 12 '21 at 20:32
  • @fra-san updated – SnakeDoc May 12 '21 at 20:46
  • Sorry, my fault: I should have specified that set -x is likely to be revealing if you use it when running the command interactively (the one that works). As you have found out, it doesn't tell much about what happens in your script. – fra-san May 12 '21 at 20:48
  • @fra-san No, I should have known to try both... I'm not that green ;) - anyway, updated with a new screenshot... seems something (bash, or firewalld) interprets the command differently when run interactively... which is quite unexpected behavior. – SnakeDoc May 12 '21 at 20:53

1 Answers1

3

firewall-cmd has nothing to do with that, and the difference is not between running your command in a script and running it interactively. Rather, you are executing two fundamentally different commands.

What you are seeing in action is brace expansion: the command

firewall-cmd --permanent --remove-service={cockpit,dhcpv6-client,ssh}

is expanded by Bash into

firewall-cmd --permanent --remove-service=cockpit --remove-service=dhcpv6-client --remove-service=ssh

This happens both in scripts and on the command line. Note, however, that the result of expansions is never parsed by the shell in search of further expansion-triggering characters. Hence, the command

firewall-cmd --permanent --remove-service="${EXISTING_SERVICES}"

in your script is expanded to

firewall-cmd --permanent --remove-service={cockpit,dhcpv6-client,ssh}

and run in that form (the expression in curly braces is taken literally at that point).

That seems not to be a valid firewall-cmd command. Quoting man firewall-cmd, the syntax for --remove-service is

... --remove-service=service

Remove a service. This option can be specified multiple times.

suggesting that the expected way of removing multiple services in a single run is

firewall-cmd ... --remove-service=foo --remove-service=bar ...

Using Bash, you may use an array to store the enabled services and build the corresponding list of options to remove them:

services=( $(firewall-cmd --permanent --list-services) )
firewall-cmd --permanent "${services[@]/#/--remove-service=}"

Where ${services[@]/#/--remove-service=} is Bash's pattern-substitution form of parameter expansion — # matches the empty string at the beginning of each array element and replaces it with --remove-service=.

While less efficient, in some situations it may however be more practical to add/remove one service at a time, because the exit status of firewall-cmd is set to 0 if at least one operation succeeds, regardless of how many operations fail. You may then prefer something as:

services=( $(firewall-cmd --permanent --list-services) )
for serv in "${services[@]}"
do
  firewall-cmd --permanent --remove-service="$serv" || echo "failed: $serv" >&2
done
fra-san
  • 10,205
  • 2
  • 22
  • 43
  • I should have known about brace expansion, having used it many times to create lists of directories... that's what I get for looking for the easy way to add/remove multiple services at once, tunnel vision and all. Nice catch! – SnakeDoc May 12 '21 at 21:12
  • 1
    No worries, overlooking things happens all the time, to anyone. – fra-san May 12 '21 at 21:18
  • another possible solution to add to your answer is using eval to force the brace expansion to happen anyway. eval firewall-cmd --permanent --remove-service="${EXISTING_SERVICES}" - https://unix.stackexchange.com/questions/7738/how-can-i-use-variable-in-a-shell-brace-expansion-of-a-sequence – SnakeDoc May 12 '21 at 21:19
  • 1
    Yep. Though eval comes with caveats -- the risk of code injection and the need for sanitizing the value of the to-be-evaluated string (maybe not so relevant here, since the dynamic part would be the output of firewall-cmd). I added an alternative based on arrays which should be still readable and safer without adding much typing. – fra-san May 13 '21 at 07:35
  • solid update! thanks again! – SnakeDoc May 13 '21 at 20:26