Firewall rules/IP-sets for NTS with chrony

r7Y2

New Member
Dec 13, 2025
2
0
1
Hi,

I'm trying to run a tight nftables firewall rule set where outbound traffic is only allowed to specific IPs and ports. I would like to use secure NTP aka NTS for syncing the time of my cluster. The NTS-KE servers IP addresses will be regularly updated with the help of one of the scripts from this thread Firewall Alias with Domainname. However, according to the NTS spec, the NTS-KE server will tell the client the IP address of the actual NTP server, thus it can differ from the aforementioned NTS-KE servers IP address.

My question is, where can I find the NTP IPs (within chronys data) and is there already a way to get them into an IP set?


Best regards
 
To answer my own question:
The IPs of the actual NTP servers are not written into any file by chrony, except if the daemon is stopped, see link. If the daemon is stopped, they are written into the .nts cookie files (in the fourth row followed by the port, as of now) in the ntsdumpdir defined in the chrony.conf.
Since I don't want to regularly stop the daemon and so far all NTS-KE servers provide their own IP for NTP, I'll just go with the assumption that the NTS-KE server is the NTP server as well.

The following is my script for updating the firewall with the IP addresses of the NTS-KE servers.
For some reason I had some .nts files in my ntsdumpdir when I wrote the script, hence it still has the section that reads the cookies and extracts the IPs.
A chronjob will run it from time to time.

Bash:
#!/usr/bin/env bash

# Script Name: NTSautoIPSet.sh
# Description: Automatically updates an IPSet with resolved NTS-KE servers.
#              The following rules and the block '[IPSET nts-ke]'
#              is necessary in /etc/pve/firewall/cluster.fw
#              OUT ACCEPT -dest +dc/nts-ke -p tcp -dport 4460 -log nolog # NTS-KE Kex
#              OUT ACCEPT -dest +dc/nts-ke -p udp -dport 123 -log nolog # NTS-KE NTP
# Version: 1.0
# Author: r7Y2
# Date: 2025-12-14

set -euo pipefail

# Redirect all errors to journalctl
exec 2> >(systemd-cat -t "NTSautoIPSet" -p warning)

# Set config paths
CHRONYCONF="/etc/chrony/chrony.conf"
CLUSTERFW="/etc/pve/firewall/cluster.fw"

# 1. Extract server names from chrony.conf, resolve IPv4 + IPv6
NTSIPS=$(
    grep -oP '^\s*server\s+\K.*?\s' "$CHRONYCONF" | \
    while read -r host; do
        getent ahosts "$host" | awk '{print $1}'
    done | sort -u
) || true

# 2. Stop if no servers resolved to retain old IPs
if [[ -z "${NTSIPS//[[:space:]]/}" ]]; then
    echo "ERROR: No NTS IPs resolved" >&2
    exit 1
fi

## 3. Extract NTP IPs from NTS cookies
#NTSDIR=$( awk '$1 == "ntsdumpdir" { print $2 }' "$CHRONYCONF" )
#
#shopt -s nullglob
#nts_files=($NTSDIR/*.nts)
#shopt -u nullglob
#
#NTPIPS=""
#
#if ((${#nts_files[@]})); then
#  NTPIPS=$( awk 'FNR==4 {
#    ip=$1
#
#    # IPv4 regex
#    ipv4="^([0-9]{1,3}\\.){3}[0-9]{1,3}$"
#
#    # Due to the limitations of awk-regex, only the string containing exclusively IPv6 characters is checked.
#    ipv6="[^a-fA-F0-9:]"
#
#    if (ip ~ ipv4 || ip !~ ipv6) {
#        print ip
#    } else {
#        printf "Invalid IP in NTS cookie %s: %s\n", FILENAME, ip > "/proc/self/fd/2"
#        exit 1
#    }
#
#  }' "${nts_files[@]}")
#fi

# 4. Update IP Sets in cluster.fw
awk -v ntsips="$NTSIPS" -v ntpips="$NTPIPS"  '
/^\s*\[IPSET nts-ke\]/ {
    print
    print ""
    print ntsips
    print ""
    skip=1
    next
}

/^\s*\[IPSET ntp\]/ {
    print
    print ""
    print ntpips
    print ""
    skip=1
    next
}

skip && /^\s*\[/ {
    skip=0
}

!skip {
    print
}
' "$CLUSTERFW" > "$CLUSTERFW.tmp" && mv "$CLUSTERFW.tmp" "$CLUSTERFW"