NetworkManager Wired 802.1X

Overview

This guide configures wired 802.1X EAP-TLS authentication using NetworkManager (nmcli). This is the recommended approach for modern Linux distributions with desktop environments.

For headless servers or minimal installs, see wpa_supplicant Configuration.

Prerequisites

  • User certificate issued by trusted CA (AD CS or Vault)

  • CA certificate in ISE trust store

  • Switch port configured for 802.1X

Certificate Locations

/etc/ssl/certs/<hostname>-eaptls.pem      # Client certificate
/etc/ssl/private/<hostname>-eaptls.key    # Private key (chmod 600)
/etc/ssl/certs/HOME-ROOT-CA.pem           # CA certificate (AD CS)
# OR
/etc/ssl/certs/DOMUS-ROOT-CA.pem          # CA certificate (Vault)

Step 1: Obtain User Certificate

Option A: From AD CS (current)

# Generate CSR
openssl req -new -newkey rsa:2048 -nodes \
  -keyout /tmp/$(hostname)-eaptls.key \
  -out /tmp/$(hostname)-eaptls.csr \
  -subj "/CN=$(hostname).inside.domusdigitalis.dev"

# Submit to AD CS (from Windows or via certreq)
# Download signed certificate as <hostname>-eaptls.pem

Option B: From Vault (future)

dsource d000 dev/vault
netapi vault pki-issue $(hostname).inside.domusdigitalis.dev \
  -o /tmp/$(hostname)-eaptls

Install Certificates

# Install certificates
sudo cp /tmp/$(hostname)-eaptls.pem /etc/ssl/certs/
sudo cp /tmp/$(hostname)-eaptls.key /etc/ssl/private/
sudo chmod 644 /etc/ssl/certs/$(hostname)-eaptls.pem
sudo chmod 600 /etc/ssl/private/$(hostname)-eaptls.key

# Install CA certificate (if not already present)
sudo cp /tmp/HOME-ROOT-CA.pem /etc/ssl/certs/
# OR for Vault:
sudo cp /tmp/DOMUS-ROOT-CA.pem /etc/ssl/certs/

Step 2: Identify Network Interface

# List interfaces
nmcli device status

# Example output:
# DEVICE       TYPE      STATE         CONNECTION
# enp0s31f6    ethernet  disconnected  --
# wlp4s0       wifi      connected     HomeWiFi

Step 3: Migration from wpa_supplicant (if applicable)

If currently using wpa_supplicant for wired 802.1X, disable it first.

3.1 Verify wpa_supplicant is Running

# Quick status check
systemctl status wpa_supplicant-wired@enp0s31f6.service | grep -E "Active:|loaded|running"

# Deep validation - confirm EAP-TLS is working
sudo wpa_cli -i enp0s31f6 status | grep -E "wpa_state|eap_state|suppPortStatus"
Expected output (before migration):
Loaded: loaded (/usr/lib/systemd/system/wpa_supplicant-wired@.service; enabled; preset: disabled)
Active: active (running) since ...
wpa_state=COMPLETED
suppPortStatus=Authorized

3.2 Stop and Disable wpa_supplicant

sudo systemctl stop wpa_supplicant-wired@enp0s31f6.service
sudo systemctl disable wpa_supplicant-wired@enp0s31f6.service

3.3 Verify it’s Stopped

systemctl status wpa_supplicant-wired@enp0s31f6.service | grep -E "Active:|loaded"
Expected output (after stop):
Loaded: loaded (/usr/lib/systemd/system/wpa_supplicant-wired@.service; disabled; preset: disabled)
Active: inactive (dead)

Keep WiFi connected during wired migration! If NetworkManager wired fails, you still have network access via WiFi to troubleshoot.

Step 4: Create 802.1X Connection

Arch Linux doesn’t have hostname command by default. Use cat /etc/hostname or the literal hostname.

You MUST include identity-flags and private-key-password-flags to prevent NetworkManager from prompting for secrets on every connection!

# Get your hostname first
MYHOST=$(cat /etc/hostname)
echo "Hostname: $MYHOST"

# Create wired 802.1X connection
sudo nmcli connection add \
  type ethernet \
  con-name "Wired-802.1X" \
  ifname enp0s31f6 \
  802-1x.eap tls \
  802-1x.identity "$<your-hostname>.inside.domusdigitalis.dev" \
  802-1x.identity-flags 0 \
  802-1x.ca-cert /etc/ssl/certs/HOME-ROOT-CA.pem \
  802-1x.client-cert /etc/ssl/certs/$<your-hostname>-eaptls.pem \
  802-1x.private-key /etc/ssl/private/$<your-hostname>-eaptls.key \
  802-1x.private-key-password-flags 4 \
  connection.autoconnect yes
Table 1. Flag Values:
Flag Meaning

identity-flags=0

Store identity in connection file (not as secret)

private-key-password-flags=4

Not required (key has no password)

With Private Key Password

If your private key has a password:

sudo nmcli connection add \
  type ethernet \
  con-name "Wired-802.1X" \
  ifname enp0s31f6 \
  802-1x.eap tls \
  802-1x.identity "$(hostname).inside.domusdigitalis.dev" \
  802-1x.ca-cert /etc/ssl/certs/HOME-ROOT-CA.pem \
  802-1x.client-cert /etc/ssl/certs/$(hostname)-eaptls.pem \
  802-1x.private-key /etc/ssl/private/$(hostname)-eaptls.key \
  802-1x.private-key-password "your-password-here" \
  connection.autoconnect yes

Step 5: Activate Connection

# Bring up the connection
sudo nmcli connection up "Wired-802.1X"

Step 6: Verify Authentication

6.1 Quick Validation

# NetworkManager connection state
nmcli connection show "Wired-802.1X" | grep -E "GENERAL.STATE|connection.type|802-1x"

# Device status
nmcli device show enp0s31f6 | grep -E "GENERAL.STATE|IP4.ADDRESS|WIRED-PROPERTIES"
Expected output (success):
GENERAL.STATE:    100 (connected)
connection.type:  802-3-ethernet
802-1x.eap:       tls
802-1x.identity:  modestus-p50.inside.domusdigitalis.dev
GENERAL.STATE:    100 (connected)
IP4.ADDRESS[1]:   10.50.40.xxx/24

6.2 Verify IP Assignment

ip addr show enp0s31f6 | grep -E "state|inet "
Expected output:
enp0s31f6: <BROADCAST,MULTICAST,UP,LOWER_UP> ... state UP ...
    inet 10.50.40.xxx/24 brd 10.50.40.255 scope global dynamic ...

6.3 ISE Session Verification

# Load credentials
dsource d000 dev/network

# Check ISE session (should show dot1x/EAP-TLS)
netapi ise mnt session $(cat /sys/class/net/enp0s31f6/address)
Expected fields in ISE output:
Method:    dot1x
Protocol:  EAP-TLS
Username:  <hostname>.inside.domusdigitalis.dev
Status:    PASSED

6.4 Full Validation One-Liner

Quick validation that covers everything:

# All-in-one validation
echo "=== wpa_supplicant ===" && \
systemctl status wpa_supplicant-wired@enp0s31f6.service | grep -E "Active:|loaded" && \
echo "=== NetworkManager ===" && \
nmcli connection show --active | grep -E "Wired|enp0" && \
echo "=== 802.1X Config ===" && \
nmcli connection show "Wired-802.1X" | grep -E "GENERAL.STATE|802-1x.eap|802-1x.identity" && \
echo "=== IP Address ===" && \
ip addr show enp0s31f6 | grep "inet "
Expected output:
=== wpa_supplicant ===
Loaded: loaded (...; disabled; preset: disabled)
Active: inactive (dead)
=== NetworkManager ===
Wired-802.1X  <uuid>  ethernet  enp0s31f6
=== 802.1X Config ===
802-1x.eap:       tls
802-1x.identity:  modestus-p50.inside.domusdigitalis.dev
GENERAL.STATE:    activated
=== IP Address ===
    inet 10.50.40.xxx/24 ...

Daily Operations Commands

Commands for daily 802.1X troubleshooting and monitoring.

NetworkManager Commands

# List all connections
nmcli connection show

# Show active connections
nmcli connection show --active

# Show connection details
nmcli connection show "Wired-802.1X"

# Show 802.1X specific settings
nmcli connection show "Wired-802.1X" | grep "802-1x"

# Reconnect (bounce the connection)
sudo nmcli connection down "Wired-802.1X" && sudo nmcli connection up "Wired-802.1X"

# Check device status
nmcli device status
nmcli device show enp0s31f6

# View connection file
sudo cat /etc/NetworkManager/system-connections/Wired-802.1X.nmconnection

wpa_cli Commands (for wpa_supplicant interfaces)

wpa_cli still works for interfaces managed by wpa_supplicant (e.g., WiFi before migration).
# Status (comprehensive)
sudo wpa_cli -i wlan0 status

# Key fields to check:
# - wpa_state=COMPLETED (authenticated)
# - key_mgmt=WPA2/IEEE 802.1X/EAP
# - EAP state=SUCCESS
# - selectedMethod=13 (EAP-TLS)
# - suppPortStatus=Authorized

# Quick status check
sudo wpa_cli -i wlan0 status | grep -E "wpa_state|key_mgmt|EAP state|selectedMethod|suppPortStatus"

# Scan for networks
sudo wpa_cli -i wlan0 scan
sudo wpa_cli -i wlan0 scan_results

# Reassociate (reconnect)
sudo wpa_cli -i wlan0 reassociate

# Disconnect
sudo wpa_cli -i wlan0 disconnect

# List configured networks
sudo wpa_cli -i wlan0 list_networks

# Get interface info
sudo wpa_cli -i wlan0 get_capability eap

ISE Verification (netapi)

# Load credentials first
dsource d000 dev/network

MnT API (Monitoring & Troubleshooting)

Real-time session data, live authentications.

# Session by MAC (most common)
netapi ise mnt session c8:5b:76:c6:59:62

# All active sessions
netapi ise mnt sessions

# Session count
netapi ise mnt count

# MnT version/health
netapi ise mnt version

# Auth status (recent auths for MAC)
netapi ise mnt auth-status c8:5b:76:c6:59:62

DataConnect API (SQL queries, historical data)

Deep historical analysis, detailed session info.

# Detailed session info
netapi ise dc session c8:5b:76:c6:59:62

# Auth history (last 24h for MAC)
netapi ise dc auth-history c8:5b:76:c6:59:62

# Recent authentications (all)
netapi ise dc recent

# Failed authentications
netapi ise dc failed

# Endpoint details
netapi ise dc endpoint c8:5b:76:c6:59:62

# Profiler data
netapi ise dc profiler

# Device types
netapi ise dc device-types

# Custom SQL query
netapi ise dc query "SELECT * FROM radius_authentications WHERE calling_station_id='C8:5B:76:C6:59:62' ORDER BY timestamp DESC LIMIT 10"

ERS API (Configuration, CRUD operations)

Endpoint management, policy objects.

# Get endpoint by MAC
netapi ise get-endpoint c8:5b:76:c6:59:62

# List all endpoints (paginated)
netapi ise get-endpoints --size 50

# Endpoint groups
netapi ise get-endpoint-groups

# Authorization profiles
netapi ise get-authz-profiles

# dACLs
netapi ise get-dacls

# Network devices (NADs)
netapi ise get-nads

# Policy sets
netapi ise get-policy-sets

# ISE nodes (deployment info)
netapi ise get-nodes --detailed

pxGrid API (Real-time events, pub/sub)

Live session tracking, CoA, threat response.

# pxGrid session by MAC
netapi ise pxgrid session c8:5b:76:c6:59:62

# pxGrid session by IP
netapi ise pxgrid session 10.50.40.101

# All active sessions
netapi ise pxgrid sessions

# ISE health metrics
netapi ise pxgrid health

# Performance stats
netapi ise pxgrid performance

Quick Reference Table

Need Command API

Live session status

mnt session <MAC>

MnT

Why auth failed

dc auth-history <MAC>

DataConnect

Detailed failure reason

dc failed

DataConnect

Endpoint profile/group

get-endpoint <MAC>

ERS

Real-time session events

pxgrid sessions

pxGrid

Change endpoint group

update-endpoint <MAC> --group <name>

ERS

ISE node health

pxgrid health

pxGrid

Custom SQL analysis

dc query "SELECT …​"

DataConnect

Quick Troubleshooting

# Check logs (last 5 min)
journalctl -u NetworkManager --since "5 minutes ago" | grep -iE "eap|tls|auth|fail|error"

# Check wpa_supplicant logs
journalctl -t wpa_supplicant --since "5 minutes ago"

# Certificate verification
openssl x509 -in /etc/ssl/certs/modestus-p50-eaptls.pem -noout -subject -dates

# Check cert chain
openssl verify -CAfile /etc/ssl/certs/HOME-ROOT-CA.pem /etc/ssl/certs/modestus-p50-eaptls.pem

Modifying Connection

# Change identity
sudo nmcli connection modify "Wired-802.1X" \
  802-1x.identity "new-identity@domain"

# Update certificate paths
sudo nmcli connection modify "Wired-802.1X" \
  802-1x.client-cert /etc/ssl/certs/new-cert.pem \
  802-1x.private-key /etc/ssl/private/new-key.key

# Disable autoconnect
sudo nmcli connection modify "Wired-802.1X" \
  connection.autoconnect no

DNS Configuration Issues

Issue: Empty /etc/resolv.conf

Symptom:

cat /etc/resolv.conf
# Generated by NetworkManager
# (file is empty)

# DNS queries fail
dig google.com
# connection timed out; no servers could be reached

Cause: NetworkManager isn’t configured to manage DNS, or dhcpcd is conflicting with NetworkManager.

Solution A: Configure NetworkManager DNS

# Check NetworkManager configuration
cat /etc/NetworkManager/NetworkManager.conf

# If empty or missing [main] section, add it
sudo nvim /etc/NetworkManager/NetworkManager.conf

# Add or ensure these lines exist:
[main]
dns=default

# Restart NetworkManager
sudo systemctl restart NetworkManager

# Reconnect to apply
sudo nmcli connection down "Wired-802.1X"
sudo nmcli connection up "Wired-802.1X"

# Verify resolv.conf is populated
cat /etc/resolv.conf

Solution B: Disable dhcpcd (if running)

# Check if dhcpcd is running
systemctl status dhcpcd
systemctl status dhcpcd@enp0s31f6

# If active, it's conflicting with NetworkManager
# Stop and disable it
sudo systemctl stop dhcpcd
sudo systemctl disable dhcpcd
sudo systemctl stop dhcpcd@enp0s31f6
sudo systemctl disable dhcpcd@enp0s31f6

# NetworkManager has its own internal DHCP client
# No need for dhcpcd when using NetworkManager

# Restart NetworkManager
sudo systemctl restart NetworkManager

# Reconnect
sudo nmcli connection up "Wired-802.1X"

# Verify DNS
cat /etc/resolv.conf

NetworkManager vs dhcpcd:

  • NetworkManager: Uses internal DHCP client (dhclient or internal), manages /etc/resolv.conf

  • dhcpcd: Standalone DHCP client, conflicts with NetworkManager

Do NOT run both simultaneously! Choose one:

  • For desktop/laptop with NetworkManager: Disable dhcpcd

  • For minimal server without NetworkManager: Use dhcpcd or systemd-networkd

Symptom: Multiple IPs on Same Interface

If you see duplicate IPs on one interface:

ip addr show wlan0
# inet 10.50.10.121/24 ...
# inet 10.50.10.126/24 scope global secondary ...  ← TWO IPs!

Cause: Both NetworkManager and dhcpcd are requesting DHCP leases.

Diagnosis:

# Check if dhcpcd is running despite being disabled
ps aux | grep dhcpcd

# Check which IP is from NetworkManager
nmcli device show wlan0 | grep IP4.ADDRESS
# Or
nmcli connection show "Domus-Secure" | grep IP4.ADDRESS

# Test which IP responds
ping -c 2 10.50.10.121
ping -c 2 10.50.10.126

Solution:

# 1. Kill all dhcpcd processes
sudo killall dhcpcd

# 2. Remove dead/duplicate IP (keep the working one!)
# First verify which IP is from NetworkManager
nmcli device show wlan0 | grep IP4.ADDRESS
# Example output: IP4.ADDRESS[1]: 10.50.10.126/24

# Delete the OTHER IP (not the NetworkManager one!)
sudo ip addr del 10.50.10.121/24 dev wlan0

# 3. Mask dhcpcd permanently to prevent it from starting
sudo systemctl mask dhcpcd

# 4. Verify only one IP remains
ip addr show wlan0 | grep "inet "

# 5. Verify dhcpcd is gone
ps aux | grep dhcpcd

Before deleting ANY IP, verify which one is your active SSH connection!

Test connectivity:

ping -c 2 10.50.10.121
ping -c 2 10.50.10.126

Delete the IP that does NOT respond. If you delete the wrong one, you’ll lose SSH access and need console/physical access to recover.

Verify DNS is Received from DHCP

# Check what NetworkManager received from DHCP
nmcli device show enp0s31f6 | grep -E "IP4.DNS|IP4.GATEWAY|IP4.ADDRESS"

# Expected output:
# IP4.ADDRESS[1]:    10.50.40.101/24
# IP4.GATEWAY:       10.50.40.1
# IP4.DNS[1]:        10.50.1.50
# IP4.DNS[2]:        10.50.1.1

# If DNS is shown but resolv.conf is empty:
# NetworkManager is receiving DNS but not writing it
# Configure dns=default in NetworkManager.conf (see above)

# Check connection DNS settings
nmcli connection show "Wired-802.1X" | grep -E "ipv4.dns|ipv4.ignore-auto-dns"

# If ipv4.ignore-auto-dns is "yes", change it:
sudo nmcli connection modify "Wired-802.1X" ipv4.ignore-auto-dns no
sudo nmcli connection up "Wired-802.1X"

resolv.conf Management Comparison

Tool Manages resolv.conf Notes

NetworkManager

Yes (with dns=default)

Recommended for desktops/laptops

dhcpcd

Yes (conflicts with NM)

Use for servers without NetworkManager

systemd-resolved

Yes (via symlink)

Alternative DNS resolver, can work with NetworkManager

wpa_supplicant alone

No (manual or script)

Need separate DHCP client

Troubleshooting

Check Logs

# NetworkManager logs
journalctl -u NetworkManager -f

# wpa_supplicant logs (NetworkManager uses this internally)
journalctl -t wpa_supplicant -f

Common Issues

Issue Solution

"Secrets were required, but not provided"

Missing identity-flags=0 and private-key-password-flags=4. See Fix: "Secrets Required" Prompt

"No secrets provided"

Check certificate paths and permissions

"TLS handshake failed"

CA cert chain issue - see Fix: TLS Handshake Failed (Chain Certificate)

"Authentication rejected"

Check ISE logs, verify identity matches certificate CN

Connection keeps disconnecting

Check switch port configuration, verify VLAN assignment

"IP configuration could not be reserved"

EAP succeeded but DHCP slow/failed. Check switch port authorization, wait and retry

"invalid message format" in journal

Certificate path issue - verify files exist with ls -la

Fix: "Secrets Required" Prompt

If NetworkManager prompts for identity/password on every connection, the flags weren’t saved properly.

# Check current connection file
sudo cat /etc/NetworkManager/system-connections/Wired-802.1X.nmconnection

# The [802-1x] section MUST have these flags:
# identity-flags=0
# private-key-password-flags=4
Manual fix if flags are missing:
# Edit the connection file
sudo nvim /etc/NetworkManager/system-connections/Wired-802.1X.nmconnection

# Add to [802-1x] section:
# identity-flags=0
# private-key-password-flags=4

# Reload and retry
sudo nmcli connection reload
sudo nmcli connection up "Wired-802.1X"
Expected [802-1x] section:
[802-1x]
ca-cert=/etc/ssl/certs/HOME-ROOT-CA.pem
client-cert=/etc/ssl/certs/modestus-p50-eaptls.pem
eap=tls;
identity=modestus-p50.inside.domusdigitalis.dev
identity-flags=0
private-key=/etc/ssl/private/modestus-p50-eaptls.key
private-key-password-flags=4

Fix: TLS Handshake Failed (Chain Certificate)

Symptom: 802.1X authentication fails with "TLS handshake failed" or "SSL alert: unknown CA" in journal logs.

Cause: NetworkManager’s ca-cert points to the ROOT CA only, but ISE presents a certificate signed by a SUBORDINATE CA. The client needs the full chain to validate.

EAP-TLS certificate chain:

ISE presents:    ISE cert → Signed by SUBCA
Client needs:    ROOT CA → SUBCA chain to validate

