Implementing Enterprise-Grade 802.1X EAP-TLS Authentication in My Home Lab
Implementing Enterprise-Grade 802.1X EAP-TLS Authentication in My Home Lab
Introduction
802.1X with EAP-TLS is the gold standard for wired network access control -- and it is notoriously painful to get right, even in production. In this post, I walk through my full implementation using FreeRADIUS, Samba4 Active Directory, and a Cisco Catalyst 2960, covering the PKI design, every configuration file that matters, and the five troubleshooting issues I hit along the way.
This goes beyond simple password-based authentication by leveraging X.509 certificates for device authentication, providing the same level of security found in enterprise environments.
Architecture Overview
Network Infrastructure:
- Management VLAN:
172.16.99.0/24 - Domain:
tillynet.lan - Samba4 Active Directory Domain Controller:
172.30.30.30 - pfSense Firewall with FreeRADIUS:
172.16.99.1 - Cisco Catalyst 2960 Switch (Network Access Server)
- Windows 10/11 domain-joined workstation:
TILLYPC
Security Infrastructure:
- Two-tier PKI with offline Root CA
- Samba4-hosted Intermediate Certificate Authority
- EAP-TLS authentication protocol
- Certificate-based device authentication
The architecture separates the authentication server (FreeRADIUS), directory services (Samba4 AD), and network enforcement (Cisco switch) into distinct components communicating via standardized protocols.
Certificate Infrastructure Design
EAP-TLS lives and dies by its PKI. I designed a two-tier hierarchy that mirrors enterprise deployments.
Root Certificate Authority
The Root CA operates offline for maximum security, issuing only intermediate certificates. The root private key stays protected while the intermediate CA handles day-to-day operations.
Intermediate Certificate Authority
I integrated the Intermediate CA directly into my Samba4 AD environment, giving me centralized certificate management, integration with AD security groups, and a realistic enterprise-like setup.
Server Certificate Generation
For the FreeRADIUS server certificate, I wrote a script that automates generation while ensuring proper extensions and SANs:
#!/bin/bash
SERVICE_FQDN=$1
BASE_DIR="/usr/local/samba/private/tls"
KEY_SIZE=2048
DAYS_VALID=825
INTERMEDIATE_KEY="${BASE_DIR}/intermediate.key"
INTERMEDIATE_CRT="${BASE_DIR}/intermediate.crt"
CA_CHAIN="${BASE_DIR}/ca-chain.crt"
if [[ -z "$SERVICE_FQDN" ]]; then
echo "Usage: $0 <service.fqdn>"
exit 1
fi
OUTPUT_DIR="${BASE_DIR}/${SERVICE_FQDN}"
mkdir -p "${OUTPUT_DIR}"
# Generate private key and certificate signing request
openssl req -new -newkey rsa:${KEY_SIZE} -nodes \
-keyout "${OUTPUT_DIR}/${SERVICE_FQDN}.key" \
-out "${OUTPUT_DIR}/${SERVICE_FQDN}.csr" \
-subj "/CN=${SERVICE_FQDN}" \
-addext "subjectAltName = DNS:${SERVICE_FQDN}"
# Create certificate extensions
EXT_FILE="${OUTPUT_DIR}/v3_ext.cnf"
cat > "${EXT_FILE}" <<EOF
[ v3_req ]
basicConstraints = CA:FALSE
keyUsage = digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
subjectAltName = @alt_names
[ alt_names ]
DNS.1 = ${SERVICE_FQDN}
EOF
# Sign certificate with intermediate CA
openssl x509 -req \
-in "${OUTPUT_DIR}/${SERVICE_FQDN}.csr" \
-CA "${INTERMEDIATE_CRT}" \
-CAkey "${INTERMEDIATE_KEY}" \
-CAcreateserial \
-out "${OUTPUT_DIR}/${SERVICE_FQDN}.crt" \
-days ${DAYS_VALID} -sha256 \
-extfile "${EXT_FILE}" \
-extensions v3_req
# Build complete certificate chain
cat "${OUTPUT_DIR}/${SERVICE_FQDN}.crt" "${CA_CHAIN}" > "${OUTPUT_DIR}/${SERVICE_FQDN}-fullchain.crt"
rm -f "${EXT_FILE}"
This script ensures the server certificate includes the proper Extended Key Usage (EKU) for server authentication and maintains the complete chain required for client validation.
Client Certificate Generation
Client certificates require different extensions -- specifically the Client Authentication EKU. I created a separate script:
#!/bin/bash
FQDN="$1"
CERT_DIR="/usr/local/samba/private/tls/$FQDN"
mkdir -p "$CERT_DIR"
# Generate client certificate with appropriate extensions
openssl req -new -nodes -newkey rsa:2048 \
-keyout "$CERT_DIR/$FQDN.key" \
-out "$CERT_DIR/$FQDN.csr" \
-subj "/CN=$FQDN" \
-config <(cat <<EOF
[ req ]
default_bits = 2048
prompt = no
default_md = sha256
req_extensions = v3_req
distinguished_name = dn
[ dn ]
CN = $FQDN
[ v3_req ]
basicConstraints = CA:FALSE
keyUsage = digitalSignature, keyEncipherment
extendedKeyUsage = clientAuth
subjectAltName = @alt_names
[ alt_names ]
DNS.1 = $FQDN
EOF
)
# Sign with intermediate CA
openssl x509 -req \
-in "$CERT_DIR/$FQDN.csr" \
-CA /usr/local/samba/private/tls/intermediate.crt \
-CAkey /usr/local/samba/private/tls/intermediate.key \
-CAcreateserial \
-out "$CERT_DIR/$FQDN.crt" \
-days 825 -sha256 \
-extfile <(cat <<EOF
[ v3_req ]
basicConstraints = CA:FALSE
keyUsage = digitalSignature, keyEncipherment
extendedKeyUsage = clientAuth
subjectAltName = @alt_names
[ alt_names ]
DNS.1 = $FQDN
EOF
)
cat "$CERT_DIR/$FQDN.crt" /usr/local/samba/private/tls/ca-chain.crt > "$CERT_DIR/$FQDN-fullchain.crt"
PKCS#12 Export for Windows
Windows workstations require certificates in PKCS#12 format. I automated the conversion:
#!/bin/bash
FQDN="$1"
PFX_PASSWORD="${2:-""}"
CERT_BASE_DIR="/usr/local/samba/private/tls"
CERT_DIR="$CERT_BASE_DIR/$FQDN"
KEY_FILE="$CERT_DIR/$FQDN.key"
CERT_FILE="$CERT_DIR/$FQDN.crt"
CHAIN_FILE="$CERT_BASE_DIR/ca-chain.crt"
PFX_OUTPUT="$CERT_DIR/$FQDN.pfx"
# Validate all required files exist
for file in "$KEY_FILE" "$CERT_FILE" "$CHAIN_FILE"; do
if [[ ! -f "$file" ]]; then
echo "Error: Required file not found: $file"
exit 1
fi
done
# Generate PKCS#12 bundle
openssl pkcs12 -export \
-inkey "$KEY_FILE" \
-in "$CERT_FILE" \
-certfile "$CHAIN_FILE" \
-out "$PFX_OUTPUT" \
-passout pass:"$PFX_PASSWORD"
This ensures Windows clients receive certificates with the complete chain, enabling proper validation against the root CA.
FreeRADIUS Configuration
Configuring FreeRADIUS for EAP-TLS with Active Directory integration required careful attention to several components. The pfSense FreeRADIUS package provides a web interface, but understanding the underlying configuration is essential for troubleshooting.
RADIUS Client Configuration
I configured the Cisco switch as a RADIUS client (NAS):
- Client Name: TL-ASW1-2960C
- Client IP Address: The management IP of the switch
- Shared Secret: A 32-character randomly generated pre-shared key matching the switch's AAA configuration
- NAS Type: Cisco
LDAP Integration Challenges
This was the most complex part of the implementation. Windows computer accounts authenticate differently from user accounts, and getting FreeRADIUS to handle that correctly took several attempts.
Initial (broken) LDAP configuration:
- Base DN:
DC=tillynet,DC=lan - Filter:
(sAMAccountName=%{Stripped-User-Name:-%{User-Name}}) - Base Filter:
(objectClass=person)
This failed because Windows computer authentication uses the host/computername.domain format, computer accounts live in CN=Computers by default, and their sAMAccountName includes a trailing dollar sign.
Corrected LDAP configuration:
- Base DN:
CN=Computers,DC=tillynet,DC=lan - Filter:
(&(objectClass=computer)(|(sAMAccountName=%{mschap:User-Name}$)(servicePrincipalName=%{User-Name}))) - Base Filter:
(objectClass=computer) - LDAP Authorization: Enabled (critical -- without this, FreeRADIUS cannot process LDAP responses for authorization decisions)
The filter handles two authentication formats: the sAMAccountName with trailing $ for the computer account name, and the servicePrincipalName for the host/computername.domain format used during authentication.
EAP-TLS Configuration
Critical EAP settings:
- Default EAP Type: TLS
- TLS Configuration:
- Server Certificate:
radius.tillynet.lan - Private Key: Corresponding private key
- Certificate Chain: Complete CA chain
- Server Certificate:
- PEAP Configuration:
- Default EAP Type: MSCHAPV2 (for inner tunnel, though not used in pure EAP-TLS)
- Copy Request to Tunnel: Yes
- Use Tunneled Reply: Yes
The "Copy Request to Tunnel" and "Use Tunneled Reply" settings are crucial -- they pass authentication attributes between the outer and inner authentication contexts.
Interface Binding
I bound FreeRADIUS specifically to the management interface (172.16.99.1) rather than all interfaces, for better security and clearer troubleshooting.
Group Policy Configuration for 802.1X
Certificate Deployment Strategy
I chose manual certificate deployment for this lab to better understand the process. In production, I would recommend auto-enrollment via certificate templates and Group Policy.
Wired Network Policy Configuration
I created a GPO at: Computer Configuration > Policies > Windows Settings > Security Settings > Wired Network (IEEE 802.3) Policies
Policy Settings:
- Policy Name: "Enterprise 802.1X EAP-TLS Authentication"
- Use Windows Wired Auto Config: Enabled
- Network Authentication Method: Smart Card or other Certificate (EAP-TLS)
- Authentication Mode: Computer authentication
- Validate server certificate: Enabled
- Connect to these servers:
radius.tillynet.lan - Trusted Root Certification Authorities: My root CA
- Use simple certificate selection: Enabled
GPO Deployment and Verification
I linked the GPO to the Workstations OU where my test computer resides. To verify:
gpresult /r /scope computer
gpupdate /force
The gpresult output confirmed the wired network policy was being applied correctly.
Cisco Catalyst 2960 Configuration
The switch serves as the 802.1X authenticator, blocking network access until authentication succeeds.
Global AAA Configuration
# Enable new AAA model
aaa new-model
# Configure RADIUS server
radius-server host 172.16.99.1 auth-port 1812 acct-port 1813 key YourSharedSecret
radius-server timeout 10
radius-server retransmit 3
# Configure 802.1X authentication
aaa authentication dot1x default group radius
The 10-second timeout balances responsiveness with reliability, allowing for network latency without excessive delays during authentication failures.
802.1X Global Configuration
# Enable 802.1X system-wide
dot1x system-auth-control
This enables 802.1X globally on the switch. Individual ports still need per-port configuration.
Port-Level Configuration
I configured a test port (FastEthernet0/2) for 802.1X:
interface FastEthernet0/2
switchport mode access
authentication port-control auto
dot1x pae authenticator
spanning-tree portfast
switchport mode access: Configures the port as an access portauthentication port-control auto: Blocks the port until successful 802.1X authenticationdot1x pae authenticator: Sets the port as an 802.1X authenticatorspanning-tree portfast: Reduces convergence time for end-device connections
Troubleshooting Journey
The implementation involved extensive troubleshooting that taught me more than the configuration itself. Here are the five major issues I hit.
Issue 1: RADIUS Port Mismatch
Symptoms: No communication between the switch and FreeRADIUS.
I enabled debugging on the switch:
terminal monitor
debug radius
debug dot1x all
The debug output revealed:
*Feb 10 23:25:41.810: RADIUS(00000000): Send Access-Request to 172.16.99.1:1645
Root Cause: The switch was using legacy RADIUS ports (1645/1646) instead of the standard ports (1812/1813).
Fix: Explicitly specified the correct ports:
radius-server host 172.16.99.1 auth-port 1812 acct-port 1813 key YourSharedSecret
After the change, debug output confirmed RADIUS communication was working (though authentication still failed -- see Issue 2).
Issue 2: LDAP Filter Misconfigured for Computer Accounts
Symptoms: RADIUS packets reached the server, but authentication consistently returned Access-Reject.
Checking /var/log/radius.log showed:
[ldap] ERROR: Unable to create filter
[ldap] ERROR: Failed to create LDAP filter
Root Cause: The LDAP filter was configured for user authentication, not computer authentication. Windows computer accounts use the host/computername.domain format, live in CN=Computers, and have a trailing $ on their sAMAccountName.
Fix: I changed the Base DN to CN=Computers,DC=tillynet,DC=lan, updated the filter to (&(objectClass=computer)(|(sAMAccountName=%{mschap:User-Name}$)(servicePrincipalName=%{User-Name}))), changed the Base Filter to (objectClass=computer), and enabled LDAP Authorization.
Issue 3: EAP-TLS Tunnel Attribute Passing
Symptoms: After resolving the LDAP issues, authentication intermittently failed with TLS-related errors.
Running radiusd -X in debug mode showed the EAP-TLS handshake completing, but inner authentication failing.
Root Cause: The EAP tunnel was not passing authentication attributes between the outer and inner contexts.
Fix: I changed "Copy Request to Tunnel" and "Use Tunneled Reply" from "No" to "Yes" in the EAP-PEAP configuration.
Issue 4: Certificate Validation Failures on the Client
Symptoms: Some authentication attempts failed with certificate validation errors on the client side.
Windows Event Viewer (System log) showed certificate chain validation errors.
Root Cause: The certificate chain was incomplete, and the server certificate's CN did not match the configured server name in the client's 802.1X profile.
Fix:
- Ensured FreeRADIUS was configured with the complete chain (server cert + intermediate + root)
- Verified the server certificate's CN matched the FQDN in the client's 802.1X profile
- Confirmed the root CA was properly installed in the client's Trusted Root Certification Authorities store
Issue 5: Authentication Timeouts
Symptoms: Authentication worked but took excessively long, sometimes timing out.
Root Cause: LDAP query timeouts, certificate validation delays, and network latency between components.
Fix: I adjusted the RADIUS server timeout to 10 seconds on the switch, enabled persistent LDAP connections (connection pooling), and configured client-side certificate caching.
Advanced Troubleshooting Techniques
Packet Capture Analysis
I used tcpdump on the pfSense firewall to capture RADIUS traffic:
tcpdump -i em0 -s0 -w radius_capture.pcap udp port 1812
This let me verify RADIUS packets were reaching the server and analyze authentication flow timing.
LDAP Testing
I validated LDAP connectivity and search filters directly from the FreeRADIUS host:
ldapsearch -x -H ldaps://172.30.30.30:636 \
-D "CN=ldapbind,CN=Users,DC=tillynet,DC=lan" \
-W -b "CN=Computers,DC=tillynet,DC=lan" \
"(&(objectClass=computer)(sAMAccountName=TILLYPC$))"
Certificate Validation Testing
openssl verify -CAfile ca-chain.crt radius.tillynet.lan.crt
Windows Event Log Analysis
On the Windows client, the most useful logs were:
- System Log: 802.1X authentication events
- Security Log: Authentication and authorization events
- Applications and Services Logs > Microsoft > Windows > Wired-AutoConfig: Detailed 802.1X debugging
Lessons Learned
- Computer auth is not user auth. Windows machines authenticate as
host/computername.domainwith a trailing$on the sAMAccountName. If your LDAP filter is built for user accounts, it will silently fail. TargetCN=Computersand use the compound filter shown above. - "Copy Request to Tunnel" is not optional. Without it, the inner EAP context cannot see the authentication attributes from the outer context. Turn it on from the start.
- Legacy RADIUS ports will bite you. Cisco switches default to 1645/1646 unless you explicitly specify 1812/1813. Always check with
debug radiusoutput. - Troubleshoot layer by layer. Start with
debug radiusanddebug dot1x allon the switch. Verify RADIUS reachability first, then LDAP connectivity, then certificate chain validation. Trying to debug the whole stack at once is a losing game. - Automate certificate lifecycle in production. Manual cert deployment worked for this lab, but any real deployment needs auto-enrollment via certificate templates and Group Policy.
Future Enhancements
- Dynamic VLAN Assignment: Assign VLANs based on computer group membership for automatic network segmentation.
- Certificate Lifecycle Management: Automated enrollment and renewal using AD Certificate Services templates and Group Policy.
- Monitoring and Analytics: Track authentication patterns, identify potential security issues, and build detailed reporting on network access.
- Extended NAC Integration: More granular access control based on device health, compliance status, and user context.
Conclusion
Implementing 802.1X EAP-TLS in my home lab was one of the most challenging projects I have taken on, and one of the most rewarding. The key takeaways:
- PKI is the foundation. A well-designed certificate hierarchy, proper EKUs, and chain validation are non-negotiable for EAP-TLS.
- LDAP integration is where complexity hides. Computer authentication against AD requires understanding Windows auth mechanisms, LDAP schema quirks, and the differences between user and machine accounts.
- Systematic troubleshooting wins. Debug logs, packet captures, and component-by-component testing saved me hours of guessing.
- Security and performance are always a tradeoff. Connection pooling, caching, and timeout tuning are just as important as getting the auth flow working in the first place.
This setup now serves as a robust platform for exploring advanced NAC concepts and testing enterprise security scenarios.