Zero Trust VPN
on RHEL 9
A CIS Level 2 hardened RHEL 9 base pre-configured for Zero Trust VPN deployments using WireGuard. Includes the full PTG security stack — SELinux enforcing, auditd, firewalld, fail2ban — plus WireGuard pre-installed and the PTG validation script for ongoing security posture verification.
Two known issues affect this image after launch due to CIS hardening side effects. The PATH fix and Lynis profile fix must be applied before running any validation or security scans. See the Post-Launch Fixes section. The PTG validation script with --fix handles both automatically.
What is included
WireGuard Pre-installed
WireGuard kernel module and tools installed. Kernel module enabled at boot. Ready for key generation and peer configuration.
Zero Trust Architecture
Default-deny firewall. No implicit trust. Every peer must be explicitly authorized. Key-pair only authentication throughout.
CIS Level 2 Base
Full ansible-lockdown RHEL9-CIS hardening. SELinux enforcing. Auditd immutable. All PTG kernel sysctl hardening applied.
PTG Validation Script
Included validation script checks VPN config, security posture, PATH integrity, and Lynis profile. Client-ready reporting.
Quick Start
Launch, fix the two known post-launch issues, configure WireGuard, and validate — in under 20 minutes.
Launch the instance
Instance type: t3.medium minimum. Assign a key pair. Allow inbound SSH port 22 and UDP port 51820 (WireGuard) in your security group.
Connect and fix PATH immediately
ssh -i your-key.pem ec2-user@PUBLIC_IP
# Fix PATH before doing anything else
export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
Run the PTG auto-fix script
sudo bash /opt/PTG/ptg_validate.sh --fix
This fixes PATH in all profile files and removes the invalid Lynis directive. Both post-launch issues resolved in one command.
Configure WireGuard
See the WireGuard Setup section for full key generation and peer configuration steps.
Run full validation
# Security posture + VPN check sudo bash /opt/PTG/ptg_validate.sh # Include full Lynis scan (takes 3-5 min) sudo bash /opt/PTG/ptg_validate.sh --full
Requirements
Security group — required ports
| Port | Protocol | Direction | Purpose |
|---|---|---|---|
| 22 | TCP | Inbound | SSH management — restrict to your IP |
| 51820 | UDP | Inbound | WireGuard VPN tunnel |
PATH Fix Required
The CIS Level 2 hardening role aggressively restricts the system PATH. This causes grep, tr, ls, awk, uname, and other core utilities to appear missing when switching to root with sudo su.
When you run sudo su you see errors like: bash: tr: command not found, grep: command not found, /usr/libexec/grepconf.sh: line 5: grep: command not found. This is a PATH issue — the binaries exist but the shell cannot find them.
Option 1 — PTG auto-fix script (recommended)
# Run as ec2-user immediately after login
export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
sudo bash /opt/PTG/ptg_validate.sh --fix
This fixes PATH in all 6 profile locations and also removes the Lynis profile error in one shot.
Option 2 — Manual fix
# Step 1: Fix current session immediately export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin # Step 2: Fix all profile files permanently sudo bash -c 'echo "export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" >> /root/.bashrc' sudo bash -c 'echo "export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" >> /root/.bash_profile' sudo bash -c 'echo "export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" >> /etc/bashrc' sudo bash -c 'echo "export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" >> /etc/profile' echo 'export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin' >> ~/.bashrc # Step 3: Reload source /etc/bashrc # Step 4: Verify which grep && which tr && which ls && echo "PATH OK"
Why this happens
The CIS role sets secure_path in sudoers and restricts PATH in several profile files to reduce attack surface. This is technically correct per CIS guidelines but breaks utilities during the shell initialization sequence when switching users. The fix restores the standard PATH without compromising the security configuration — all the actual CIS controls remain in place.
This fix is baked into the updated rhel_hardened.yml playbook as tasks 4 through 4f. New AMIs built from that playbook will not have this issue at launch.
Lynis Profile Fix Required
Lynis aborts before scanning due to an invalid directive in /etc/lynis/default.prf. This must be removed before running any Lynis scan.
When you run sudo lynis audit system you see: Error: found one or more errors in profile /etc/lynis/default.prf — Unknown option 'profile' found (with value: /etc/lynis/ptg.prf). Lynis exits immediately without scanning.
Fix — one command
sudo sed -i '/^profile=/d' /etc/lynis/default.prf
# Verify it is removed
sudo grep "profile=" /etc/lynis/default.prf && echo "STILL PRESENT" || echo "CLEAN"
Verify Lynis runs correctly
# Quick test — should begin scanning without errors
sudo lynis audit system --quick
After this fix Lynis will initialize and run normally. Expected score on this image is 85 or higher.
WireGuard Setup
WireGuard is pre-installed. These steps configure it as either a server (accepting peer connections) or a client (connecting to a WireGuard server).
Step 1 — Generate server key pair
# Generate private and public keys sudo bash -c 'wg genkey | tee /etc/wireguard/server_private.key | wg pubkey > /etc/wireguard/server_public.key' # Lock down key permissions sudo chmod 600 /etc/wireguard/server_private.key sudo chmod 644 /etc/wireguard/server_public.key # View your public key (share this with peers) sudo cat /etc/wireguard/server_public.key # View your private key (never share this) sudo cat /etc/wireguard/server_private.key
Step 2 — Create WireGuard interface config
# Get your private key PRIVATE_KEY=$(sudo cat /etc/wireguard/server_private.key) # Create wg0.conf sudo bash -c "cat > /etc/wireguard/wg0.conf" << EOF [Interface] # This server's VPN IP address Address = 10.0.0.1/24 # WireGuard listening port ListenPort = 51820 # This server's private key PrivateKey = $PRIVATE_KEY # Save peers when they connect SaveConfig = true # NAT — enable if this is a gateway (clients route internet traffic through VPN) # PostUp = firewall-cmd --permanent --add-masquerade # PostDown = firewall-cmd --permanent --remove-masquerade [Peer] # Add peer blocks below — one per client # PublicKey = CLIENT_PUBLIC_KEY_HERE # AllowedIPs = 10.0.0.2/32 EOF # Secure the config file sudo chmod 600 /etc/wireguard/wg0.conf
Step 3 — Open WireGuard port in firewall
# Allow WireGuard UDP port sudo firewall-cmd --permanent --add-port=51820/udp sudo firewall-cmd --reload # Verify sudo firewall-cmd --list-ports
Step 4 — Start and enable WireGuard
# Start WireGuard interface sudo wg-quick up wg0 # Enable at boot sudo systemctl enable wg-quick@wg0 # Check status sudo wg show
Step 5 — Add a peer (client)
On each client machine, generate a key pair and share the public key with the server.
# Generate client keys wg genkey | tee client_private.key | wg pubkey > client_public.key # Client wg0.conf cat > /etc/wireguard/wg0.conf << EOF [Interface] Address = 10.0.0.2/24 PrivateKey = $(cat client_private.key) DNS = 8.8.8.8 [Peer] PublicKey = SERVER_PUBLIC_KEY_HERE Endpoint = SERVER_PUBLIC_IP:51820 AllowedIPs = 10.0.0.0/24 PersistentKeepalive = 25 EOF
# Add peer dynamically (no restart needed) sudo wg set wg0 peer CLIENT_PUBLIC_KEY allowed-ips 10.0.0.2/32 # Save config so it persists sudo wg-quick save wg0
After the client connects, run sudo wg show on the server. You will see the peer listed with a recent handshake timestamp and transfer bytes if traffic is flowing.
VPN Validation
Use the PTG validation script to verify your VPN configuration is correct and active.
VPN-only check
sudo bash /opt/PTG/ptg_validate.sh --vpn
Manual VPN verification commands
# Show all active WireGuard interfaces and peers sudo wg show # Show transfer statistics sudo wg show wg0 transfer # Show handshake times (confirm peers are connecting) sudo wg show wg0 latest-handshakes # Confirm interface is up ip addr show wg0 # Confirm firewall port is open sudo firewall-cmd --list-ports | grep 51820 # Test connectivity from a peer ping 10.0.0.1
PTG Validation Script
The PTG validation script is included at /opt/PTG/ptg_validate.sh. It checks VPN configuration, security posture, and produces a timestamped report suitable for client delivery.
Usage modes
| Command | What it does |
|---|---|
| sudo bash ptg_validate.sh | Full security posture check — SSH, SELinux, firewall, auditd, fail2ban, kernel, permissions |
| sudo bash ptg_validate.sh --full | All of the above plus a full Lynis scan (3–5 min) |
| sudo bash ptg_validate.sh --vpn | VPN checks only — WireGuard status, peers, ports, firewall |
| sudo bash ptg_validate.sh --fix | Auto-fix PATH and Lynis profile issues — run this first on any new instance |
Output
Results are printed to the terminal with color-coded PASS/FAIL/WARN/INFO labels and written to a timestamped file at /opt/PTG/compliance/ptg_validation_YYYYMMDD_HHMMSS.txt. Share this file with clients as a security posture report.
Install or update the script
# Make executable sudo chmod +x /opt/PTG/ptg_validate.sh # Run auto-fix first on any new instance export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin sudo bash /opt/PTG/ptg_validate.sh --fix # Then run full validation sudo bash /opt/PTG/ptg_validate.sh --full
Security Posture
This AMI inherits the full PTG Hardened RHEL 9 CIS Level 2 security stack plus WireGuard-specific additions.
Controls applied
- CIS Level 2 hardening — ansible-lockdown RHEL9-CIS role
- SELinux enforcing mode (targeted policy)
- auditd with full CIS ruleset and -e 2 immutable flag
- firewalld — default zone drop, SSH and WireGuard port open
- fail2ban with SSH jail (5 retries, 1-hour ban)
- WireGuard config files chmod 600
- IP forwarding configurable for gateway vs client mode
- Kernel sysctl hardening — 12 values applied via 99-ptg-lynis.conf
- SSH hardened — no root login, no password auth, MaxAuthTries 3
- No baked-in SSH keys — regenerated on every customer launch
- AIDE file integrity monitoring
- rkhunter rootkit detection
- dnf-automatic unattended security updates
- Compliance manifest at /opt/PTG/compliance/manifest.txt
- PTG validation script at /opt/PTG/ptg_validate.sh
Lynis Scoring
Target score for this image is 85 or higher. Apply the post-launch fixes before running any scan.
Lynis scan commands
# Standard full scan sudo lynis audit system # Quick scan — faster, same score sudo lynis audit system --quick # Quiet — score and warnings only sudo lynis audit system --quiet # Get score from last scan sudo grep "hardening_index" /var/log/lynis-report.dat | tail -1 # Get all warnings from last scan sudo grep "^warning" /var/log/lynis-report.dat # Investigate a specific test sudo lynis show details TEST-ID
Changelog
v1.0 — April 2026 Initial Release
- CIS Level 2 hardening via ansible-lockdown RHEL9-CIS role
- WireGuard pre-installed and kernel module enabled
- SELinux enforcing mode — targeted policy
- auditd with CIS ruleset and immutable flag
- firewalld — default drop, SSH and UDP 51820 open
- fail2ban SSH jail active
- PTG validation script included at /opt/PTG/ptg_validate.sh
- --fix mode resolves PATH and Lynis profile post-launch issues
- Lynis score: 85+ / 100
- Known issues: PATH fix and Lynis profile fix required post-launch
Support
| Type | Contact | Response |
|---|---|---|
| General | support@powelltechnologygroup.com | 48 hours Mon–Fri ET |
| Urgent | Subject: PTG VPN RHEL9 - Urgent | Best effort same day |
Refunds within 7 days of initial subscription for any reason. Contact support with your AWS account ID and subscription date.