If client only has ROOT CA, handshake fails because it can't build the chain.

Field-tested at CHLA (2026-02-06): SSL handshake failures resolved after creating combined chain cert.

Step 1: Identify the Certificates

# List your CA certificates
ls -la /usr/local/share/ca-certificates/
# or
ls -la /etc/ssl/certs/ | grep -i "root\|sub\|chain"

# Example output:
# CHLAROOTCA.crt     ← Root CA
# CHLASUBCA.crt      ← Subordinate CA (signs endpoint certs)

Step 2: Create Combined Chain Certificate

The chain file must contain ROOT CA first, then SUBORDINATE CA(s), in order:

# Create chain file (ROOT CA first, then SUB CA)
cat /usr/local/share/ca-certificates/CHLAROOTCA.crt \
    /usr/local/share/ca-certificates/CHLASUBCA.crt \
    > /tmp/chla-chain.pem

# Install chain file
sudo cp /tmp/chla-chain.pem /etc/ssl/certs/chla-chain.pem
sudo chmod 644 /etc/ssl/certs/chla-chain.pem

Certificate order in chain file:

  1. Root CA (self-signed, top of hierarchy)

  2. Intermediate/Subordinate CA (signed by root)

  3. (Additional intermediates if any)

The chain builds from root → intermediates. OpenSSL reads top-to-bottom.

Step 3: Update NetworkManager Connection

# Update 802.1X connection to use chain cert
sudo nmcli connection modify "Wired-802.1X" \
  802-1x.ca-cert /etc/ssl/certs/chla-chain.pem

# Reload and reconnect
sudo nmcli connection reload
sudo nmcli connection down "Wired-802.1X"
sudo nmcli connection up "Wired-802.1X"

Step 4: Verify

# Check connection status
nmcli connection show "Wired-802.1X" | grep -E "GENERAL.STATE|802-1x.ca-cert"

# Check journal for TLS success
journalctl -u NetworkManager --since "2 minutes ago" | grep -iE "eap|tls|handshake"

Verify Certificate Chain Manually

# Test that client cert validates against chain
openssl verify -CAfile /etc/ssl/certs/chla-chain.pem \
  /etc/ssl/certs/$(hostname)-eaptls.pem

# Expected output:
# /etc/ssl/certs/chlxsbg-eaptls.pem: OK

# If it fails, the chain is incomplete or wrong order

Alternative: System Trust Store

If you don’t want to manage a separate chain file, ensure both CA certs are in the system trust store:

# Copy CA certs to system trust directory
sudo cp CHLAROOTCA.crt CHLASUBCA.crt /usr/local/share/ca-certificates/

# Update system trust store
sudo update-ca-certificates

# Use system bundle in NetworkManager
sudo nmcli connection modify "Wired-802.1X" \
  802-1x.ca-cert /etc/ssl/certs/ca-certificates.crt

Using the full system bundle (ca-certificates.crt) is less secure than a specific chain file, as it trusts ALL system CAs. For enterprise deployments, use the specific chain file.

Debug Mode

# Enable debug logging
sudo nmcli general logging level DEBUG domains ALL

# Attempt connection
sudo nmcli connection up "Wired-802.1X"

# View debug output
journalctl -u NetworkManager --since "1 minute ago"

# Reset logging
sudo nmcli general logging level INFO domains DEFAULT

Rollback (If Migration Fails)

If NetworkManager 802.1X fails and you need to revert to wpa_supplicant:

# 1. Delete the failed NetworkManager connection
sudo nmcli connection delete "Wired-802.1X"

# 2. Re-enable wpa_supplicant
sudo systemctl enable wpa_supplicant-wired@enp0s31f6.service
sudo systemctl start wpa_supplicant-wired@enp0s31f6.service

# 3. Verify wpa_supplicant is working
sudo systemctl status wpa_supplicant-wired@enp0s31f6.service
sudo wpa_cli -i enp0s31f6 status

Delete Connection

sudo nmcli connection delete "Wired-802.1X"

GUI Alternative

