Hardened dACL Configuration
1. Overview
This document defines the hardened downloadable ACLs (dACLs) for Linux research workstations. The design principle is zero-trust: block all internal network access, permit only internet and essential infrastructure.
|
The default |
2. dACL Design Principles
| Principle | Implementation |
|---|---|
Deny Internal First |
Block RFC1918 (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16) before any permits |
Least Privilege |
Only permit traffic explicitly required for operations |
Defense in Depth |
dACL + UFW + network segmentation = layered security |
64 ACE Limit |
Stay within Cisco’s recommended ACE count for switch performance |
Document Everything |
Every ACE must have a business justification |
3. Current State vs Target State
|
Current dACL (INSECURE):
This allows the workstation to reach any internal system. A compromised endpoint can pivot to domain controllers, file servers, and other critical infrastructure. |
| Attribute | Current | Target |
|---|---|---|
dACL Name |
|
|
Internal Access |
Full (permit any any) |
Blocked (deny RFC1918) |
Internet Access |
Full |
HTTP/HTTPS/SSH only |
Infrastructure |
Full |
DNS, NTP, ISE only |
ACE Count |
1 |
~15 |
4. Hardened dACL Definition
4.1. LINUX_RESEARCH_HARDENED
! =============================================================================
! dACL: LINUX_RESEARCH_HARDENED
! Purpose: Zero-trust access for Linux research workstations
! Author: InfoSec Team
! Created: 2026-01-22
! ACE Count: 15 (well within 64 ACE limit)
! =============================================================================
! -----------------------------------------------------------------------------
! SECTION 1: BLOCK INTERNAL NETWORKS (MUST BE FIRST)
! Prevents lateral movement to internal systems
! -----------------------------------------------------------------------------
remark === DENY RFC1918 - No Lateral Movement ===
deny ip any 10.0.0.0 0.255.255.255 ! (1)
deny ip any 172.16.0.0 0.15.255.255 ! (2)
deny ip any 192.168.0.0 0.0.255.255 ! (3)
! -----------------------------------------------------------------------------
! SECTION 2: PERMIT INFRASTRUCTURE (Minimal Required)
! Only essential services for workstation operation
! -----------------------------------------------------------------------------
remark === DNS ===
permit udp any host 10.50.1.50 eq 53 ! (4)
remark === NTP ===
permit udp any any eq 123 ! (5)
remark === ISE Posture ===
permit tcp any host 10.50.1.21 eq 8443 ! (6)
permit tcp any host 10.50.1.21 eq 8905 ! (7)
! -----------------------------------------------------------------------------
! SECTION 3: PERMIT INTERNET ACCESS
! Research requires external connectivity
! -----------------------------------------------------------------------------
remark === External Traffic ===
permit tcp any any eq 80 ! (8)
permit tcp any any eq 443 ! (9)
permit tcp any any eq 22 ! (10)
! -----------------------------------------------------------------------------
! SECTION 4: IMPLICIT DENY WITH LOGGING
! Catch and log any unexpected traffic
! -----------------------------------------------------------------------------
remark === Deny All Other ===
deny ip any any log ! (11)
| 1 | Block all 10.x.x.x (Class A private) |
| 2 | Block all 172.16-31.x.x (Class B private) |
| 3 | Block all 192.168.x.x (Class C private) |
| 4 | DNS to domain controller only |
| 5 | NTP for time sync (required for Kerberos) |
| 6 | ISE admin/posture portal |
| 7 | ISE posture agent communication |
| 8 | HTTP for redirects and package repos |
| 9 | HTTPS for updates, research, cloud services |
| 10 | SSH/SFTP for research collaboration |
| 11 | Log denied traffic for security monitoring |
|
Why deny before permit? ACLs are processed top-to-bottom. By denying RFC1918 first, we ensure internal traffic is blocked even if a later rule accidentally permits it. |
4.2. ACE Summary Table
| # | ACE | Purpose | Direction | Risk if Removed |
|---|---|---|---|---|
1-3 |
|
Block internal networks |
Outbound |
CRITICAL - Lateral movement |
4 |
|
DNS resolution |
Outbound |
No name resolution |
5 |
|
NTP time sync |
Outbound |
Kerberos failures |
6-7 |
|
ISE posture |
Outbound |
Posture fails |
8-9 |
|
Web/HTTPS |
Outbound |
No internet |
10 |
|
SSH/SFTP |
Outbound |
No remote access |
11 |
|
Catch-all deny |
Both |
Implicit deny anyway |
5. ISE Configuration Steps
5.1. Step 1: Create the dACL
|
Path: Policy → Policy Elements → Results → Authorization → Downloadable ACLs |
-
Click Add
-
Configure:
Field Value Name
LINUX_RESEARCH_HARDENEDDescription
Zero-trust dACL for Linux research workstations - blocks RFC1918, permits internet
DACL Content
(paste ACL from above, without remarks)
-
Click Submit
5.2. Step 2: Update Authorization Profile
|
Do not delete the old profile yet! Create a new one and test before replacing. |
|
Path: Policy → Policy Elements → Results → Authorization → Authorization Profiles |
-
Find or create profile:
Linux_Research_EAP_TLS -
Configure:
Field Value Name
Linux_Research_EAP_TLSAccess Type
ACCESS_ACCEPTDACL Name
LINUX_RESEARCH_HARDENEDVLAN
40(Research VLAN)Reauthentication Timer
3600(1 hour)Reauthentication Connectivity
RADIUS Request
|
Reauth Timer is CRITICAL! Without a reauth timer, a session persists indefinitely. If posture status changes (e.g., ClamAV stops), the workstation keeps its current access until manually cleared.
|
5.3. Step 3: Verify Policy Configuration
5.3.1. Policy Sets
$ uv run netapi ise get-policy-sets
Policy Sets
┏━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━┓
┃ Name ┃ ID ┃ State ┃
┡━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━┩
│ Domus-Wired 802.1X │ 2c2e6b05-a29c-46a4-a0ee-7fcea4853e47 │ enabled │ (1)
│ ... │ ... │ ... │
└────────────────────┴──────────────────────────────────────┴─────────┘
| 1 | Linux workstations hit this policy set |
5.3.2. Policy Set Condition
$ uv run netapi ise get-policy-set "Domus-Wired 802.1X"
State enabled
Rank 3
Hit Count 276
Service Default Network Access
Description Wired 802.1X closed mode authentication
Condition:
Attribute NAS-Port-Type (1)
Operator equals
Value Ethernet (2)
| 1 | RADIUS attribute from NAD (switch) |
| 2 | Matches wired Ethernet ports only |
5.3.3. Certificate Authentication Profile
$ uv run netapi ise get-cert-profile "AD_Cert_Profile"
Description Certificate authentication for AD-joined Linux workstations
Certificate Attribute SUBJECT_COMMON_NAME (1)
Username From CERTIFICATE (2)
Match Mode NEVER
| 1 | Extracts CN from certificate subject (e.g., modestus-p50.inside.domusdigitalis.dev) |
| 2 | Username derived from certificate, not user input |
5.3.4. Authentication Rules
$ uv run netapi ise get-auth-rules "Domus-Wired 802.1X"
# Rule Name Identity Source If Fail If Not Found State
0 EAP_TLS_Certificate_Auth AD_Cert_Profile REJECT REJECT enabled (1)
1 Default All_User_ID_Stores REJECT REJECT enabled
| 1 | Certificate-based authentication against AD certificate profile |
5.3.5. Authorization Rules
$ uv run netapi ise get-authz-rules "Domus-Wired 802.1X"
# Rule Name Profile(s) SGT State
0 Linux_EAPTLS_Test Linux_EAPTLS_Permit - enabled (1)
1 Linux_Posture_Compliant Linux_Posture_Compliant - enabled
2 Linux_Posture_NonCompliant Linux_Posture_NonCompliant - enabled
3 Linux_Posture_Unknown Linux_Posture_Unknown - enabled
4 Default DenyAccess - enabled (2)
| 1 | Matches EAP-TLS cert auth → Linux_EAPTLS_Permit profile (VLAN 40 + hardened dACL + 3600s reauth) |
| 2 | Unmatched endpoints denied (closed mode) |
6. Validation Evidence (2026-01-22)
6.1. dACL and Reauth Timer via netapi
# Create hardened dACL
$ uv run netapi ise create-dacl LINUX_RESEARCH_HARDENED \
--file /tmp/LINUX_RESEARCH_HARDENED.txt \
--descr "Zero-trust dACL for Linux research workstations - blocks RFC1918, permits internet"
Creating DACL: LINUX_RESEARCH_HARDENED
OK (ID: 2416bc00-f805-11f0-b76e-52c54a1d1f56)
# Update profile with dACL
$ uv run netapi ise update-authz-profile "Linux_EAPTLS_Permit" \
--dacl LINUX_RESEARCH_HARDENED
✓ Updated authorization profile: Linux_EAPTLS_Permit
DACL: LINUX_RESEARCH_HARDENED
# Set reauth timer (1 hour)
$ uv run netapi ise update-authz-profile "Linux_EAPTLS_Permit" \
--reauth-timer 3600
✓ Updated authorization profile: Linux_EAPTLS_Permit
Reauth Timer: 3600s (60 min)
6.2. ISE Profile Verification
$ uv run netapi ise get-authz-profile "Linux_EAPTLS_Permit"
name Linux_EAPTLS_Permit
vlan {'nameID': 'RESEARCH_VLAN', 'tagID': 1}
reauth {'timer': 3600, 'connectivity': 'RADIUS_REQUEST'} (1)
daclName LINUX_RESEARCH_HARDENED (2)
| 1 | Reauth timer set to 3600s with RADIUS_REQUEST connectivity |
| 2 | Hardened dACL applied |
6.3. Session Verification
$ uv run netapi ios exec "show access-session interface GigabitEthernet1/0/5 detail" \
| grep -E "User-Name|Status|Vlan|ACS ACL|IPv4|MAC|Session timeout"
MAC Address: c85b.76c6.5962
IPv4 Address: 10.50.40.101
User-Name: modestus-p50.inside.domusdigitalis.dev
Status: Authorized
Session timeout: 3600s (server), Remaining: 3584s (1)
Vlan Group: Vlan: 40
Security Status: Link Unsecure
ACS ACL: xACSACLx-IP-LINUX_RESEARCH_HARDENED-6972e00d (2)
| 1 | Reauth timer pushed from ISE - session will reauthenticate hourly |
| 2 | Hardened dACL successfully applied |
6.4. ACL on Switch
$ uv run netapi ios exec "show ip access-list xACSACLx-IP-LINUX_RESEARCH_HARDENED-6972e00d"
Extended IP access list xACSACLx-IP-LINUX_RESEARCH_HARDENED-6972e00d (per-user)
1 deny ip any 10.0.0.0 0.255.255.255
2 deny ip any 172.16.0.0 0.15.255.255
3 deny ip any 192.168.0.0 0.0.255.255
4 permit udp any host 10.50.1.50 eq domain
5 permit udp any any eq ntp
6 permit tcp any host 10.50.1.21 eq 8443
7 permit tcp any host 10.50.1.21 eq 8905
8 permit tcp any any eq www
9 permit tcp any any eq 443
10 permit tcp any any eq 22
11 deny ip any any log
6.5. Connectivity Test from P50
# Internal BLOCKED (RFC1918 denied)
$ ping -c 3 10.50.1.1
PING 10.50.1.1 (10.50.1.1) 56(84) bytes of data.
--- 10.50.1.1 ping statistics ---
3 packets transmitted, 0 received, 100% packet loss, time 2056ms (1)
# Internet WORKS (HTTPS permitted)
$ curl -sI https://google.com | head -1
HTTP/2 301 (2)
| 1 | Internal traffic blocked by dACL - zero-trust enforced |
| 2 | Internet connectivity confirmed |
7. Rollback Plan
|
If the hardened dACL breaks connectivity:
|
8. netapi Commands
8.1. ISE Policy Query Commands
# Load secrets
dsource d000 dev/network
# Policy Sets and Rules
uv run netapi ise get-policy-sets
uv run netapi ise get-policy-set "Domus-Wired 802.1X"
uv run netapi ise get-auth-rules "Domus-Wired 802.1X"
uv run netapi ise get-authz-rules "Domus-Wired 802.1X"
# Certificate Profile
uv run netapi ise get-cert-profiles
uv run netapi ise get-cert-profile "AD_Cert_Profile"
# Authorization Profiles and dACLs
uv run netapi ise get-authz-profiles
uv run netapi ise get-authz-profile "Linux_EAPTLS_Permit"
uv run netapi ise get-dacls
uv run netapi ise get-dacl "LINUX_RESEARCH_HARDENED"
8.2. ISE Configuration Commands
# Create dACL from file
uv run netapi ise create-dacl LINUX_RESEARCH_HARDENED \
--file /path/to/dacl.txt \
--descr "Zero-trust dACL - blocks RFC1918, permits internet"
# Update profile with dACL
uv run netapi ise update-authz-profile "Linux_EAPTLS_Permit" \
--dacl LINUX_RESEARCH_HARDENED
# Set reauth timer (seconds)
uv run netapi ise update-authz-profile "Linux_EAPTLS_Permit" \
--reauth-timer 3600
# Verify profile
uv run netapi ise get-authz-profile "Linux_EAPTLS_Permit"
8.3. Switch Session Verification
# Check session with dACL and timeout (elegant one-liner)
INT="GigabitEthernet1/0/5" && \
uv run netapi ios exec "show access-session interface $INT detail" \
| grep -E "User-Name|Status|Vlan|ACS ACL|IPv4|MAC|Session timeout" && echo "---" && \
ACL=$(uv run netapi ios exec "show access-session interface $INT detail" \
| awk '/ACS ACL/{print $3}') && \
uv run netapi ios exec "show ip access-list $ACL"
# Force re-authentication (use 'run' not 'exec' for clear commands)
uv run netapi ios run "clear access-session interface GigabitEthernet1/0/5"
# Check ISE live logs
uv run netapi ise mnt session C8:5B:76:C6:59:62
|
exec vs run: Use |
|
--reauth-timer options:
|
8.4. Change of Authorization (CoA) Configuration
|
To enable ISE to send Change of Authorization (CoA) commands to switches, the switch must:
|
8.4.1. Check Current CoA Configuration
# Check if CoA is configured
uv run netapi ios exec "show run | section dynamic-author"
# Expected output:
# aaa server radius dynamic-author
# client 10.50.1.20 server-key Cisco123!ISE <-- ISE PSN 1
# client 10.50.1.21 server-key Cisco123!ISE <-- ISE PSN 2
# auth-type any
# Check NAD configuration in ISE
netapi ise get-nad --name "Home-3560CX-01"
8.4.2. Configure CoA Using Heredoc Method
When configuring switches via netapi ios run, netmiko may timeout detecting the prompt, but commands still execute successfully. Use heredoc with || true to handle timeouts gracefully:
# Create configuration script using heredoc
cat >| /tmp/configure-coa.sh << 'EOF'
#!/bin/bash
# Note: Use --timeout 5 and || true to ignore netmiko prompt timeout
# Commands execute successfully even if netmiko times out
uv run netapi ios run --timeout 5 "configure terminal" || true
uv run netapi ios run --timeout 5 "aaa server radius dynamic-author" || true
uv run netapi ios run --timeout 5 "client 10.50.1.20 server-key Cisco123\!ISE" || true
uv run netapi ios run --timeout 5 "client 10.50.1.21 server-key Cisco123\!ISE" || true
uv run netapi ios run --timeout 5 "exit" || true
uv run netapi ios run --timeout 5 "end" || true
uv run netapi ios run --timeout 5 "write memory" || true
EOF
# Execute the script
chmod +x /tmp/configure-coa.sh
/tmp/configure-coa.sh
# Verify configuration applied
uv run netapi ios exec "show run | section dynamic-author"
|
Why Netmiko may fail to detect the switch prompt after configuration commands, causing |
8.4.3. Remove Invalid CoA Clients
# Remove a misconfigured or non-existent CoA client
cat >| /tmp/remove-coa-client.sh << 'EOF'
#!/bin/bash
uv run netapi ios run --timeout 5 "configure terminal" || true
uv run netapi ios run --timeout 5 "aaa server radius dynamic-author" || true
uv run netapi ios run --timeout 5 "no client 10.50.1.22" || true
uv run netapi ios run --timeout 5 "exit" || true
uv run netapi ios run --timeout 5 "end" || true
uv run netapi ios run --timeout 5 "write memory" || true
EOF
chmod +x /tmp/remove-coa-client.sh
/tmp/remove-coa-client.sh
# Verify removal
uv run netapi ios exec "show run | section dynamic-author"
8.4.4. Test CoA
# Send CoA Reauth to endpoint
netapi ise mnt coa c8:5b:76:c6:59:62
# If successful:
# ✓ CoA Reauth sent to c8:5b:76:c6:59:62
# If it fails:
# Error: [404] CoA Reauth failed for C8:5B:76:C6:59:62: Session not found...
# check ISE can reach NAD on UDP 1700
# Verify new dACL applied after CoA
netapi ise mnt auth-logs c8:5b:76:c6:59:62 --limit 1
|
CoA Common Issues:
|
8.5. Updating an In-Use dACL
When a dACL is actively referenced by an authorization profile, ISE prevents deletion to avoid breaking active sessions. The safe workflow is:
# Step 1: Find which authorization profile is using the dACL
uv run netapi ise get-authz-profiles | grep -i linux
# Output:
# │ Linux_EAPTLS_Permit │ 94aab910-f18b-11f0-b76e-52c54a1d1f56 │
# │ Linux_Posture_Compliant │ 0027b090-f1ae-11f0-b76e-52c54a1d1f56 │
# Step 2: Create fixed ACL content using heredoc
cat > /tmp/LINUX_RESEARCH_ZERO_TRUST.txt << 'EOF'
deny icmp any 10.0.0.0 0.255.255.255
deny icmp any 172.16.0.0 0.15.255.255
deny icmp any 192.168.0.0 0.0.255.255
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 eq 53 any gt 1023
permit udp any host 10.50.1.1 eq 123
permit udp any eq 123 any gt 1023
permit tcp any host 10.50.1.50 eq 88
permit udp any host 10.50.1.50 eq 88
permit tcp any host 10.50.1.50 eq 389
permit tcp any host 10.50.1.50 eq 636
permit tcp any host 10.50.1.50 eq 445
permit tcp any host 10.50.1.21 eq 8443
permit tcp any host 10.50.1.21 eq 8905
permit tcp any eq 22 10.50.0.0 0.0.255.255 gt 1023
permit tcp any any eq 80
permit tcp any eq 80 any gt 1023
permit tcp any any eq 443
permit tcp any eq 443 any gt 1023
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
EOF
# Step 3: Create V2 dACL (can't delete original - it's in use)
uv run netapi ise create-dacl LINUX_RESEARCH_ZERO_TRUST_V2 \
--file /tmp/LINUX_RESEARCH_ZERO_TRUST.txt \
--descr "Zero-trust ACL for domain-joined Linux workstations - ICMP hardened V2"
# Output:
# Creating DACL: LINUX_RESEARCH_ZERO_TRUST_V2
# OK (ID: 2254dfe0-fbb7-11f0-9bb2-fafc6167f873)
# Step 4: Update authorization profile to use V2
uv run netapi ise update-authz-profile "Linux_EAPTLS_Permit" \
--dacl LINUX_RESEARCH_ZERO_TRUST_V2
# Output:
# ✓ Updated authorization profile: Linux_EAPTLS_Permit
# DACL: LINUX_RESEARCH_ZERO_TRUST_V2
# Step 5: Verify current session still has old ACL
uv run netapi ios exec "show access-s int g1/0/5 d" | grep "ACS ACL"
# Output:
# ACS ACL: xACSACLx-IP-LINUX_RESEARCH_ZERO_TRUST-69790b85
# ^-- Still old ACL! Session is already active
# Step 6: Trigger CoA to apply new ACL
uv run netapi ise mnt coa 10:60:4b:e9:7e:ed
# Output:
# ✓ CoA Reauth sent to 10:60:4b:e9:7e:ed
# Step 7: Verify new ACL is applied
uv run netapi ios exec "show access-s int g1/0/5 d" | grep "ACS ACL"
# Output:
# ACS ACL: xACSACLx-IP-LINUX_RESEARCH_ZERO_TRUST_V2-<new-hash>
# ^-- NEW ACL applied!
# Step 8: Delete old dACL (now safe since not in use)
uv run netapi ise delete-dacl LINUX_RESEARCH_ZERO_TRUST
# Step 9: Optionally rename V2 to production name
# (Requires repeating steps 3-7 with final name)
|
Why this workflow matters:
Common mistake: Trying to delete an in-use dACL results in:
This is by design - follow the versioned workflow above. |
9. Testing Zero-Trust dACL
After applying a zero-trust dACL, validation is CRITICAL to ensure it’s blocking lateral movement while allowing required services.
9.1. Automated Test Script
Create a comprehensive test script to validate zero-trust restrictions:
# Create test script locally
cat > /tmp/test-zero-trust.sh << 'EOF'
#!/bin/bash
echo "=== Testing PERMITTED traffic ==="
echo ""
echo "Test 1: Internet ICMP (8.8.8.8)"
timeout 3 ping -c 2 8.8.8.8 && echo "✓ PASS" || echo "✗ FAIL"
echo ""
echo "Test 2: DNS to pfSense (10.50.1.1)"
timeout 3 dig google.com @10.50.1.1 +short && echo "✓ PASS" || echo "✗ FAIL"
echo ""
echo "Test 3: DNS to DC (10.50.1.50)"
timeout 3 dig google.com @10.50.1.50 +short && echo "✓ PASS" || echo "✗ FAIL"
echo ""
echo "Test 4: HTTPS to internet"
timeout 3 curl -sI https://google.com | head -1 && echo "✓ PASS" || echo "✗ FAIL"
echo ""
echo "Test 5: Kerberos to DC (10.50.1.50:88)"
timeout 3 bash -c '</dev/tcp/10.50.1.50/88' 2>/dev/null && echo "✓ PASS" || echo "✗ FAIL"
echo ""
echo "Test 6: LDAP to DC (10.50.1.50:389)"
timeout 3 bash -c '</dev/tcp/10.50.1.50/389' 2>/dev/null && echo "✓ PASS" || echo "✗ FAIL"
echo ""
echo "Test 7: LDAPS to DC (10.50.1.50:636)"
timeout 3 bash -c '</dev/tcp/10.50.1.50/636' 2>/dev/null && echo "✓ PASS" || echo "✗ FAIL"
echo ""
echo "Test 8: SMB to DC (10.50.1.50:445)"
timeout 3 bash -c '</dev/tcp/10.50.1.50/445' 2>/dev/null && echo "✓ PASS" || echo "✗ FAIL"
echo ""
echo "=== Testing BLOCKED traffic (should timeout) ==="
echo ""
echo "Test 9: Ping to pfSense (10.50.1.1) - should FAIL"
timeout 3 ping -c 2 10.50.1.1 && echo "✗ SECURITY ISSUE: Internal ICMP allowed!" || echo "✓ CORRECTLY BLOCKED"
echo ""
echo "Test 10: SSH to switch (10.50.1.10:22) - should FAIL"
timeout 3 bash -c '</dev/tcp/10.50.1.10/22' 2>/dev/null && echo "✗ SECURITY ISSUE: Lateral movement allowed!" || echo "✓ CORRECTLY BLOCKED"
echo ""
echo "Test 11: HTTP to internal server (10.50.2.1:80) - should FAIL"
timeout 3 bash -c '</dev/tcp/10.50.2.1/80' 2>/dev/null && echo "✗ SECURITY ISSUE: Internal access allowed!" || echo "✓ CORRECTLY BLOCKED"
echo ""
echo "Test 12: RDP to Windows host (10.50.3.10:3389) - should FAIL"
timeout 3 bash -c '</dev/tcp/10.50.3.10/3389' 2>/dev/null && echo "✗ SECURITY ISSUE: RDP lateral movement!" || echo "✓ CORRECTLY BLOCKED"
EOF
# Deploy and run on target workstation
scp /tmp/test-zero-trust.sh <target-host>:/tmp/
ssh <target-host> 'chmod +x /tmp/test-zero-trust.sh && /tmp/test-zero-trust.sh'
|
Why
|
9.2. Expected Results
| Test | Expected Result | Security Impact if Failed |
|---|---|---|
Internet ICMP |
✓ PASS |
Updates/troubleshooting broken |
DNS (pfSense/DC) |
✓ PASS |
Name resolution broken |
HTTPS to internet |
✓ PASS |
Updates/repos broken |
Kerberos (88) |
✓ PASS |
AD authentication fails |
LDAP (389/636) |
✓ PASS |
Group policy fails |
SMB to DC (445) |
✓ PASS |
Cert enrollment fails |
Ping to internal |
✓ BLOCKED |
CRITICAL - Lateral movement possible |
SSH to switch |
✓ BLOCKED |
CRITICAL - Infrastructure access |
HTTP to internal |
✓ BLOCKED |
CRITICAL - Internal service access |
RDP to Windows |
✓ BLOCKED |
CRITICAL - Workstation compromise |
|
If any "should FAIL" tests pass, your zero-trust ACL is BROKEN! This indicates lateral movement is possible - the workstation can reach internal systems it shouldn’t. |
9.3. Validation Success (2026-01-27)
After applying LINUX_RESEARCH_ZERO_TRUST_V2 with ICMP hardening to modestus-p50:
❯ ssh modestus-p50 '/tmp/test-zero-trust.sh'
=== Testing PERMITTED traffic ===
Test 1: Internet ICMP (8.8.8.8)
✓ PASS
Test 2: DNS to pfSense (10.50.1.1)
✓ PASS
Test 3: DNS to DC (10.50.1.50)
✓ PASS
Test 4: HTTPS to internet
✓ PASS
Test 5: Kerberos to DC (10.50.1.50:88)
✓ PASS
Test 6: LDAP to DC (10.50.1.50:389)
✓ PASS
Test 7: LDAPS to DC (10.50.1.50:636)
✓ PASS
Test 8: SMB to DC (10.50.1.50:445)
✓ PASS
=== Testing BLOCKED traffic (should timeout) ===
Test 9: Ping to pfSense (10.50.1.1) - should FAIL
✓ CORRECTLY BLOCKED <-- ICMP HARDENING WORKING!
Test 10: SSH to switch (10.50.1.10:22) - should FAIL
✓ CORRECTLY BLOCKED
Test 11: HTTP to internal server (10.50.2.1:80) - should FAIL
✓ CORRECTLY BLOCKED
Test 12: RDP to Windows host (10.50.3.10:3389) - should FAIL
✓ CORRECTLY BLOCKED
|
Key Success Indicators: ✅ All 8 permitted tests PASS - Workstation can reach required services ✅ All 4 blocked tests CORRECTLY BLOCKED - Zero-trust isolation working ✅ Test 9 fixed - RFC1918 ICMP denies prevent internal reconnaissance ✅ External ICMP works - Troubleshooting capability preserved ACL Applied:
Status: ✅ READY FOR HOME DEPLOYMENT |
9.4. Domain Requirements Validation
Beyond zero-trust isolation, domain-joined workstations have additional requirements. Use this script to validate all prerequisites:
cat > /tmp/test-requirements.sh << 'EOF'
#!/bin/bash
echo "=== Zero-Trust Requirements Validation ==="
echo ""
# Test 1: Domain Join
echo "Test 1: Domain Join Status"
if realm list | grep -q "configured: kerberos-member"; then
echo "✓ PASS - Domain joined to $(realm list | grep 'realm-name' | awk '{print $2}')"
else
echo "✗ FAIL - Not domain joined"
fi
echo ""
# Test 2: DNS Resolution (Internal)
echo "Test 2: DNS Resolution (Internal)"
if host dc-01.inside.domusdigitalis.dev 10.50.1.50 >/dev/null 2>&1; then
echo "✓ PASS - Can resolve internal DNS"
else
echo "✗ FAIL - Cannot resolve internal DNS"
fi
echo ""
# Test 3: DNS Resolution (External)
echo "Test 3: DNS Resolution (External)"
if host google.com 10.50.1.1 >/dev/null 2>&1; then
echo "✓ PASS - Can resolve external DNS"
else
echo "✗ FAIL - Cannot resolve external DNS"
fi
echo ""
# Test 4: Kerberos TGT
echo "Test 4: Kerberos Ticket"
if klist -s 2>/dev/null; then
echo "✓ PASS - Valid Kerberos ticket"
klist | grep "Default principal" || true
else
echo "✗ FAIL - No valid Kerberos ticket (expected via SSH)"
fi
echo ""
# Test 5: LDAP to DC
echo "Test 5: LDAP Connectivity"
if timeout 3 bash -c '</dev/tcp/10.50.1.50/389' 2>/dev/null; then
echo "✓ PASS - LDAP reachable"
else
echo "✗ FAIL - LDAP not reachable"
fi
echo ""
# Test 6: LDAPS to DC
echo "Test 6: LDAPS Connectivity"
if timeout 3 bash -c '</dev/tcp/10.50.1.50/636' 2>/dev/null; then
echo "✓ PASS - LDAPS reachable"
else
echo "✗ FAIL - LDAPS not reachable"
fi
echo ""
# Test 7: SMB to DC (for cert enrollment)
echo "Test 7: SMB to DC (Certificate Enrollment)"
if timeout 3 bash -c '</dev/tcp/10.50.1.50/445' 2>/dev/null; then
echo "✓ PASS - SMB reachable for certs"
else
echo "✗ FAIL - SMB not reachable"
fi
echo ""
# Test 8: Internet Connectivity
echo "Test 8: Internet HTTPS"
if timeout 3 curl -sI https://google.com >/dev/null 2>&1; then
echo "✓ PASS - Internet accessible"
else
echo "✗ FAIL - Internet not accessible"
fi
echo ""
# Test 9: ISE Posture Access
echo "Test 9: ISE Posture Portal"
if timeout 3 bash -c '</dev/tcp/10.50.1.21/8443' 2>/dev/null; then
echo "✓ PASS - ISE posture reachable"
else
echo "✗ FAIL - ISE posture not reachable"
fi
echo ""
# Test 10: Zero-Trust Validation (Internal blocked)
echo "Test 10: Zero-Trust Isolation (Lateral Movement)"
if timeout 3 bash -c '</dev/tcp/10.50.1.10/22' 2>/dev/null; then
echo "✗ SECURITY ISSUE - Can reach switch (lateral movement!)"
else
echo "✓ PASS - Internal lateral movement blocked"
fi
echo ""
echo "=== Summary ==="
echo "All critical requirements validated for zero-trust workstation"
EOF
chmod +x /tmp/test-requirements.sh
scp /tmp/test-requirements.sh modestus-p50:/tmp/
ssh modestus-p50 '/tmp/test-requirements.sh'
Expected output (2026-01-27):
=== Zero-Trust Requirements Validation ===
Test 1: Domain Join Status
✓ PASS - Domain joined to INSIDE.DOMUSDIGITALIS.DEV
Test 2: DNS Resolution (Internal)
✓ PASS - Can resolve internal DNS
Test 3: DNS Resolution (External)
✓ PASS - Can resolve external DNS
Test 4: Kerberos Ticket
✗ FAIL - No valid Kerberos ticket (expected via SSH)
Test 5: LDAP Connectivity
✓ PASS - LDAP reachable
Test 6: LDAPS Connectivity
✓ PASS - LDAPS reachable
Test 7: SMB to DC (Certificate Enrollment)
✓ PASS - SMB reachable for certs
Test 8: Internet HTTPS
✓ PASS - Internet accessible
Test 9: ISE Posture Portal
✗ FAIL - ISE posture not reachable
Test 10: Zero-Trust Isolation (Lateral Movement)
✓ PASS - Internal lateral movement blocked
=== Summary ===
All critical requirements validated for zero-trust workstation
|
Expected failures via SSH:
Critical tests that MUST pass:
|
9.5. Common Test Failures and Fixes
9.5.1. Issue: Internal ICMP Allowed (Test 9 fails)
Problem:
Test 9: Ping to pfSense (10.50.1.1) - should FAIL
✗ SECURITY ISSUE: Internal ICMP allowed!
Root cause: ACL has permit icmp any any BEFORE RFC1918 denies. First match wins - internal ICMP is permitted before it can be blocked.
The CORRECT Fix: Add RFC1918 ICMP denies BEFORE the general ICMP permit:
# Create fixed ACL using heredoc
cat > /tmp/LINUX_RESEARCH_ZERO_TRUST.txt << 'EOF'
deny icmp any 10.0.0.0 0.255.255.255
deny icmp any 172.16.0.0 0.15.255.255
deny icmp any 192.168.0.0 0.0.255.255
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 eq 53 any gt 1023
permit udp any host 10.50.1.1 eq 123
permit udp any eq 123 any gt 1023
permit tcp any host 10.50.1.50 eq 88
permit udp any host 10.50.1.50 eq 88
permit tcp any host 10.50.1.50 eq 389
permit tcp any host 10.50.1.50 eq 636
permit tcp any host 10.50.1.50 eq 445
permit tcp any host 10.50.1.21 eq 8443
permit tcp any host 10.50.1.21 eq 8905
permit tcp any eq 22 10.50.0.0 0.0.255.255 gt 1023
permit tcp any any eq 80
permit tcp any eq 80 any gt 1023
permit tcp any any eq 443
permit tcp any eq 443 any gt 1023
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
EOF
# Upload to ISE
uv run netapi ise create-dacl LINUX_RESEARCH_ZERO_TRUST \
--file /tmp/LINUX_RESEARCH_ZERO_TRUST.txt \
--descr "Zero-trust ACL for domain-joined Linux workstations - ICMP hardened"
# Trigger CoA to apply immediately
uv run netapi ise mnt coa <mac-address>
|
Why this works:
|
|
Do NOT simply remove External ICMP (like |
9.5.2. Issue: Lateral Movement Allowed (Tests 10-12 fail)
Problem: Workstation can reach internal systems (switches, servers, workstations).
Root cause: RFC1918 denies are not at the END of the ACL, or specific permits came after them.
Fix: Ensure RFC1918 denies are the LAST rules before implicit deny:
# All permits FIRST
permit tcp any host 10.50.1.50 eq 88
permit tcp any host 10.50.1.50 eq 389
# ... other permits ...
# RFC1918 denies LAST (just before implicit deny)
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
9.6. Deployment Checklist for HOME
| Step | Action | Status |
|---|---|---|
1 |
Create test workstation in isolated VLAN |
☐ |
2 |
Apply zero-trust dACL to test profile |
☐ |
3 |
Run automated test script |
☐ |
4 |
Verify ALL "should FAIL" tests actually fail |
☐ |
5 |
Verify ALL "should PASS" tests actually pass |
☐ |
6 |
Test domain join and cert enrollment |
☐ |
7 |
Test application access (Epic, VPN, etc.) |
☐ |
8 |
Document any additional permits needed |
☐ |
9 |
Apply to pilot group (5-10 workstations) |
☐ |
10 |
Monitor for 48 hours, check ISE denied logs |
☐ |
11 |
Roll out to production incrementally |
☐ |
|
NEVER deploy to production without thorough testing! A broken dACL can:
Always test in isolated environment first. |
9.7. ISE Monitoring During Rollout
Check for denied traffic that should be permitted:
# Check ISE live logs for denies
netapi ise mnt auth-logs <mac-address> --limit 20
# Query DataConnect for denied ACL hits (if available)
netapi ise dc query "SELECT * FROM radius_accounting WHERE acl_name LIKE '%ZERO_TRUST%'"
# Check switch for ACL hit counts
uv run netapi ios exec "show ip access-list xACSACLx-IP-LINUX_RESEARCH_ZERO_TRUST-<hash>"
Look for high deny counts on specific rules - may indicate a legitimate service being blocked.