Skip to main content

IPset (Linux Netfilter/IPTables)

IPsets are a powerful and highly efficient way of making a dynamic firewall on a normal Linux machine. A native feature of the Netfilter code, an IPset is an in-memory list of IPs, that can be referenced in any fireawall rules.

Knocknoc can add and remove IPs from an IPset, thereby allowing an arbitrary application of dynamic allow-listing to any linux box.

IPsets need to exist before you can update them, so the overall process is:

  1. Create an IPset by name and type using the IPset utility
  2. Use a systemd or similar script to ensure the IPset is created on startup, or at least on firewall start. This could be added to an existing firewall set up script.
  3. Integrate the IPset into your firewall software scripting, example for UFW is here, and Shorewall is here. A raw example script is at the bottom of this page and within the Agent installer folders.
  4. Install the Knocknoc agent on your linux machine and enrol it into the Knocknoc server
  5. Configure Knocknoc to run an IPset script as a backend. The agent includes a basic script, and further customisation is easy.
  6. Some applications work better if you also add the user to a second blocklist IPset once the access is revoked. An example of how to do this is at the bottom of the page. This can be used to actively drop established TCP connections where streams or other connections are intentionally actively terminated.

See more on IPset at the Netfilter project: https://ipset.netfilter.org/ 

Sudoers first

To create a custom sudoers file in the /etc/sudoers.d/ directory for the user knocknoc-agent, allowing them to run the command /usr/sbin/ipset with any arguments, follow these steps:

Note that an example sudoers file is packaged with the agent (/opt/knocknoc-agent/scripts/sudoers_knocknoc-agent) 

  1. Create a New File in /etc/sudoers.d/:

    • Choose a meaningful name for the file, such as knocknoc-agent.
    • The command would be sudo visudo -f /etc/sudoers.d/knocknoc-agent.
    • This opens a new file in the sudoers.d directory for editing with proper syntax checking.
  2. Add the Necessary Rule:

    • In the editor that opens, add the following line:

      knocknoc-agent ALL=(ALL) NOPASSWD: /usr/sbin/ipset *

    • This line follows the same syntax and meaning as described previously.
  3. Save and Exit:

    • Save the file and exit the editor. visudo will automatically check the syntax.
  4. Set Correct Permissions:

    • Ensure that the file has the correct permissions. It should be readable by root only and should not be writable by any other user.
    • You can set the appropriate permissions using: sudo chmod 0440 /etc/sudoers.d/knocknoc-agent.
  5. Verify the Configuration:

    • To check if your configuration works, switch to the knocknoc-agent user (if possible) and try executing the ipset command with sudo without a password.

Important Notes:

  • Always use visudo to edit sudoers files to prevent syntax errors.
  • Ensure that the files in /etc/sudoers.d/ have strict permissions (like 0440) to maintain security.
  • Be cautious with NOPASSWD: as it allows executing the specified command without a password, which can be a security risk if not properly managed.

IPSet creation and initialization

The IPSet definitions in the create_ipsets script should be edited to match any ACLs you intend to pass through to the agent for granting/revoking access. 

An example would be adding "ssh_allowed" (editing /opt/knocknoc-agent/scripts/create_ipsets.sh)

IPSET_NAMES="knocknoc_blocked ssh_allowed ssh_blocked"

The IPSet must be created on startup or otherwise exist prior to being added/removed from. An example systemd script is packaged with the agent at /opt/knocknoc-agent/scripts/systemd_create-ipset.service, which can be moved to /etc/systemd/system/create-ipset.service and the service started. 

# mv /opt/knocknoc-agent/scripts/systemd_create-ipset.service /etc/systemd/system/create-ipset.service
# systemctl start create-ipset

The IPSets can then be listed and verified using: ipset -L

Configuring the IPSet name as the ACL

If you have configured an IPSet to be "ssh_allowed", this is then defined as the "ACL name" on the Knocknoc server, which effectively passes the logged-in users IP address through to this IPSet - which is ultimately then used by your netfilter/iptables firewall. 

iptables -A INPUT -i eth0 -p tcp --dport 22 -m set --match-set ssh_allowed src -j ACCEPT

See Shorewall for other examples.

Complete the configuration

You are now ready to create a User and a Group, then link the User to the ACL (eg: ssh_allowed) through the Group. If you don't use the Group to link the User & ACL then your agent won't know to process it - this is a key final step.

See Creating Groups.

 

Example script with IPset add and blocking

Paste this script into /opt/knocknoc-agent/scripts/ipset_block.sh, make it executable and update your Knocknoc server backend config to have this path. Note this script assumes you have given the knocknoc-agent sudo rights to run the ipset command.

Note that the benefit of the 'blocking' script is that it will effectively terminate any pre-established connections by allowing you to DROP based on the blocked IPSet. 

#!/bin/bash
# Wrapper script to allow safe parsing of an ipset command line from sudo
# sudo is run in this scrip itself, so you need to enable the ipset command in sudoers
# like so:
# make a file in /etc/sudoers.d/knocknoc-agent with this contents:
# knocknoc-agent ALL=(ALL:ALL) NOPASSWD: /usr/sbin/ipset *

set -e -o pipefail

# Define the general_blocked ipset name
general_blocked="knocknoc_blocked"

# Validate IP address
function validate_ip() {
    local ip=$1
    if [[ $ip =~ ^[0-9]{1,3}(\.[0-9]{1,3}){3}$ ]]; then
        return 0
    else
        echo "Invalid IP address"
        exit 1
    fi
}

# Validate setname
function validate_setname() {
    local setname=$1
    if [[ $setname =~ ^[A-Za-z0-9_]+$ ]]; then
        return 0
    else
        echo "Invalid setname"
        exit 1
    fi
}

# Validate operation
function validate_op() {
    local op=$1
    if [[ $op =~ ^(add|del|flush)$ ]]; then
        return 0
    else
        echo "Invalid operation"
        exit 1
    fi
}

# Validate and assign operation
validate_op "$1"
op="$1"

# Validate and assign setname
validate_setname "$2"
setname="$2"

# Execute ipset command
if [[ "$op" = "flush" ]]; then
    sudo /usr/sbin/ipset "$op" "$setname"
else
    # Validate and assign IP address
    validate_ip "$3"
    ip="$3"

    # If the operation is to delete an IP, also add it to the general_blocked set
    if [[ "$op" = "del" ]]; then
        # Add IP to general_blocked set
        sudo /usr/sbin/ipset add "$general_blocked" "$ip" ||true
    fi

    if [[ "$op" = "add" ]]; then
        # Add IP to general_blocked set
        sudo /usr/sbin/ipset del "$general_blocked" "$ip" ||true
    fi

    # Execute the original ipset command
    sudo /usr/sbin/ipset "$op" "$setname" "$ip"
fi