Merge pull request 'CIS + inital' (#1) from CIS into main
Reviewed-on: #1
This commit is contained in:
commit
8434e5fe8c
119
CHANGELOG.md
Normal file
119
CHANGELOG.md
Normal file
|
|
@ -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
|
||||||
445
README.md
445
README.md
|
|
@ -1,3 +1,444 @@
|
||||||
# resist-vpn-infra
|
---
|
||||||
|
# Secure VPN Server - Ansible Collection
|
||||||
|
|
||||||
Fuck Fascism
|
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
|
||||||
|
|
|
||||||
493
README_v2.md
Normal file
493
README_v2.md
Normal file
|
|
@ -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.
|
||||||
491
docs/TWO_TIER_DEPLOYMENT.md
Normal file
491
docs/TWO_TIER_DEPLOYMENT.md
Normal file
|
|
@ -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)
|
||||||
467
docs/USAGE.md
Normal file
467
docs/USAGE.md
Normal file
|
|
@ -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
|
||||||
25
galaxy.yml
Normal file
25
galaxy.yml
Normal file
|
|
@ -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
|
||||||
107
inventory/group_vars/vpn_servers.yml
Normal file
107
inventory/group_vars/vpn_servers.yml
Normal file
|
|
@ -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
|
||||||
14
inventory/host_vars/vpn1.yml
Normal file
14
inventory/host_vars/vpn1.yml
Normal file
|
|
@ -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
|
||||||
13
inventory/host_vars/vpn2.yml
Normal file
13
inventory/host_vars/vpn2.yml
Normal file
|
|
@ -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
|
||||||
13
inventory/host_vars/vpn3.yml
Normal file
13
inventory/host_vars/vpn3.yml
Normal file
|
|
@ -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
|
||||||
39
inventory/hosts.yml
Normal file
39
inventory/hosts.yml
Normal file
|
|
@ -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
|
||||||
50
playbooks/add_user.yml
Normal file
50
playbooks/add_user.yml
Normal file
|
|
@ -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
|
||||||
11
playbooks/firewall.yml
Normal file
11
playbooks/firewall.yml
Normal file
|
|
@ -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']
|
||||||
11
playbooks/hardening.yml
Normal file
11
playbooks/hardening.yml
Normal file
|
|
@ -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']
|
||||||
50
playbooks/remove_user.yml
Normal file
50
playbooks/remove_user.yml
Normal file
|
|
@ -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' }}"
|
||||||
|
- "========================================="
|
||||||
137
playbooks/site.yml
Normal file
137
playbooks/site.yml
Normal file
|
|
@ -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'
|
||||||
31
playbooks/users.yml
Normal file
31
playbooks/users.yml
Normal file
|
|
@ -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
|
||||||
39
playbooks/validate.yml
Normal file
39
playbooks/validate.yml
Normal file
|
|
@ -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 }}"
|
||||||
11
playbooks/wireguard.yml
Normal file
11
playbooks/wireguard.yml
Normal file
|
|
@ -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']
|
||||||
3
requirements.txt
Normal file
3
requirements.txt
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
ansible>=2.15.0
|
||||||
|
ansible-core>=2.15.0
|
||||||
|
jinja2>=3.1.0
|
||||||
8
requirements.yml
Normal file
8
requirements.yml
Normal file
|
|
@ -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"
|
||||||
59
roles/secure_firewall/defaults/main.yml
Normal file
59
roles/secure_firewall/defaults/main.yml
Normal file
|
|
@ -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
|
||||||
6
roles/secure_firewall/handlers/main.yml
Normal file
6
roles/secure_firewall/handlers/main.yml
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
---
|
||||||
|
# Secure Firewall Role - Handlers
|
||||||
|
|
||||||
|
- name: reload ufw
|
||||||
|
community.general.ufw:
|
||||||
|
state: reloaded
|
||||||
24
roles/secure_firewall/meta/main.yml
Normal file
24
roles/secure_firewall/meta/main.yml
Normal file
|
|
@ -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
|
||||||
10
roles/secure_firewall/tasks/main.yml
Normal file
10
roles/secure_firewall/tasks/main.yml
Normal file
|
|
@ -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
|
||||||
46
roles/secure_firewall/tasks/ufw.yml
Normal file
46
roles/secure_firewall/tasks/ufw.yml
Normal file
|
|
@ -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
|
||||||
83
roles/secure_firewall/tasks/vpn_only.yml
Normal file
83
roles/secure_firewall/tasks/vpn_only.yml
Normal file
|
|
@ -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"
|
||||||
55
roles/ssh_users/defaults/main.yml
Normal file
55
roles/ssh_users/defaults/main.yml
Normal file
|
|
@ -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
|
||||||
7
roles/ssh_users/handlers/main.yml
Normal file
7
roles/ssh_users/handlers/main.yml
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
---
|
||||||
|
# SSH Users Role - Handlers
|
||||||
|
|
||||||
|
- name: restart sshd
|
||||||
|
ansible.builtin.service:
|
||||||
|
name: sshd
|
||||||
|
state: restarted
|
||||||
24
roles/ssh_users/meta/main.yml
Normal file
24
roles/ssh_users/meta/main.yml
Normal file
|
|
@ -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: []
|
||||||
63
roles/ssh_users/tasks/create_users.yml
Normal file
63
roles/ssh_users/tasks/create_users.yml
Normal file
|
|
@ -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'
|
||||||
104
roles/ssh_users/tasks/generate_keys.yml
Normal file
104
roles/ssh_users/tasks/generate_keys.yml
Normal file
|
|
@ -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
|
||||||
31
roles/ssh_users/tasks/main.yml
Normal file
31
roles/ssh_users/tasks/main.yml
Normal file
|
|
@ -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 }}"
|
||||||
|
- "========================================="
|
||||||
91
roles/ssh_users/tasks/password_policy.yml
Normal file
91
roles/ssh_users/tasks/password_policy.yml
Normal file
|
|
@ -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
|
||||||
38
roles/ssh_users/tasks/root_restrictions.yml
Normal file
38
roles/ssh_users/tasks/root_restrictions.yml
Normal file
|
|
@ -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(', ') }}"
|
||||||
76
roles/ssh_users/tasks/sudo.yml
Normal file
76
roles/ssh_users/tasks/sudo.yml
Normal file
|
|
@ -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'
|
||||||
128
roles/system_hardening/defaults/main.yml
Normal file
128
roles/system_hardening/defaults/main.yml
Normal file
|
|
@ -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
|
||||||
19
roles/system_hardening/defaults/main_cis.yml
Normal file
19
roles/system_hardening/defaults/main_cis.yml
Normal file
|
|
@ -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
|
||||||
16
roles/system_hardening/handlers/main.yml
Normal file
16
roles/system_hardening/handlers/main.yml
Normal file
|
|
@ -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
|
||||||
25
roles/system_hardening/meta/main.yml
Normal file
25
roles/system_hardening/meta/main.yml
Normal file
|
|
@ -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
|
||||||
42
roles/system_hardening/tasks/apparmor.yml
Normal file
42
roles/system_hardening/tasks/apparmor.yml
Normal file
|
|
@ -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
|
||||||
57
roles/system_hardening/tasks/audit.yml
Normal file
57
roles/system_hardening/tasks/audit.yml
Normal file
|
|
@ -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
|
||||||
39
roles/system_hardening/tasks/core_dumps.yml
Normal file
39
roles/system_hardening/tasks/core_dumps.yml
Normal file
|
|
@ -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'
|
||||||
49
roles/system_hardening/tasks/disable_protocols.yml
Normal file
49
roles/system_hardening/tasks/disable_protocols.yml
Normal file
|
|
@ -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
|
||||||
22
roles/system_hardening/tasks/fail2ban.yml
Normal file
22
roles/system_hardening/tasks/fail2ban.yml
Normal file
|
|
@ -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
|
||||||
121
roles/system_hardening/tasks/main.yml
Normal file
121
roles/system_hardening/tasks/main.yml
Normal file
|
|
@ -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' }}"
|
||||||
|
- "========================================="
|
||||||
53
roles/system_hardening/tasks/ssh.yml
Normal file
53
roles/system_hardening/tasks/ssh.yml
Normal file
|
|
@ -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"
|
||||||
12
roles/system_hardening/tasks/sysctl.yml
Normal file
12
roles/system_hardening/tasks/sysctl.yml
Normal file
|
|
@ -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
|
||||||
157
roles/system_hardening/tasks/sysctl_cis.yml
Normal file
157
roles/system_hardening/tasks/sysctl_cis.yml
Normal file
|
|
@ -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
|
||||||
25
roles/system_hardening/tasks/unattended_upgrades.yml
Normal file
25
roles/system_hardening/tasks/unattended_upgrades.yml
Normal file
|
|
@ -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'
|
||||||
4
roles/system_hardening/templates/20auto-upgrades.j2
Normal file
4
roles/system_hardening/templates/20auto-upgrades.j2
Normal file
|
|
@ -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";
|
||||||
22
roles/system_hardening/templates/50unattended-upgrades.j2
Normal file
22
roles/system_hardening/templates/50unattended-upgrades.j2
Normal file
|
|
@ -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";
|
||||||
86
roles/system_hardening/templates/audit.rules.j2
Normal file
86
roles/system_hardening/templates/audit.rules.j2
Normal file
|
|
@ -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
|
||||||
14
roles/system_hardening/templates/fail2ban.local.j2
Normal file
14
roles/system_hardening/templates/fail2ban.local.j2
Normal file
|
|
@ -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 }}
|
||||||
44
roles/system_hardening/templates/sshd_config.j2
Normal file
44
roles/system_hardening/templates/sshd_config.j2
Normal file
|
|
@ -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
|
||||||
41
roles/wireguard_server/defaults/main.yml
Normal file
41
roles/wireguard_server/defaults/main.yml
Normal file
|
|
@ -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
|
||||||
7
roles/wireguard_server/handlers/main.yml
Normal file
7
roles/wireguard_server/handlers/main.yml
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
---
|
||||||
|
# WireGuard Server Role - Handlers
|
||||||
|
|
||||||
|
- name: restart wireguard
|
||||||
|
ansible.builtin.systemd:
|
||||||
|
name: "wg-quick@{{ wg_interface }}"
|
||||||
|
state: restarted
|
||||||
25
roles/wireguard_server/meta/main.yml
Normal file
25
roles/wireguard_server/meta/main.yml
Normal file
|
|
@ -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
|
||||||
34
roles/wireguard_server/tasks/configure.yml
Normal file
34
roles/wireguard_server/tasks/configure.yml
Normal file
|
|
@ -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 }}"
|
||||||
59
roles/wireguard_server/tasks/install.yml
Normal file
59
roles/wireguard_server/tasks/install.yml
Normal file
|
|
@ -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 }}"
|
||||||
12
roles/wireguard_server/tasks/main.yml
Normal file
12
roles/wireguard_server/tasks/main.yml
Normal file
|
|
@ -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
|
||||||
73
roles/wireguard_server/tasks/users.yml
Normal file
73
roles/wireguard_server/tasks/users.yml
Normal file
|
|
@ -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'
|
||||||
11
roles/wireguard_server/templates/client.conf.j2
Normal file
11
roles/wireguard_server/templates/client.conf.j2
Normal file
|
|
@ -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 }}
|
||||||
49
roles/wireguard_server/templates/summary.md.j2
Normal file
49
roles/wireguard_server/templates/summary.md.j2
Normal file
|
|
@ -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 <username>_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 }}`
|
||||||
17
roles/wireguard_server/templates/wg0.conf.j2
Normal file
17
roles/wireguard_server/templates/wg0.conf.j2
Normal file
|
|
@ -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 %}
|
||||||
Loading…
Reference in New Issue
Block a user