For GNOME/KDE users:

  1. Settings → Network → Wired → ⚙️ (gear icon)

  2. Security tab → Enable 802.1X

  3. Authentication: TLS

  4. Identity: hostname.inside.domusdigitalis.dev

  5. CA Certificate: Browse to /etc/ssl/certs/HOME-ROOT-CA.pem

  6. User Certificate: Browse to /etc/ssl/certs/<hostname>-eaptls.pem

  7. User Private Key: Browse to /etc/ssl/private/<hostname>-eaptls.key

  8. Apply

Quick Reference

# Create connection
sudo nmcli connection add type ethernet con-name "Wired-802.1X" \
  ifname enp0s31f6 802-1x.eap tls \
  802-1x.identity "$(hostname).inside.domusdigitalis.dev" \
  802-1x.ca-cert /etc/ssl/certs/HOME-ROOT-CA.pem \
  802-1x.client-cert /etc/ssl/certs/$(hostname)-eaptls.pem \
  802-1x.private-key /etc/ssl/private/$(hostname)-eaptls.key

# Activate
sudo nmcli connection up "Wired-802.1X"

# Status
nmcli device status

# Logs
journalctl -u NetworkManager -f

dACL Troubleshooting

Understanding dACLs

Downloadable ACLs (dACLs) from ISE are applied as egress ACLs on the switch port. Key characteristics:

  1. Source must be 'any': ISE replaces 'any' with the client’s IP when pushed to switch

  2. Egress only: Controls traffic FROM the client TO network

  3. Not stateful: Must explicitly permit return traffic

  4. Order matters: Specific permits must come BEFORE blanket denies

Verifying Applied dACL

# Check what dACL is applied to session
netapi ios exec "show access-session interface GigabitEthernet1/0/5 detail" | grep "ACS ACL"

# Output shows:
# ACS ACL:  xACSACLx-IP-LINUX_RESEARCH_HARDENED_V3-697858bf
#                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
#                      This is the ISE dACL name

# View the actual ACL on switch
netapi ios exec "show ip access-lists xACSACLx-IP-LINUX_RESEARCH_HARDENED_V3-697858bf"

Common dACL Issues

Issue 1: No Connectivity Despite Authentication Success

Symptoms: - 802.1X authentication succeeds - VLAN assigned correctly - Cannot ping, browse, or SSH

Cause: dACL blocking traffic

Diagnosis:

# Check ISE dACL content
netapi ise get-dacl LINUX_RESEARCH_HARDENED

# Verify it's applied to session
netapi ise mnt session c8:5b:76:c6:59:62

Issue 2: ACL Ordering Problems

WRONG (specific permits after blanket deny):

deny ip any 10.0.0.0 0.255.255.255      ← Blocks ALL 10.x
permit udp any host 10.50.1.50 eq 53    ← Never reached!

CORRECT (specific permits BEFORE blanket deny):

permit udp any host 10.50.1.50 eq 53    ← DNS works
deny ip any 10.0.0.0 0.255.255.255      ← Then block rest

Issue 3: Missing Return Traffic

WRONG (outbound only):

permit tcp any any eq 80      ← Allows TO port 80
permit tcp any any eq 443     ← Allows TO port 443
deny ip any any log           ← Blocks ALL return traffic!

CORRECT (with return traffic):

permit tcp any any eq 80           ← Allows TO port 80
permit tcp any any eq 443          ← Allows TO port 443
permit tcp any gt 1023 any         ← Allows return traffic to ephemeral ports
permit udp any gt 1023 any         ← Allows UDP return traffic
deny ip any any log

Issue 4: Missing ICMP

Without ICMP permit, ping fails:

permit icmp any any    ← Add this for ping/traceroute

Updating dACL in Production

Step 1: Create New Version (Don’t Delete Old)

# Create V2 with fixes
netapi ise create-dacl LINUX_RESEARCH_HARDENED_V2 \
  --acl "permit icmp any any; permit udp any host 10.50.1.1 eq 53; ... " \
  --descr "Fixed version - correct ordering and return traffic"

Step 2: Update Authorization Profile

# Get current profile
netapi ise get-authz-profile Linux_EAPTLS_Permit

# Update to new dACL
netapi ise update-authz-profile Linux_EAPTLS_Permit --dacl LINUX_RESEARCH_HARDENED_V2

Step 3: Apply to Active Session

Method A: CoA Reauth (preferred for production):

# Trigger re-authentication (preserves session)
netapi ise mnt coa c8:5b:76:c6:59:62 --type Reauth

Method B: Manual Reconnect (if CoA fails):

# From the client machine
sudo nmcli connection down "Wired-802.1X"
sudo nmcli connection up "Wired-802.1X"

Step 4: Verify New ACL Applied

# Check switch session (look for new hash)
netapi ios exec "show access-session interface GigabitEthernet1/0/5 detail" | grep "ACS ACL"

# Expected: xACSACLx-IP-LINUX_RESEARCH_HARDENED_V2-<new-hash>

# Test connectivity from client
ping 8.8.8.8
dig google.com
curl -I https://google.com

Example: Zero-Trust Linux Workstation dACL (WORKING)

This is the final working version (V5) after troubleshooting ACL ordering issues.

Permits: - ICMP (ping/traceroute) - DNS to internal servers (10.50.1.1, 10.50.1.50) - NTP to any - ISE API access (10.50.1.21:8443, 8905) - Internet HTTP/HTTPS/SSH (outbound connections) - SSH/HTTP/HTTPS server return traffic (inbound from source ports) - All high-port return traffic (TCP/UDP gt 1023)

Blocks: - All other RFC1918 destinations (after all permits are evaluated) - All other traffic

netapi ise create-dacl LINUX_RESEARCH_HARDENED_V5 \
  --acl "permit icmp any any; \
         permit udp any host 10.50.1.1 eq 53; \
         permit udp any host 10.50.1.50 eq 53; \
         permit udp any any eq 123; \
         permit tcp any host 10.50.1.21 eq 8443; \
         permit tcp any host 10.50.1.21 eq 8905; \
         permit tcp any any eq 80; \
         permit tcp any any eq 443; \
         permit tcp any any eq 22; \
         permit tcp any eq 22 any; \
         permit tcp any eq 80 any; \
         permit tcp any eq 443 any; \
         permit tcp any gt 1023 any; \
         permit udp any gt 1023 any; \
         deny ip any 10.0.0.0 0.255.255.255; \
         deny ip any 172.16.0.0 0.15.255.255; \
         deny ip any 192.168.0.0 0.0.255.255; \
         deny ip any any" \
  --descr "Zero-trust Linux research - working V5"

Critical ACL ordering rules:

  1. Specific permits FIRST (DNS, ISE) before RFC1918 denies

  2. Return traffic permits for services (tcp any eq 22/80/443 any)

  3. High-port return traffic (tcp/udp any gt 1023 any)

  4. RFC1918 denies LAST (after all legitimate permits)

  5. NO log keyword on Cisco switches (not supported in dACLs)

ACL processing flow:

1. permit icmp any any                    ← ICMP allowed
2. permit udp any host 10.50.1.1 eq 53   ← DNS to pfSense
3. permit udp any host 10.50.1.50 eq 53  ← DNS to DC
4. permit udp any any eq 123             ← NTP to any
5. permit tcp any host 10.50.1.21 ...    ← ISE API
6. permit tcp any any eq 80/443/22       ← Outbound connections
7. permit tcp any eq 22/80/443 any       ← Server return traffic
8. permit tcp any gt 1023 any            ← Client return traffic
9. deny ip any 10.0.0.0 ...              ← Block other RFC1918
10. deny ip any any                       ← Block everything else

Common mistake: Placing RFC1918 denies before specific permits.

This breaks DNS and ISE access because the deny catches traffic before the permit is evaluated!

Wrong order (V1-V3):

deny ip any 10.0.0.0 0.255.255.255      ← Blocks 10.50.1.1!
permit udp any host 10.50.1.1 eq 53     ← Never reached!

Correct order (V5):

permit udp any host 10.50.1.1 eq 53     ← DNS works
deny ip any 10.0.0.0 0.255.255.255      ← Then block rest

CoA Troubleshooting

If CoA fails but profile is updated, the new dACL still applies on next session:

# CoA may fail due to:
# - Switch not configured for CoA
# - Firewall blocking RADIUS CoA (UDP 1700)
# - Wrong shared secret

# Workaround: Manual reconnect
sudo nmcli connection down "Wired-802.1X"
sudo nmcli connection up "Wired-802.1X"

# Verify new ACL applied
netapi ios exec "show access-session interface GigabitEthernet1/0/5 detail"