1

If you have experience, could you please advise, how to safely apply firewall rules remotely on Linux using NFT ?

Particularly on Debian, we used for a long time iptables-apply(8) to safely apply remotely firewall rules to avoid lock out ourselfs in case of some mistakes in rules. As of now, latest Debian release comes with nftables instead of iptables, and official advise is to start using new tool nft. I known that there is wrapper that converting old style iptables rules on the fly, but everywhere it advised to not mix old style with new one, so we finally decided to switch all rules to a new (pf kinda) style, but we are still a humans and won't lock out remote servers in case of mistake in rules, so shortly, is there some procedure to do the same as iptables-apply but using nft ?

For some reason, google and bing keep it either in a secret, so I appreciate upfront if one would show a road to a truth.

P.S. I asked the same question half month ago on superuser but no one found any solution, so I'm sorry for cross-posting, but half month waiting on one resource I thing is enough to time to ask it here...

2 Answers 2

1

iptables-apply is a very basic bash script which I'm sure you can edit to make it compatible with nft. In case you do, it would be great if you published it somewhere.

0

Here is the free adaptation I made of iptables-apply for nftables:

#!/usr/bin/env bash
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation, either version 3 of the License, or (at your
# option) any later version.  Please see LICENSE.txt at the top level of
# the source code distribution for details.
#
# @package nftables-apply
# @author <[email protected]>
# @link https://github.com/fbouynot/scripts/blob/main/nftables-apply
# @copyright <[email protected]>
#
# Free adaptation for nftables of iptables-apply (https://github.com/wertarbyte/iptables/blob/master/iptables-apply)
#

# -e: When a command fails, bash exits instead of continuing with the rest of the script
# -u: This will make the script fail, when accessing an unset variable
# -o pipefail: This will ensure that a pipeline command is treated as failed, even if one command in the pipeline fails
set -euo pipefail

# Replace the Internal Field Separator ' \n\t' by '\n\t' so you can loop through names with spaces 
IFS=$'\n\t'

# Enable debug mode by running your script as TRACE=1 ./script.sh instead of ./script.sh
if [[ "${TRACE-0}" == "1" ]]
then
    set -o xtrace
fi

# Define constants
PROGNAME="${0##*/}"
VERSION='1.2.4'
RED="$(tput setaf 1)"
NC="$(tput sgr0)" # No Color

DEFAULT_TIMEOUT=15
DEFAULT_DESTINATION_FILE='/etc/nftables.conf'
DEFAULT_SOURCE_FILE='/etc/nftables-candidate.conf'

readonly PROGNAME VERSION RED NC DEFAULT_TIMEOUT DEFAULT_DESTINATION_FILE DEFAULT_SOURCE_FILE

help() {
    cat << EOF
Usage: ${PROGNAME} [-Vh] [ { -s | --source-file } <source-file> ] [ { -d | --destination-file } <destination-file> ] [ { -t | --timeout } <timeout> ]
-h    --help                                                     Print this message.
-V    --version                                                  Print the version.
-s    --source-file        STRING                                The source file for candidate config.           (default: ${DEFAULT_SOURCE_FILE})
-d    --destination-file   STRING                                The destination file where to write the config. (default: ${DEFAULT_DESTINATION_FILE})
-t    --timeout            INT                                   The time to wait before rolling back.           (default: ${DEFAULT_TIMEOUT})
EOF

    exit 2
}

version() {
    cat << EOF
${PROGNAME} version ${VERSION} under GPLv3 licence.
EOF

    exit 2
}

# Deal with arguments
while [[ $# -gt 1 ]]
do
    key="${1}"

    case $key in
        -h|--help)
            help
            ;;
        -s|--source-file)
            export source_file="${2}"
            shift # consume -s
            ;;
        -d|--destination-file)
            export destination_file="${2}"
            shift # consume -d
            ;;
        -t|--timeout)
            export timeout="${2}"
            shift # consume -t
            ;;
        -V|--version)
            version
            ;;
        *)
        ;;
    esac
    shift # consume $1
done

# Set defaults if no options specified
source_file="${source_file:-$DEFAULT_SOURCE_FILE}"
destination_file="${destination_file:-$DEFAULT_DESTINATION_FILE}"
timeout="${timeout:-$DEFAULT_TIMEOUT}"

# Change directory to base script directory
cd "$(dirname "${0}")"

# Check root permissions
check_root() {
    # Check the command is run as root
    if [ "${EUID}" -ne 0 ]
    then
        echo -e "${RED}E:${NC} please run as root" >&2
        exit 3
    fi

    return 0
}

restore() {
    nft flush ruleset
    nft -f /tmp/nftables.conf.bak
    rm -f /tmp/nftables.conf.bak

    # Start fail2ban
    if systemctl is-enabled fail2ban > /dev/null 2>&1
    then
        systemctl start fail2ban 2>/dev/null
    fi

    return 0
}

save() {
    cp "${source_file}" "${destination_file}"
    echo -e "\nConfiguration changed"

    return 0
}

# Main function
main() {
    # Check the command is run as root
    check_root

    # Check if we can read the destination file
    if [[ ! -r "${destination_file}" ]]
    then
        echo -e "${RED}E:${NC} cannot read ${destination_file}" >&2
        exit 4
    fi

    # Backup current ruleset
    nft list ruleset > /tmp/nftables.conf.bak

    # Check if we can read the source file
    if [[ ! -r "${source_file}" ]]
    then
        echo -e "${RED}E:${NC} cannot read ${source_file}" >&2
        exit 5
    fi

    # Dry run new ruleset, exit if failures
    nft -f "${source_file}" || (echo -e "${RED}E:${NC} Invalid rules, exiting" >&2 && exit 6)

    # Check the candidate configuration starts by flushing ruleset
    if [[ $(head -n 1 /etc/nftables-candidate.conf) != "flush ruleset" ]]
    then
        sed -i '1s/^/flush ruleset\n/' "${source_file}"
    fi

    # Stop fail2ban
    if systemctl is-active fail2ban > /dev/null 2>&1
    then
        systemctl stop fail2ban 2>/dev/null
    fi

    # Apply new ruleset, rollback if timeout
    timeout "${timeout}"s nft -f "${source_file}" || (echo -e "${RED}E:${NC} timeout while applying new configuration, rolling back to the previous ruleset" >&2 && restore && exit 7)

    # Ask the user if they can open a new connection
    # If they can't, rollback
    # If they can, save
    echo -n "Can you establish NEW connections to the machine? (y/N) "
    read -r -n1 -t "${timeout}" answer 2>&1 || :
    if [[ "${answer}" == "y" ]]
    then
        save
    else
        echo -e "\n${RED}E:${NC} rolling back to the previous ruleset" >&2
        restore
        exit 8
    fi
    rm -f /tmp/nftables.conf.bak

    # Start fail2ban
    if systemctl is-enabled fail2ban > /dev/null 2>&1
    then
        systemctl start fail2ban 2>/dev/null
    fi

    exit 0
}

main "$@"

gist link

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .