wiretastix

WireGuard NFTables Firewall Integration Guide

Complete guide to Wiretastix's nftables firewall integration: two-layer architecture, group-based access control rules, CLI/API management, and troubleshooting.

Wiretastix integrates deeply with Linux nftables to provide dynamic, group-based firewall rules for VPN peers. This guide explains the architecture, how rules are applied, and how to manage both the base firewall configuration and group-specific rules.

Architecture Overview

Two-Layer Firewall Approach

Wiretastix uses a two-layer firewall architecture that separates general network security from VPN-specific access control:

Layer 1: Base NFTables Configuration (Manual Management)

  • General firewall rules for the server
  • Input/output chains for server protection
  • NAT rules for masquerading
  • ICMP, connection tracking, basic security
  • Managed by you through standard nftables configuration files

Layer 2: Group-Based Rules (Automatic Management)

  • VPN peer access control based on OIDC groups
  • Dynamic chains and IP sets per group
  • Automatically updated when peers connect/disconnect
  • Managed by Wiretastix through CLI/API

This separation ensures:

  • Your base security policies remain stable
  • VPN rules update automatically without manual intervention
  • Clear separation of concerns (server security vs. user access control)

How It Works

┌────────────────────────────────────────────────────────┐
│  User Authenticates via OIDC                           │
│  → Gets Group Membership (e.g., "engineering")         │
└────────────────┬───────────────────────────────────────┘
                 │
                 ▼
┌────────────────────────────────────────────────────────┐
│  Wiretastix Creates Peer                               │
│  → Assigns IP: 10.0.100.5/32                          │
│  → Associates with Group: "engineering"                │
└────────────────┬───────────────────────────────────────┘
                 │
                 ▼
┌────────────────────────────────────────────────────────┐
│  Wiretastix Updates NFTables Automatically:            │
│  1. Adds 10.0.100.5 to set_peers4_group_engineering   │
│  2. Ensures group_engineering_handler chain exists     │
│  3. Populates chain with group rules from database     │
│  4. Updates wghandlers to jump to group chain          │
└────────────────┬───────────────────────────────────────┘
                 │
                 ▼
┌────────────────────────────────────────────────────────┐
│  Traffic Forwarding:                                   │
│  Packet from 10.0.100.5 → forward chain → wghandlers  │
│  → Matches IP in set → Jump to group_engineering      │
│  → Apply group rules → accept/drop                     │
└────────────────────────────────────────────────────────┘

Base NFTables Configuration

Initial Setup

Wiretastix requires a base nftables configuration that defines the fundamental firewall structure. This configuration is managed by you and typically located at /etc/nftables.conf.

Minimal Working Example

#!/usr/sbin/nft -f

flush ruleset

define wiretastix_ext_if      = eth0           # External interface
define wiretastix_tunnel_if   = wg0            # WireGuard interface
define wiretastix_tunnel_port = 51820          # WireGuard port
define wiretastix_gui_port    = 443            # HTTPS port

table inet wiretastix {

  # Connection tracking and ICMP
  chain keeptrack {
    # Allow ICMP
    ip protocol icmp accept
    ip6 nexthdr icmpv6 accept
    
    # Allow established connections
    ct state {established,related} accept
    
    # Drop invalid connections
    ct state invalid drop
  }

  # Input chain (traffic TO the server)
  chain input {
    type filter hook input priority 0; policy drop;
    jump keeptrack
    
    # Allow WireGuard
    udp dport $wiretastix_tunnel_port accept
    
    # Allow HTTPS (Web UI)
    tcp dport $wiretastix_gui_port accept
    
    # Allow HTTP (for Let's Encrypt)
    tcp dport 80 accept
    
    # Allow SSH
    tcp dport 22 accept
    
    # Allow from loopback
    meta iif lo accept
    
    reject
  }

  # Output chain (traffic FROM the server)
  chain output {
    type filter hook output priority 0; policy drop;
    jump keeptrack
    
    # Allow WireGuard
    udp sport $wiretastix_tunnel_port accept
    
    # Allow DNS queries
    udp dport 53 accept
    
    # Allow HTTPS (for OIDC, updates)
    tcp dport 443 accept
    
    # Allow HTTP (for system updates)
    tcp dport 80 accept
    
    # Allow to loopback
    meta oif lo accept
    
    reject
  }

  # Forward chain (traffic THROUGH the server - VPN traffic)
  chain forward {
    type filter hook forward priority 0; policy drop;
    
    jump keeptrack
    jump wghandlers    # ← Wiretastix manages this!
    
    # Allow outgoing via external interface
    oifname $wiretastix_ext_if accept
    
    reject
  }

  # This chain is managed by Wiretastix - DO NOT EDIT
  chain wghandlers {
  }
}

# NAT table for masquerading
table ip nat {
  chain postrouting {
    type nat hook postrouting priority -100; policy accept;
    
    # Masquerade outgoing traffic
    oifname $wiretastix_ext_if counter masquerade
  }
}

Complete Production Example

A full production configuration is available in the Wiretastix repository:

# View the example configuration
sudo cat /etc/wiretastix/nftables.conf.example

# Or download from repository
curl -O https://raw.githubusercontent.com/wiretastix/wiretastix-vpn/main/nftables.conf

This includes:

  • Advanced ICMP handling (ICMPv6 neighbor discovery)
  • Port forwarding examples
  • Additional security features
  • Prometheus metrics interface rules
  • DNS server rules
  • Multiple interface handling

Key Requirements

The wghandlers chain is essential:

chain forward {
    type filter hook forward priority 0; policy drop;
    jump keeptrack
    jump wghandlers    # Must jump to wghandlers!
    oifname $wiretastix_ext_if accept
}

chain wghandlers {
    # Leave empty - Wiretastix populates this
}

Why this matters:

  • Wiretastix creates group-specific chains
  • wghandlers acts as the entry point
  • Traffic is routed to appropriate group chains based on source/destination IP
  • Without this jump, group rules won’t apply

Table Name Configuration

By default, Wiretastix uses the table name wiretastix. If you need a different name:

# wiretastix.yaml
wireguard:
    nftables-table: "custom_table_name"

Then adjust your nftables.conf:

table inet custom_table_name {
    # ... your chains ...
}

Applying Base Configuration

# Load nftables configuration
sudo nft -f /etc/nftables.conf

# Enable nftables at boot
sudo systemctl enable nftables
sudo systemctl start nftables

# Verify
sudo nft list ruleset

Group-Based Rules

How Wiretastix Manages Groups

When you create rules for groups through the CLI, Wiretastix automatically:

  1. Creates IP sets for each group (IPv4 and IPv6)
  2. Populates sets with peer IPs belonging to that group
  3. Creates group chains with the rules you defined
  4. Updates wghandlers to jump to group chains

All this happens automatically whenever:

  • A peer is added or removed
  • A user’s group membership changes (via OIDC refresh)
  • A rule is added, modified, or deleted
  • You run rule apply

NFTables Structure

After Wiretastix processes groups, the nftables structure looks like:

table inet wiretastix {
    # IP sets for each group
    set set_peers4_group_engineering {
        type ipv4_addr
        elements = { 10.0.100.5, 10.0.100.12, 10.0.100.19 }
    }
    
    set set_peers6_group_engineering {
        type ipv6_addr
        elements = { fd00::5, fd00::12, fd00::19 }
    }
    
    set set_peers4_group_sales {
        type ipv4_addr
        elements = { 10.0.100.50, 10.0.100.51 }
    }
    
    # Group-specific chains
    chain group_engineering_handler {
        # Rules for engineering group
        ip saddr 10.0.0.0/8 tcp dport 22 accept
        ip daddr 192.168.10.0/24 accept
        drop
    }
    
    chain group_sales_handler {
        # Rules for sales group
        ip daddr 192.168.20.0/24 tcp dport 443 accept
        drop
    }
    
    # Entry point (managed by Wiretastix)
    chain wghandlers {
        ip saddr @set_peers4_group_engineering jump group_engineering_handler
        ip6 saddr @set_peers6_group_engineering jump group_engineering_handler
        ip daddr @set_peers4_group_engineering jump group_engineering_handler
        ip6 daddr @set_peers6_group_engineering jump group_engineering_handler
        
        ip saddr @set_peers4_group_sales jump group_sales_handler
        ip daddr @set_peers4_group_sales jump group_sales_handler
    }
}

Viewing Active Rules

# View entire wiretastix table
sudo nft list table inet wiretastix

# View only wghandlers chain
sudo nft list chain inet wiretastix wghandlers

# View specific group chain
sudo nft list chain inet wiretastix group_engineering_handler

# View IP sets
sudo nft list set inet wiretastix set_peers4_group_engineering

Managing Rules via CLI

Rule Structure

Each rule has these properties:

PropertyDescriptionRequiredExample
groupOIDC group nameYesengineering
nfprotoIP versionYesip4 or ip6
l4protoLayer 4 protocolNotcp, udp, icmp
srcSource CIDRNo10.0.0.0/8
sportSource portNo1024
dstDestination CIDRNo192.168.10.0/24
dportDestination portNo443
actionAction to takeYesaccept or drop

Creating Rules

Example 1: Allow SSH to Internal Network

wiretastix-cli

# In the CLI
rule add group engineering nfproto ip4 l4proto tcp dst 10.0.0.0/8 dport 22 action accept

This allows the engineering group to SSH (port 22) to any server in 10.0.0.0/8.

Example 2: Allow HTTPS to Specific Server

rule add group sales nfproto ip4 l4proto tcp dst 192.168.20.50/32 dport 443 action accept

Example 3: Allow All Traffic to Subnet

rule add group admin nfproto ip4 dst 192.168.0.0/16 action accept

No protocol specified = any protocol.

Example 4: Block Outgoing SMTP

rule add group users nfproto ip4 l4proto tcp dport 25 action drop

Example 5: Allow ICMP (Ping)

rule add group monitoring nfproto ip4 l4proto icmp action accept

Example 6: IPv6 Rules

rule add group engineering nfproto ip6 l4proto tcp dst 2001:db8::/32 dport 443 action accept

Rule Application Order

Rules are evaluated in the order they were created (by rule ID) within the group chain. To implement default-deny by hand:

# Specific allows first
rule add group engineering nfproto ip4 dst 192.168.10.0/24 action accept
rule add group engineering nfproto ip4 dst 192.168.20.0/24 action accept

# Default deny last
rule add group engineering nfproto ip4 action drop

Important: Without an explicit drop rule, traffic not matching any rule will continue to subsequent chains and may be allowed by the base forward chain. This manual pattern also has a footgun: if you add another rule to the group later, it’s appended after the existing rules by creation order – meaning after your manual “default deny” rule, where it will never be reached. Prefer Group.Policy (below) over this pattern; it doesn’t have that ordering problem.

Group Policy Enforcement

Separately from individual rules, every group has a policy field (accept, drop, or none) that acts as the group’s catch-all. Unlike a manually added last-rule, it’s always applied after every one of the group’s own rules regardless of when those rules were created – there’s no ordering footgun.

  • accept: after the group’s specific rules, an unconditional accept catches anything they didn’t match.
  • drop: same, but an unconditional drop – this is how you implement default-deny for a group without manually maintaining a last rule.
  • none: no catch-all is added. Unmatched traffic falls through past the group’s chain entirely (the group makes no policy statement).

policy is a required field when creating a group – group create rejects an empty or missing value.

Because the catch-all is enforced per network family, it’s added twice internally (once for IPv4, once for IPv6) whenever policy is accept or drop.

This matters even for a brand-new group with zero rules. A group created with policy drop and no rules yet enforces that drop immediately – its chain is built specifically to carry the catch-all, not skipped just because there are no individual rules. Don’t assume an empty group is a no-op; a drop-policy group blocks all traffic for its members from the moment it’s created, and an accept-policy group allows all of it, in both cases before you’ve written a single rule.

# A deny-by-default group: peers in it get nothing until rules explicitly allow it
group create oidc contractors name "Contractors" policy drop

# An allow-by-default group: peers get everything except what rules explicitly block
group create oidc admins name "Admins" policy accept

Multiple Group Membership and Priority Order

A user’s OIDC claims can match more than one group at once, so a peer can belong to several groups simultaneously – each with its own rules and its own policy. When that happens, priority (the prio key on group create/group update) decides which group’s chain runs first for that peer’s traffic: groups are evaluated in ascending priority order, lowest number first.

This matters because policy accept/drop is a terminal verdict: whichever group’s chain the peer hits first with a non-none policy wins, and evaluation stops there – later, lower-priority groups never get a say for that traffic. policy none is the tool for a group that shouldn’t be the one to decide: it makes no catch-all statement, so evaluation continues on to the next group in priority order.

Two groups left at the same priority have no defined tie-break – this is by design, not a bug. Priority is exactly the mechanism an admin has to control ordering, so if you have overlapping groups and the outcome between them matters, give them distinct priorities and/or set policy none on whichever one shouldn’t force a decision.

# A peer might be a member of both. Contractors (prio 10) is evaluated
# first and its "drop" wins for anything its own rules don't allow --
# the broader "employees" group below never gets a chance to accept it.
group create oidc contractors name "Contractors" prio 10 policy drop
group create oidc employees name "Employees" prio 20 policy accept

Listing Rules

wiretastix-cli

# List all rules
rule list

Output:

┌────┬──────────────┬─────────┬─────────┬──────────────────┬───────┬──────────────────┬───────┬────────┐
│ #  │ GROUP        │ NFPROTO │ L4PROTO │ SRC              │ SPORT │ DST              │ DPORT │ ACTION │
├────┼──────────────┼─────────┼─────────┼──────────────────┼───────┼──────────────────┼───────┼────────┤
│ 1  │ engineering  │ IPv4    │ tcp     │ -                │       │ 10.0.0.0/8       │ 22    │ accept │
│ 2  │ engineering  │ IPv4    │ tcp     │ -                │       │ 192.168.10.0/24  │ 443   │ accept │
│ 3  │ engineering  │ IPv4    │ -       │ -                │       │ -                │       │ drop   │
│ 4  │ sales        │ IPv4    │ tcp     │ -                │       │ 192.168.20.0/24  │ 443   │ accept │
│ 5  │ sales        │ IPv4    │ -       │ -                │       │ -                │       │ drop   │
└────┴──────────────┴─────────┴─────────┴──────────────────┴───────┴──────────────────┴───────┴────────┘

Deleting Rules

# Delete by rule ID
rule del 3

# Delete multiple rules
rule del 3 5 7

Applying Rules

After adding, modifying, or deleting rules, apply them:

rule apply

This triggers Wiretastix to:

  1. Flush existing group chains
  2. Recreate chains with updated rules
  3. Update IP sets with current peer IPs
  4. Rebuild wghandlers jumps

Changes take effect immediately for all active connections.

rule apply is atomic. Every group’s chain rebuild is staged in memory and sent to the kernel as a single netlink batch at the end, rather than one commit per group. Either the whole batch lands, or – on any error – none of it does and the previously-active ruleset is left completely untouched; a failure partway through can’t leave some groups re-applied and others still running stale rules.

Common Use Cases

1. Developer Access to Internal Services

Scenario: Engineering team needs SSH and HTTPS to development servers (192.168.10.0/24).

# Allow SSH
rule add group engineering nfproto ip4 l4proto tcp dst 192.168.10.0/24 dport 22 action accept

# Allow HTTPS
rule add group engineering nfproto ip4 l4proto tcp dst 192.168.10.0/24 dport 443 action accept

# Allow ICMP for troubleshooting
rule add group engineering nfproto ip4 l4proto icmp dst 192.168.10.0/24 action accept

# Default deny
rule add group engineering nfproto ip4 action drop

# Apply
rule apply

2. Sales CRM Access Only

Scenario: Sales team only needs HTTPS access to CRM server (192.168.20.50).

# Allow HTTPS to CRM only
rule add group sales nfproto ip4 l4proto tcp dst 192.168.20.50/32 dport 443 action accept

# Deny everything else
rule add group sales nfproto ip4 action drop

rule apply

3. Administrator Full Access

Scenario: Admins need unrestricted access to internal networks.

# Allow all protocols to all internal networks
rule add group admin nfproto ip4 dst 10.0.0.0/8 action accept
rule add group admin nfproto ip4 dst 192.168.0.0/16 action accept
rule add group admin nfproto ip4 dst 172.16.0.0/12 action accept

# IPv6 access
rule add group admin nfproto ip6 dst fd00::/8 action accept

rule apply

4. Contractor Limited Access

Scenario: Contractors can only access specific services during business hours (implement via external firewall or time-based rules).

# Limited access to specific ports
rule add group contractors nfproto ip4 l4proto tcp dst 192.168.30.0/24 dport 80 action accept
rule add group contractors nfproto ip4 l4proto tcp dst 192.168.30.0/24 dport 443 action accept

# No SSH
# No ping

# Default deny
rule add group contractors nfproto ip4 action drop

rule apply

5. Monitoring and Metrics

Scenario: Monitoring group needs to ping all servers and access Prometheus exporters.

# Allow ICMP
rule add group monitoring nfproto ip4 l4proto icmp action accept

# Allow Prometheus node_exporter (port 9100)
rule add group monitoring nfproto ip4 l4proto tcp dst 10.0.0.0/8 dport 9100 action accept

# Allow Prometheus (port 9090)
rule add group monitoring nfproto ip4 l4proto tcp dst 10.0.0.0/8 dport 9090 action accept

# Default deny
rule add group monitoring nfproto ip4 action drop

rule apply

6. Inter-Peer Communication

Scenario: Allow peers in the same group to communicate with each other.

# Assuming VPN network is 10.0.100.0/24
rule add group engineering nfproto ip4 src 10.0.100.0/24 dst 10.0.100.0/24 action accept

# This allows peer-to-peer within the group
rule apply

7. Deny Specific Protocols

Scenario: Block SMTP to prevent spam/malware from VPN.

# Add allow rules first (see other examples)

# Block SMTP/SMTPS before default allow
rule add group users nfproto ip4 l4proto tcp dport 25 action drop
rule add group users nfproto ip4 l4proto tcp dport 587 action drop
rule add group users nfproto ip4 l4proto tcp dport 465 action drop

# Then allow other traffic
rule add group users nfproto ip4 action accept

rule apply

Automatic Rule Updates

When Rules Are Applied

Wiretastix automatically updates nftables in these scenarios:

  1. Peer Added: New peer IPs added to appropriate group sets
  2. Peer Removed: IPs removed from sets
  3. Peer Activated/Deactivated: Sets updated to reflect active status
  4. Group Membership Changes: When OIDC token refreshes with new groups
  5. Rule Changes: Via rule add, rule del, rule apply
  6. Group Enable/Disable: Group chains created/removed
  7. Service Restart: Full refresh on startup

Manual Refresh

To force a complete refresh:

# Via CLI
rule apply

# Or via API
curl -X POST -H "Authorization: Bearer ${TOKEN}" \
    https://vpn.example.com/api/v1/rules/apply

This rebuilds all chains and sets from the database.

Monitoring Rule Changes

Check logs for rule application events:

# System journal
sudo journalctl -u wiretastix -f | grep -i "rule\|nftable"

# Look for entries like:
# "Applying nftables rules"
# "Created chain group_engineering_handler"
# "Updated set set_peers4_group_engineering with 5 IPs"

Troubleshooting

Rules Not Applied

Symptom: Created rules via CLI but traffic isn’t filtered.

Diagnosis:

# 1. Verify rule in database
rule list

# 2. Check if rule applied to nftables
sudo nft list chain inet wiretastix group_<groupname>_handler

# 3. Check wghandlers has jump
sudo nft list chain inet wiretastix wghandlers

Solutions:

# Apply rules manually
rule apply

# Check for errors
sudo journalctl -u wiretastix -n 50

Peers Not in IP Sets

Symptom: Peer connected but not in group IP set.

Diagnosis:

# Check peer group membership
wiretastix-cli
peer list

# Check IP set
sudo nft list set inet wiretastix set_peers4_group_<groupname>

Possible causes:

  • Peer is inactive (active: false)
  • Group is disabled (enabled: false)
  • OIDC group name mismatch

Solutions:

# Activate peer
peer enable <peer-id>

# Check group
group list

# Reapply rules
rule apply

wghandlers Chain Empty

Symptom: wghandlers chain exists but has no jump rules.

Diagnosis:

sudo nft list chain inet wiretastix wghandlers
# If empty or only has comment:

Possible causes:

  • No groups have rules defined
  • All groups are disabled
  • No active peers in any group

Solutions:

# Check groups
group list

# Check rules
rule list

# Ensure groups enabled
group enable <group-name>

# Add at least one rule per group
rule add group <groupname> nfproto ip4 action accept

# Apply
rule apply

Traffic Blocked Unexpectedly

Symptom: Traffic should be allowed by rules but is blocked.

Diagnosis:

# 1. Verify rule exists and order
rule list

# 2. Check nftables rule
sudo nft list chain inet wiretastix group_<groupname>_handler

# 3. Check if peer IP in set
sudo nft list set inet wiretastix set_peers4_group_<groupname>

# 4. Test with nftables tracing
sudo nft add rule inet wiretastix wghandlers meta nftrace set 1
sudo nft monitor trace

# Generate traffic, observe trace
# Then remove trace rule:
sudo nft delete rule inet wiretastix wghandlers handle <handle-id>

Common issues:

  • Rule order (drop before accept)
  • Wrong CIDR notation
  • Protocol mismatch (tcp vs udp)
  • Base forward chain blocking before wghandlers

Group Chain Not Created

Symptom: Group exists but no chain in nftables.

Possible causes:

  • Group has no active peers
  • Group is disabled
  • Group has no rules

Wiretastix only creates chains for groups that have:

  • At least one active peer
  • At least one rule
  • enabled: true

Solution: Ensure all three conditions are met.

Base Configuration Conflicts

Symptom: Base nftables rules interfere with Wiretastix rules.

Example conflict:

chain forward {
    type filter hook forward priority 0; policy drop;
    
    # This drop happens BEFORE wghandlers!
    ip daddr 192.168.0.0/16 drop
    
    jump wghandlers    # Never reached for 192.168.0.0/16
    oifname eth0 accept
}

Solution: Order matters. jump wghandlers should be early:

chain forward {
    type filter hook forward priority 0; policy drop;
    
    jump keeptrack
    jump wghandlers    # Early in chain
    
    # Base rules after
    oifname eth0 accept
}

Performance Issues

Symptom: High CPU usage, slow connections with many rules.

Optimization:

  1. Consolidate rules: Use broader CIDRs instead of many single IPs

    # Instead of:
    # rule add ... dst 192.168.1.1/32 ...
    # rule add ... dst 192.168.1.2/32 ...
    # ...
    
    # Use:
    rule add group eng nfproto ip4 dst 192.168.1.0/24 action accept
    
  2. Use IP sets for large lists: Nftables sets are efficient for large IP lists (Wiretastix does this automatically for peer IPs)

  3. Remove unused groups: Disable or delete groups with no active users

  4. Minimize rule count: Combine similar rules

Integration with External Firewall

Upstream Firewall

If Wiretastix is behind a firewall appliance:

Firewall requirements:

  • Allow UDP port 51820 (WireGuard) to Wiretastix
  • Allow TCP port 443 (HTTPS) to Wiretastix
  • Optionally forward other ports (SSH, Prometheus)

Wiretastix handles:

  • VPN peer access control
  • Group-based filtering
  • NAT for VPN traffic

Port Forwarding for Internal Services

You can add port forwarding in the base nftables.conf:

table ip nat {
    chain prerouting {
        type nat hook prerouting priority dstnat; policy accept;
        
        # Forward external port 8080 to internal 192.168.10.50:80
        iifname eth0 tcp dport 8080 counter dnat 192.168.10.50:80
    }
    
    chain postrouting {
        type nat hook postrouting priority -100; policy accept;
        oifname eth0 counter masquerade
    }
}

# Allow forwarded traffic in filter table
table inet wiretastix {
    chain forward {
        type filter hook forward priority 0; policy drop;
        
        jump keeptrack
        jump wghandlers
        
        # Allow forwarded port
        iifname eth0 oifname eth1 ip daddr 192.168.10.50 tcp dport 80 accept
        
        oifname eth0 accept
    }
}

API Management

Rules can also be managed via API (useful for automation):

List Rules

curl -H "Authorization: Bearer ${TOKEN}" \
    https://vpn.example.com/api/v1/rules

Create Rule

curl -X PUT \
    -H "Authorization: Bearer ${TOKEN}" \
    -H "Content-Type: application/json" \
    -d '{
        "group-id": "engineering",
        "nf-proto": 4,
        "l4-proto": "tcp",
        "dst": "192.168.10.0/24",
        "dst-port": 443,
        "action": "accept"
    }' \
    https://vpn.example.com/api/v1/rules

Delete Rule

curl -X DELETE \
    -H "Authorization: Bearer ${TOKEN}" \
    https://vpn.example.com/api/v1/rules/5

Apply Rules

curl -X POST \
    -H "Authorization: Bearer ${TOKEN}" \
    https://vpn.example.com/api/v1/rules/apply

Best Practices

1. Default Deny Policy

Always end group rules with an explicit drop:

# Allow specific traffic
rule add group users nfproto ip4 dst 192.168.0.0/16 action accept

# Default deny
rule add group users nfproto ip4 action drop

2. Least Privilege

Grant only necessary access:

# Bad: Too permissive
rule add group contractors nfproto ip4 action accept

# Good: Specific access
rule add group contractors nfproto ip4 l4proto tcp dst 192.168.30.10/32 dport 443 action accept
rule add group contractors nfproto ip4 action drop

3. Document Rules

Use descriptive group names and maintain documentation:

# Create groups for different environments
group create oidc prod-dev name "prod-developers" policy drop
group create oidc staging-dev name "staging-developers" policy drop

# Different rules per environment
rule add group prod-dev nfproto ip4 dst 10.10.0.0/16 action accept
rule add group staging-dev nfproto ip4 dst 10.20.0.0/16 action accept

4. Test Before Production

Test rules in a staging environment:

# Test group
rule add group test-group nfproto ip4 dst 192.168.99.0/24 action accept
rule apply

# Verify connectivity
# Then replicate to production groups

5. Monitor Rule Effectiveness

# Add counters to important rules (manual nftables)
sudo nft add rule inet wiretastix group_engineering_handler ip daddr 192.168.10.0/24 counter accept

# Check counters
sudo nft list chain inet wiretastix group_engineering_handler -a

6. Regular Audits

Periodically review rules:

# List all rules
rule list

# Check for:
# - Unused rules (old projects)
# - Overly permissive rules
# - Missing default deny
# - Duplicate rules

7. Backup Rules

Rules are stored in the database and backed up automatically, but export for reference:

# Export current rules
wiretastix-cli rule list > rules-$(date +%Y%m%d).txt

# Or via API
curl -H "Authorization: Bearer ${TOKEN}" \
    https://vpn.example.com/api/v1/rules > rules.json

Limitations and Considerations

1. No Time-Based Rules

Wiretastix doesn’t support time-based rules directly. For time-based access:

  • Use OIDC IdP to control group membership by time
  • Or add time-based rules in base nftables.conf

2. Rule Complexity

Very complex rules (many conditions) should be split:

# Instead of one complex rule, use multiple simpler rules
rule add group eng nfproto ip4 l4proto tcp dst 192.168.10.0/24 dport 22 action accept
rule add group eng nfproto ip4 l4proto tcp dst 192.168.10.0/24 dport 443 action accept

3. Port Ranges

Nftables supports port ranges, but CLI currently accepts single ports. For ranges, add manually:

# Add via API or directly to nftables
sudo nft add rule inet wiretastix group_engineering_handler tcp dport 8000-8999 accept

(This will be overwritten on next rule apply)

4. Stateful vs Stateless

Wiretastix rules are stateless within group chains. Connection tracking happens in keeptrack:

  • Return traffic allowed by ct state {established,related} accept
  • You only need to allow NEW connections in group rules
  • This is why you don’t need to create “allow return SSH traffic” rules

Summary

Wiretastix’s nftables integration provides a powerful, flexible, and automatic firewall management system:

You manage:

  • Base nftables configuration (server security)
  • NAT rules
  • Port forwarding
  • General firewall policies

Wiretastix manages:

  • Group-based chains and rules
  • IP sets for group members
  • Dynamic updates on peer/group changes
  • Integration between groups and chains

Key takeaways:

  • Base configuration must have wghandlers chain and jump in forward chain
  • Rules are created via CLI/API and automatically applied to nftables
  • Rules are group-based and automatically associated with peers
  • Changes take effect immediately after rule apply
  • Always use default deny for security

For more information, see: