From 28db1d21044177d167fb828dc98dfc224013d63d Mon Sep 17 00:00:00 2001 From: Sparticus Date: Mon, 26 Jan 2026 21:22:41 -0500 Subject: [PATCH] CIS + inital --- CHANGELOG.md | 119 +++++ README.md | 445 +++++++++++++++- README_v2.md | 493 ++++++++++++++++++ docs/TWO_TIER_DEPLOYMENT.md | 491 +++++++++++++++++ docs/USAGE.md | 467 +++++++++++++++++ galaxy.yml | 25 + inventory/group_vars/vpn_servers.yml | 107 ++++ inventory/host_vars/vpn1.yml | 14 + inventory/host_vars/vpn2.yml | 13 + inventory/host_vars/vpn3.yml | 13 + inventory/hosts.yml | 39 ++ playbooks/add_user.yml | 50 ++ playbooks/firewall.yml | 11 + playbooks/hardening.yml | 11 + playbooks/remove_user.yml | 50 ++ playbooks/site.yml | 137 +++++ playbooks/users.yml | 31 ++ playbooks/validate.yml | 39 ++ playbooks/wireguard.yml | 11 + requirements.txt | 3 + requirements.yml | 8 + roles/secure_firewall/defaults/main.yml | 59 +++ roles/secure_firewall/handlers/main.yml | 6 + roles/secure_firewall/meta/main.yml | 24 + roles/secure_firewall/tasks/main.yml | 10 + roles/secure_firewall/tasks/ufw.yml | 46 ++ roles/secure_firewall/tasks/vpn_only.yml | 83 +++ roles/ssh_users/defaults/main.yml | 55 ++ roles/ssh_users/handlers/main.yml | 7 + roles/ssh_users/meta/main.yml | 24 + roles/ssh_users/tasks/create_users.yml | 63 +++ roles/ssh_users/tasks/generate_keys.yml | 104 ++++ roles/ssh_users/tasks/main.yml | 31 ++ roles/ssh_users/tasks/password_policy.yml | 91 ++++ roles/ssh_users/tasks/root_restrictions.yml | 38 ++ roles/ssh_users/tasks/sudo.yml | 76 +++ roles/system_hardening/defaults/main.yml | 128 +++++ roles/system_hardening/defaults/main_cis.yml | 19 + roles/system_hardening/handlers/main.yml | 16 + roles/system_hardening/meta/main.yml | 25 + roles/system_hardening/tasks/apparmor.yml | 42 ++ roles/system_hardening/tasks/audit.yml | 57 ++ roles/system_hardening/tasks/core_dumps.yml | 39 ++ .../tasks/disable_protocols.yml | 49 ++ roles/system_hardening/tasks/fail2ban.yml | 22 + roles/system_hardening/tasks/main.yml | 121 +++++ roles/system_hardening/tasks/ssh.yml | 53 ++ roles/system_hardening/tasks/sysctl.yml | 12 + roles/system_hardening/tasks/sysctl_cis.yml | 157 ++++++ .../tasks/unattended_upgrades.yml | 25 + .../templates/20auto-upgrades.j2 | 4 + .../templates/50unattended-upgrades.j2 | 22 + .../system_hardening/templates/audit.rules.j2 | 86 +++ .../templates/fail2ban.local.j2 | 14 + .../system_hardening/templates/sshd_config.j2 | 44 ++ roles/wireguard_server/defaults/main.yml | 41 ++ roles/wireguard_server/handlers/main.yml | 7 + roles/wireguard_server/meta/main.yml | 25 + roles/wireguard_server/tasks/configure.yml | 34 ++ roles/wireguard_server/tasks/install.yml | 59 +++ roles/wireguard_server/tasks/main.yml | 12 + roles/wireguard_server/tasks/users.yml | 73 +++ .../wireguard_server/templates/client.conf.j2 | 11 + .../wireguard_server/templates/summary.md.j2 | 49 ++ roles/wireguard_server/templates/wg0.conf.j2 | 17 + 65 files changed, 4555 insertions(+), 2 deletions(-) create mode 100644 CHANGELOG.md create mode 100644 README_v2.md create mode 100644 docs/TWO_TIER_DEPLOYMENT.md create mode 100644 docs/USAGE.md create mode 100644 galaxy.yml create mode 100644 inventory/group_vars/vpn_servers.yml create mode 100644 inventory/host_vars/vpn1.yml create mode 100644 inventory/host_vars/vpn2.yml create mode 100644 inventory/host_vars/vpn3.yml create mode 100644 inventory/hosts.yml create mode 100644 playbooks/add_user.yml create mode 100644 playbooks/firewall.yml create mode 100644 playbooks/hardening.yml create mode 100644 playbooks/remove_user.yml create mode 100644 playbooks/site.yml create mode 100644 playbooks/users.yml create mode 100644 playbooks/validate.yml create mode 100644 playbooks/wireguard.yml create mode 100644 requirements.txt create mode 100644 requirements.yml create mode 100644 roles/secure_firewall/defaults/main.yml create mode 100644 roles/secure_firewall/handlers/main.yml create mode 100644 roles/secure_firewall/meta/main.yml create mode 100644 roles/secure_firewall/tasks/main.yml create mode 100644 roles/secure_firewall/tasks/ufw.yml create mode 100644 roles/secure_firewall/tasks/vpn_only.yml create mode 100644 roles/ssh_users/defaults/main.yml create mode 100644 roles/ssh_users/handlers/main.yml create mode 100644 roles/ssh_users/meta/main.yml create mode 100644 roles/ssh_users/tasks/create_users.yml create mode 100644 roles/ssh_users/tasks/generate_keys.yml create mode 100644 roles/ssh_users/tasks/main.yml create mode 100644 roles/ssh_users/tasks/password_policy.yml create mode 100644 roles/ssh_users/tasks/root_restrictions.yml create mode 100644 roles/ssh_users/tasks/sudo.yml create mode 100644 roles/system_hardening/defaults/main.yml create mode 100644 roles/system_hardening/defaults/main_cis.yml create mode 100644 roles/system_hardening/handlers/main.yml create mode 100644 roles/system_hardening/meta/main.yml create mode 100644 roles/system_hardening/tasks/apparmor.yml create mode 100644 roles/system_hardening/tasks/audit.yml create mode 100644 roles/system_hardening/tasks/core_dumps.yml create mode 100644 roles/system_hardening/tasks/disable_protocols.yml create mode 100644 roles/system_hardening/tasks/fail2ban.yml create mode 100644 roles/system_hardening/tasks/main.yml create mode 100644 roles/system_hardening/tasks/ssh.yml create mode 100644 roles/system_hardening/tasks/sysctl.yml create mode 100644 roles/system_hardening/tasks/sysctl_cis.yml create mode 100644 roles/system_hardening/tasks/unattended_upgrades.yml create mode 100644 roles/system_hardening/templates/20auto-upgrades.j2 create mode 100644 roles/system_hardening/templates/50unattended-upgrades.j2 create mode 100644 roles/system_hardening/templates/audit.rules.j2 create mode 100644 roles/system_hardening/templates/fail2ban.local.j2 create mode 100644 roles/system_hardening/templates/sshd_config.j2 create mode 100644 roles/wireguard_server/defaults/main.yml create mode 100644 roles/wireguard_server/handlers/main.yml create mode 100644 roles/wireguard_server/meta/main.yml create mode 100644 roles/wireguard_server/tasks/configure.yml create mode 100644 roles/wireguard_server/tasks/install.yml create mode 100644 roles/wireguard_server/tasks/main.yml create mode 100644 roles/wireguard_server/tasks/users.yml create mode 100644 roles/wireguard_server/templates/client.conf.j2 create mode 100644 roles/wireguard_server/templates/summary.md.j2 create mode 100644 roles/wireguard_server/templates/wg0.conf.j2 diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..02244db --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,119 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [2.0.0] - 2025-01-26 + +### Added + +#### CIS Compliance +- **CIS Ubuntu 24.04 Level 1** benchmark compliance +- AppArmor mandatory access control (CIS 1.3.x) +- Comprehensive audit rules (CIS 4.1.6-17) +- Enhanced sysctl parameters (CIS 3.1.x, 3.2.x) +- Uncommon network protocols disabled (CIS 3.3.x) +- Core dumps restricted (CIS 1.5.1) +- Security banners (CIS 1.4.x) + +#### SSH User Management +- New `ssh_users` role for admin user management +- Automatic SSH key pair generation on control node +- Password policy enforcement (CIS 5.4.x, 5.5.x) +- PAM configuration for password complexity (CIS 5.5.1) +- Account lockout policies (CIS 5.5.2) +- Password reuse prevention (CIS 5.5.3) +- Sudo configuration with logging (CIS 5.3.x) +- Root SSH login disabled by default +- Root account locking option + +#### Playbooks +- `users.yml` - User management playbook +- `add_user.yml` - Interactive user addition +- `remove_user.yml` - Interactive user removal +- `validate.yml` - Pre-deployment configuration validation + +#### Documentation +- `CIS_REQUIREMENTS.md` - CIS compliance details +- `TWO_TIER_DEPLOYMENT.md` - Two-tier architecture guide +- Updated README with v2.0 features +- SSH key usage instructions + +### Changed + +#### Firewall Role +- **BREAKING**: `vpn_network` replaced with `management_allowed_sources` (list) +- Now supports multiple management sources (IPs and CIDRs) +- Better validation and error messages +- Improved two-tier architecture support + +#### System Hardening Role +- Enhanced with CIS-specific tasks +- New `sysctl_cis.yml` for CIS network parameters +- New `apparmor.yml` for mandatory access control +- New `disable_protocols.yml` for uncommon protocols +- New `core_dumps.yml` for core dump restriction +- Updated `audit.yml` with comprehensive CIS rules + +#### Inventory +- Added `admin_users` configuration examples +- Added `management_allowed_sources` configuration +- Added CIS-specific variables +- Better documentation and comments + +#### Site Playbook +- Integrated `ssh_users` role +- Enhanced deployment summary with user info +- CIS compliance status in summary + +### Fixed +- SSH hardening now properly disables root login +- Audit rules now immutable after loading +- Firewall rules properly handle multiple management sources +- Sudo logging configured correctly + +### Security +- Root SSH login disabled by default +- Password authentication disabled +- Strong SSH ciphers enforced +- AppArmor profiles enforcing +- Comprehensive audit logging +- Account lockout after 5 failed attempts +- Password complexity requirements +- Automatic security updates + +## [1.1.0] - 2025-01-26 + +### Added +- Two-tier architecture support +- `management_allowed_sources` for firewall +- Validation playbook +- Host-specific variables for VPN networks + +### Changed +- Firewall role supports multiple management sources +- Updated documentation for two-tier architecture + +## [1.0.0] - 2025-01-26 + +### Added +- Initial release +- System hardening role +- WireGuard server role +- Secure firewall role +- Basic playbooks (site, hardening, wireguard, firewall) +- Documentation + +### Security +- SSH hardening +- Sysctl parameters +- Fail2ban +- Auditd +- Automatic updates +- UFW firewall + +[2.0.0]: https://git.hacker.supply/valleyforge/secure-vpn-server/compare/v1.1.0...v2.0.0 +[1.1.0]: https://git.hacker.supply/valleyforge/secure-vpn-server/compare/v1.0.0...v1.1.0 +[1.0.0]: https://git.hacker.supply/valleyforge/secure-vpn-server/releases/tag/v1.0.0 diff --git a/README.md b/README.md index 5d54a7c..ceaa8ac 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,444 @@ -# resist-vpn-infra +--- +# Secure VPN Server - Ansible Collection -Fuck Fascism \ No newline at end of file +Production-grade Ansible collection for deploying secure VPN infrastructure with **two-tier architecture**: admin control plane (ValleyForge) managing user VPN endpoints (VPN1/VPN2/VPN3). + +## Architecture + +### Two-Tier VPN Infrastructure + +``` +Tier 1: Admin Control Plane (ValleyForge) + • WireGuard admin VPN (10.100.0.0/24) + • Ansible control node + • GitHub Actions runner (future) + • 2-5 admin users + +Tier 2: User Data Plane (VPN1/VPN2/VPN3) + • User-facing VPN endpoints + • 50-70 users per endpoint (200 total) + • Separate VPN networks (10.200.x.0/24) + • Gateway to collaboration tools +``` + +**Management Flow**: Admin → ValleyForge admin VPN → ValleyForge → Ansible → VPN1/VPN2/VPN3 + +**User Flow**: User → VPN1/VPN2/VPN3 → Collaboration Server + +--- + +## Features + +### System Hardening +- SSH hardening (key-only auth, strong ciphers, rate limiting) +- Kernel parameter hardening (sysctl) +- Automatic security updates +- Fail2ban intrusion prevention +- Auditd logging +- Minimal package installation + +### WireGuard VPN +- Modern VPN with forward secrecy +- Per-user key management +- Automatic client config generation +- QR codes for mobile devices +- DNS encryption + +### Secure Firewall +- **Management access restricted to ValleyForge** +- User VPN port accessible from internet +- SSH/management ports blocked from public +- Rate limiting +- Connection tracking + +--- + +## Quick Start + +### Prerequisites + +- **ValleyForge deployed** with WireGuard admin VPN +- **Ansible installed** on ValleyForge +- **SSH access** from ValleyForge to VPN endpoints +- **Ubuntu 24.04** on all servers + +### 1. Configure Inventory + +On ValleyForge: + +```bash +cd /root/ansible/secure_vpn_server +nano inventory/hosts.yml +``` + +Set your VPN endpoint IPs: + +```yaml +vpn_servers: + hosts: + vpn1: + ansible_host: 203.0.113.10 # Your VPN1 public IP + vpn2: + ansible_host: 203.0.113.11 # Your VPN2 public IP + vpn3: + ansible_host: 203.0.113.12 # Your VPN3 public IP + + vars: + valleyforge_public_ip: "185.112.147.205" # Your ValleyForge public IP +``` + +### 2. Configure Variables + +```bash +nano inventory/group_vars/vpn_servers.yml +``` + +**CRITICAL**: Set management access sources: + +```yaml +# Allow management from ValleyForge +management_allowed_sources: + - "185.112.147.205" # Your ValleyForge public IP + +# Configure users +wg_peers: + - name: user1 + - name: user2 + - name: user3 +``` + +### 3. Validate Configuration + +```bash +ansible-playbook -i inventory/hosts.yml playbooks/validate.yml +``` + +### 4. Deploy + +```bash +# Test deployment +ansible-playbook -i inventory/hosts.yml playbooks/site.yml --check + +# Deploy to single endpoint (test) +ansible-playbook -i inventory/hosts.yml playbooks/site.yml --limit vpn1 + +# Deploy to all endpoints +ansible-playbook -i inventory/hosts.yml playbooks/site.yml +``` + +### 5. Retrieve Configs + +```bash +scp -r root@203.0.113.10:/root/wireguard-client-configs/ /root/vpn1-configs/ +scp -r root@203.0.113.11:/root/wireguard-client-configs/ /root/vpn2-configs/ +scp -r root@203.0.113.12:/root/wireguard-client-configs/ /root/vpn3-configs/ +``` + +--- + +## Documentation + +- **[TWO_TIER_DEPLOYMENT.md](docs/TWO_TIER_DEPLOYMENT.md)** - Complete two-tier architecture deployment guide +- **[USAGE.md](docs/USAGE.md)** - Detailed usage guide +- **[VALLEYFORGE_BOOTSTRAP.md](../VALLEYFORGE_BOOTSTRAP.md)** - ValleyForge setup guide +- **[TWO_TIER_VPN_ARCHITECTURE.md](../TWO_TIER_VPN_ARCHITECTURE.md)** - Architecture overview + +--- + +## Firewall Configuration + +### Management Access + +**Restricted to ValleyForge only**: + +```yaml +management_allowed_sources: + - "185.112.147.205" # ValleyForge public IP + # - "10.100.0.0/24" # Or ValleyForge admin VPN (if routing configured) +``` + +**Management ports** (SSH, HTTP, HTTPS, etc.): +- ✅ Accessible from ValleyForge +- ❌ Blocked from internet + +**User VPN port** (51820): +- ✅ Accessible from internet + +### Access Matrix + +| Source | Destination | Port | Result | +|--------|-------------|------|--------| +| Internet | VPN1/2/3 | 51820 (user VPN) | ✅ ALLOWED | +| ValleyForge | VPN1/2/3 | 22 (SSH) | ✅ ALLOWED | +| Internet | VPN1/2/3 | 22 (SSH) | ❌ BLOCKED | +| Internet | VPN1/2/3 | 80/443 | ❌ BLOCKED | + +--- + +## Directory Structure + +``` +secure_vpn_server/ +├── galaxy.yml # Collection metadata +├── README.md # This file +├── requirements.txt # Python dependencies +├── requirements.yml # Ansible collections +├── playbooks/ +│ ├── site.yml # Full deployment +│ ├── hardening.yml # Hardening only +│ ├── wireguard.yml # WireGuard only +│ ├── firewall.yml # Firewall only +│ └── validate.yml # Configuration validation +├── roles/ +│ ├── system_hardening/ # SSH, sysctl, fail2ban, auditd +│ ├── wireguard_server/ # WireGuard VPN +│ └── secure_firewall/ # UFW firewall + management access control +├── inventory/ +│ ├── hosts.yml # Server inventory +│ ├── group_vars/ +│ │ └── vpn_servers.yml # VPN endpoint configuration +│ └── host_vars/ +│ ├── vpn1.yml # VPN1 specific config +│ ├── vpn2.yml # VPN2 specific config +│ └── vpn3.yml # VPN3 specific config +└── docs/ + ├── TWO_TIER_DEPLOYMENT.md # Two-tier deployment guide + └── USAGE.md # Detailed usage guide +``` + +--- + +## Configuration Examples + +### Minimal Configuration + +```yaml +# inventory/group_vars/vpn_servers.yml + +# REQUIRED: Management access sources +management_allowed_sources: + - "185.112.147.205" # ValleyForge public IP + +# Users +wg_peers: + - name: user1 + - name: user2 + +# VPN settings (defaults are fine) +wg_network: "10.200.0.0/24" # Overridden per host +vpn_only_mode: true +``` + +### Advanced Configuration + +```yaml +# System settings +system_timezone: "UTC" +ssh_port: 2222 # Custom SSH port + +# WireGuard settings +wg_port: 51820 + +# Users with manual IPs +wg_peers: + - name: alice + ip: 10.200.0.10 + - name: bob + ip: 10.200.0.11 + +# Management access +management_allowed_sources: + - "185.112.147.205" # ValleyForge public IP + - "10.100.0.0/24" # ValleyForge admin VPN (if routing configured) + +# Additional management ports +management_ports: + - port: 2222 + proto: tcp + comment: "SSH" + - port: 8080 + proto: tcp + comment: "Outline Manager" + +# Security features +fail2ban_enabled: true +auditd_enabled: true +unattended_upgrades_enabled: true +ssh_rate_limit: true +``` + +--- + +## Usage + +### Deploy to All Endpoints + +```bash +ansible-playbook -i inventory/hosts.yml playbooks/site.yml +``` + +### Deploy to Single Endpoint + +```bash +ansible-playbook -i inventory/hosts.yml playbooks/site.yml --limit vpn1 +``` + +### Deploy Specific Components + +```bash +# Only hardening +ansible-playbook -i inventory/hosts.yml playbooks/hardening.yml + +# Only WireGuard +ansible-playbook -i inventory/hosts.yml playbooks/wireguard.yml + +# Only firewall +ansible-playbook -i inventory/hosts.yml playbooks/firewall.yml +``` + +### Add Users + +```bash +# Edit variables +nano inventory/group_vars/vpn_servers.yml + +# Add user +wg_peers: + - name: user1 + - name: user2 + - name: new_user3 # Add this + +# Re-deploy WireGuard +ansible-playbook -i inventory/hosts.yml playbooks/wireguard.yml --limit vpn1 + +# Retrieve new config +scp root@vpn1-ip:/root/wireguard-client-configs/new_user3.conf /root/ +``` + +### Monitor Endpoints + +```bash +# Check WireGuard status +ansible vpn_servers -i inventory/hosts.yml -m shell -a "wg show" + +# Check firewall status +ansible vpn_servers -i inventory/hosts.yml -m shell -a "ufw status" + +# Check services +ansible vpn_servers -i inventory/hosts.yml -m shell -a "systemctl status wg-quick@wg0" +``` + +--- + +## Troubleshooting + +### Can't SSH from ValleyForge After Deployment + +**Use VPS console/VNC**: + +```bash +# Check firewall +sudo ufw status verbose + +# Temporarily allow your IP +sudo ufw allow from YOUR_VALLEYFORGE_IP to any port 22 + +# Or disable firewall temporarily +sudo ufw disable +``` + +### Wrong ValleyForge IP in Firewall + +```bash +# On ValleyForge, update group_vars +nano inventory/group_vars/vpn_servers.yml + +# Fix the IP +management_allowed_sources: + - "CORRECT.IP.ADDRESS.HERE" + +# Re-deploy firewall +ansible-playbook -i inventory/hosts.yml playbooks/firewall.yml +``` + +### Validation Fails + +```bash +# Check that management_allowed_sources is set +cat inventory/group_vars/vpn_servers.yml | grep management_allowed_sources + +# Should show: +# management_allowed_sources: +# - "185.112.147.205" +``` + +--- + +## Security Best Practices + +### ✅ What This Collection Does + +- **SSH Hardening**: Key-only auth, strong ciphers, rate limiting +- **Kernel Hardening**: Secure sysctl parameters +- **Automatic Updates**: Security patches applied automatically +- **Intrusion Prevention**: Fail2ban blocks brute force +- **Audit Logging**: Track security-relevant events +- **Management Access Control**: Only from ValleyForge +- **Forward Secrecy**: VPN traffic protected even if keys compromised + +### ⚠️ Important Notes + +**Management Access**: Once deployed, management ports are ONLY accessible from ValleyForge. Test SSH access before deploying! + +**ValleyForge IP**: Ensure `management_allowed_sources` contains your actual ValleyForge IP. + +**Idempotent**: Safe to re-run playbooks anytime. + +--- + +## Requirements + +- **Control Machine**: ValleyForge with Ansible 2.15+ +- **Target Servers**: Ubuntu 24.04 LTS (or 22.04) +- **SSH Access**: Root or sudo user with SSH key authentication +- **Python**: Python 3.8+ on target servers + +--- + +## Installation + +### On ValleyForge + +```bash +# Install Ansible +pip3 install -r requirements.txt + +# Install Ansible collections +ansible-galaxy collection install -r requirements.yml +``` + +--- + +## Support + +- **Documentation**: See `docs/` directory +- **Issues**: GitHub issues +- **Security**: Report security issues privately + +--- + +## License + +MIT License + +--- + +## Version + +1.1.0 - Two-Tier Architecture Support + +--- + +## Author + +Security Infrastructure Team diff --git a/README_v2.md b/README_v2.md new file mode 100644 index 0000000..e50a51e --- /dev/null +++ b/README_v2.md @@ -0,0 +1,493 @@ +# Secure VPN Server - Ansible Collection v2.0 + +**CIS Ubuntu 24.04 Level 1 Compliant** | Production-Ready | Two-Tier Architecture + +Complete Ansible collection for deploying secure, hardened VPN servers with comprehensive user management, CIS benchmark compliance, and multi-server architecture support. + +## What's New in v2.0 + +### 🔐 CIS Compliance +- **CIS Ubuntu 24.04 Level 1** benchmark compliance +- AppArmor mandatory access control +- Comprehensive audit logging (4.1.x) +- Enhanced network hardening (3.x) +- Password policies and PAM configuration (5.4.x, 5.5.x) + +### 👥 SSH User Management +- **Automated user creation** with sudo access +- **SSH key generation** on control node +- **Root SSH restrictions** (disabled by default) +- **Password policies** (CIS compliant) +- User management playbooks (add/remove users) + +### 🏗️ Two-Tier Architecture Support +- **ValleyForge** (admin control plane) manages infrastructure +- **VPN endpoints** (VPN1/VPN2/VPN3) serve end users +- **Firewall lockdown** to management sources only +- **Scalable** to hundreds of users across multiple servers + +## Features + +### Security Hardening (CIS Level 1) +- ✅ SSH hardening with strong ciphers (CIS 5.2.x) +- ✅ Root login disabled, admin users with sudo +- ✅ AppArmor enforcing mode (CIS 1.3.x) +- ✅ Comprehensive audit rules (CIS 4.1.x) +- ✅ Password complexity and expiration (CIS 5.4.x, 5.5.x) +- ✅ Account lockout policies (CIS 5.5.2) +- ✅ Kernel hardening via sysctl (CIS 3.x) +- ✅ Uncommon protocols disabled (CIS 3.3.x) +- ✅ Core dumps restricted (CIS 1.5.1) +- ✅ Automatic security updates +- ✅ Fail2ban intrusion prevention + +### User Management +- ✅ Create admin users with SSH keys +- ✅ Automatic SSH key pair generation +- ✅ Sudo configuration (password/nopassword) +- ✅ Root account restrictions +- ✅ Add/remove user playbooks +- ✅ Password policy enforcement + +### VPN Server +- ✅ WireGuard VPN with modern cryptography +- ✅ Per-user key generation +- ✅ QR codes for mobile devices +- ✅ Forward secrecy +- ✅ DNS encryption + +### Firewall +- ✅ UFW with default deny +- ✅ Management access restricted to authorized sources +- ✅ VPN-only mode for infrastructure protection +- ✅ Rate limiting on SSH +- ✅ Two-tier architecture support + +## Architecture + +### Two-Tier VPN Infrastructure + +``` +┌─────────────────────────────────────────┐ +│ ValleyForge (Admin Control Plane) │ +│ - WireGuard admin VPN (10.100.0.0/24) │ +│ - Ansible control node │ +│ - GitHub Actions runner │ +│ - 2-5 admin users │ +└──────────────┬──────────────────────────┘ + │ SSH (from ValleyForge IP only) + ↓ +┌──────────────────────────────────────────┐ +│ VPN Endpoints (User Data Plane) │ +│ ┌────────────────────────────────────┐ │ +│ │ VPN1 (10.200.0.0/24) - 50-70 users │ │ +│ │ VPN2 (10.201.0.0/24) - 50-70 users │ │ +│ │ VPN3 (10.202.0.0/24) - 50-70 users │ │ +│ └────────────────────────────────────┘ │ +└──────────────┬──────────────────────────┘ + │ User VPN (public access) + ↓ + End Users (200+) + ↓ + Collaboration Infrastructure + (Mattermost, Nextcloud, Jitsi) +``` + +## Quick Start + +### 1. Install Collection + +```bash +tar xzf secure_vpn_server_v2.0.tar.gz +cd secure_vpn_server + +# Install dependencies +pip3 install -r requirements.txt +ansible-galaxy collection install -r requirements.yml +``` + +### 2. Configure Inventory + +```bash +# Edit inventory +nano inventory/hosts.yml +``` + +Set your servers: +```yaml +vpn_servers: + hosts: + vpn1: + ansible_host: 203.0.113.10 + vpn2: + ansible_host: 203.0.113.11 + vpn3: + ansible_host: 203.0.113.12 +``` + +### 3. Configure Variables + +```bash +nano inventory/group_vars/vpn_servers.yml +``` + +**CRITICAL - Set ValleyForge IP**: +```yaml +valleyforge_public_ip: "185.112.147.205" # Your actual IP! + +admin_users: + - username: alice + comment: "Alice - Admin" + groups: ["sudo"] + generate_keys: true +``` + +### 4. Deploy + +```bash +# Validate configuration +ansible-playbook -i inventory/hosts.yml playbooks/validate.yml + +# Deploy everything +ansible-playbook -i inventory/hosts.yml playbooks/site.yml + +# Or deploy to single server +ansible-playbook -i inventory/hosts.yml playbooks/site.yml --limit vpn1 +``` + +### 5. Retrieve SSH Keys + +```bash +# SSH keys are generated on control node +ls -la ssh-keys/vpn1/ + +# Copy to your machine +cp ssh-keys/vpn1/alice_id_ed25519 ~/.ssh/ +chmod 600 ~/.ssh/alice_id_ed25519 + +# Test SSH +ssh -i ~/.ssh/alice_id_ed25519 alice@vpn1 +``` + +## Playbooks + +### Main Playbooks + +| Playbook | Purpose | Usage | +|----------|---------|-------| +| `site.yml` | Complete deployment | Full server setup | +| `hardening.yml` | Security hardening only | Apply CIS controls | +| `users.yml` | User management only | Create admin users | +| `wireguard.yml` | VPN setup only | Deploy WireGuard | +| `firewall.yml` | Firewall config only | Configure UFW | +| `validate.yml` | Configuration validation | Pre-deployment check | + +### User Management Playbooks + +| Playbook | Purpose | Usage | +|----------|---------|-------| +| `add_user.yml` | Add single admin user | Interactive user creation | +| `remove_user.yml` | Remove admin user | Interactive user removal | + +### Examples + +```bash +# Full deployment +ansible-playbook -i inventory/hosts.yml playbooks/site.yml + +# Add new admin user +ansible-playbook -i inventory/hosts.yml playbooks/add_user.yml + +# Apply hardening to existing servers +ansible-playbook -i inventory/hosts.yml playbooks/hardening.yml + +# Update firewall rules +ansible-playbook -i inventory/hosts.yml playbooks/firewall.yml +``` + +## Roles + +### 1. system_hardening +**CIS Level 1 compliant system hardening** + +Features: +- SSH hardening (strong ciphers, key-only auth) +- Sysctl kernel parameters (network, security) +- AppArmor mandatory access control +- Comprehensive audit logging +- Fail2ban intrusion prevention +- Automatic security updates +- Uncommon protocols disabled +- Core dumps restricted +- Security banners + +### 2. ssh_users +**SSH user management with key generation** + +Features: +- Create admin users with sudo access +- Generate SSH key pairs automatically +- Configure authorized_keys +- Password policy enforcement +- Sudo configuration (CIS 5.3.x) +- Root account restrictions + +### 3. wireguard_server +**WireGuard VPN server deployment** + +Features: +- WireGuard installation and configuration +- Per-user key generation +- Client config generation (desktop + mobile) +- QR codes for mobile devices +- Forward secrecy +- DNS encryption + +### 4. secure_firewall +**UFW firewall with VPN-only mode** + +Features: +- Default deny incoming +- Management access restricted to authorized sources +- User VPN publicly accessible +- Rate limiting on SSH +- Two-tier architecture support + +## CIS Compliance + +This collection implements **CIS Ubuntu 24.04 Level 1** controls: + +| CIS Section | Controls | Status | +|-------------|----------|--------| +| 1.3.x | AppArmor | ✅ Implemented | +| 1.4.x | Warning Banners | ✅ Implemented | +| 1.5.x | Process Hardening | ✅ Implemented | +| 3.1.x | Network Parameters (Host) | ✅ Implemented | +| 3.2.x | Network Parameters (All) | ✅ Implemented | +| 3.3.x | Uncommon Protocols | ✅ Implemented | +| 3.4.x | Firewall Configuration | ✅ Implemented | +| 4.1.x | Audit Configuration | ✅ Implemented | +| 5.2.x | SSH Configuration | ✅ Implemented | +| 5.3.x | Sudo Configuration | ✅ Implemented | +| 5.4.x | User Accounts | ✅ Implemented | +| 5.5.x | PAM Configuration | ✅ Implemented | + +### CIS Audit + +Run CIS audit after deployment: +```bash +ssh alice@vpn1 +sudo lynis audit system +``` + +## Security Features + +### SSH Hardening +- Key-only authentication (passwords disabled) +- Root login disabled +- Strong ciphers (ChaCha20, AES-GCM) +- Strong MACs (SHA2-512/256 ETM) +- Strong KEX (Curve25519) +- Rate limiting (fail2ban) +- Verbose logging + +### Network Hardening +- SYN cookies enabled +- IP forwarding controlled +- ICMP redirects disabled +- Source routing disabled +- Reverse path filtering +- Martian packet logging +- IPv6 disabled (optional) + +### Access Control +- AppArmor enforcing +- Sudo logging +- Password complexity requirements +- Account lockout (5 failed attempts) +- Password expiration (365 days) +- Inactive account locking (30 days) + +### Audit Logging +- Comprehensive audit rules (CIS 4.1.6-17) +- Time changes logged +- User/group changes logged +- Network changes logged +- Permission changes logged +- File access attempts logged +- File deletions logged +- Sudo usage logged +- Kernel module changes logged + +## Configuration + +### Admin Users + +```yaml +admin_users: + - username: alice + comment: "Alice - Infrastructure Admin" + groups: ["sudo", "adm"] + generate_keys: true # Auto-generate SSH keys + shell: /bin/bash + state: present +``` + +### Management Access + +```yaml +# Allow management from ValleyForge only +management_allowed_sources: + - "185.112.147.205" # ValleyForge public IP + - "10.100.0.0/24" # ValleyForge admin VPN (optional) +``` + +### VPN Configuration + +```yaml +# Per-host in host_vars/vpn1.yml +wg_network: "10.200.0.0/24" +wg_server_ip: "10.200.0.1" +wg_port: 51820 + +wg_peers: + - name: user1 + - name: user2 + # ... 50-70 users per endpoint +``` + +## Files Generated + +### On VPS Servers + +``` +/etc/wireguard/ +├── wg0.conf # Server config +└── keys/ # Server + user keys + +/root/wireguard-client-configs/ +├── user1.conf # Desktop configs +├── user1_qr.txt # Mobile QR codes +└── README.md + +/root/ +├── deployment-summary.txt # Deployment info +└── firewall-config.txt # Firewall rules + +/var/log/ +├── sudo.log # Sudo usage +└── audit/audit.log # Audit events +``` + +### On Control Node (ValleyForge) + +``` +ssh-keys/ +└── vpn1/ + ├── alice_id_ed25519 # Private key + ├── alice_id_ed25519.pub # Public key + ├── bob_id_ed25519 + ├── bob_id_ed25519.pub + └── README.md # Usage instructions +``` + +## Troubleshooting + +### SSH Access Issues + +```bash +# Test SSH with verbose output +ssh -vvv -i ~/.ssh/alice_id_ed25519 alice@vpn1 + +# Check SSH logs on server +sudo journalctl -u sshd -f + +# Verify user exists +sudo getent passwd alice + +# Check sudo access +sudo -l +``` + +### Firewall Issues + +```bash +# Check UFW status +sudo ufw status verbose + +# Check if management IP is allowed +sudo ufw status numbered + +# Temporarily disable firewall (DANGEROUS!) +sudo ufw disable +``` + +### VPN Issues + +```bash +# Check WireGuard status +sudo wg show + +# Check WireGuard logs +sudo journalctl -u wg-quick@wg0 -f + +# Restart WireGuard +sudo systemctl restart wg-quick@wg0 +``` + +## Best Practices + +### User Management +1. **Always create admin users** before disabling root SSH +2. **Test SSH access** with new users before disconnecting +3. **Keep private keys secure** - never commit to git +4. **Rotate SSH keys** every 90 days +5. **Remove users** when they leave the team + +### Security +1. **Run validation playbook** before deployment +2. **Review audit logs** regularly +3. **Keep systems updated** (automatic updates enabled) +4. **Monitor fail2ban** for attack attempts +5. **Rotate VPN keys** for compromised users + +### Operations +1. **Use version control** for inventory and variables +2. **Document changes** in git commits +3. **Test on single server** before deploying to all +4. **Keep backups** of SSH keys and configs +5. **Monitor resource usage** (CPU, RAM, bandwidth) + +## Support + +### Documentation +- `docs/TWO_TIER_DEPLOYMENT.md` - Two-tier architecture guide +- `docs/USAGE.md` - Detailed usage guide +- `CIS_REQUIREMENTS.md` - CIS compliance details + +### Validation +```bash +ansible-playbook -i inventory/hosts.yml playbooks/validate.yml +``` + +### Audit +```bash +# CIS audit with Lynis +ssh alice@vpn1 +sudo lynis audit system + +# Check audit logs +sudo ausearch -ts recent +``` + +## License + +MIT + +## Version + +2.0.0 - CIS Compliant with User Management + +## Changelog + +See `CHANGELOG.md` for version history. diff --git a/docs/TWO_TIER_DEPLOYMENT.md b/docs/TWO_TIER_DEPLOYMENT.md new file mode 100644 index 0000000..7d65e98 --- /dev/null +++ b/docs/TWO_TIER_DEPLOYMENT.md @@ -0,0 +1,491 @@ +--- +# Two-Tier VPN Architecture Deployment Guide + +## Architecture Overview + +This Ansible collection is designed for a **two-tier VPN architecture**: + +### Tier 1: Admin Control Plane (ValleyForge) +- **WireGuard admin VPN** (10.100.0.0/24) +- **Ansible control node** +- **GitHub Actions runner** (future) +- **2-5 admin users** + +### Tier 2: User Data Plane (VPN1/VPN2/VPN3) +- **User-facing VPN endpoints** (Algo/Outline) +- **50-70 users per endpoint** (200 total) +- **Separate VPN networks**: + - VPN1: 10.200.0.0/24 + - VPN2: 10.201.0.0/24 + - VPN3: 10.202.0.0/24 + +--- + +## Prerequisites + +### Before You Start + +1. **ValleyForge deployed** with WireGuard admin VPN +2. **Ansible installed** on ValleyForge +3. **SSH access** from ValleyForge to VPN1/VPN2/VPN3 +4. **ValleyForge public IP** known + +--- + +## Step 1: Configure Inventory + +### Edit hosts.yml + +On ValleyForge: + +```bash +cd /root/ansible/secure_vpn_server +nano inventory/hosts.yml +``` + +**Set your VPN endpoint IPs**: + +```yaml +vpn_servers: + hosts: + vpn1: + ansible_host: 203.0.113.10 # Your VPN1 public IP + vpn2: + ansible_host: 203.0.113.11 # Your VPN2 public IP + vpn3: + ansible_host: 203.0.113.12 # Your VPN3 public IP + + vars: + valleyforge_public_ip: "185.112.147.205" # Your ValleyForge public IP +``` + +--- + +## Step 2: Configure Variables + +### Edit group_vars/vpn_servers.yml + +```bash +nano inventory/group_vars/vpn_servers.yml +``` + +**CRITICAL: Set management_allowed_sources**: + +```yaml +# Allow management from ValleyForge +management_allowed_sources: + - "185.112.147.205" # Your ValleyForge public IP + +# Or if you have VPN routing configured: +# management_allowed_sources: +# - "10.100.0.0/24" # ValleyForge admin VPN network +``` + +**Configure users**: + +```yaml +wg_peers: + - name: user1 + - name: user2 + - name: user3 + # Add up to 70 users per endpoint +``` + +### Verify host_vars + +Check that each VPN endpoint has unique networks: + +```bash +cat inventory/host_vars/vpn1.yml +# wg_network: "10.200.0.0/24" + +cat inventory/host_vars/vpn2.yml +# wg_network: "10.201.0.0/24" + +cat inventory/host_vars/vpn3.yml +# wg_network: "10.202.0.0/24" +``` + +--- + +## Step 3: Validate Configuration + +**Run validation playbook**: + +```bash +ansible-playbook -i inventory/hosts.yml playbooks/validate.yml +``` + +**Expected output**: + +``` +TASK [Validate management_allowed_sources is defined] +ok: [vpn1] => { + "msg": "✓ management_allowed_sources is configured" +} + +TASK [Validate ValleyForge IP is set] +ok: [vpn1] => { + "msg": "✓ ValleyForge IP is configured: 185.112.147.205" +} + +TASK [Display configuration summary] +ok: [vpn1] => { + "msg": [ + "Host: vpn1", + "VPN Network: 10.200.0.0/24", + "Management allowed from: 185.112.147.205", + "Users configured: 3" + ] +} +``` + +**If validation fails**: +- Check that `management_allowed_sources` is set in `group_vars/vpn_servers.yml` +- Check that `valleyforge_public_ip` is set in `inventory/hosts.yml` +- Check that each host has unique `wg_network` in `host_vars/` + +--- + +## Step 4: Test SSH Access + +**From ValleyForge, test SSH to each endpoint**: + +```bash +ssh root@203.0.113.10 # VPN1 +ssh root@203.0.113.11 # VPN2 +ssh root@203.0.113.12 # VPN3 +``` + +**If SSH fails**: + +```bash +# Generate SSH key on ValleyForge +ssh-keygen -t ed25519 + +# Copy to VPN endpoints +ssh-copy-id root@203.0.113.10 +ssh-copy-id root@203.0.113.11 +ssh-copy-id root@203.0.113.12 +``` + +--- + +## Step 5: Deploy (Dry Run) + +**Test deployment without making changes**: + +```bash +ansible-playbook -i inventory/hosts.yml playbooks/site.yml --check --diff +``` + +**Review output for errors**: +- Syntax errors +- Missing variables +- Connection issues + +--- + +## Step 6: Deploy to Single Endpoint (Test) + +**Deploy to VPN1 only**: + +```bash +ansible-playbook -i inventory/hosts.yml playbooks/site.yml --limit vpn1 +``` + +**Monitor deployment** (~10-15 minutes): +- System hardening +- WireGuard installation +- Firewall configuration + +**Expected final output**: + +``` +TASK [Display deployment summary] +ok: [vpn1] => { + "msg": [ + "=========================================", + "Deployment Complete!", + "=========================================", + "Server: vpn1", + "Public IP: 203.0.113.10", + "VPN Network: 10.200.0.0/24", + "Client configs: /root/wireguard-client-configs/", + "Firewall config: /root/firewall-config.txt" + ] +} +``` + +--- + +## Step 7: Verify VPN1 Deployment + +### Check Services + +```bash +# SSH to VPN1 (should still work from ValleyForge) +ssh root@203.0.113.10 + +# Check WireGuard +sudo wg show +# Should show wg0 interface + +# Check firewall +sudo ufw status verbose +# Should show: +# - Port 51820/udp ALLOW from Anywhere (user VPN) +# - Port 22/tcp ALLOW from 185.112.147.205 (ValleyForge) +# - Port 22/tcp DENY from Anywhere (default deny) + +# Check services +systemctl status wg-quick@wg0 +systemctl status fail2ban +systemctl status sshd + +# Exit +exit +``` + +### Test Management Access + +**From ValleyForge**: + +```bash +# SSH should work (you're from allowed source) +ssh root@203.0.113.10 +# Connected! +``` + +**From your local machine** (NOT ValleyForge): + +```bash +# SSH should be BLOCKED +ssh root@203.0.113.10 +# Connection refused or timeout +``` + +**This is correct!** Management is restricted to ValleyForge. + +--- + +## Step 8: Retrieve User Configs + +**From ValleyForge**: + +```bash +# Download VPN1 user configs +scp -r root@203.0.113.10:/root/wireguard-client-configs/ /root/vpn1-configs/ + +# Check configs +ls /root/vpn1-configs/ +# user1.conf user1_qr.txt +# user2.conf user2_qr.txt +# user3.conf user3_qr.txt +``` + +--- + +## Step 9: Deploy to All Endpoints + +**If VPN1 test was successful**: + +```bash +# Deploy to VPN2 and VPN3 +ansible-playbook -i inventory/hosts.yml playbooks/site.yml --limit vpn2,vpn3 + +# Or deploy to all at once +ansible-playbook -i inventory/hosts.yml playbooks/site.yml +``` + +--- + +## Step 10: Retrieve All User Configs + +```bash +# Download from all endpoints +scp -r root@203.0.113.10:/root/wireguard-client-configs/ /root/vpn1-configs/ +scp -r root@203.0.113.11:/root/wireguard-client-configs/ /root/vpn2-configs/ +scp -r root@203.0.113.12:/root/wireguard-client-configs/ /root/vpn3-configs/ +``` + +--- + +## Firewall Rules Explained + +### What Gets Configured + +**On each VPN endpoint (VPN1/VPN2/VPN3)**: + +``` +# Public ports (user VPN) +Port 51820/udp → ALLOW from Anywhere + +# Management ports (restricted to ValleyForge) +Port 22/tcp → ALLOW from 185.112.147.205 +Port 22/tcp → DENY from Anywhere + +# Default policy +Incoming → DENY +Outgoing → ALLOW +``` + +### Why This Works + +1. **User VPN access**: Port 51820 is open to internet for end users +2. **Management access**: SSH only from ValleyForge public IP +3. **Security**: All other management blocked from internet + +### Access Matrix + +| Source | Destination | Port | Result | +|--------|-------------|------|--------| +| Internet | VPN1/2/3 | 51820 (user VPN) | ✅ ALLOWED | +| ValleyForge | VPN1/2/3 | 22 (SSH) | ✅ ALLOWED | +| Internet | VPN1/2/3 | 22 (SSH) | ❌ BLOCKED | +| Internet | VPN1/2/3 | 80/443 | ❌ BLOCKED | + +--- + +## Troubleshooting + +### Can't SSH from ValleyForge After Deployment + +**Problem**: SSH connection refused from ValleyForge + +**Solution**: + +```bash +# Use VPS console/VNC to access VPN endpoint +# Check firewall rules +sudo ufw status verbose + +# Check if ValleyForge IP is allowed +sudo ufw status | grep 185.112.147.205 + +# If not, add it +sudo ufw allow from 185.112.147.205 to any port 22 + +# Or temporarily disable firewall +sudo ufw disable +``` + +### Wrong ValleyForge IP in Firewall + +**Problem**: Set wrong IP in `management_allowed_sources` + +**Solution**: + +```bash +# On ValleyForge, update group_vars +nano inventory/group_vars/vpn_servers.yml + +# Fix the IP +management_allowed_sources: + - "CORRECT.IP.ADDRESS.HERE" + +# Re-deploy firewall only +ansible-playbook -i inventory/hosts.yml playbooks/firewall.yml +``` + +### Users Can't Connect to VPN + +**Problem**: User VPN port not accessible + +**Solution**: + +```bash +# Check firewall on VPN endpoint +ssh root@vpn-ip # From ValleyForge +sudo ufw status | grep 51820 + +# Should show: +# 51820/udp ALLOW Anywhere + +# If not, add it +sudo ufw allow 51820/udp + +# Or re-deploy +ansible-playbook -i inventory/hosts.yml playbooks/firewall.yml +``` + +### Validation Playbook Fails + +**Problem**: `management_allowed_sources` not defined + +**Solution**: + +```bash +# Edit group_vars +nano inventory/group_vars/vpn_servers.yml + +# Add this section +management_allowed_sources: + - "YOUR_VALLEYFORGE_IP" + +# Re-run validation +ansible-playbook -i inventory/hosts.yml playbooks/validate.yml +``` + +--- + +## Adding Users + +### Add Users to Existing Endpoint + +```bash +# On ValleyForge +cd /root/ansible/secure_vpn_server + +# Edit group_vars or host_vars +nano inventory/group_vars/vpn_servers.yml + +# Add user +wg_peers: + - name: user1 + - name: user2 + - name: new_user4 # Add this + +# Re-deploy WireGuard only +ansible-playbook -i inventory/hosts.yml playbooks/wireguard.yml --limit vpn1 + +# Retrieve new config +scp root@203.0.113.10:/root/wireguard-client-configs/new_user4.conf /root/ +``` + +--- + +## Monitoring + +### Check All Endpoints Status + +```bash +# From ValleyForge +ansible vpn_servers -i inventory/hosts.yml -m shell -a "wg show" +ansible vpn_servers -i inventory/hosts.yml -m shell -a "ufw status" +ansible vpn_servers -i inventory/hosts.yml -m shell -a "systemctl status wg-quick@wg0" +``` + +--- + +## Summary + +**Deployment complete when**: + +1. ✅ All VPN endpoints deployed (VPN1/VPN2/VPN3) +2. ✅ Firewall restricts management to ValleyForge +3. ✅ User VPN ports open to internet +4. ✅ User configs retrieved +5. ✅ Services running (WireGuard, fail2ban, SSH) + +**Your infrastructure**: +- **Secure**: Management only from ValleyForge +- **Scalable**: 200 users across 3 endpoints +- **Manageable**: Centralized Ansible control +- **Resilient**: Multiple endpoints for redundancy + +**Next steps**: +- Distribute user configs +- Deploy collaboration server +- Set up monitoring +- Configure GitHub Actions (future) diff --git a/docs/USAGE.md b/docs/USAGE.md new file mode 100644 index 0000000..6bafd8d --- /dev/null +++ b/docs/USAGE.md @@ -0,0 +1,467 @@ +# Usage Guide + +## Installation + +### Prerequisites + +**Control Machine** (your laptop/desktop): +- Linux or macOS +- Python 3.8+ +- Ansible 2.15+ +- SSH client + +**Target Servers**: +- Ubuntu 24.04 LTS (or 22.04) +- Root or sudo access +- SSH key authentication configured +- Minimum 1GB RAM, 10GB disk + +### Step 1: Install Ansible + +```bash +# Using pip +pip3 install ansible + +# Or using your package manager +# Ubuntu/Debian +sudo apt install ansible + +# macOS +brew install ansible +``` + +### Step 2: Clone/Download Collection + +```bash +# If from Git +git clone https://github.com/your-org/secure-vpn-server.git +cd secure-vpn-server + +# Install dependencies +pip3 install -r requirements.txt +ansible-galaxy collection install -r requirements.yml +``` + +### Step 3: Configure SSH Access + +```bash +# Generate SSH key if you don't have one +ssh-keygen -t ed25519 + +# Copy to server +ssh-copy-id root@185.112.147.205 + +# Test access +ssh root@185.112.147.205 "echo Connected" +``` + +## Configuration + +### Inventory Setup + +Create your inventory file: + +```bash +cp inventory/hosts.yml inventory/production.yml +nano inventory/production.yml +``` + +**Single server**: +```yaml +all: + children: + vpn_servers: + hosts: + vpn-node1: + ansible_host: 185.112.147.205 + ansible_user: root +``` + +**Multiple servers**: +```yaml +all: + children: + vpn_servers: + hosts: + vpn-node1: + ansible_host: 185.112.147.205 + ansible_user: root + vpn-node2: + ansible_host: 203.0.113.11 + ansible_user: root + vpn-node3: + ansible_host: 203.0.113.12 + ansible_user: root +``` + +### Variable Configuration + +Edit group variables: + +```bash +nano inventory/group_vars/vpn_servers.yml +``` + +**Minimal configuration**: +```yaml +# WireGuard users +wg_peers: + - name: alice + - name: bob + - name: charlie + +# VPN network +wg_network: "10.100.0.0/24" + +# Enable VPN-only access +vpn_only_mode: true +``` + +**Advanced configuration**: +```yaml +# System settings +system_timezone: "America/New_York" +ssh_port: 2222 # Custom SSH port + +# WireGuard settings +wg_network: "10.100.0.0/24" +wg_server_ip: "10.100.0.1" +wg_port: 51820 + +# WireGuard users with manual IPs +wg_peers: + - name: alice + ip: 10.100.0.10 + - name: bob + ip: 10.100.0.11 + - name: charlie + ip: 10.100.0.12 + +# Firewall settings +vpn_only_mode: true +management_ports: + - port: 2222 + proto: tcp + comment: "SSH" + - port: 8080 + proto: tcp + comment: "Outline Manager" + +# Security features +fail2ban_enabled: true +auditd_enabled: true +unattended_upgrades_enabled: true +``` + +## Deployment + +### Full Deployment + +Deploy everything (hardening + VPN + firewall): + +```bash +ansible-playbook -i inventory/production.yml playbooks/site.yml +``` + +### Partial Deployment + +Deploy specific components: + +```bash +# Only hardening +ansible-playbook -i inventory/production.yml playbooks/hardening.yml + +# Only WireGuard +ansible-playbook -i inventory/production.yml playbooks/wireguard.yml + +# Only firewall +ansible-playbook -i inventory/production.yml playbooks/firewall.yml +``` + +### Targeted Deployment + +Deploy to specific servers: + +```bash +# Single server +ansible-playbook -i inventory/production.yml playbooks/site.yml --limit vpn-node1 + +# Multiple servers +ansible-playbook -i inventory/production.yml playbooks/site.yml --limit vpn-node1,vpn-node2 +``` + +### Dry Run + +Test without making changes: + +```bash +ansible-playbook -i inventory/production.yml playbooks/site.yml --check --diff +``` + +## Post-Deployment + +### Retrieve Client Configs + +```bash +# Download all configs +scp -r root@185.112.147.205:/root/wireguard-client-configs/ ./ + +# View configs +ls wireguard-client-configs/ +# alice.conf alice_qr.txt +# bob.conf bob_qr.txt +# charlie.conf charlie_qr.txt +``` + +### Distribute to Users + +**Desktop users** (Linux/macOS/Windows): +- Send `.conf` file +- Install WireGuard: https://www.wireguard.com/install/ +- Import config + +**Mobile users** (iOS/Android): +- Send QR code text file +- Install WireGuard app +- Scan QR code: `cat alice_qr.txt` + +### Verify Deployment + +```bash +# Check services on server +ssh root@185.112.147.205 + +# WireGuard status +sudo wg show + +# Firewall status +sudo ufw status verbose + +# Service status +systemctl status wg-quick@wg0 +systemctl status fail2ban +systemctl status auditd +``` + +## User Management + +### Add New Users + +1. Edit variables: +```bash +nano inventory/group_vars/vpn_servers.yml +``` + +2. Add user to `wg_peers`: +```yaml +wg_peers: + - name: alice + - name: bob + - name: charlie + - name: dave # New user +``` + +3. Re-run playbook: +```bash +ansible-playbook -i inventory/production.yml playbooks/wireguard.yml +``` + +4. Retrieve new config: +```bash +scp root@185.112.147.205:/root/wireguard-client-configs/dave.conf ./ +``` + +### Remove Users + +1. Remove from `wg_peers` list +2. Re-run playbook +3. Manually remove keys from server: +```bash +ssh root@185.112.147.205 +rm /etc/wireguard/keys/dave_* +``` + +### Rotate Keys + +```bash +# Backup old keys +ssh root@185.112.147.205 +cp -r /etc/wireguard/keys /root/wireguard-keys-backup-$(date +%Y%m%d) + +# Remove old keys +rm -rf /etc/wireguard/keys/* + +# Re-run playbook to generate new keys +ansible-playbook -i inventory/production.yml playbooks/wireguard.yml + +# Distribute new configs to all users +``` + +## Maintenance + +### Update System Packages + +```bash +# Automatic updates are enabled by default +# Manual update: +ssh root@185.112.147.205 +apt update && apt upgrade -y +``` + +### Monitor Logs + +```bash +# WireGuard logs +journalctl -u wg-quick@wg0 -f + +# SSH attempts +journalctl -u sshd -f + +# Fail2ban +journalctl -u fail2ban -f + +# Audit logs +ausearch -ts recent +``` + +### Backup Configuration + +```bash +# Backup from server +ssh root@185.112.147.205 +tar czf /root/vpn-backup-$(date +%Y%m%d).tar.gz \ + /etc/wireguard/ \ + /root/wireguard-client-configs/ \ + /etc/ssh/sshd_config \ + /etc/ufw/ + +# Download backup +scp root@185.112.147.205:/root/vpn-backup-*.tar.gz ./ +``` + +### Re-run Playbook + +Safe to re-run anytime (idempotent): + +```bash +ansible-playbook -i inventory/production.yml playbooks/site.yml +``` + +## Advanced Usage + +### Custom SSH Port + +```yaml +# In group_vars/vpn_servers.yml +ssh_port: 2222 + +management_ports: + - port: 2222 # Update firewall rule + proto: tcp + comment: "SSH" +``` + +### Multiple VPN Networks + +Deploy separate VPN servers with different networks: + +```yaml +# vpn-node1 (group_vars or host_vars) +wg_network: "10.100.0.0/24" + +# vpn-node2 +wg_network: "10.101.0.0/24" +``` + +### Disable VPN-Only Mode + +Allow management access from internet: + +```yaml +vpn_only_mode: false +``` + +⚠️ **Not recommended for production!** + +### Custom Firewall Rules + +```yaml +management_ports: + - port: 22 + proto: tcp + comment: "SSH" + - port: 8080 + proto: tcp + comment: "Outline Manager" + - port: 8065 + proto: tcp + comment: "Mattermost" + - port: 443 + proto: tcp + comment: "HTTPS" + +public_ports: + - port: 51820 + proto: udp + comment: "WireGuard" + - port: 80 + proto: tcp + comment: "HTTP (public website)" +``` + +## Testing + +### Test VPN Connection + +**Desktop**: +```bash +# Import config +sudo wg-quick up alice + +# Check connection +curl https://ifconfig.me +# Should show server IP + +# Disconnect +sudo wg-quick down alice +``` + +**Mobile**: +1. Import config via QR code +2. Connect +3. Visit https://ifconfig.me +4. Verify server IP + +### Test VPN-Only Access + +```bash +# Before VPN: SSH should fail (if vpn_only_mode enabled) +ssh root@185.112.147.205 +# Connection refused or timeout + +# Connect to VPN +sudo wg-quick up alice + +# After VPN: SSH should work +ssh root@10.100.0.1 +# Connected! +``` + +### Test Firewall + +```bash +# Check open ports from outside +nmap -p 22,51820 185.112.147.205 + +# Should show: +# 22/tcp closed (if vpn_only_mode) +# 51820/udp open +``` + +## Next Steps + +- Read [SECURITY.md](SECURITY.md) for security best practices +- Read [TROUBLESHOOTING.md](TROUBLESHOOTING.md) for common issues +- Set up monitoring and alerting +- Configure backups +- Document your deployment diff --git a/galaxy.yml b/galaxy.yml new file mode 100644 index 0000000..4da04b6 --- /dev/null +++ b/galaxy.yml @@ -0,0 +1,25 @@ +--- +namespace: secure_infra +name: vpn_server +version: 1.0.0 +readme: README.md +authors: + - "Security Infrastructure Team" +description: > + Production-grade Ansible collection for hardening Ubuntu 24.04 servers, + installing WireGuard VPN, and configuring firewall for VPN-only access. + Follows security best practices and Ansible Galaxy standards. +license: + - MIT +tags: + - security + - hardening + - wireguard + - vpn + - firewall + - ubuntu +dependencies: {} +repository: https://github.com/your-org/secure-vpn-server +documentation: https://github.com/your-org/secure-vpn-server/blob/main/README.md +homepage: https://github.com/your-org/secure-vpn-server +issues: https://github.com/your-org/secure-vpn-server/issues diff --git a/inventory/group_vars/vpn_servers.yml b/inventory/group_vars/vpn_servers.yml new file mode 100644 index 0000000..8c54b43 --- /dev/null +++ b/inventory/group_vars/vpn_servers.yml @@ -0,0 +1,107 @@ +--- +# Group Variables for VPN Servers +# These settings apply to VPN1, VPN2, VPN3 (user-facing VPN endpoints) + +# ========================================== +# CRITICAL: Set ValleyForge Public IP +# ========================================== +valleyforge_public_ip: "185.112.147.205" # CHANGE THIS! + +# ========================================== +# Admin Users (SSH Access Management) +# ========================================== +# Create admin users with SSH keys and sudo access +admin_users: + - username: alice + comment: "Alice - Infrastructure Admin" + groups: ["sudo", "adm"] + generate_keys: true # Auto-generate SSH key pair + shell: /bin/bash + state: present + + - username: bob + comment: "Bob - Security Admin" + groups: ["sudo"] + generate_keys: true + shell: /bin/bash + state: present + +# Example with existing SSH key: +# - username: charlie +# comment: "Charlie - Operations" +# groups: ["sudo"] +# generate_keys: false +# authorized_keys: +# - "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA... charlie@laptop" +# shell: /bin/bash +# state: present + +# ========================================== +# Management Access Control (Two-Tier Architecture) +# ========================================== +# Allow management from ValleyForge only +management_allowed_sources: + - "{{ valleyforge_public_ip }}" # ValleyForge public IP + # - "10.100.0.0/24" # Optional: ValleyForge admin VPN network + +# Management ports (restricted to management_allowed_sources) +management_ports: + - port: 22 + proto: tcp + comment: "SSH" + +# Public ports (user VPN - always accessible) +public_ports: + - port: "{{ wg_port }}" + proto: udp + comment: "WireGuard User VPN" + +# Enable VPN-only mode (restrict management to management_allowed_sources) +vpn_only_mode: true + +# ========================================== +# WireGuard User VPN Configuration +# ========================================== +# Each VPN endpoint has its own network (override in host_vars) +wg_network: "10.200.0.0/24" # Default +wg_server_ip: "10.200.0.1" # Default +wg_port: 51820 + +# VPN users (end users, not admins) +wg_peers: + - name: user1 + - name: user2 + - name: user3 + # Add 50-70 users per endpoint + +# ========================================== +# System Hardening (CIS Level 1 Compliant) +# ========================================== +system_timezone: "UTC" + +# SSH Configuration (CIS 5.2.x) +ssh_port: 22 +ssh_permit_root_login: "no" # CIS 5.2.8 +ssh_password_authentication: "no" # Key-only auth +ssh_allowed_users: [] # Empty = allow all users + +# Security Features +fail2ban_enabled: true +auditd_enabled: true +apparmor_enabled: true +unattended_upgrades_enabled: true +ssh_rate_limit: true + +# Password Policies (CIS 5.4.x, 5.5.x) +password_max_days: 365 +password_min_days: 1 +password_warn_age: 7 +password_inactive_days: 30 + +# Sudo Configuration (CIS 5.3.x) +sudo_nopasswd: true # For automation +sudo_timeout: 15 # Minutes + +# Root Account +disable_root_login: true # Disable root SSH +lock_root_account: false # Allow sudo to root diff --git a/inventory/host_vars/vpn1.yml b/inventory/host_vars/vpn1.yml new file mode 100644 index 0000000..be9a998 --- /dev/null +++ b/inventory/host_vars/vpn1.yml @@ -0,0 +1,14 @@ +--- +# Host Variables for VPN1 +# User-facing VPN endpoint #1 + +# VPN Network (unique per endpoint) +wg_network: "10.200.0.0/24" +wg_server_ip: "10.200.0.1" + +# Users for this endpoint (50-70 recommended) +# Can override group_vars wg_peers or add additional users +# wg_peers: +# - name: vpn1_user1 +# - name: vpn1_user2 +# # ... up to 70 users diff --git a/inventory/host_vars/vpn2.yml b/inventory/host_vars/vpn2.yml new file mode 100644 index 0000000..2ce5d52 --- /dev/null +++ b/inventory/host_vars/vpn2.yml @@ -0,0 +1,13 @@ +--- +# Host Variables for VPN2 +# User-facing VPN endpoint #2 + +# VPN Network (unique per endpoint) +wg_network: "10.201.0.0/24" +wg_server_ip: "10.201.0.1" + +# Users for this endpoint (50-70 recommended) +# wg_peers: +# - name: vpn2_user1 +# - name: vpn2_user2 +# # ... up to 70 users diff --git a/inventory/host_vars/vpn3.yml b/inventory/host_vars/vpn3.yml new file mode 100644 index 0000000..a36033b --- /dev/null +++ b/inventory/host_vars/vpn3.yml @@ -0,0 +1,13 @@ +--- +# Host Variables for VPN3 +# User-facing VPN endpoint #3 + +# VPN Network (unique per endpoint) +wg_network: "10.202.0.0/24" +wg_server_ip: "10.202.0.1" + +# Users for this endpoint (50-70 recommended) +# wg_peers: +# - name: vpn3_user1 +# - name: vpn3_user2 +# # ... up to 70 users diff --git a/inventory/hosts.yml b/inventory/hosts.yml new file mode 100644 index 0000000..a5d6dd7 --- /dev/null +++ b/inventory/hosts.yml @@ -0,0 +1,39 @@ +--- +# Inventory File for Two-Tier VPN Architecture +# +# This inventory is for managing VPN1, VPN2, VPN3 (user-facing VPN endpoints) +# from ValleyForge (admin control plane) +# +# Deploy this FROM ValleyForge server after: +# 1. ValleyForge is set up with WireGuard admin VPN +# 2. Ansible is installed on ValleyForge +# 3. SSH keys are configured from ValleyForge to VPN endpoints + +all: + children: + # User-facing VPN endpoints + vpn_servers: + hosts: + vpn1: + ansible_host: 203.0.113.10 # VPN1 public IP (CHANGE THIS!) + ansible_user: root + + vpn2: + ansible_host: 203.0.113.11 # VPN2 public IP (CHANGE THIS!) + ansible_user: root + + vpn3: + ansible_host: 203.0.113.12 # VPN3 public IP (CHANGE THIS!) + ansible_user: root + + vars: + # Common variables for all VPN servers + ansible_python_interpreter: /usr/bin/python3 + + # ValleyForge public IP (for firewall rules) + # IMPORTANT: Change this to your actual ValleyForge IP! + valleyforge_public_ip: "185.112.147.205" + +# Note: ValleyForge itself is NOT in this inventory +# ValleyForge is the control plane where you run Ansible FROM +# It should be configured separately with its own WireGuard admin VPN diff --git a/playbooks/add_user.yml b/playbooks/add_user.yml new file mode 100644 index 0000000..1aa10d7 --- /dev/null +++ b/playbooks/add_user.yml @@ -0,0 +1,50 @@ +--- +# Add Single User Playbook +# Quick playbook to add one admin user + +- name: Add Admin User + hosts: all + become: yes + gather_facts: yes + + vars_prompt: + - name: new_username + prompt: "Enter username to create" + private: no + + - name: new_user_comment + prompt: "Enter full name/comment" + private: no + default: "" + + - name: generate_ssh_key + prompt: "Generate SSH key pair? (yes/no)" + private: no + default: "yes" + + vars: + admin_users: + - username: "{{ new_username }}" + comment: "{{ new_user_comment if new_user_comment else new_username }}" + groups: ["sudo", "adm"] + generate_keys: "{{ generate_ssh_key | bool }}" + shell: /bin/bash + state: present + + roles: + - role: ssh_users + + post_tasks: + - name: Display success message + ansible.builtin.debug: + msg: + - "=========================================" + - "User {{ new_username }} created successfully!" + - "=========================================" + - "{% if generate_ssh_key | bool %}SSH keys: {{ ssh_keys_local_dir }}/{{ inventory_hostname }}/{{ new_username }}_id_ed25519{% endif %}" + - "" + - "{% if generate_ssh_key | bool %}Test SSH access:{% endif %}" + - "{% if generate_ssh_key | bool %}ssh -i {{ ssh_keys_local_dir }}/{{ inventory_hostname }}/{{ new_username }}_id_ed25519 {{ new_username }}@{{ inventory_hostname }}{% endif %}" + - "=========================================" + delegate_to: localhost + run_once: true diff --git a/playbooks/firewall.yml b/playbooks/firewall.yml new file mode 100644 index 0000000..d4ecaa2 --- /dev/null +++ b/playbooks/firewall.yml @@ -0,0 +1,11 @@ +--- +# Firewall Configuration Only Playbook + +- name: Secure Firewall Configuration + hosts: vpn_servers + become: yes + gather_facts: yes + + roles: + - role: secure_firewall + tags: ['firewall', 'security'] diff --git a/playbooks/hardening.yml b/playbooks/hardening.yml new file mode 100644 index 0000000..3c1ddb9 --- /dev/null +++ b/playbooks/hardening.yml @@ -0,0 +1,11 @@ +--- +# System Hardening Only Playbook + +- name: System Hardening + hosts: vpn_servers + become: yes + gather_facts: yes + + roles: + - role: system_hardening + tags: ['hardening', 'security'] diff --git a/playbooks/remove_user.yml b/playbooks/remove_user.yml new file mode 100644 index 0000000..d2dd15a --- /dev/null +++ b/playbooks/remove_user.yml @@ -0,0 +1,50 @@ +--- +# Remove User Playbook +# Remove admin user and optionally delete SSH keys + +- name: Remove Admin User + hosts: all + become: yes + gather_facts: yes + + vars_prompt: + - name: remove_username + prompt: "Enter username to remove" + private: no + + - name: remove_home + prompt: "Remove home directory? (yes/no)" + private: no + default: "yes" + + - name: delete_ssh_keys + prompt: "Delete SSH keys from control node? (yes/no)" + private: no + default: "no" + + tasks: + - name: Remove user account + ansible.builtin.user: + name: "{{ remove_username }}" + state: absent + remove: "{{ remove_home | bool }}" + + - name: Delete SSH keys from control node + ansible.builtin.file: + path: "{{ ssh_keys_local_dir }}/{{ inventory_hostname }}/{{ remove_username }}_id_ed25519{{ item }}" + state: absent + loop: + - "" + - ".pub" + delegate_to: localhost + when: delete_ssh_keys | bool + + - name: Display success message + ansible.builtin.debug: + msg: + - "=========================================" + - "User {{ remove_username }} removed successfully!" + - "=========================================" + - "Home directory: {{ 'REMOVED' if remove_home | bool else 'KEPT' }}" + - "SSH keys: {{ 'DELETED' if delete_ssh_keys | bool else 'KEPT' }}" + - "=========================================" diff --git a/playbooks/site.yml b/playbooks/site.yml new file mode 100644 index 0000000..3bcf8f1 --- /dev/null +++ b/playbooks/site.yml @@ -0,0 +1,137 @@ +--- +# Main Site Playbook - Complete Server Hardening + User Management + WireGuard VPN + Firewall + +- name: Secure VPN Server Deployment + hosts: vpn_servers + become: yes + gather_facts: yes + + pre_tasks: + - name: Display deployment information + ansible.builtin.debug: + msg: + - "Deploying secure VPN server to: {{ inventory_hostname }}" + - "IP Address: {{ ansible_default_ipv4.address }}" + - "OS: {{ ansible_distribution }} {{ ansible_distribution_version }}" + - "VPN Network: {{ wg_network | default('10.100.0.0/24') }}" + + - name: Verify Ubuntu 24.04 + ansible.builtin.assert: + that: + - ansible_distribution == "Ubuntu" + - ansible_distribution_version is version('22.04', '>=') + fail_msg: "This playbook requires Ubuntu 22.04 or newer" + success_msg: "OS version check passed" + + roles: + - role: system_hardening + tags: ['hardening', 'security', 'cis'] + + - role: ssh_users + tags: ['users', 'ssh', 'security'] + when: admin_users is defined and admin_users | length > 0 + + - role: wireguard_server + tags: ['wireguard', 'vpn'] + + - role: secure_firewall + tags: ['firewall', 'security'] + + post_tasks: + - name: Display deployment summary + ansible.builtin.debug: + msg: + - "=========================================" + - "Deployment Complete!" + - "=========================================" + - "" + - "Server: {{ inventory_hostname }}" + - "Public IP: {{ ansible_default_ipv4.address }}" + - "VPN Network: {{ wg_network }}" + - "Admin Users: {{ admin_users | map(attribute='username') | list | join(', ') if admin_users is defined else 'none' }}" + - "" + - "Client configs: /root/wireguard-client-configs/" + - "{% if admin_users is defined and admin_users | length > 0 %}SSH keys: {{ ssh_keys_local_dir }}/{{ inventory_hostname }}/{% endif %}" + - "Firewall config: /root/firewall-config.txt" + - "" + - "Next steps:" + - "1. Download client configs from server" + - "{% if admin_users is defined and admin_users | length > 0 %}2. Test SSH with new admin users{% endif %}" + - "3. Distribute VPN configs to users" + - "4. Test VPN connection" + - "5. Verify firewall rules" + - "" + - "=========================================" + + - name: Save deployment summary + ansible.builtin.copy: + dest: /root/deployment-summary.txt + content: | + Secure VPN Server Deployment Summary + ===================================== + + Deployment Date: {{ ansible_date_time.iso8601 }} + Server: {{ inventory_hostname }} + Public IP: {{ ansible_default_ipv4.address }} + + Components Deployed: + - System Hardening (CIS Level 1 compliant) + - SSH User Management + - WireGuard VPN Server + - Secure Firewall (Management access restricted) + + Admin Users: + {% if admin_users is defined %} + {% for user in admin_users %} + - {{ user.username }} ({{ user.comment | default('') }}) + {% endfor %} + {% else %} + - None created (using root) + {% endif %} + + VPN Configuration: + - Network: {{ wg_network }} + - Server IP: {{ wg_server_ip }} + - Port: {{ wg_port }} + - Users: {{ wg_peers | length }} + + Security Features (CIS Compliant): + - SSH hardened (key-only, strong ciphers) + - Root SSH login disabled + - Password policies enforced + - AppArmor enabled and enforcing + - Comprehensive audit logging + - Automatic security updates enabled + - Fail2ban active + - Uncommon network protocols disabled + - Core dumps restricted + - Management ports restricted to authorized sources + + Client Configurations: + VPN: /root/wireguard-client-configs/ + {% if admin_users is defined and admin_users | length > 0 %} + SSH Keys (on control node): {{ ssh_keys_local_dir }}/{{ inventory_hostname }}/ + {% endif %} + + {% for peer in wg_peers %} + - {{ peer.name }}: {{ peer.ip }} + {% endfor %} + + Important Files: + - VPN client configs: /root/wireguard-client-configs/ + - Firewall config: /root/firewall-config.txt + - WireGuard keys: /etc/wireguard/keys/ + - Sudo log: /var/log/sudo.log + - Audit logs: /var/log/audit/audit.log + + Next Steps: + 1. Download VPN configs: scp root@{{ ansible_default_ipv4.address }}:/root/wireguard-client-configs/* ./ + {% if admin_users is defined and admin_users | length > 0 %} + 2. Test SSH with admin users (root SSH will be disabled) + 3. Verify sudo access works for admin users + {% endif %} + 4. Distribute VPN configs to users + 5. Test VPN connection + 6. Monitor logs: journalctl -u wg-quick@wg0 + 7. Review audit logs: ausearch -ts recent + mode: '0600' diff --git a/playbooks/users.yml b/playbooks/users.yml new file mode 100644 index 0000000..6019d4a --- /dev/null +++ b/playbooks/users.yml @@ -0,0 +1,31 @@ +--- +# User Management Playbook +# Create admin users, generate SSH keys, configure sudo + +- name: Manage SSH Users + hosts: all + become: yes + gather_facts: yes + + roles: + - role: ssh_users + when: admin_users is defined and admin_users | length > 0 + + post_tasks: + - name: Display SSH keys location + ansible.builtin.debug: + msg: + - "=========================================" + - "SSH Keys Generated" + - "=========================================" + - "Location: {{ ssh_keys_local_dir }}/{{ inventory_hostname }}/" + - "" + - "Copy private keys to your machine:" + - "scp -r {{ ssh_keys_local_dir }}/{{ inventory_hostname }}/ ~/.ssh/" + - "" + - "Test SSH access:" + - "ssh -i ~/.ssh/{{ inventory_hostname }}/USERNAME_id_ed25519 USERNAME@{{ inventory_hostname }}" + - "=========================================" + when: admin_users | selectattr('generate_keys', 'defined') | selectattr('generate_keys') | list | length > 0 + delegate_to: localhost + run_once: true diff --git a/playbooks/validate.yml b/playbooks/validate.yml new file mode 100644 index 0000000..dce030d --- /dev/null +++ b/playbooks/validate.yml @@ -0,0 +1,39 @@ +--- +# Validation Playbook - Test Configuration Before Deployment + +- name: Validate Configuration + hosts: vpn_servers + gather_facts: no + + tasks: + - name: Test connectivity + ansible.builtin.ping: + + - name: Validate management_allowed_sources is defined + ansible.builtin.assert: + that: + - management_allowed_sources is defined + - management_allowed_sources | length > 0 + fail_msg: "ERROR: management_allowed_sources must be defined in group_vars!" + success_msg: "✓ management_allowed_sources is configured" + + - name: Validate VPN network is unique per host + ansible.builtin.debug: + msg: "{{ inventory_hostname }}: VPN network {{ wg_network }}, Server IP {{ wg_server_ip }}" + + - name: Validate ValleyForge IP is set + ansible.builtin.assert: + that: + - valleyforge_public_ip is defined + - valleyforge_public_ip != "185.112.147.205" # Default placeholder + fail_msg: "ERROR: Please set valleyforge_public_ip to your actual ValleyForge IP!" + success_msg: "✓ ValleyForge IP is configured: {{ valleyforge_public_ip }}" + when: "'185.112.147.205' in management_allowed_sources" + + - name: Display configuration summary + ansible.builtin.debug: + msg: + - "Host: {{ inventory_hostname }}" + - "VPN Network: {{ wg_network }}" + - "Management allowed from: {{ management_allowed_sources | join(', ') }}" + - "Users configured: {{ wg_peers | length }}" diff --git a/playbooks/wireguard.yml b/playbooks/wireguard.yml new file mode 100644 index 0000000..190f61a --- /dev/null +++ b/playbooks/wireguard.yml @@ -0,0 +1,11 @@ +--- +# WireGuard VPN Only Playbook + +- name: WireGuard VPN Installation + hosts: vpn_servers + become: yes + gather_facts: yes + + roles: + - role: wireguard_server + tags: ['wireguard', 'vpn'] diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..cceaa7d --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +ansible>=2.15.0 +ansible-core>=2.15.0 +jinja2>=3.1.0 diff --git a/requirements.yml b/requirements.yml new file mode 100644 index 0000000..e82060f --- /dev/null +++ b/requirements.yml @@ -0,0 +1,8 @@ +--- +collections: + - name: ansible.posix + version: ">=1.5.0" + - name: community.general + version: ">=8.0.0" + - name: community.crypto + version: ">=2.15.0" diff --git a/roles/secure_firewall/defaults/main.yml b/roles/secure_firewall/defaults/main.yml new file mode 100644 index 0000000..69987ff --- /dev/null +++ b/roles/secure_firewall/defaults/main.yml @@ -0,0 +1,59 @@ +--- +# Secure Firewall Role - Default Variables + +# Firewall backend (ufw or iptables) +firewall_backend: ufw + +# Default policies +firewall_default_input_policy: deny +firewall_default_output_policy: allow +firewall_default_forward_policy: deny + +# VPN-only mode: Only allow management access from specified networks/IPs +vpn_only_mode: true + +# Management access sources +# Can be: +# - VPN network CIDR (e.g., "10.100.0.0/24" for admin VPN) +# - Single IP (e.g., "185.112.147.205" for ValleyForge public IP) +# - List of both +management_allowed_sources: [] +# Example: +# management_allowed_sources: +# - "10.100.0.0/24" # Admin VPN network +# - "185.112.147.205" # ValleyForge public IP + +# Management ports (restricted to management_allowed_sources if vpn_only_mode is true) +management_ports: + - port: 22 + proto: tcp + comment: "SSH" + - port: 80 + proto: tcp + comment: "HTTP" + - port: 443 + proto: tcp + comment: "HTTPS" + - port: 8080 + proto: tcp + comment: "Outline Manager" + - port: 8065 + proto: tcp + comment: "Mattermost" + - port: 8443 + proto: tcp + comment: "Nextcloud HTTPS" + +# Public ports (always accessible from internet) +public_ports: + - port: 51820 + proto: udp + comment: "WireGuard VPN" + +# Rate limiting for SSH +ssh_rate_limit: true +ssh_rate_limit_burst: 10 +ssh_rate_limit_rate: "30/minute" + +# Logging +firewall_logging: "low" # off, low, medium, high, full diff --git a/roles/secure_firewall/handlers/main.yml b/roles/secure_firewall/handlers/main.yml new file mode 100644 index 0000000..60f5aeb --- /dev/null +++ b/roles/secure_firewall/handlers/main.yml @@ -0,0 +1,6 @@ +--- +# Secure Firewall Role - Handlers + +- name: reload ufw + community.general.ufw: + state: reloaded diff --git a/roles/secure_firewall/meta/main.yml b/roles/secure_firewall/meta/main.yml new file mode 100644 index 0000000..4971e46 --- /dev/null +++ b/roles/secure_firewall/meta/main.yml @@ -0,0 +1,24 @@ +--- +galaxy_info: + role_name: secure_firewall + author: Security Infrastructure Team + description: Secure firewall configuration with VPN-only access mode + company: Your Organization + license: MIT + min_ansible_version: "2.15" + platforms: + - name: Ubuntu + versions: + - noble # 24.04 + - jammy # 22.04 + galaxy_tags: + - firewall + - ufw + - security + - vpn + +dependencies: + - role: wireguard_server + +collections: + - community.general diff --git a/roles/secure_firewall/tasks/main.yml b/roles/secure_firewall/tasks/main.yml new file mode 100644 index 0000000..db0f943 --- /dev/null +++ b/roles/secure_firewall/tasks/main.yml @@ -0,0 +1,10 @@ +--- +# Secure Firewall Role - Main Tasks + +- name: Include UFW firewall tasks + ansible.builtin.include_tasks: ufw.yml + when: firewall_backend == "ufw" + +- name: Include VPN-only access tasks + ansible.builtin.include_tasks: vpn_only.yml + when: vpn_only_mode | bool diff --git a/roles/secure_firewall/tasks/ufw.yml b/roles/secure_firewall/tasks/ufw.yml new file mode 100644 index 0000000..6a660d7 --- /dev/null +++ b/roles/secure_firewall/tasks/ufw.yml @@ -0,0 +1,46 @@ +--- +# UFW Firewall Configuration Tasks + +- name: Ensure UFW is installed + ansible.builtin.apt: + name: ufw + state: present + +- name: Reset UFW to default state + community.general.ufw: + state: reset + when: firewall_reset | default(false) | bool + +- name: Set UFW default policies + community.general.ufw: + direction: "{{ item.direction }}" + policy: "{{ item.policy }}" + loop: + - { direction: 'incoming', policy: "{{ firewall_default_input_policy }}" } + - { direction: 'outgoing', policy: "{{ firewall_default_output_policy }}" } + - { direction: 'routed', policy: "{{ firewall_default_forward_policy }}" } + +- name: Allow public ports (unrestricted) + community.general.ufw: + rule: allow + port: "{{ item.port }}" + proto: "{{ item.proto }}" + comment: "{{ item.comment }}" + loop: "{{ public_ports }}" + +- name: Configure UFW logging + community.general.ufw: + logging: "{{ firewall_logging }}" + +- name: Enable UFW + community.general.ufw: + state: enabled + +- name: Display firewall status + ansible.builtin.command: ufw status verbose + register: ufw_status + changed_when: false + +- name: Show firewall status + ansible.builtin.debug: + var: ufw_status.stdout_lines diff --git a/roles/secure_firewall/tasks/vpn_only.yml b/roles/secure_firewall/tasks/vpn_only.yml new file mode 100644 index 0000000..bdbfceb --- /dev/null +++ b/roles/secure_firewall/tasks/vpn_only.yml @@ -0,0 +1,83 @@ +--- +# VPN-Only Access Configuration Tasks + +- name: Validate management_allowed_sources is defined + ansible.builtin.assert: + that: + - management_allowed_sources is defined + - management_allowed_sources | length > 0 + fail_msg: "management_allowed_sources must be defined and non-empty when vpn_only_mode is true" + success_msg: "Management access sources configured: {{ management_allowed_sources | join(', ') }}" + +- name: Restrict management ports to allowed sources only + community.general.ufw: + rule: allow + port: "{{ item.0.port }}" + proto: "{{ item.0.proto }}" + from_ip: "{{ item.1 }}" + comment: "{{ item.0.comment }} (from {{ item.1 }})" + loop: "{{ management_ports | product(management_allowed_sources) | list }}" + loop_control: + label: "{{ item.0.comment }} port {{ item.0.port }} from {{ item.1 }}" + +- name: Apply SSH rate limiting for each allowed source + community.general.ufw: + rule: limit + port: "22" + proto: tcp + from_ip: "{{ item }}" + loop: "{{ management_allowed_sources }}" + when: ssh_rate_limit | bool + +- name: Deny management ports from all other sources + community.general.ufw: + rule: deny + port: "{{ item.port }}" + proto: "{{ item.proto }}" + comment: "Block {{ item.comment }} from public" + loop: "{{ management_ports }}" + +- name: Create VPN-only access summary + ansible.builtin.copy: + dest: /root/firewall-config.txt + content: | + # Firewall Configuration Summary + + ## VPN-Only Mode: ENABLED + + ## Management Access Allowed From: + {% for source in management_allowed_sources %} + - {{ source }} + {% endfor %} + + ## Management Ports (Restricted Access): + {% for port in management_ports %} + - {{ port.port }}/{{ port.proto }} - {{ port.comment }} + {% endfor %} + + ## Public Ports (Unrestricted): + {% for port in public_ports %} + - {{ port.port }}/{{ port.proto }} - {{ port.comment }} + {% endfor %} + + ## Security Notes: + - Management access ONLY from: {{ management_allowed_sources | join(', ') }} + - SSH is rate-limited to prevent brute force + - Default policy: DENY all incoming (except allowed above) + - Logging level: {{ firewall_logging }} + + ## To view firewall rules: + sudo ufw status verbose + + ## To modify rules: + Edit inventory variables and re-run playbook + mode: '0600' + +- name: Display VPN-only configuration + ansible.builtin.debug: + msg: + - "VPN-Only Mode: ENABLED" + - "Management access allowed from: {{ management_allowed_sources | join(', ') }}" + - "Management ports are ONLY accessible from allowed sources" + - "Public ports remain accessible from internet" + - "Configuration saved to /root/firewall-config.txt" diff --git a/roles/ssh_users/defaults/main.yml b/roles/ssh_users/defaults/main.yml new file mode 100644 index 0000000..91f9cde --- /dev/null +++ b/roles/ssh_users/defaults/main.yml @@ -0,0 +1,55 @@ +--- +# SSH Users Role - Default Variables + +# Admin users to create +# Each user will get: +# - User account created +# - Added to sudo group +# - SSH key pair generated (if generate_keys: true) +# - Authorized SSH keys configured +# - Shell set to /bin/bash + +admin_users: [] +# Example: +# admin_users: +# - username: alice +# comment: "Alice Admin" +# groups: ["sudo", "adm"] +# generate_keys: true # Generate SSH key pair on control node +# authorized_keys: [] # List of public keys to add +# shell: /bin/bash +# state: present +# +# - username: bob +# comment: "Bob Admin" +# groups: ["sudo"] +# generate_keys: false +# authorized_keys: +# - "ssh-ed25519 AAAAC3... bob@laptop" +# shell: /bin/bash +# state: present + +# SSH key generation settings +ssh_key_type: "ed25519" +ssh_key_bits: 4096 # Only used for RSA +ssh_key_comment: "{{ ansible_user }}@{{ inventory_hostname }}" + +# Directory to store generated SSH keys on control node +ssh_keys_local_dir: "{{ playbook_dir }}/../ssh-keys" + +# Sudo configuration +sudo_nopasswd: true # Allow sudo without password (for automation) +sudo_timeout: 15 # Sudo timeout in minutes + +# Root account restrictions +disable_root_login: true # Disable root SSH login +lock_root_account: false # Lock root account (prevents su/sudo to root) + +# Password policies (CIS compliance) +password_max_days: 365 +password_min_days: 1 +password_warn_age: 7 +password_inactive_days: 30 + +# Default umask +default_umask: "027" # More restrictive than default 022 diff --git a/roles/ssh_users/handlers/main.yml b/roles/ssh_users/handlers/main.yml new file mode 100644 index 0000000..4ded4e2 --- /dev/null +++ b/roles/ssh_users/handlers/main.yml @@ -0,0 +1,7 @@ +--- +# SSH Users Role - Handlers + +- name: restart sshd + ansible.builtin.service: + name: sshd + state: restarted diff --git a/roles/ssh_users/meta/main.yml b/roles/ssh_users/meta/main.yml new file mode 100644 index 0000000..12f791d --- /dev/null +++ b/roles/ssh_users/meta/main.yml @@ -0,0 +1,24 @@ +--- +galaxy_info: + role_name: ssh_users + author: Security Infrastructure Team + description: SSH user management with key generation, sudo configuration, and CIS compliance + license: MIT + min_ansible_version: "2.15" + + platforms: + - name: Ubuntu + versions: + - focal + - jammy + - noble + + galaxy_tags: + - security + - ssh + - users + - cis + - hardening + - sudo + +dependencies: [] diff --git a/roles/ssh_users/tasks/create_users.yml b/roles/ssh_users/tasks/create_users.yml new file mode 100644 index 0000000..a4f1390 --- /dev/null +++ b/roles/ssh_users/tasks/create_users.yml @@ -0,0 +1,63 @@ +--- +# Create Admin Users + +- name: Create admin user accounts + ansible.builtin.user: + name: "{{ item.username }}" + comment: "{{ item.comment | default(item.username) }}" + groups: "{{ item.groups | default(['sudo']) }}" + append: yes + shell: "{{ item.shell | default('/bin/bash') }}" + create_home: yes + state: "{{ item.state | default('present') }}" + loop: "{{ admin_users }}" + loop_control: + label: "{{ item.username }}" + +- name: Set password policies for admin users + ansible.builtin.shell: | + chage -M {{ password_max_days }} -m {{ password_min_days }} -W {{ password_warn_age }} -I {{ password_inactive_days }} {{ item.username }} + loop: "{{ admin_users }}" + loop_control: + label: "{{ item.username }}" + when: item.state | default('present') == 'present' + changed_when: false + +- name: Configure authorized SSH keys for admin users + ansible.posix.authorized_key: + user: "{{ item.0.username }}" + key: "{{ item.1 }}" + state: present + exclusive: no + loop: "{{ admin_users | subelements('authorized_keys', skip_missing=True) }}" + loop_control: + label: "{{ item.0.username }}" + when: + - item.0.state | default('present') == 'present' + - item.0.authorized_keys is defined + - item.0.authorized_keys | length > 0 + +- name: Ensure .ssh directory exists for admin users + ansible.builtin.file: + path: "/home/{{ item.username }}/.ssh" + state: directory + owner: "{{ item.username }}" + group: "{{ item.username }}" + mode: '0700' + loop: "{{ admin_users }}" + loop_control: + label: "{{ item.username }}" + when: item.state | default('present') == 'present' + +- name: Set umask for admin users + ansible.builtin.lineinfile: + path: "/home/{{ item.username }}/.bashrc" + line: "umask {{ default_umask }}" + create: yes + owner: "{{ item.username }}" + group: "{{ item.username }}" + mode: '0644' + loop: "{{ admin_users }}" + loop_control: + label: "{{ item.username }}" + when: item.state | default('present') == 'present' diff --git a/roles/ssh_users/tasks/generate_keys.yml b/roles/ssh_users/tasks/generate_keys.yml new file mode 100644 index 0000000..95e9534 --- /dev/null +++ b/roles/ssh_users/tasks/generate_keys.yml @@ -0,0 +1,104 @@ +--- +# Generate SSH Keys for Admin Users + +- name: Create local SSH keys directory on control node + ansible.builtin.file: + path: "{{ ssh_keys_local_dir }}/{{ inventory_hostname }}" + state: directory + mode: '0700' + delegate_to: localhost + run_once: false + +- name: Generate SSH key pairs on control node + community.crypto.openssh_keypair: + path: "{{ ssh_keys_local_dir }}/{{ inventory_hostname }}/{{ item.username }}_id_{{ ssh_key_type }}" + type: "{{ ssh_key_type }}" + size: "{{ ssh_key_bits if ssh_key_type == 'rsa' else omit }}" + comment: "{{ item.username }}@{{ inventory_hostname }}" + state: present + loop: "{{ admin_users }}" + loop_control: + label: "{{ item.username }}" + when: + - item.generate_keys | default(false) + - item.state | default('present') == 'present' + delegate_to: localhost + run_once: false + register: generated_keys + +- name: Read generated public keys + ansible.builtin.slurp: + src: "{{ ssh_keys_local_dir }}/{{ inventory_hostname }}/{{ item.username }}_id_{{ ssh_key_type }}.pub" + loop: "{{ admin_users }}" + loop_control: + label: "{{ item.username }}" + when: + - item.generate_keys | default(false) + - item.state | default('present') == 'present' + delegate_to: localhost + run_once: false + register: public_keys + +- name: Add generated public keys to authorized_keys + ansible.posix.authorized_key: + user: "{{ item.item.username }}" + key: "{{ item.content | b64decode }}" + state: present + loop: "{{ public_keys.results }}" + loop_control: + label: "{{ item.item.username }}" + when: + - not item.skipped | default(false) + - item.content is defined + +- name: Create SSH key summary file + ansible.builtin.copy: + dest: "{{ ssh_keys_local_dir }}/{{ inventory_hostname }}/README.md" + content: | + # SSH Keys for {{ inventory_hostname }} + + Generated: {{ ansible_date_time.iso8601 }} + + ## Admin Users + + {% for user in admin_users %} + {% if user.generate_keys | default(false) %} + ### {{ user.username }} + + - **Private Key**: `{{ user.username }}_id_{{ ssh_key_type }}` + - **Public Key**: `{{ user.username }}_id_{{ ssh_key_type }}.pub` + - **Comment**: {{ user.username }}@{{ inventory_hostname }} + + **Usage**: + ```bash + # Copy private key to your machine + scp {{ ssh_keys_local_dir }}/{{ inventory_hostname }}/{{ user.username }}_id_{{ ssh_key_type }} ~/.ssh/ + + # Set correct permissions + chmod 600 ~/.ssh/{{ user.username }}_id_{{ ssh_key_type }} + + # SSH to server + ssh -i ~/.ssh/{{ user.username }}_id_{{ ssh_key_type }} {{ user.username }}@{{ inventory_hostname }} + ``` + + {% endif %} + {% endfor %} + + ## Security Notes + + - Private keys are stored on the Ansible control node only + - Public keys are deployed to the servers + - Keep private keys secure and never commit to git + - Rotate keys regularly (every 90 days recommended) + + ## Key Rotation + + To rotate keys: + 1. Generate new keys by re-running the playbook + 2. Test new keys work + 3. Remove old keys from authorized_keys + 4. Delete old private keys securely + mode: '0600' + delegate_to: localhost + run_once: false + when: admin_users | selectattr('generate_keys', 'defined') | selectattr('generate_keys') | list | length > 0 diff --git a/roles/ssh_users/tasks/main.yml b/roles/ssh_users/tasks/main.yml new file mode 100644 index 0000000..ef88e3d --- /dev/null +++ b/roles/ssh_users/tasks/main.yml @@ -0,0 +1,31 @@ +--- +# SSH Users Role - Main Tasks + +- name: Include user creation tasks + ansible.builtin.include_tasks: create_users.yml + when: admin_users | length > 0 + +- name: Include SSH key generation tasks + ansible.builtin.include_tasks: generate_keys.yml + when: admin_users | length > 0 + +- name: Include sudo configuration tasks + ansible.builtin.include_tasks: sudo.yml + +- name: Include root restrictions tasks + ansible.builtin.include_tasks: root_restrictions.yml + +- name: Include password policy tasks + ansible.builtin.include_tasks: password_policy.yml + +- name: Display user management summary + ansible.builtin.debug: + msg: + - "=========================================" + - "SSH User Management Complete" + - "=========================================" + - "Admin users created: {{ admin_users | map(attribute='username') | list | join(', ') }}" + - "Root SSH login: {{ 'DISABLED' if disable_root_login else 'ENABLED' }}" + - "Sudo without password: {{ 'ENABLED' if sudo_nopasswd else 'DISABLED' }}" + - "SSH keys saved to: {{ ssh_keys_local_dir }}" + - "=========================================" diff --git a/roles/ssh_users/tasks/password_policy.yml b/roles/ssh_users/tasks/password_policy.yml new file mode 100644 index 0000000..d58a2af --- /dev/null +++ b/roles/ssh_users/tasks/password_policy.yml @@ -0,0 +1,91 @@ +--- +# Password Policy Configuration (CIS 5.4.x, 5.5.x) + +- name: Set default password expiration (CIS 5.4.1) + ansible.builtin.lineinfile: + path: /etc/login.defs + regexp: '^PASS_MAX_DAYS' + line: "PASS_MAX_DAYS {{ password_max_days }}" + state: present + +- name: Set minimum password change days (CIS 5.4.2) + ansible.builtin.lineinfile: + path: /etc/login.defs + regexp: '^PASS_MIN_DAYS' + line: "PASS_MIN_DAYS {{ password_min_days }}" + state: present + +- name: Set password expiration warning (CIS 5.4.3) + ansible.builtin.lineinfile: + path: /etc/login.defs + regexp: '^PASS_WARN_AGE' + line: "PASS_WARN_AGE {{ password_warn_age }}" + state: present + +- name: Set default umask (CIS 5.4.4) + ansible.builtin.lineinfile: + path: /etc/login.defs + regexp: '^UMASK' + line: "UMASK {{ default_umask }}" + state: present + +- name: Configure PAM password quality requirements (CIS 5.5.1) + ansible.builtin.apt: + name: libpam-pwquality + state: present + +- name: Set password quality requirements + ansible.builtin.lineinfile: + path: /etc/security/pwquality.conf + regexp: "^{{ item.key }}" + line: "{{ item.key }} = {{ item.value }}" + state: present + loop: + - { key: 'minlen', value: '14' } + - { key: 'dcredit', value: '-1' } # At least 1 digit + - { key: 'ucredit', value: '-1' } # At least 1 uppercase + - { key: 'lcredit', value: '-1' } # At least 1 lowercase + - { key: 'ocredit', value: '-1' } # At least 1 special char + - { key: 'minclass', value: '4' } # All 4 character classes + +- name: Configure account lockout (CIS 5.5.2) + ansible.builtin.lineinfile: + path: /etc/pam.d/common-auth + line: "auth required pam_faillock.so preauth audit silent deny=5 unlock_time=900" + insertbefore: '^auth\s+\[success=1 default=ignore\]\s+pam_unix.so' + state: present + +- name: Configure account lockout (part 2) + ansible.builtin.lineinfile: + path: /etc/pam.d/common-auth + line: "auth [default=die] pam_faillock.so authfail audit deny=5 unlock_time=900" + insertafter: '^auth\s+\[success=1 default=ignore\]\s+pam_unix.so' + state: present + +- name: Configure account lockout (part 3) + ansible.builtin.lineinfile: + path: /etc/pam.d/common-auth + line: "auth sufficient pam_faillock.so authsucc audit deny=5 unlock_time=900" + insertafter: '^auth\s+\[default=die\]\s+pam_faillock.so' + state: present + +- name: Configure password reuse prevention (CIS 5.5.3) + ansible.builtin.lineinfile: + path: /etc/pam.d/common-password + regexp: '^password\s+\[success=1 default=ignore\]\s+pam_unix.so' + line: "password [success=1 default=ignore] pam_unix.so obscure use_authtok try_first_pass yescrypt remember=5" + state: present + +- name: Ensure password hashing algorithm is yescrypt (CIS 5.5.4) + ansible.builtin.lineinfile: + path: /etc/pam.d/common-password + regexp: '^password\s+\[success=1 default=ignore\]\s+pam_unix.so' + line: "password [success=1 default=ignore] pam_unix.so obscure use_authtok try_first_pass yescrypt" + state: present + +- name: Set inactive password lock (CIS 5.4.4) + ansible.builtin.lineinfile: + path: /etc/default/useradd + regexp: '^INACTIVE=' + line: "INACTIVE={{ password_inactive_days }}" + state: present diff --git a/roles/ssh_users/tasks/root_restrictions.yml b/roles/ssh_users/tasks/root_restrictions.yml new file mode 100644 index 0000000..26d574e --- /dev/null +++ b/roles/ssh_users/tasks/root_restrictions.yml @@ -0,0 +1,38 @@ +--- +# Root Account Restrictions + +- name: Disable root SSH login + ansible.builtin.lineinfile: + path: /etc/ssh/sshd_config + regexp: '^PermitRootLogin' + line: 'PermitRootLogin no' + state: present + validate: '/usr/sbin/sshd -t -f %s' + when: disable_root_login + notify: restart sshd + +- name: Lock root account + ansible.builtin.user: + name: root + password_lock: yes + when: lock_root_account + +- name: Ensure root group is GID 0 (CIS 5.4.5) + ansible.builtin.group: + name: root + gid: 0 + state: present + +- name: Verify root is the only UID 0 account + ansible.builtin.shell: | + awk -F: '($3 == 0) { print $1 }' /etc/passwd + register: uid_zero_accounts + changed_when: false + failed_when: uid_zero_accounts.stdout_lines | length > 1 + +- name: Display root restrictions status + ansible.builtin.debug: + msg: + - "Root SSH login: {{ 'DISABLED' if disable_root_login else 'ENABLED' }}" + - "Root account: {{ 'LOCKED' if lock_root_account else 'UNLOCKED' }}" + - "UID 0 accounts: {{ uid_zero_accounts.stdout_lines | join(', ') }}" diff --git a/roles/ssh_users/tasks/sudo.yml b/roles/ssh_users/tasks/sudo.yml new file mode 100644 index 0000000..31ec8fe --- /dev/null +++ b/roles/ssh_users/tasks/sudo.yml @@ -0,0 +1,76 @@ +--- +# Sudo Configuration (CIS 5.3.x) + +- name: Ensure sudo is installed + ansible.builtin.apt: + name: sudo + state: present + update_cache: yes + +- name: Configure sudo to use pty (CIS 5.3.2) + ansible.builtin.lineinfile: + path: /etc/sudoers.d/cis-hardening + line: "Defaults use_pty" + create: yes + mode: '0440' + validate: '/usr/sbin/visudo -cf %s' + +- name: Configure sudo logfile (CIS 5.3.3) + ansible.builtin.lineinfile: + path: /etc/sudoers.d/cis-hardening + line: "Defaults logfile=\"/var/log/sudo.log\"" + create: yes + mode: '0440' + validate: '/usr/sbin/visudo -cf %s' + +- name: Configure sudo timeout + ansible.builtin.lineinfile: + path: /etc/sudoers.d/cis-hardening + line: "Defaults timestamp_timeout={{ sudo_timeout }}" + create: yes + mode: '0440' + validate: '/usr/sbin/visudo -cf %s' + +- name: Configure sudo password requirement (CIS 5.3.4) + ansible.builtin.lineinfile: + path: /etc/sudoers.d/cis-hardening + line: "Defaults !authenticate" + state: "{{ 'present' if sudo_nopasswd else 'absent' }}" + create: yes + mode: '0440' + validate: '/usr/sbin/visudo -cf %s' + +- name: Allow sudo group to use sudo + ansible.builtin.lineinfile: + path: /etc/sudoers.d/sudo-group + line: "%sudo ALL=(ALL:ALL) {{ 'NOPASSWD:' if sudo_nopasswd else '' }}ALL" + create: yes + mode: '0440' + validate: '/usr/sbin/visudo -cf %s' + +- name: Create sudo log file + ansible.builtin.file: + path: /var/log/sudo.log + state: touch + owner: root + group: root + mode: '0600' + modification_time: preserve + access_time: preserve + +- name: Configure sudo log rotation + ansible.builtin.copy: + dest: /etc/logrotate.d/sudo + content: | + /var/log/sudo.log { + weekly + rotate 4 + compress + delaycompress + missingok + notifempty + create 0600 root root + } + owner: root + group: root + mode: '0644' diff --git a/roles/system_hardening/defaults/main.yml b/roles/system_hardening/defaults/main.yml new file mode 100644 index 0000000..d9a19b1 --- /dev/null +++ b/roles/system_hardening/defaults/main.yml @@ -0,0 +1,128 @@ +--- +# System Hardening Role - Default Variables + +# SSH Configuration +ssh_port: 22 +ssh_permit_root_login: "no" +ssh_password_authentication: "no" +ssh_pubkey_authentication: "yes" +ssh_challenge_response_auth: "no" +ssh_x11_forwarding: "no" +ssh_max_auth_tries: 3 +ssh_client_alive_interval: 300 +ssh_client_alive_count_max: 2 +ssh_allowed_users: [] # List of users allowed to SSH +ssh_listen_address: "0.0.0.0" + +# Strong SSH ciphers and algorithms +ssh_ciphers: + - "chacha20-poly1305@openssh.com" + - "aes256-gcm@openssh.com" + - "aes128-gcm@openssh.com" +ssh_macs: + - "hmac-sha2-512-etm@openssh.com" + - "hmac-sha2-256-etm@openssh.com" +ssh_kex_algorithms: + - "curve25519-sha256" + - "curve25519-sha256@libssh.org" + - "diffie-hellman-group-exchange-sha256" + +# System packages +hardening_install_packages: + - ufw + - fail2ban + - unattended-upgrades + - apt-listchanges + - auditd + - aide + - rkhunter + - lynis + +hardening_remove_packages: + - telnet + - rsh-client + - rsh-redone-client + +# Automatic security updates +unattended_upgrades_enabled: true +unattended_upgrades_auto_reboot: false +unattended_upgrades_auto_reboot_time: "03:00" + +# Fail2ban configuration +fail2ban_enabled: true +fail2ban_bantime: 3600 +fail2ban_findtime: 600 +fail2ban_maxretry: 5 +fail2ban_destemail: "root@localhost" + +# Sysctl hardening +sysctl_config: + # IP Forwarding (required for VPN) + net.ipv4.ip_forward: 1 + + # Disable IPv6 (optional, set to 0 to enable) + net.ipv6.conf.all.disable_ipv6: 1 + net.ipv6.conf.default.disable_ipv6: 1 + + # Protect against SYN flood attacks + net.ipv4.tcp_syncookies: 1 + net.ipv4.tcp_syn_retries: 2 + net.ipv4.tcp_synack_retries: 2 + net.ipv4.tcp_max_syn_backlog: 4096 + + # Protect against IP spoofing + net.ipv4.conf.all.rp_filter: 1 + net.ipv4.conf.default.rp_filter: 1 + + # Ignore ICMP redirects + net.ipv4.conf.all.accept_redirects: 0 + net.ipv4.conf.default.accept_redirects: 0 + net.ipv4.conf.all.secure_redirects: 0 + net.ipv4.conf.default.secure_redirects: 0 + + # Do not send ICMP redirects + net.ipv4.conf.all.send_redirects: 0 + net.ipv4.conf.default.send_redirects: 0 + + # Ignore ICMP ping requests + net.ipv4.icmp_echo_ignore_all: 0 + net.ipv4.icmp_echo_ignore_broadcasts: 1 + + # Ignore bogus ICMP error responses + net.ipv4.icmp_ignore_bogus_error_responses: 1 + + # Log martian packets + net.ipv4.conf.all.log_martians: 1 + net.ipv4.conf.default.log_martians: 1 + + # Disable source packet routing + net.ipv4.conf.all.accept_source_route: 0 + net.ipv4.conf.default.accept_source_route: 0 + + # Increase system file descriptor limit + fs.file-max: 65535 + + # Protect against time-wait assassination + net.ipv4.tcp_rfc1337: 1 + + # Kernel hardening + kernel.dmesg_restrict: 1 + kernel.kptr_restrict: 2 + kernel.yama.ptrace_scope: 1 + +# Auditd configuration +auditd_enabled: true +auditd_rules: + - "-w /etc/passwd -p wa -k identity" + - "-w /etc/group -p wa -k identity" + - "-w /etc/shadow -p wa -k identity" + - "-w /etc/sudoers -p wa -k actions" + - "-w /var/log/auth.log -p wa -k auth" + - "-w /var/log/faillog -p wa -k logins" + - "-w /etc/ssh/sshd_config -p wa -k sshd" + +# Timezone +system_timezone: "UTC" + +# Hostname +system_hostname: "" # Leave empty to keep current hostname diff --git a/roles/system_hardening/defaults/main_cis.yml b/roles/system_hardening/defaults/main_cis.yml new file mode 100644 index 0000000..5a52e8d --- /dev/null +++ b/roles/system_hardening/defaults/main_cis.yml @@ -0,0 +1,19 @@ +--- +# CIS-Specific Variables for System Hardening + +# AppArmor (CIS 1.3.x) +apparmor_enabled: true +apparmor_enforce_all: true + +# Auditd (CIS 4.1.x) +auditd_enabled: true +auditd_max_log_file: 8 # MB + +# Network (CIS 3.x) +disable_ipv6: true # Set to false if IPv6 is needed + +# Core dumps (CIS 1.5.1) +disable_core_dumps: true + +# Uncommon protocols (CIS 3.3.x) +disable_uncommon_protocols: true diff --git a/roles/system_hardening/handlers/main.yml b/roles/system_hardening/handlers/main.yml new file mode 100644 index 0000000..dbe94b7 --- /dev/null +++ b/roles/system_hardening/handlers/main.yml @@ -0,0 +1,16 @@ +--- +# System Hardening Role - Handlers + +- name: restart sshd + ansible.builtin.systemd: + name: sshd + state: restarted + +- name: restart fail2ban + ansible.builtin.systemd: + name: fail2ban + state: restarted + +- name: restart auditd + ansible.builtin.command: service auditd restart + # Note: auditd requires special restart command diff --git a/roles/system_hardening/meta/main.yml b/roles/system_hardening/meta/main.yml new file mode 100644 index 0000000..abdd6f3 --- /dev/null +++ b/roles/system_hardening/meta/main.yml @@ -0,0 +1,25 @@ +--- +galaxy_info: + role_name: system_hardening + author: Security Infrastructure Team + description: Comprehensive Ubuntu 24.04 server hardening following security best practices + company: Your Organization + license: MIT + min_ansible_version: "2.15" + platforms: + - name: Ubuntu + versions: + - noble # 24.04 + - jammy # 22.04 + galaxy_tags: + - security + - hardening + - ssh + - firewall + - ubuntu + +dependencies: [] + +collections: + - community.general + - ansible.posix diff --git a/roles/system_hardening/tasks/apparmor.yml b/roles/system_hardening/tasks/apparmor.yml new file mode 100644 index 0000000..6b51cb3 --- /dev/null +++ b/roles/system_hardening/tasks/apparmor.yml @@ -0,0 +1,42 @@ +--- +# AppArmor Configuration (CIS 1.3.x) + +- name: Install AppArmor packages (CIS 1.3.1) + ansible.builtin.apt: + name: + - apparmor + - apparmor-utils + state: present + update_cache: yes + +- name: Enable AppArmor service (CIS 1.3.2) + ansible.builtin.service: + name: apparmor + state: started + enabled: yes + +- name: Check AppArmor status + ansible.builtin.command: aa-status --json + register: apparmor_status + changed_when: false + failed_when: false + +- name: Parse AppArmor status + ansible.builtin.set_fact: + apparmor_json: "{{ apparmor_status.stdout | from_json }}" + when: apparmor_status.rc == 0 + +- name: Set all AppArmor profiles to enforce mode (CIS 1.3.3) + ansible.builtin.command: aa-enforce /etc/apparmor.d/* + register: apparmor_enforce + changed_when: "'Setting' in apparmor_enforce.stdout" + failed_when: false + when: apparmor_enforce_all | default(true) + +- name: Display AppArmor status + ansible.builtin.debug: + msg: + - "AppArmor status: {{ apparmor_json.apparmor if apparmor_json is defined else 'unknown' }}" + - "Profiles loaded: {{ apparmor_json.profiles | length if apparmor_json is defined and apparmor_json.profiles is defined else 0 }}" + - "Profiles in enforce mode: {{ apparmor_json.profiles | selectattr('mode', 'equalto', 'enforce') | list | length if apparmor_json is defined and apparmor_json.profiles is defined else 0 }}" + when: apparmor_json is defined diff --git a/roles/system_hardening/tasks/audit.yml b/roles/system_hardening/tasks/audit.yml new file mode 100644 index 0000000..337a4aa --- /dev/null +++ b/roles/system_hardening/tasks/audit.yml @@ -0,0 +1,57 @@ +--- +# Auditd Configuration Tasks (CIS 4.1.x) + +- name: Ensure auditd is installed (CIS 4.1.1) + ansible.builtin.apt: + name: + - auditd + - audispd-plugins + state: present + +- name: Configure auditd max log file size (CIS 4.1.3) + ansible.builtin.lineinfile: + path: /etc/audit/auditd.conf + regexp: '^max_log_file\s*=' + line: "max_log_file = {{ auditd_max_log_file }}" + state: present + +- name: Configure auditd log retention (CIS 4.1.4) + ansible.builtin.lineinfile: + path: /etc/audit/auditd.conf + regexp: '^max_log_file_action\s*=' + line: "max_log_file_action = keep_logs" + state: present + +- name: Configure auditd space left action (CIS 4.1.5) + ansible.builtin.lineinfile: + path: /etc/audit/auditd.conf + regexp: '^space_left_action\s*=' + line: "space_left_action = email" + state: present + +- name: Configure auditd admin space left action + ansible.builtin.lineinfile: + path: /etc/audit/auditd.conf + regexp: '^admin_space_left_action\s*=' + line: "admin_space_left_action = halt" + state: present + +- name: Deploy CIS-compliant audit rules + ansible.builtin.template: + src: audit.rules.j2 + dest: /etc/audit/rules.d/cis.rules + owner: root + group: root + mode: '0640' + notify: restart auditd + +- name: Load audit rules + ansible.builtin.command: augenrules --load + changed_when: false + failed_when: false + +- name: Ensure auditd is started and enabled (CIS 4.1.2) + ansible.builtin.systemd: + name: auditd + state: started + enabled: yes diff --git a/roles/system_hardening/tasks/core_dumps.yml b/roles/system_hardening/tasks/core_dumps.yml new file mode 100644 index 0000000..475a9dc --- /dev/null +++ b/roles/system_hardening/tasks/core_dumps.yml @@ -0,0 +1,39 @@ +--- +# Restrict Core Dumps (CIS 1.5.1) + +- name: Disable core dumps via limits.conf + ansible.builtin.lineinfile: + path: /etc/security/limits.conf + line: "* hard core 0" + create: yes + owner: root + group: root + mode: '0644' + +- name: Disable core dumps via sysctl (already done in sysctl_cis.yml) + ansible.posix.sysctl: + name: fs.suid_dumpable + value: '0' + state: present + sysctl_set: yes + reload: yes + +- name: Disable coredump in systemd + ansible.builtin.lineinfile: + path: /etc/systemd/coredump.conf + regexp: '^Storage=' + line: "Storage=none" + create: yes + owner: root + group: root + mode: '0644' + +- name: Disable coredump processing + ansible.builtin.lineinfile: + path: /etc/systemd/coredump.conf + regexp: '^ProcessSizeMax=' + line: "ProcessSizeMax=0" + create: yes + owner: root + group: root + mode: '0644' diff --git a/roles/system_hardening/tasks/disable_protocols.yml b/roles/system_hardening/tasks/disable_protocols.yml new file mode 100644 index 0000000..f9a04a8 --- /dev/null +++ b/roles/system_hardening/tasks/disable_protocols.yml @@ -0,0 +1,49 @@ +--- +# Disable Uncommon Network Protocols (CIS 3.3.x) + +- name: Disable DCCP protocol (CIS 3.3.1) + ansible.builtin.lineinfile: + path: /etc/modprobe.d/cis.conf + line: "install dccp /bin/true" + create: yes + owner: root + group: root + mode: '0644' + +- name: Disable SCTP protocol (CIS 3.3.2) + ansible.builtin.lineinfile: + path: /etc/modprobe.d/cis.conf + line: "install sctp /bin/true" + create: yes + owner: root + group: root + mode: '0644' + +- name: Disable RDS protocol (CIS 3.3.3) + ansible.builtin.lineinfile: + path: /etc/modprobe.d/cis.conf + line: "install rds /bin/true" + create: yes + owner: root + group: root + mode: '0644' + +- name: Disable TIPC protocol (CIS 3.3.4) + ansible.builtin.lineinfile: + path: /etc/modprobe.d/cis.conf + line: "install tipc /bin/true" + create: yes + owner: root + group: root + mode: '0644' + +- name: Unload uncommon protocols if loaded + community.general.modprobe: + name: "{{ item }}" + state: absent + loop: + - dccp + - sctp + - rds + - tipc + failed_when: false diff --git a/roles/system_hardening/tasks/fail2ban.yml b/roles/system_hardening/tasks/fail2ban.yml new file mode 100644 index 0000000..703bb02 --- /dev/null +++ b/roles/system_hardening/tasks/fail2ban.yml @@ -0,0 +1,22 @@ +--- +# Fail2ban Configuration Tasks + +- name: Ensure fail2ban is installed + ansible.builtin.apt: + name: fail2ban + state: present + +- name: Configure fail2ban + ansible.builtin.template: + src: fail2ban.local.j2 + dest: /etc/fail2ban/jail.local + owner: root + group: root + mode: '0644' + notify: restart fail2ban + +- name: Ensure fail2ban is started and enabled + ansible.builtin.systemd: + name: fail2ban + state: started + enabled: yes diff --git a/roles/system_hardening/tasks/main.yml b/roles/system_hardening/tasks/main.yml new file mode 100644 index 0000000..349498c --- /dev/null +++ b/roles/system_hardening/tasks/main.yml @@ -0,0 +1,121 @@ +--- +# System Hardening Role - Main Tasks + +- name: Set timezone + community.general.timezone: + name: "{{ system_timezone }}" + when: system_timezone is defined and system_timezone != "" + +- name: Set hostname + ansible.builtin.hostname: + name: "{{ system_hostname }}" + when: system_hostname is defined and system_hostname != "" + +- name: Update apt cache + ansible.builtin.apt: + update_cache: yes + cache_valid_time: 3600 + +- name: Upgrade all packages + ansible.builtin.apt: + upgrade: dist + autoremove: yes + autoclean: yes + +- name: Install security packages + ansible.builtin.apt: + name: "{{ hardening_install_packages }}" + state: present + +- name: Remove insecure packages + ansible.builtin.apt: + name: "{{ hardening_remove_packages }}" + state: absent + purge: yes + +- name: Configure SSH hardening + ansible.builtin.include_tasks: ssh.yml + +- name: Configure sysctl parameters (basic) + ansible.builtin.include_tasks: sysctl.yml + +- name: Configure CIS-compliant sysctl parameters + ansible.builtin.include_tasks: sysctl_cis.yml + +- name: Configure AppArmor (CIS 1.3.x) + ansible.builtin.include_tasks: apparmor.yml + when: apparmor_enabled | default(true) + +- name: Configure fail2ban + ansible.builtin.include_tasks: fail2ban.yml + when: fail2ban_enabled | bool + +- name: Configure auditd (CIS 4.1.x) + ansible.builtin.include_tasks: audit.yml + when: auditd_enabled | bool + +- name: Configure unattended upgrades + ansible.builtin.include_tasks: unattended_upgrades.yml + when: unattended_upgrades_enabled | bool + +- name: Disable uncommon network protocols (CIS 3.3.x) + ansible.builtin.include_tasks: disable_protocols.yml + +- name: Configure core dumps restriction (CIS 1.5.1) + ansible.builtin.include_tasks: core_dumps.yml + +- name: Disable unnecessary services + ansible.builtin.systemd: + name: "{{ item }}" + state: stopped + enabled: no + loop: + - avahi-daemon + - cups + - isc-dhcp-server + - isc-dhcp-server6 + - rpcbind + - rsync + - snmpd + failed_when: false # Don't fail if service doesn't exist + +- name: Set secure file permissions (CIS 6.1.x) + ansible.builtin.file: + path: "{{ item.path }}" + mode: "{{ item.mode }}" + loop: + - { path: '/etc/passwd', mode: '0644' } + - { path: '/etc/shadow', mode: '0600' } + - { path: '/etc/group', mode: '0644' } + - { path: '/etc/gshadow', mode: '0600' } + - { path: '/etc/ssh/sshd_config', mode: '0600' } + +- name: Create security banners (CIS 1.4.x) + ansible.builtin.copy: + dest: "{{ item }}" + content: | + ************************************************************************** + * * + * WARNING: Unauthorized access to this system is forbidden and will * + * be prosecuted by law. By accessing this system, you agree that your * + * actions may be monitored if unauthorized usage is suspected. * + * * + ************************************************************************** + mode: '0644' + loop: + - /etc/issue + - /etc/issue.net + - /etc/motd + +- name: Display hardening summary + ansible.builtin.debug: + msg: + - "=========================================" + - "System Hardening Complete" + - "=========================================" + - "CIS Level 1 controls applied" + - "AppArmor: {{ 'ENABLED' if apparmor_enabled | default(true) else 'DISABLED' }}" + - "Auditd: {{ 'ENABLED' if auditd_enabled else 'DISABLED' }}" + - "Fail2ban: {{ 'ENABLED' if fail2ban_enabled else 'DISABLED' }}" + - "Unattended upgrades: {{ 'ENABLED' if unattended_upgrades_enabled else 'DISABLED' }}" + - "=========================================" diff --git a/roles/system_hardening/tasks/ssh.yml b/roles/system_hardening/tasks/ssh.yml new file mode 100644 index 0000000..8ac1ec3 --- /dev/null +++ b/roles/system_hardening/tasks/ssh.yml @@ -0,0 +1,53 @@ +--- +# SSH Hardening Tasks + +- name: Backup original sshd_config + ansible.builtin.copy: + src: /etc/ssh/sshd_config + dest: /etc/ssh/sshd_config.backup + remote_src: yes + force: no + +- name: Configure SSH daemon + ansible.builtin.template: + src: sshd_config.j2 + dest: /etc/ssh/sshd_config + owner: root + group: root + mode: '0600' + validate: '/usr/sbin/sshd -t -f %s' + notify: restart sshd + +- name: Ensure SSH directory exists for root + ansible.builtin.file: + path: /root/.ssh + state: directory + owner: root + group: root + mode: '0700' + +- name: Generate strong SSH host keys + ansible.builtin.command: ssh-keygen -A + args: + creates: /etc/ssh/ssh_host_ed25519_key + +- name: Remove weak SSH host keys + ansible.builtin.file: + path: "{{ item }}" + state: absent + loop: + - /etc/ssh/ssh_host_dsa_key + - /etc/ssh/ssh_host_dsa_key.pub + - /etc/ssh/ssh_host_ecdsa_key + - /etc/ssh/ssh_host_ecdsa_key.pub + +- name: Set permissions on SSH host keys + ansible.builtin.file: + path: "{{ item }}" + owner: root + group: root + mode: '0600' + loop: + - /etc/ssh/ssh_host_rsa_key + - /etc/ssh/ssh_host_ed25519_key + when: ansible_facts['os_family'] == "Debian" diff --git a/roles/system_hardening/tasks/sysctl.yml b/roles/system_hardening/tasks/sysctl.yml new file mode 100644 index 0000000..5323a75 --- /dev/null +++ b/roles/system_hardening/tasks/sysctl.yml @@ -0,0 +1,12 @@ +--- +# Sysctl Hardening Tasks + +- name: Apply sysctl hardening parameters + ansible.posix.sysctl: + name: "{{ item.key }}" + value: "{{ item.value }}" + state: present + sysctl_set: yes + reload: yes + loop: "{{ sysctl_config | dict2items }}" + when: sysctl_config is defined diff --git a/roles/system_hardening/tasks/sysctl_cis.yml b/roles/system_hardening/tasks/sysctl_cis.yml new file mode 100644 index 0000000..3920daf --- /dev/null +++ b/roles/system_hardening/tasks/sysctl_cis.yml @@ -0,0 +1,157 @@ +--- +# CIS-Compliant Sysctl Parameters + +# CIS 3.1.1 - Disable IP forwarding (unless VPN server needs it) +- name: Disable IPv4 forwarding + ansible.posix.sysctl: + name: net.ipv4.ip_forward + value: '1' # Enabled for VPN server + state: present + sysctl_set: yes + reload: yes + +# CIS 3.1.2 - Disable packet redirect sending +- name: Disable send packet redirects + ansible.posix.sysctl: + name: "{{ item }}" + value: '0' + state: present + sysctl_set: yes + reload: yes + loop: + - net.ipv4.conf.all.send_redirects + - net.ipv4.conf.default.send_redirects + +# CIS 3.2.1 - Do not accept source routed packets +- name: Disable source routed packets + ansible.posix.sysctl: + name: "{{ item }}" + value: '0' + state: present + sysctl_set: yes + reload: yes + loop: + - net.ipv4.conf.all.accept_source_route + - net.ipv4.conf.default.accept_source_route + - net.ipv6.conf.all.accept_source_route + - net.ipv6.conf.default.accept_source_route + +# CIS 3.2.2 - Do not accept ICMP redirects +- name: Disable ICMP redirects + ansible.posix.sysctl: + name: "{{ item }}" + value: '0' + state: present + sysctl_set: yes + reload: yes + loop: + - net.ipv4.conf.all.accept_redirects + - net.ipv4.conf.default.accept_redirects + - net.ipv6.conf.all.accept_redirects + - net.ipv6.conf.default.accept_redirects + +# CIS 3.2.3 - Do not accept secure ICMP redirects +- name: Disable secure ICMP redirects + ansible.posix.sysctl: + name: "{{ item }}" + value: '0' + state: present + sysctl_set: yes + reload: yes + loop: + - net.ipv4.conf.all.secure_redirects + - net.ipv4.conf.default.secure_redirects + +# CIS 3.2.4 - Log suspicious packets +- name: Enable suspicious packet logging + ansible.posix.sysctl: + name: "{{ item }}" + value: '1' + state: present + sysctl_set: yes + reload: yes + loop: + - net.ipv4.conf.all.log_martians + - net.ipv4.conf.default.log_martians + +# CIS 3.2.5 - Ignore broadcast ICMP requests +- name: Ignore ICMP broadcast requests + ansible.posix.sysctl: + name: net.ipv4.icmp_echo_ignore_broadcasts + value: '1' + state: present + sysctl_set: yes + reload: yes + +# CIS 3.2.6 - Ignore bogus ICMP responses +- name: Ignore bogus ICMP error responses + ansible.posix.sysctl: + name: net.ipv4.icmp_ignore_bogus_error_responses + value: '1' + state: present + sysctl_set: yes + reload: yes + +# CIS 3.2.7 - Enable reverse path filtering +- name: Enable reverse path filtering + ansible.posix.sysctl: + name: "{{ item }}" + value: '1' + state: present + sysctl_set: yes + reload: yes + loop: + - net.ipv4.conf.all.rp_filter + - net.ipv4.conf.default.rp_filter + +# CIS 3.2.8 - Enable TCP SYN cookies +- name: Enable TCP SYN cookies + ansible.posix.sysctl: + name: net.ipv4.tcp_syncookies + value: '1' + state: present + sysctl_set: yes + reload: yes + +# CIS 3.2.9 - Do not accept IPv6 router advertisements +- name: Disable IPv6 router advertisements + ansible.posix.sysctl: + name: "{{ item }}" + value: '0' + state: present + sysctl_set: yes + reload: yes + loop: + - net.ipv6.conf.all.accept_ra + - net.ipv6.conf.default.accept_ra + +# Additional hardening +- name: Disable IPv6 (if not used) + ansible.posix.sysctl: + name: "{{ item }}" + value: '1' + state: present + sysctl_set: yes + reload: yes + loop: + - net.ipv6.conf.all.disable_ipv6 + - net.ipv6.conf.default.disable_ipv6 + when: disable_ipv6 | default(false) + +# CIS 1.5.2 - Enable ASLR +- name: Enable address space layout randomization + ansible.posix.sysctl: + name: kernel.randomize_va_space + value: '2' + state: present + sysctl_set: yes + reload: yes + +# CIS 1.5.1 - Restrict core dumps +- name: Restrict core dumps + ansible.posix.sysctl: + name: fs.suid_dumpable + value: '0' + state: present + sysctl_set: yes + reload: yes diff --git a/roles/system_hardening/tasks/unattended_upgrades.yml b/roles/system_hardening/tasks/unattended_upgrades.yml new file mode 100644 index 0000000..723bcb2 --- /dev/null +++ b/roles/system_hardening/tasks/unattended_upgrades.yml @@ -0,0 +1,25 @@ +--- +# Unattended Upgrades Configuration Tasks + +- name: Ensure unattended-upgrades is installed + ansible.builtin.apt: + name: + - unattended-upgrades + - apt-listchanges + state: present + +- name: Configure unattended-upgrades + ansible.builtin.template: + src: 50unattended-upgrades.j2 + dest: /etc/apt/apt.conf.d/50unattended-upgrades + owner: root + group: root + mode: '0644' + +- name: Enable automatic updates + ansible.builtin.template: + src: 20auto-upgrades.j2 + dest: /etc/apt/apt.conf.d/20auto-upgrades + owner: root + group: root + mode: '0644' diff --git a/roles/system_hardening/templates/20auto-upgrades.j2 b/roles/system_hardening/templates/20auto-upgrades.j2 new file mode 100644 index 0000000..3144819 --- /dev/null +++ b/roles/system_hardening/templates/20auto-upgrades.j2 @@ -0,0 +1,4 @@ +APT::Periodic::Update-Package-Lists "1"; +APT::Periodic::Unattended-Upgrade "1"; +APT::Periodic::Download-Upgradeable-Packages "1"; +APT::Periodic::AutocleanInterval "7"; diff --git a/roles/system_hardening/templates/50unattended-upgrades.j2 b/roles/system_hardening/templates/50unattended-upgrades.j2 new file mode 100644 index 0000000..c4c2b47 --- /dev/null +++ b/roles/system_hardening/templates/50unattended-upgrades.j2 @@ -0,0 +1,22 @@ +Unattended-Upgrade::Allowed-Origins { + "${distro_id}:${distro_codename}"; + "${distro_id}:${distro_codename}-security"; + "${distro_id}ESMApps:${distro_codename}-apps-security"; + "${distro_id}ESM:${distro_codename}-infra-security"; +}; + +Unattended-Upgrade::Package-Blacklist { +}; + +Unattended-Upgrade::DevRelease "false"; +Unattended-Upgrade::AutoFixInterruptedDpkg "true"; +Unattended-Upgrade::MinimalSteps "true"; +Unattended-Upgrade::InstallOnShutdown "false"; +Unattended-Upgrade::Mail "{{ fail2ban_destemail }}"; +Unattended-Upgrade::MailReport "on-change"; +Unattended-Upgrade::Remove-Unused-Kernel-Packages "true"; +Unattended-Upgrade::Remove-New-Unused-Dependencies "true"; +Unattended-Upgrade::Remove-Unused-Dependencies "true"; +Unattended-Upgrade::Automatic-Reboot "{{ unattended_upgrades_auto_reboot | lower }}"; +Unattended-Upgrade::Automatic-Reboot-Time "{{ unattended_upgrades_auto_reboot_time }}"; +Unattended-Upgrade::SyslogEnable "true"; diff --git a/roles/system_hardening/templates/audit.rules.j2 b/roles/system_hardening/templates/audit.rules.j2 new file mode 100644 index 0000000..e4cc6b3 --- /dev/null +++ b/roles/system_hardening/templates/audit.rules.j2 @@ -0,0 +1,86 @@ +# CIS-Compliant Audit Rules for Ubuntu 24.04 +# Generated by Ansible - Do not edit manually + +# Remove any existing rules +-D + +# Buffer Size +-b 8192 + +# Failure Mode (0=silent 1=printk 2=panic) +-f 1 + +# CIS 4.1.6 - Audit time changes +-a always,exit -F arch=b64 -S adjtimex -S settimeofday -k time-change +-a always,exit -F arch=b32 -S adjtimex -S settimeofday -S stime -k time-change +-a always,exit -F arch=b64 -S clock_settime -k time-change +-a always,exit -F arch=b32 -S clock_settime -k time-change +-w /etc/localtime -p wa -k time-change + +# CIS 4.1.7 - Audit user/group changes +-w /etc/group -p wa -k identity +-w /etc/passwd -p wa -k identity +-w /etc/gshadow -p wa -k identity +-w /etc/shadow -p wa -k identity +-w /etc/security/opasswd -p wa -k identity + +# CIS 4.1.8 - Audit network environment +-a always,exit -F arch=b64 -S sethostname -S setdomainname -k system-locale +-a always,exit -F arch=b32 -S sethostname -S setdomainname -k system-locale +-w /etc/issue -p wa -k system-locale +-w /etc/issue.net -p wa -k system-locale +-w /etc/hosts -p wa -k system-locale +-w /etc/network -p wa -k system-locale + +# CIS 4.1.9 - Audit AppArmor changes +-w /etc/apparmor/ -p wa -k MAC-policy +-w /etc/apparmor.d/ -p wa -k MAC-policy + +# CIS 4.1.10 - Audit login/logout events +-w /var/log/faillog -p wa -k logins +-w /var/log/lastlog -p wa -k logins +-w /var/log/tallylog -p wa -k logins + +# CIS 4.1.11 - Audit session initiation +-w /var/run/utmp -p wa -k session +-w /var/log/wtmp -p wa -k logins +-w /var/log/btmp -p wa -k logins + +# CIS 4.1.12 - Audit permission changes +-a always,exit -F arch=b64 -S chmod -S fchmod -S fchmodat -F auid>=1000 -F auid!=4294967295 -k perm_mod +-a always,exit -F arch=b32 -S chmod -S fchmod -S fchmodat -F auid>=1000 -F auid!=4294967295 -k perm_mod +-a always,exit -F arch=b64 -S chown -S fchown -S fchownat -S lchown -F auid>=1000 -F auid!=4294967295 -k perm_mod +-a always,exit -F arch=b32 -S chown -S fchown -S fchownat -S lchown -F auid>=1000 -F auid!=4294967295 -k perm_mod +-a always,exit -F arch=b64 -S setxattr -S lsetxattr -S fsetxattr -S removexattr -S lremovexattr -S fremovexattr -F auid>=1000 -F auid!=4294967295 -k perm_mod +-a always,exit -F arch=b32 -S setxattr -S lsetxattr -S fsetxattr -S removexattr -S lremovexattr -S fremovexattr -F auid>=1000 -F auid!=4294967295 -k perm_mod + +# CIS 4.1.13 - Audit unsuccessful file access attempts +-a always,exit -F arch=b64 -S creat -S open -S openat -S truncate -S ftruncate -F exit=-EACCES -F auid>=1000 -F auid!=4294967295 -k access +-a always,exit -F arch=b32 -S creat -S open -S openat -S truncate -S ftruncate -F exit=-EACCES -F auid>=1000 -F auid!=4294967295 -k access +-a always,exit -F arch=b64 -S creat -S open -S openat -S truncate -S ftruncate -F exit=-EPERM -F auid>=1000 -F auid!=4294967295 -k access +-a always,exit -F arch=b32 -S creat -S open -S openat -S truncate -S ftruncate -F exit=-EPERM -F auid>=1000 -F auid!=4294967295 -k access + +# CIS 4.1.14 - Audit file deletion events +-a always,exit -F arch=b64 -S unlink -S unlinkat -S rename -S renameat -F auid>=1000 -F auid!=4294967295 -k delete +-a always,exit -F arch=b32 -S unlink -S unlinkat -S rename -S renameat -F auid>=1000 -F auid!=4294967295 -k delete + +# CIS 4.1.15 - Audit sudoers changes +-w /etc/sudoers -p wa -k scope +-w /etc/sudoers.d/ -p wa -k scope + +# CIS 4.1.16 - Audit sudo usage +-w /var/log/sudo.log -p wa -k actions + +# CIS 4.1.17 - Audit kernel module loading/unloading +-w /sbin/insmod -p x -k modules +-w /sbin/rmmod -p x -k modules +-w /sbin/modprobe -p x -k modules +-a always,exit -F arch=b64 -S init_module -S delete_module -k modules + +# Additional security-relevant events +-w /etc/ssh/sshd_config -p wa -k sshd +-w /etc/pam.d/ -p wa -k pam +-w /etc/security/ -p wa -k security + +# Make configuration immutable +-e 2 diff --git a/roles/system_hardening/templates/fail2ban.local.j2 b/roles/system_hardening/templates/fail2ban.local.j2 new file mode 100644 index 0000000..9f05713 --- /dev/null +++ b/roles/system_hardening/templates/fail2ban.local.j2 @@ -0,0 +1,14 @@ +[DEFAULT] +bantime = {{ fail2ban_bantime }} +findtime = {{ fail2ban_findtime }} +maxretry = {{ fail2ban_maxretry }} +destemail = {{ fail2ban_destemail }} +sendername = Fail2Ban +action = %(action_mwl)s + +[sshd] +enabled = true +port = {{ ssh_port }} +filter = sshd +logpath = /var/log/auth.log +maxretry = {{ fail2ban_maxretry }} diff --git a/roles/system_hardening/templates/sshd_config.j2 b/roles/system_hardening/templates/sshd_config.j2 new file mode 100644 index 0000000..3845006 --- /dev/null +++ b/roles/system_hardening/templates/sshd_config.j2 @@ -0,0 +1,44 @@ +# Hardened SSH Configuration +# Generated by Ansible - Do not edit manually + +# Network +Port {{ ssh_port }} +AddressFamily inet +ListenAddress {{ ssh_listen_address }} + +# Authentication +PermitRootLogin {{ ssh_permit_root_login }} +PubkeyAuthentication {{ ssh_pubkey_authentication }} +PasswordAuthentication {{ ssh_password_authentication }} +ChallengeResponseAuthentication {{ ssh_challenge_response_auth }} +UsePAM yes +MaxAuthTries {{ ssh_max_auth_tries }} + +{% if ssh_allowed_users | length > 0 %} +AllowUsers {{ ssh_allowed_users | join(' ') }} +{% endif %} + +# Cryptography +Ciphers {{ ssh_ciphers | join(',') }} +MACs {{ ssh_macs | join(',') }} +KexAlgorithms {{ ssh_kex_algorithms | join(',') }} + +# Features +X11Forwarding {{ ssh_x11_forwarding }} +PrintMotd no +PrintLastLog yes +TCPKeepAlive yes +PermitUserEnvironment no +Compression no +ClientAliveInterval {{ ssh_client_alive_interval }} +ClientAliveCountMax {{ ssh_client_alive_count_max }} +UseDNS no +PermitTunnel no +Banner /etc/issue.net + +# Subsystems +Subsystem sftp /usr/lib/openssh/sftp-server -f AUTHPRIV -l INFO + +# Logging +SyslogFacility AUTH +LogLevel VERBOSE diff --git a/roles/wireguard_server/defaults/main.yml b/roles/wireguard_server/defaults/main.yml new file mode 100644 index 0000000..9f5e79e --- /dev/null +++ b/roles/wireguard_server/defaults/main.yml @@ -0,0 +1,41 @@ +--- +# WireGuard Server Role - Default Variables + +# WireGuard interface configuration +wg_interface: wg0 +wg_port: 51820 +wg_network: "10.100.0.0/24" +wg_server_ip: "10.100.0.1" + +# DNS servers for VPN clients +wg_dns_servers: + - "1.1.1.1" + - "1.0.0.1" + +# WireGuard users/peers +# Format: +# wg_peers: +# - name: user1 +# ip: 10.100.0.10 +# - name: user2 +# ip: 10.100.0.11 +wg_peers: [] + +# Automatic peer IP allocation +wg_auto_allocate_ips: true +wg_ip_start: 10 # Start allocating from 10.100.0.10 + +# Key management +wg_keys_dir: "/etc/wireguard/keys" +wg_config_dir: "/etc/wireguard" +wg_client_configs_dir: "/root/wireguard-client-configs" + +# Post-up and post-down rules for NAT +wg_postup: "iptables -A FORWARD -i %i -j ACCEPT; iptables -A FORWARD -o %i -j ACCEPT; iptables -t nat -A POSTROUTING -o {{ ansible_default_ipv4.interface }} -j MASQUERADE" +wg_postdown: "iptables -D FORWARD -i %i -j ACCEPT; iptables -D FORWARD -o %i -j ACCEPT; iptables -t nat -D POSTROUTING -o {{ ansible_default_ipv4.interface }} -j MASQUERADE" + +# Keepalive +wg_persistent_keepalive: 25 + +# MTU +wg_mtu: 1420 diff --git a/roles/wireguard_server/handlers/main.yml b/roles/wireguard_server/handlers/main.yml new file mode 100644 index 0000000..5421011 --- /dev/null +++ b/roles/wireguard_server/handlers/main.yml @@ -0,0 +1,7 @@ +--- +# WireGuard Server Role - Handlers + +- name: restart wireguard + ansible.builtin.systemd: + name: "wg-quick@{{ wg_interface }}" + state: restarted diff --git a/roles/wireguard_server/meta/main.yml b/roles/wireguard_server/meta/main.yml new file mode 100644 index 0000000..f3426eb --- /dev/null +++ b/roles/wireguard_server/meta/main.yml @@ -0,0 +1,25 @@ +--- +galaxy_info: + role_name: wireguard_server + author: Security Infrastructure Team + description: WireGuard VPN server installation and configuration + company: Your Organization + license: MIT + min_ansible_version: "2.15" + platforms: + - name: Ubuntu + versions: + - noble # 24.04 + - jammy # 22.04 + galaxy_tags: + - wireguard + - vpn + - security + - networking + +dependencies: + - role: system_hardening + +collections: + - ansible.posix + - ansible.utils diff --git a/roles/wireguard_server/tasks/configure.yml b/roles/wireguard_server/tasks/configure.yml new file mode 100644 index 0000000..16d1eb4 --- /dev/null +++ b/roles/wireguard_server/tasks/configure.yml @@ -0,0 +1,34 @@ +--- +# WireGuard Configuration Tasks + +- name: Configure WireGuard server + ansible.builtin.template: + src: wg0.conf.j2 + dest: "{{ wg_config_dir }}/{{ wg_interface }}.conf" + owner: root + group: root + mode: '0600' + notify: restart wireguard + +- name: Enable IP forwarding (if not already enabled by sysctl) + ansible.posix.sysctl: + name: net.ipv4.ip_forward + value: '1' + state: present + sysctl_set: yes + reload: yes + +- name: Enable WireGuard service + ansible.builtin.systemd: + name: "wg-quick@{{ wg_interface }}" + enabled: yes + state: started + +- name: Get WireGuard service status + ansible.builtin.systemd: + name: "wg-quick@{{ wg_interface }}" + register: wg_service_status + +- name: Display WireGuard status + ansible.builtin.debug: + msg: "WireGuard service is {{ wg_service_status.status.ActiveState }}" diff --git a/roles/wireguard_server/tasks/install.yml b/roles/wireguard_server/tasks/install.yml new file mode 100644 index 0000000..d0f1767 --- /dev/null +++ b/roles/wireguard_server/tasks/install.yml @@ -0,0 +1,59 @@ +--- +# WireGuard Installation Tasks + +- name: Install WireGuard + ansible.builtin.apt: + name: + - wireguard + - wireguard-tools + - qrencode + state: present + update_cache: yes + +- name: Create WireGuard directories + ansible.builtin.file: + path: "{{ item }}" + state: directory + owner: root + group: root + mode: '0700' + loop: + - "{{ wg_config_dir }}" + - "{{ wg_keys_dir }}" + - "{{ wg_client_configs_dir }}" + +- name: Check if server private key exists + ansible.builtin.stat: + path: "{{ wg_keys_dir }}/server_private.key" + register: server_private_key + +- name: Generate server private key + ansible.builtin.shell: wg genkey > {{ wg_keys_dir }}/server_private.key + when: not server_private_key.stat.exists + +- name: Set server private key permissions + ansible.builtin.file: + path: "{{ wg_keys_dir }}/server_private.key" + owner: root + group: root + mode: '0600' + +- name: Generate server public key + ansible.builtin.shell: cat {{ wg_keys_dir }}/server_private.key | wg pubkey > {{ wg_keys_dir }}/server_public.key + args: + creates: "{{ wg_keys_dir }}/server_public.key" + +- name: Read server private key + ansible.builtin.slurp: + src: "{{ wg_keys_dir }}/server_private.key" + register: server_private_key_content + +- name: Read server public key + ansible.builtin.slurp: + src: "{{ wg_keys_dir }}/server_public.key" + register: server_public_key_content + +- name: Set server keys as facts + ansible.builtin.set_fact: + wg_server_private_key: "{{ server_private_key_content['content'] | b64decode | trim }}" + wg_server_public_key: "{{ server_public_key_content['content'] | b64decode | trim }}" diff --git a/roles/wireguard_server/tasks/main.yml b/roles/wireguard_server/tasks/main.yml new file mode 100644 index 0000000..589dc7e --- /dev/null +++ b/roles/wireguard_server/tasks/main.yml @@ -0,0 +1,12 @@ +--- +# WireGuard Server Role - Main Tasks + +- name: Include installation tasks + ansible.builtin.include_tasks: install.yml + +- name: Include configuration tasks + ansible.builtin.include_tasks: configure.yml + +- name: Include user management tasks + ansible.builtin.include_tasks: users.yml + when: wg_peers | length > 0 diff --git a/roles/wireguard_server/tasks/users.yml b/roles/wireguard_server/tasks/users.yml new file mode 100644 index 0000000..1fb9b8e --- /dev/null +++ b/roles/wireguard_server/tasks/users.yml @@ -0,0 +1,73 @@ +--- +# WireGuard User Management Tasks + +- name: Auto-allocate IPs if enabled + ansible.builtin.set_fact: + wg_peers_with_ips: "{{ wg_peers_with_ips | default([]) + [item | combine({'ip': wg_network | ansible.utils.ipaddr(wg_ip_start + idx) | ansible.utils.ipaddr('address')})] }}" + loop: "{{ wg_peers }}" + loop_control: + index_var: idx + when: + - wg_auto_allocate_ips | bool + - item.ip is not defined + +- name: Use provided IPs if auto-allocation disabled + ansible.builtin.set_fact: + wg_peers_with_ips: "{{ wg_peers }}" + when: not (wg_auto_allocate_ips | bool) + +- name: Generate client private keys + ansible.builtin.shell: wg genkey > {{ wg_keys_dir }}/{{ item.name }}_private.key + args: + creates: "{{ wg_keys_dir }}/{{ item.name }}_private.key" + loop: "{{ wg_peers_with_ips }}" + +- name: Set client private key permissions + ansible.builtin.file: + path: "{{ wg_keys_dir }}/{{ item.name }}_private.key" + owner: root + group: root + mode: '0600' + loop: "{{ wg_peers_with_ips }}" + +- name: Generate client public keys + ansible.builtin.shell: cat {{ wg_keys_dir }}/{{ item.name }}_private.key | wg pubkey > {{ wg_keys_dir }}/{{ item.name }}_public.key + args: + creates: "{{ wg_keys_dir }}/{{ item.name }}_public.key" + loop: "{{ wg_peers_with_ips }}" + +- name: Read client keys + ansible.builtin.shell: | + echo "private=$(cat {{ wg_keys_dir }}/{{ item.name }}_private.key)" + echo "public=$(cat {{ wg_keys_dir }}/{{ item.name }}_public.key)" + register: client_keys + loop: "{{ wg_peers_with_ips }}" + changed_when: false + +- name: Generate client configurations + ansible.builtin.template: + src: client.conf.j2 + dest: "{{ wg_client_configs_dir }}/{{ item.item.name }}.conf" + owner: root + group: root + mode: '0600' + loop: "{{ client_keys.results }}" + vars: + client_name: "{{ item.item.name }}" + client_ip: "{{ item.item.ip }}" + client_private_key: "{{ item.stdout_lines[0].split('=')[1] }}" + client_public_key: "{{ item.stdout_lines[1].split('=')[1] }}" + +- name: Generate QR codes for mobile clients + ansible.builtin.shell: qrencode -t ansiutf8 < {{ wg_client_configs_dir }}/{{ item.name }}.conf > {{ wg_client_configs_dir }}/{{ item.name }}_qr.txt + args: + creates: "{{ wg_client_configs_dir }}/{{ item.name }}_qr.txt" + loop: "{{ wg_peers_with_ips }}" + +- name: Create summary file + ansible.builtin.template: + src: summary.md.j2 + dest: "{{ wg_client_configs_dir }}/README.md" + owner: root + group: root + mode: '0644' diff --git a/roles/wireguard_server/templates/client.conf.j2 b/roles/wireguard_server/templates/client.conf.j2 new file mode 100644 index 0000000..44e4e7c --- /dev/null +++ b/roles/wireguard_server/templates/client.conf.j2 @@ -0,0 +1,11 @@ +[Interface] +PrivateKey = {{ client_private_key }} +Address = {{ client_ip }}/{{ wg_network | ansible.utils.ipaddr('prefix') }} +DNS = {{ wg_dns_servers | join(', ') }} +MTU = {{ wg_mtu }} + +[Peer] +PublicKey = {{ wg_server_public_key }} +Endpoint = {{ ansible_default_ipv4.address }}:{{ wg_port }} +AllowedIPs = 0.0.0.0/0, ::/0 +PersistentKeepalive = {{ wg_persistent_keepalive }} diff --git a/roles/wireguard_server/templates/summary.md.j2 b/roles/wireguard_server/templates/summary.md.j2 new file mode 100644 index 0000000..a393145 --- /dev/null +++ b/roles/wireguard_server/templates/summary.md.j2 @@ -0,0 +1,49 @@ +# WireGuard VPN Client Configurations + +**Server**: {{ inventory_hostname }} +**Server IP**: {{ ansible_default_ipv4.address }} +**VPN Network**: {{ wg_network }} +**Server Public Key**: {{ wg_server_public_key }} + +## Client Configurations + +{% for peer in wg_peers_with_ips | default([]) %} +### {{ peer.name }} +- **IP Address**: {{ peer.ip }} +- **Config File**: `{{ peer.name }}.conf` +- **QR Code**: `{{ peer.name }}_qr.txt` + +{% endfor %} + +## Installation Instructions + +### Desktop (Linux/macOS/Windows) + +1. Install WireGuard: https://www.wireguard.com/install/ +2. Copy the `.conf` file to your device +3. Import configuration: + - Linux: `sudo wg-quick up /path/to/config.conf` + - macOS/Windows: Import via WireGuard GUI +4. Connect + +### Mobile (iOS/Android) + +1. Install WireGuard app from App Store/Play Store +2. View QR code: `cat _qr.txt` +3. Scan QR code in WireGuard app +4. Connect + +## Testing + +After connecting, verify your IP: +```bash +curl https://ifconfig.me +``` + +Should show: {{ ansible_default_ipv4.address }} + +## Troubleshooting + +- Ensure port {{ wg_port }}/udp is open in firewall +- Check server status: `sudo wg show` +- Check logs: `sudo journalctl -u wg-quick@{{ wg_interface }}` diff --git a/roles/wireguard_server/templates/wg0.conf.j2 b/roles/wireguard_server/templates/wg0.conf.j2 new file mode 100644 index 0000000..567ccd7 --- /dev/null +++ b/roles/wireguard_server/templates/wg0.conf.j2 @@ -0,0 +1,17 @@ +[Interface] +Address = {{ wg_server_ip }}/{{ wg_network | ansible.utils.ipaddr('prefix') }} +ListenPort = {{ wg_port }} +PrivateKey = {{ wg_server_private_key }} +MTU = {{ wg_mtu }} + +PostUp = {{ wg_postup }} +PostDown = {{ wg_postdown }} + +{% for peer in wg_peers_with_ips | default([]) %} +# {{ peer.name }} +[Peer] +PublicKey = {{ lookup('file', wg_keys_dir + '/' + peer.name + '_public.key') }} +AllowedIPs = {{ peer.ip }}/32 +PersistentKeepalive = {{ wg_persistent_keepalive }} + +{% endfor %}