New branch
This commit is contained in:
commit
58d70409b5
31 changed files with 9093 additions and 0 deletions
22
.env.example
Normal file
22
.env.example
Normal file
|
@ -0,0 +1,22 @@
|
|||
# VPN Gateway Configuration Example
|
||||
# Copy to .env and adjust
|
||||
|
||||
# Network Configuration
|
||||
LAN_INTERFACE=eth0
|
||||
LAN_NETWORK=192.168.1.0/24
|
||||
|
||||
# Provider Configuration
|
||||
VPN_PROVIDER=mullvad
|
||||
MULLVAD_ACCOUNT=1234567890123456
|
||||
|
||||
# Custom Server (if using custom provider)
|
||||
WG_ENDPOINT=your-server.com:51820
|
||||
WG_SERVER_PUBKEY=your-server-public-key-here
|
||||
WG_CLIENT_IP=10.0.0.2/32
|
||||
|
||||
# Security
|
||||
ALERT_EMAIL=admin@example.com
|
||||
CHECK_INTERVAL=10
|
||||
|
||||
# WebUI
|
||||
WEBUI_PORT=5000
|
35
.github/workflows/test.yml
vendored
Normal file
35
.github/workflows/test.yml
vendored
Normal file
|
@ -0,0 +1,35 @@
|
|||
name: Test Installation
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
|
||||
jobs:
|
||||
test-install:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Test installer syntax
|
||||
run: |
|
||||
bash -n install.sh
|
||||
for script in scripts/*.sh; do
|
||||
bash -n "$script"
|
||||
done
|
||||
|
||||
- name: Test Python syntax
|
||||
run: |
|
||||
python3 -m py_compile backend/app.py
|
||||
|
||||
- name: Security scan
|
||||
run: |
|
||||
pip install bandit
|
||||
bandit -r backend/ || true
|
||||
|
||||
- name: Shellcheck
|
||||
run: |
|
||||
sudo apt-get install -y shellcheck
|
||||
shellcheck install.sh scripts/*.sh || true
|
21
LICENSE
Normal file
21
LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2024 VPN Gateway Project
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
42
Makefile
Normal file
42
Makefile
Normal file
|
@ -0,0 +1,42 @@
|
|||
.PHONY: install uninstall update test health backup restore start stop status logs
|
||||
|
||||
install:
|
||||
@echo "Installing VPN Gateway..."
|
||||
@sudo bash install.sh
|
||||
|
||||
uninstall:
|
||||
@echo "Uninstalling VPN Gateway..."
|
||||
@sudo bash scripts/uninstall.sh
|
||||
|
||||
update:
|
||||
@echo "Updating VPN Gateway..."
|
||||
@sudo bash scripts/update.sh
|
||||
|
||||
test:
|
||||
@echo "Testing installation..."
|
||||
@bash -n install.sh
|
||||
@for script in scripts/*.sh; do bash -n $$script; done
|
||||
@python3 -m py_compile backend/app.py
|
||||
@echo "All tests passed!"
|
||||
|
||||
health:
|
||||
@sudo bash scripts/health-check.sh
|
||||
|
||||
backup:
|
||||
@echo "Creating backup..."
|
||||
@sudo tar czf vpn-gateway-backup-$$(date +%Y%m%d-%H%M%S).tar.gz \
|
||||
/opt/vpn-gateway \
|
||||
/etc/wireguard \
|
||||
/etc/systemd/system/vpn-*.service
|
||||
|
||||
start:
|
||||
@sudo systemctl start vpn-webui vpn-killswitch vpn-security-monitor
|
||||
|
||||
stop:
|
||||
@sudo systemctl stop vpn-webui vpn-security-monitor
|
||||
|
||||
status:
|
||||
@sudo systemctl status vpn-webui vpn-killswitch vpn-security-monitor
|
||||
|
||||
logs:
|
||||
@sudo journalctl -u vpn-webui -u vpn-killswitch -u vpn-security-monitor -f
|
60
README.md
Normal file
60
README.md
Normal file
|
@ -0,0 +1,60 @@
|
|||
# 🔒 Mullvad VPN Gateway for LXC - Multi-Provider Edition
|
||||
|
||||
[](https://opensource.org/licenses/MIT)
|
||||
[](https://github.com/yourusername/vpn-gateway)
|
||||
[](https://github.com/yourusername/vpn-gateway)
|
||||
|
||||
Secure VPN Gateway with **permanent killswitch** for LXC containers. Supports Mullvad, custom WireGuard servers, and imported configurations.
|
||||
|
||||
## ⚡ Quick Start
|
||||
|
||||
```bash
|
||||
curl -sSL https://raw.githubusercontent.com/yourusername/vpn-gateway/main/install.sh | bash
|
||||
```
|
||||
|
||||
## 🎯 Features
|
||||
|
||||
- ✅ **Multi-Provider Support** - Mullvad, Custom Servers, Import Configs
|
||||
- ✅ **Permanent Killswitch** - No internet without VPN, ever
|
||||
- 🌍 **Dynamic Server Selection** - Switch between countries/cities via WebUI
|
||||
- 🛡️ **Zero-Leak Protection** - DNS leak protection, IPv6 blocking
|
||||
- 🎨 **Modern WebUI** - Beautiful, responsive control panel
|
||||
- 🔄 **Auto-Reconnect** - Automatic recovery on connection drops
|
||||
- 📊 **Live Monitoring** - Real-time status and statistics
|
||||
|
||||
## 📋 Requirements
|
||||
|
||||
- LXC Container (Proxmox/LXD)
|
||||
- Ubuntu 20.04+ or Debian 11+
|
||||
- Root access
|
||||
- Mullvad account OR own WireGuard server
|
||||
|
||||
## 🚀 Installation
|
||||
|
||||
```bash
|
||||
# Clone repository
|
||||
git clone https://github.com/yourusername/vpn-gateway.git
|
||||
cd vpn-gateway
|
||||
|
||||
# Run installer
|
||||
sudo ./install.sh
|
||||
```
|
||||
|
||||
## 📖 Documentation
|
||||
|
||||
- [Quick Start Guide](docs/QUICKSTART.md)
|
||||
- [Provider Configuration](docs/PROVIDERS.md)
|
||||
- [Security Documentation](docs/SECURITY.md)
|
||||
- [FAQ](docs/FAQ.md)
|
||||
|
||||
## 🤝 Contributing
|
||||
|
||||
Contributions are welcome! Please feel free to submit a Pull Request.
|
||||
|
||||
## 📄 License
|
||||
|
||||
MIT License - see [LICENSE](LICENSE) file
|
||||
|
||||
## ⚠️ Disclaimer
|
||||
|
||||
The permanent killswitch will block ALL internet traffic when VPN is not connected. This is by design and cannot be disabled.
|
648
backend/app.py
Normal file
648
backend/app.py
Normal file
|
@ -0,0 +1,648 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Multi-Provider VPN Gateway Backend
|
||||
Supports: Mullvad, Custom WireGuard, Imported Configs
|
||||
With permanent killswitch protection
|
||||
"""
|
||||
|
||||
from flask import Flask, request, jsonify, send_from_directory
|
||||
from flask_cors import CORS
|
||||
import subprocess
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import requests
|
||||
import time
|
||||
import yaml
|
||||
import base64
|
||||
from datetime import datetime
|
||||
import threading
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
app = Flask(__name__)
|
||||
CORS(app)
|
||||
|
||||
# Configuration
|
||||
CONFIG_FILE = '/opt/vpn-gateway/config.json'
|
||||
PROVIDERS_DIR = '/opt/vpn-gateway/providers'
|
||||
WIREGUARD_DIR = '/etc/wireguard'
|
||||
|
||||
# Setup logging
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s - %(levelname)s - %(message)s',
|
||||
handlers=[
|
||||
logging.FileHandler('/var/log/vpn-gateway.log'),
|
||||
logging.StreamHandler()
|
||||
]
|
||||
)
|
||||
|
||||
class VPNProvider:
|
||||
"""Base class for VPN providers"""
|
||||
|
||||
def __init__(self, name: str):
|
||||
self.name = name
|
||||
self.servers = {}
|
||||
|
||||
def get_servers(self) -> Dict:
|
||||
raise NotImplementedError
|
||||
|
||||
def generate_config(self, server: str) -> str:
|
||||
raise NotImplementedError
|
||||
|
||||
class MullvadProvider(VPNProvider):
|
||||
"""Mullvad VPN provider"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__("mullvad")
|
||||
self.api_url = "https://api.mullvad.net/www/relays/all/"
|
||||
self.public_key = "g+9JNZp3SvLPvBb+PzXHyOPHhqNiUdATrz1YdNEPvWo="
|
||||
|
||||
def get_servers(self) -> Dict:
|
||||
try:
|
||||
response = requests.get(self.api_url, timeout=10)
|
||||
servers = response.json()
|
||||
|
||||
organized = {}
|
||||
for server in servers:
|
||||
if server.get('type') == 'wireguard' and server.get('active'):
|
||||
country = server.get('country_name', 'Unknown')
|
||||
city = server.get('city_name', 'Unknown')
|
||||
|
||||
if country not in organized:
|
||||
organized[country] = {}
|
||||
if city not in organized[country]:
|
||||
organized[country][city] = []
|
||||
|
||||
organized[country][city].append({
|
||||
'hostname': server['hostname'],
|
||||
'ipv4': server['ipv4_addr_in'],
|
||||
'ipv6': server.get('ipv6_addr_in'),
|
||||
'type': 'WireGuard',
|
||||
'provider': 'Mullvad'
|
||||
})
|
||||
|
||||
self.servers = organized
|
||||
return organized
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"Failed to fetch Mullvad servers: {e}")
|
||||
return {}
|
||||
|
||||
def generate_config(self, server_hostname: str) -> str:
|
||||
server_info = self._find_server(server_hostname)
|
||||
if not server_info:
|
||||
raise ValueError(f"Server {server_hostname} not found")
|
||||
|
||||
private_key = self._get_or_generate_key()
|
||||
|
||||
return f"""# Mullvad WireGuard Configuration
|
||||
# Server: {server_hostname}
|
||||
# Provider: Mullvad
|
||||
|
||||
[Interface]
|
||||
PrivateKey = {private_key}
|
||||
Address = 10.64.0.2/32,fc00:bbbb:bbbb:bb01::2/128
|
||||
DNS = 100.64.0.1
|
||||
|
||||
# PERMANENT KILLSWITCH - CANNOT BE DISABLED
|
||||
PreUp = iptables -F OUTPUT
|
||||
PreUp = iptables -F FORWARD
|
||||
PostUp = iptables -I OUTPUT ! -o %i -m mark ! --mark $(wg show %i fwmark) -m addrtype ! --dst-type LOCAL -j REJECT
|
||||
PostUp = iptables -I FORWARD -i eth0 -o %i -j ACCEPT
|
||||
PostUp = iptables -t nat -A POSTROUTING -o %i -j MASQUERADE
|
||||
PreDown = iptables -D OUTPUT ! -o %i -m mark ! --mark $(wg show %i fwmark) -m addrtype ! --dst-type LOCAL -j REJECT
|
||||
PostDown = iptables -t nat -D POSTROUTING -o %i -j MASQUERADE
|
||||
|
||||
[Peer]
|
||||
PublicKey = {self.public_key}
|
||||
AllowedIPs = 0.0.0.0/0,::/0
|
||||
Endpoint = {server_info['ipv4']}:51820
|
||||
PersistentKeepalive = 25
|
||||
"""
|
||||
|
||||
def _find_server(self, hostname: str) -> Optional[Dict]:
|
||||
for country in self.servers.values():
|
||||
for city in country.values():
|
||||
for server in city:
|
||||
if server['hostname'] == hostname:
|
||||
return server
|
||||
return None
|
||||
|
||||
def _get_or_generate_key(self) -> str:
|
||||
key_file = f"{WIREGUARD_DIR}/mullvad_private.key"
|
||||
if os.path.exists(key_file):
|
||||
with open(key_file, 'r') as f:
|
||||
return f.read().strip()
|
||||
else:
|
||||
private_key = subprocess.check_output(['wg', 'genkey'], text=True).strip()
|
||||
with open(key_file, 'w') as f:
|
||||
f.write(private_key)
|
||||
os.chmod(key_file, 0o600)
|
||||
return private_key
|
||||
|
||||
class CustomWireGuardProvider(VPNProvider):
|
||||
"""Custom WireGuard servers provider"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__("custom")
|
||||
self.config_file = f"{PROVIDERS_DIR}/custom_servers.json"
|
||||
self.load_servers()
|
||||
|
||||
def load_servers(self):
|
||||
"""Load custom servers from config file"""
|
||||
if os.path.exists(self.config_file):
|
||||
try:
|
||||
with open(self.config_file, 'r') as f:
|
||||
self.servers = json.load(f)
|
||||
except Exception as e:
|
||||
logging.error(f"Failed to load custom servers: {e}")
|
||||
self.servers = {}
|
||||
else:
|
||||
self.servers = {}
|
||||
|
||||
def save_servers(self):
|
||||
"""Save custom servers to config file"""
|
||||
os.makedirs(PROVIDERS_DIR, exist_ok=True)
|
||||
with open(self.config_file, 'w') as f:
|
||||
json.dump(self.servers, f, indent=2)
|
||||
|
||||
def add_server(self, name: str, config: Dict) -> bool:
|
||||
"""Add a custom WireGuard server"""
|
||||
try:
|
||||
location = config.get('location', 'Custom')
|
||||
|
||||
if location not in self.servers:
|
||||
self.servers[location] = {}
|
||||
|
||||
if 'Custom' not in self.servers[location]:
|
||||
self.servers[location]['Custom'] = []
|
||||
|
||||
self.servers[location]['Custom'].append({
|
||||
'hostname': name,
|
||||
'endpoint': config['endpoint'],
|
||||
'public_key': config['public_key'],
|
||||
'private_key': config.get('private_key'),
|
||||
'address': config.get('address', '10.0.0.2/32'),
|
||||
'dns': config.get('dns', '1.1.1.1,1.0.0.1'),
|
||||
'allowed_ips': config.get('allowed_ips', '0.0.0.0/0,::/0'),
|
||||
'keepalive': config.get('keepalive', 25),
|
||||
'type': 'WireGuard',
|
||||
'provider': 'Custom'
|
||||
})
|
||||
|
||||
self.save_servers()
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"Failed to add custom server: {e}")
|
||||
return False
|
||||
|
||||
def remove_server(self, name: str) -> bool:
|
||||
"""Remove a custom server"""
|
||||
for location in self.servers.values():
|
||||
for city in location.values():
|
||||
for i, server in enumerate(city):
|
||||
if server['hostname'] == name:
|
||||
city.pop(i)
|
||||
self.save_servers()
|
||||
return True
|
||||
return False
|
||||
|
||||
def generate_config(self, server_name: str) -> str:
|
||||
server_info = self._find_server(server_name)
|
||||
if not server_info:
|
||||
raise ValueError(f"Server {server_name} not found")
|
||||
|
||||
private_key = server_info.get('private_key')
|
||||
if not private_key:
|
||||
private_key = self._get_or_generate_key(server_name)
|
||||
|
||||
dns_servers = server_info.get('dns', '1.1.1.1,1.0.0.1')
|
||||
|
||||
return f"""# Custom WireGuard Configuration
|
||||
# Server: {server_name}
|
||||
# Provider: Custom
|
||||
|
||||
[Interface]
|
||||
PrivateKey = {private_key}
|
||||
Address = {server_info.get('address', '10.0.0.2/32')}
|
||||
DNS = {dns_servers}
|
||||
|
||||
# PERMANENT KILLSWITCH - CANNOT BE DISABLED
|
||||
PreUp = iptables -F OUTPUT
|
||||
PreUp = iptables -F FORWARD
|
||||
PostUp = iptables -I OUTPUT ! -o %i -m mark ! --mark $(wg show %i fwmark) -m addrtype ! --dst-type LOCAL -j REJECT
|
||||
PostUp = iptables -I FORWARD -i eth0 -o %i -j ACCEPT
|
||||
PostUp = iptables -t nat -A POSTROUTING -o %i -j MASQUERADE
|
||||
PreDown = iptables -D OUTPUT ! -o %i -m mark ! --mark $(wg show %i fwmark) -m addrtype ! --dst-type LOCAL -j REJECT
|
||||
PostDown = iptables -t nat -D POSTROUTING -o %i -j MASQUERADE
|
||||
|
||||
[Peer]
|
||||
PublicKey = {server_info['public_key']}
|
||||
AllowedIPs = {server_info.get('allowed_ips', '0.0.0.0/0,::/0')}
|
||||
Endpoint = {server_info['endpoint']}
|
||||
PersistentKeepalive = {server_info.get('keepalive', 25)}
|
||||
"""
|
||||
|
||||
def _find_server(self, name: str) -> Optional[Dict]:
|
||||
for location in self.servers.values():
|
||||
for city in location.values():
|
||||
for server in city:
|
||||
if server['hostname'] == name:
|
||||
return server
|
||||
return None
|
||||
|
||||
def _get_or_generate_key(self, name: str) -> str:
|
||||
key_file = f"{WIREGUARD_DIR}/custom_{name}_private.key"
|
||||
if os.path.exists(key_file):
|
||||
with open(key_file, 'r') as f:
|
||||
return f.read().strip()
|
||||
else:
|
||||
private_key = subprocess.check_output(['wg', 'genkey'], text=True).strip()
|
||||
with open(key_file, 'w') as f:
|
||||
f.write(private_key)
|
||||
os.chmod(key_file, 0o600)
|
||||
return private_key
|
||||
|
||||
class ImportedConfigProvider(VPNProvider):
|
||||
"""Provider for imported WireGuard configs"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__("imported")
|
||||
self.configs_dir = f"{PROVIDERS_DIR}/imported"
|
||||
os.makedirs(self.configs_dir, exist_ok=True)
|
||||
self.load_configs()
|
||||
|
||||
def load_configs(self):
|
||||
"""Load all imported configs"""
|
||||
self.servers = {"Imported": {"Configs": []}}
|
||||
|
||||
for config_file in Path(self.configs_dir).glob("*.conf"):
|
||||
name = config_file.stem
|
||||
self.servers["Imported"]["Configs"].append({
|
||||
'hostname': name,
|
||||
'file': str(config_file),
|
||||
'type': 'WireGuard',
|
||||
'provider': 'Imported'
|
||||
})
|
||||
|
||||
def import_config(self, name: str, config_content: str) -> bool:
|
||||
"""Import a WireGuard config"""
|
||||
try:
|
||||
# Validate config
|
||||
if '[Interface]' not in config_content or '[Peer]' not in config_content:
|
||||
raise ValueError("Invalid WireGuard configuration")
|
||||
|
||||
# Add killswitch if not present
|
||||
if 'PostUp' not in config_content:
|
||||
config_content = self._add_killswitch(config_content)
|
||||
|
||||
# Save config
|
||||
config_file = f"{self.configs_dir}/{name}.conf"
|
||||
with open(config_file, 'w') as f:
|
||||
f.write(config_content)
|
||||
os.chmod(config_file, 0o600)
|
||||
|
||||
self.load_configs()
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"Failed to import config: {e}")
|
||||
return False
|
||||
|
||||
def _add_killswitch(self, config: str) -> str:
|
||||
"""Add killswitch rules to imported config"""
|
||||
killswitch_rules = """
|
||||
# PERMANENT KILLSWITCH - ADDED AUTOMATICALLY
|
||||
PreUp = iptables -F OUTPUT
|
||||
PreUp = iptables -F FORWARD
|
||||
PostUp = iptables -I OUTPUT ! -o %i -m mark ! --mark $(wg show %i fwmark) -m addrtype ! --dst-type LOCAL -j REJECT
|
||||
PostUp = iptables -I FORWARD -i eth0 -o %i -j ACCEPT
|
||||
PostUp = iptables -t nat -A POSTROUTING -o %i -j MASQUERADE
|
||||
PreDown = iptables -D OUTPUT ! -o %i -m mark ! --mark $(wg show %i fwmark) -m addrtype ! --dst-type LOCAL -j REJECT
|
||||
PostDown = iptables -t nat -D POSTROUTING -o %i -j MASQUERADE
|
||||
"""
|
||||
|
||||
# Insert after [Interface] section
|
||||
lines = config.split('\n')
|
||||
for i, line in enumerate(lines):
|
||||
if line.strip() == '[Interface]':
|
||||
# Find next section or end
|
||||
for j in range(i+1, len(lines)):
|
||||
if lines[j].strip().startswith('['):
|
||||
lines.insert(j, killswitch_rules)
|
||||
break
|
||||
else:
|
||||
lines.insert(len(lines), killswitch_rules)
|
||||
break
|
||||
|
||||
return '\n'.join(lines)
|
||||
|
||||
def generate_config(self, name: str) -> str:
|
||||
for server in self.servers["Imported"]["Configs"]:
|
||||
if server['hostname'] == name:
|
||||
with open(server['file'], 'r') as f:
|
||||
return f.read()
|
||||
raise ValueError(f"Config {name} not found")
|
||||
|
||||
# Global provider instances
|
||||
PROVIDERS = {
|
||||
'mullvad': MullvadProvider(),
|
||||
'custom': CustomWireGuardProvider(),
|
||||
'imported': ImportedConfigProvider()
|
||||
}
|
||||
|
||||
# Current provider
|
||||
CURRENT_PROVIDER = None
|
||||
|
||||
# VPN Status
|
||||
VPN_STATUS = {
|
||||
'connected': False,
|
||||
'provider': None,
|
||||
'server': None,
|
||||
'ip': None,
|
||||
'location': None,
|
||||
'start_time': None
|
||||
}
|
||||
|
||||
def load_config():
|
||||
"""Load application configuration"""
|
||||
global CURRENT_PROVIDER
|
||||
|
||||
if os.path.exists(CONFIG_FILE):
|
||||
try:
|
||||
with open(CONFIG_FILE, 'r') as f:
|
||||
config = json.load(f)
|
||||
provider_name = config.get('provider', 'mullvad')
|
||||
CURRENT_PROVIDER = PROVIDERS.get(provider_name)
|
||||
logging.info(f"Loaded provider: {provider_name}")
|
||||
except Exception as e:
|
||||
logging.error(f"Failed to load config: {e}")
|
||||
CURRENT_PROVIDER = PROVIDERS['mullvad']
|
||||
else:
|
||||
CURRENT_PROVIDER = PROVIDERS['mullvad']
|
||||
|
||||
def save_config():
|
||||
"""Save application configuration"""
|
||||
try:
|
||||
config = {
|
||||
'provider': CURRENT_PROVIDER.name if CURRENT_PROVIDER else 'mullvad',
|
||||
'timestamp': datetime.now().isoformat()
|
||||
}
|
||||
|
||||
os.makedirs(os.path.dirname(CONFIG_FILE), exist_ok=True)
|
||||
with open(CONFIG_FILE, 'w') as f:
|
||||
json.dump(config, f, indent=2)
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"Failed to save config: {e}")
|
||||
|
||||
def check_vpn_status():
|
||||
"""Check current VPN status"""
|
||||
global VPN_STATUS
|
||||
|
||||
try:
|
||||
result = subprocess.run(['wg', 'show', 'wg0'], capture_output=True, text=True)
|
||||
|
||||
if result.returncode == 0 and 'interface:' in result.stdout.lower():
|
||||
VPN_STATUS['connected'] = True
|
||||
|
||||
# Get public IP and location
|
||||
try:
|
||||
# Try Mullvad API first
|
||||
response = requests.get('https://am.i.mullvad.net/json', timeout=5)
|
||||
data = response.json()
|
||||
VPN_STATUS['ip'] = data.get('ip')
|
||||
|
||||
if data.get('mullvad_exit_ip'):
|
||||
VPN_STATUS['location'] = f"{data.get('city')}, {data.get('country')}"
|
||||
else:
|
||||
# Fallback to ipinfo
|
||||
response = requests.get('https://ipinfo.io/json', timeout=5)
|
||||
data = response.json()
|
||||
VPN_STATUS['ip'] = data.get('ip')
|
||||
VPN_STATUS['location'] = f"{data.get('city')}, {data.get('country')}"
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
VPN_STATUS['connected'] = False
|
||||
VPN_STATUS['server'] = None
|
||||
VPN_STATUS['ip'] = None
|
||||
VPN_STATUS['location'] = None
|
||||
VPN_STATUS['provider'] = None
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"Status check error: {e}")
|
||||
VPN_STATUS['connected'] = False
|
||||
|
||||
# Flask Routes
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
return send_from_directory('/opt/vpn-gateway/static', 'index.html')
|
||||
|
||||
@app.route('/api/providers')
|
||||
def get_providers():
|
||||
"""Get available providers"""
|
||||
return jsonify({
|
||||
'providers': list(PROVIDERS.keys()),
|
||||
'current': CURRENT_PROVIDER.name if CURRENT_PROVIDER else None
|
||||
})
|
||||
|
||||
@app.route('/api/provider/<provider_name>', methods=['POST'])
|
||||
def set_provider(provider_name):
|
||||
"""Switch provider"""
|
||||
global CURRENT_PROVIDER
|
||||
|
||||
if provider_name not in PROVIDERS:
|
||||
return jsonify({'success': False, 'error': 'Invalid provider'}), 400
|
||||
|
||||
# Disconnect if connected
|
||||
if VPN_STATUS['connected']:
|
||||
subprocess.run(['wg-quick', 'down', 'wg0'], capture_output=True)
|
||||
|
||||
CURRENT_PROVIDER = PROVIDERS[provider_name]
|
||||
save_config()
|
||||
|
||||
return jsonify({'success': True, 'provider': provider_name})
|
||||
|
||||
@app.route('/api/servers')
|
||||
def get_servers():
|
||||
"""Get servers for current provider"""
|
||||
if not CURRENT_PROVIDER:
|
||||
return jsonify({'servers': {}})
|
||||
|
||||
servers = CURRENT_PROVIDER.get_servers()
|
||||
return jsonify({
|
||||
'servers': servers,
|
||||
'provider': CURRENT_PROVIDER.name
|
||||
})
|
||||
|
||||
@app.route('/api/custom/add', methods=['POST'])
|
||||
def add_custom_server():
|
||||
"""Add custom WireGuard server"""
|
||||
if not isinstance(CURRENT_PROVIDER, CustomWireGuardProvider):
|
||||
return jsonify({'success': False, 'error': 'Not in custom mode'}), 400
|
||||
|
||||
data = request.json
|
||||
name = data.get('name')
|
||||
config = {
|
||||
'endpoint': data.get('endpoint'),
|
||||
'public_key': data.get('public_key'),
|
||||
'private_key': data.get('private_key'),
|
||||
'address': data.get('address', '10.0.0.2/32'),
|
||||
'dns': data.get('dns', '1.1.1.1,1.0.0.1'),
|
||||
'allowed_ips': data.get('allowed_ips', '0.0.0.0/0,::/0'),
|
||||
'location': data.get('location', 'Custom')
|
||||
}
|
||||
|
||||
if CURRENT_PROVIDER.add_server(name, config):
|
||||
return jsonify({'success': True})
|
||||
else:
|
||||
return jsonify({'success': False, 'error': 'Failed to add server'}), 500
|
||||
|
||||
@app.route('/api/custom/remove/<name>', methods=['DELETE'])
|
||||
def remove_custom_server(name):
|
||||
"""Remove custom server"""
|
||||
if not isinstance(CURRENT_PROVIDER, CustomWireGuardProvider):
|
||||
return jsonify({'success': False, 'error': 'Not in custom mode'}), 400
|
||||
|
||||
if CURRENT_PROVIDER.remove_server(name):
|
||||
return jsonify({'success': True})
|
||||
else:
|
||||
return jsonify({'success': False, 'error': 'Server not found'}), 404
|
||||
|
||||
@app.route('/api/import', methods=['POST'])
|
||||
def import_config():
|
||||
"""Import WireGuard config"""
|
||||
data = request.json
|
||||
name = data.get('name')
|
||||
config_content = data.get('config')
|
||||
|
||||
if not name or not config_content:
|
||||
return jsonify({'success': False, 'error': 'Missing name or config'}), 400
|
||||
|
||||
provider = PROVIDERS['imported']
|
||||
if provider.import_config(name, config_content):
|
||||
return jsonify({'success': True})
|
||||
else:
|
||||
return jsonify({'success': False, 'error': 'Failed to import config'}), 500
|
||||
|
||||
@app.route('/api/status')
|
||||
def get_status():
|
||||
"""Get VPN status"""
|
||||
check_vpn_status()
|
||||
|
||||
uptime = None
|
||||
if VPN_STATUS['connected'] and VPN_STATUS['start_time']:
|
||||
uptime_seconds = int(time.time() - VPN_STATUS['start_time'])
|
||||
hours = uptime_seconds // 3600
|
||||
minutes = (uptime_seconds % 3600) // 60
|
||||
uptime = f"{hours}h {minutes}m"
|
||||
|
||||
return jsonify({
|
||||
'connected': VPN_STATUS['connected'],
|
||||
'provider': VPN_STATUS['provider'],
|
||||
'server': VPN_STATUS['server'],
|
||||
'ip': VPN_STATUS['ip'],
|
||||
'location': VPN_STATUS['location'],
|
||||
'uptime': uptime,
|
||||
'killswitch_active': True # Always true
|
||||
})
|
||||
|
||||
@app.route('/api/connect', methods=['POST'])
|
||||
def connect_vpn():
|
||||
"""Connect to VPN"""
|
||||
data = request.json
|
||||
server = data.get('server')
|
||||
|
||||
if not server or not CURRENT_PROVIDER:
|
||||
return jsonify({'success': False, 'error': 'No server or provider selected'}), 400
|
||||
|
||||
try:
|
||||
# Disconnect if connected
|
||||
subprocess.run(['wg-quick', 'down', 'wg0'], capture_output=True)
|
||||
time.sleep(1)
|
||||
|
||||
# Generate config
|
||||
config = CURRENT_PROVIDER.generate_config(server)
|
||||
|
||||
# Write config
|
||||
with open('/etc/wireguard/wg0.conf', 'w') as f:
|
||||
f.write(config)
|
||||
os.chmod('/etc/wireguard/wg0.conf', 0o600)
|
||||
|
||||
# Add firewall exception for endpoint
|
||||
endpoint_match = re.search(r'Endpoint = ([\d.]+):', config)
|
||||
if endpoint_match:
|
||||
subprocess.run([
|
||||
'iptables', '-I', 'OUTPUT', '1', '-p', 'udp',
|
||||
'--dport', '51820', '-d', endpoint_match.group(1), '-j', 'ACCEPT'
|
||||
])
|
||||
|
||||
# Connect
|
||||
result = subprocess.run(['wg-quick', 'up', 'wg0'],
|
||||
capture_output=True, text=True)
|
||||
|
||||
if result.returncode == 0:
|
||||
VPN_STATUS['start_time'] = time.time()
|
||||
VPN_STATUS['server'] = server
|
||||
VPN_STATUS['provider'] = CURRENT_PROVIDER.name
|
||||
|
||||
logging.info(f"Connected to {server} via {CURRENT_PROVIDER.name}")
|
||||
return jsonify({'success': True})
|
||||
else:
|
||||
logging.error(f"Connection failed: {result.stderr}")
|
||||
return jsonify({'success': False, 'error': result.stderr}), 500
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"Connect error: {e}")
|
||||
return jsonify({'success': False, 'error': str(e)}), 500
|
||||
|
||||
@app.route('/api/disconnect', methods=['POST'])
|
||||
def disconnect_vpn():
|
||||
"""Disconnect VPN"""
|
||||
try:
|
||||
result = subprocess.run(['wg-quick', 'down', 'wg0'],
|
||||
capture_output=True, text=True)
|
||||
|
||||
VPN_STATUS['start_time'] = None
|
||||
VPN_STATUS['connected'] = False
|
||||
VPN_STATUS['provider'] = None
|
||||
|
||||
return jsonify({
|
||||
'success': result.returncode == 0,
|
||||
'message': 'Disconnected - No internet (killswitch active)'
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"Disconnect error: {e}")
|
||||
return jsonify({'success': False, 'error': str(e)}), 500
|
||||
|
||||
@app.route('/api/keypair', methods=['GET'])
|
||||
def generate_keypair():
|
||||
"""Generate WireGuard keypair"""
|
||||
try:
|
||||
private_key = subprocess.check_output(['wg', 'genkey'], text=True).strip()
|
||||
public_key = subprocess.check_output(['wg', 'pubkey'], input=private_key, text=True).strip()
|
||||
|
||||
return jsonify({
|
||||
'private_key': private_key,
|
||||
'public_key': public_key
|
||||
})
|
||||
except Exception as e:
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Load configuration
|
||||
load_config()
|
||||
|
||||
# Create directories
|
||||
os.makedirs('/opt/vpn-gateway/static', exist_ok=True)
|
||||
os.makedirs(PROVIDERS_DIR, exist_ok=True)
|
||||
|
||||
# Start app
|
||||
app.run(host='0.0.0.0', port=5000, debug=False)
|
5
backend/requirements.txt
Normal file
5
backend/requirements.txt
Normal file
|
@ -0,0 +1,5 @@
|
|||
flask==2.3.3
|
||||
flask-cors==4.0.0
|
||||
requests==2.31.0
|
||||
gunicorn==21.2.0
|
||||
pyyaml==6.0.1
|
54
configs/iptables-save.conf
Normal file
54
configs/iptables-save.conf
Normal file
|
@ -0,0 +1,54 @@
|
|||
# VPN Gateway IPTables Rules Template
|
||||
# This is a template - actual rules are generated by killswitch.sh
|
||||
|
||||
*filter
|
||||
:INPUT DROP [0:0]
|
||||
:FORWARD DROP [0:0]
|
||||
:OUTPUT DROP [0:0]
|
||||
|
||||
# Loopback
|
||||
-A INPUT -i lo -j ACCEPT
|
||||
-A OUTPUT -o lo -j ACCEPT
|
||||
|
||||
# Established connections
|
||||
-A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
|
||||
-A OUTPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
|
||||
-A FORWARD -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
|
||||
|
||||
# LAN (will be replaced with actual interface/network)
|
||||
-A INPUT -i eth0 -s 192.168.1.0/24 -j ACCEPT
|
||||
-A OUTPUT -o eth0 -d 192.168.1.0/24 -j ACCEPT
|
||||
|
||||
# DNS for root only (for initial VPN connection)
|
||||
-A OUTPUT -p udp --dport 53 -m owner --uid-owner 0 -j ACCEPT
|
||||
-A OUTPUT -p tcp --dport 53 -m owner --uid-owner 0 -j ACCEPT
|
||||
|
||||
# VPN Forward
|
||||
-A FORWARD -i eth0 -s 192.168.1.0/24 -j ACCEPT
|
||||
|
||||
# Log dropped packets (optional)
|
||||
# -A INPUT -j LOG --log-prefix "DROP-IN: " --log-level 4
|
||||
# -A OUTPUT -j LOG --log-prefix "DROP-OUT: " --log-level 4
|
||||
# -A FORWARD -j LOG --log-prefix "DROP-FWD: " --log-level 4
|
||||
|
||||
COMMIT
|
||||
|
||||
*nat
|
||||
:PREROUTING ACCEPT [0:0]
|
||||
:INPUT ACCEPT [0:0]
|
||||
:OUTPUT ACCEPT [0:0]
|
||||
:POSTROUTING ACCEPT [0:0]
|
||||
|
||||
# NAT will be added dynamically when VPN connects
|
||||
# -A POSTROUTING -o wg0 -j MASQUERADE
|
||||
|
||||
COMMIT
|
||||
|
||||
*mangle
|
||||
:PREROUTING ACCEPT [0:0]
|
||||
:INPUT ACCEPT [0:0]
|
||||
:FORWARD ACCEPT [0:0]
|
||||
:OUTPUT ACCEPT [0:0]
|
||||
:POSTROUTING ACCEPT [0:0]
|
||||
|
||||
COMMIT
|
30
configs/logrotate.conf
Normal file
30
configs/logrotate.conf
Normal file
|
@ -0,0 +1,30 @@
|
|||
# VPN Gateway Log Rotation
|
||||
# Place in: /etc/logrotate.d/vpn-gateway
|
||||
|
||||
/var/log/vpn-*.log {
|
||||
daily
|
||||
rotate 7
|
||||
compress
|
||||
delaycompress
|
||||
missingok
|
||||
notifempty
|
||||
create 640 root root
|
||||
sharedscripts
|
||||
postrotate
|
||||
systemctl reload vpn-webui 2>/dev/null || true
|
||||
endscript
|
||||
}
|
||||
|
||||
/var/log/nginx/vpn-gateway*.log {
|
||||
daily
|
||||
rotate 14
|
||||
compress
|
||||
delaycompress
|
||||
missingok
|
||||
notifempty
|
||||
create 640 www-data adm
|
||||
sharedscripts
|
||||
postrotate
|
||||
systemctl reload nginx 2>/dev/null || true
|
||||
endscript
|
||||
}
|
135
configs/nginx.conf
Normal file
135
configs/nginx.conf
Normal file
|
@ -0,0 +1,135 @@
|
|||
# VPN Gateway Nginx Configuration
|
||||
# Place in: /etc/nginx/sites-available/vpn-gateway
|
||||
|
||||
# Rate limiting zones
|
||||
limit_req_zone $binary_remote_addr zone=vpn_general:10m rate=10r/s;
|
||||
limit_req_zone $binary_remote_addr zone=vpn_api:10m rate=5r/s;
|
||||
limit_conn_zone $binary_remote_addr zone=vpn_conn:10m;
|
||||
|
||||
# Upstream backend
|
||||
upstream vpn_backend {
|
||||
server 127.0.0.1:5000 fail_timeout=10s;
|
||||
keepalive 32;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name _;
|
||||
|
||||
# Access and error logs
|
||||
access_log /var/log/nginx/vpn-gateway.access.log;
|
||||
error_log /var/log/nginx/vpn-gateway.error.log;
|
||||
|
||||
# Security headers
|
||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
add_header X-XSS-Protection "1; mode=block" always;
|
||||
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||||
add_header Content-Security-Policy "default-src 'self' 'unsafe-inline' 'unsafe-eval'; img-src 'self' data:; font-src 'self' data:;" always;
|
||||
|
||||
# Connection limits
|
||||
limit_conn vpn_conn 10;
|
||||
|
||||
# Client body size (for config uploads)
|
||||
client_max_body_size 1M;
|
||||
client_body_buffer_size 128k;
|
||||
|
||||
# Timeouts
|
||||
client_body_timeout 10s;
|
||||
client_header_timeout 10s;
|
||||
send_timeout 10s;
|
||||
|
||||
# Root location - WebUI
|
||||
location / {
|
||||
limit_req zone=vpn_general burst=20 nodelay;
|
||||
|
||||
proxy_pass http://vpn_backend;
|
||||
proxy_http_version 1.1;
|
||||
|
||||
# Headers
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
|
||||
# WebSocket support
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
|
||||
# Timeouts for proxy
|
||||
proxy_connect_timeout 60s;
|
||||
proxy_send_timeout 60s;
|
||||
proxy_read_timeout 60s;
|
||||
|
||||
# Buffering
|
||||
proxy_buffering off;
|
||||
proxy_request_buffering off;
|
||||
}
|
||||
|
||||
# API endpoints - stricter rate limiting
|
||||
location /api/ {
|
||||
limit_req zone=vpn_api burst=10 nodelay;
|
||||
limit_conn vpn_conn 5;
|
||||
|
||||
proxy_pass http://vpn_backend;
|
||||
proxy_http_version 1.1;
|
||||
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
|
||||
# API specific timeouts
|
||||
proxy_connect_timeout 30s;
|
||||
proxy_send_timeout 30s;
|
||||
proxy_read_timeout 30s;
|
||||
}
|
||||
|
||||
# Static files (if any)
|
||||
location /static/ {
|
||||
alias /opt/vpn-gateway/static/;
|
||||
expires 1h;
|
||||
add_header Cache-Control "public, immutable";
|
||||
}
|
||||
|
||||
# Health check endpoint
|
||||
location /health {
|
||||
access_log off;
|
||||
add_header Content-Type text/plain;
|
||||
return 200 "healthy\n";
|
||||
}
|
||||
|
||||
# Block sensitive paths
|
||||
location ~ /\. {
|
||||
deny all;
|
||||
access_log off;
|
||||
log_not_found off;
|
||||
}
|
||||
|
||||
# Block access to backup files
|
||||
location ~ ~$ {
|
||||
deny all;
|
||||
}
|
||||
}
|
||||
|
||||
# HTTPS configuration (optional - uncomment if using SSL)
|
||||
# server {
|
||||
# listen 443 ssl http2;
|
||||
# listen [::]:443 ssl http2;
|
||||
# server_name vpn.yourdomain.com;
|
||||
#
|
||||
# ssl_certificate /etc/letsencrypt/live/vpn.yourdomain.com/fullchain.pem;
|
||||
# ssl_certificate_key /etc/letsencrypt/live/vpn.yourdomain.com/privkey.pem;
|
||||
#
|
||||
# # SSL configuration
|
||||
# ssl_protocols TLSv1.2 TLSv1.3;
|
||||
# ssl_ciphers HIGH:!aNULL:!MD5;
|
||||
# ssl_prefer_server_ciphers on;
|
||||
# ssl_session_cache shared:SSL:10m;
|
||||
# ssl_session_timeout 10m;
|
||||
#
|
||||
# # HSTS
|
||||
# add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
|
||||
#
|
||||
# # Rest of configuration same as above...
|
||||
# }
|
12
configs/systemd/vpn-auto-update.service
Normal file
12
configs/systemd/vpn-auto-update.service
Normal file
|
@ -0,0 +1,12 @@
|
|||
[Unit]
|
||||
Description=VPN Gateway Auto-Update Check
|
||||
Documentation=https://github.com/yourusername/vpn-gateway
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
User=root
|
||||
ExecStart=/usr/local/bin/vpn-update.sh --check-only
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
13
configs/systemd/vpn-auto-update.timer
Normal file
13
configs/systemd/vpn-auto-update.timer
Normal file
|
@ -0,0 +1,13 @@
|
|||
[Unit]
|
||||
Description=VPN Gateway Auto-Update Timer
|
||||
Documentation=https://github.com/yourusername/vpn-gateway
|
||||
|
||||
[Timer]
|
||||
# Run daily at 3 AM
|
||||
OnCalendar=daily
|
||||
OnCalendar=*-*-* 03:00:00
|
||||
RandomizedDelaySec=1h
|
||||
Persistent=true
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
38
configs/systemd/vpn-killswitch.service
Normal file
38
configs/systemd/vpn-killswitch.service
Normal file
|
@ -0,0 +1,38 @@
|
|||
[Unit]
|
||||
Description=VPN Killswitch - Permanent Network Protection
|
||||
Documentation=https://github.com/yourusername/vpn-gateway
|
||||
DefaultDependencies=no
|
||||
Before=network-pre.target
|
||||
Wants=network-pre.target
|
||||
|
||||
# This service MUST start before networking
|
||||
After=local-fs.target
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
RemainAfterExit=yes
|
||||
|
||||
# Execute killswitch enable
|
||||
ExecStart=/usr/local/bin/vpn-killswitch.sh enable
|
||||
|
||||
# On reload, restart the killswitch
|
||||
ExecReload=/usr/local/bin/vpn-killswitch.sh restart
|
||||
|
||||
# On stop, we still keep killswitch active for security
|
||||
ExecStop=/bin/echo "Killswitch remains active for security"
|
||||
|
||||
# Logging
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
|
||||
# Security
|
||||
User=root
|
||||
Group=root
|
||||
|
||||
# We want this to always succeed
|
||||
SuccessExitStatus=0 1
|
||||
|
||||
[Install]
|
||||
# Critical: Start at earliest possible stage
|
||||
WantedBy=sysinit.target
|
||||
RequiredBy=network.target
|
40
configs/systemd/vpn-security-monitor.service
Normal file
40
configs/systemd/vpn-security-monitor.service
Normal file
|
@ -0,0 +1,40 @@
|
|||
[Unit]
|
||||
Description=VPN Security Monitor - Continuous Protection Monitoring
|
||||
Documentation=https://github.com/yourusername/vpn-gateway
|
||||
After=vpn-killswitch.service network-online.target
|
||||
Requires=vpn-killswitch.service
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=root
|
||||
Group=root
|
||||
|
||||
# Execute monitoring script
|
||||
ExecStart=/usr/local/bin/vpn-security-monitor.sh
|
||||
|
||||
# Restart policy
|
||||
Restart=always
|
||||
RestartSec=30
|
||||
StartLimitInterval=300
|
||||
StartLimitBurst=5
|
||||
|
||||
# Logging
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
|
||||
# Resource limits
|
||||
CPUQuota=10%
|
||||
MemoryLimit=100M
|
||||
|
||||
# Security
|
||||
NoNewPrivileges=true
|
||||
PrivateTmp=true
|
||||
|
||||
# Kill settings
|
||||
KillMode=process
|
||||
KillSignal=SIGTERM
|
||||
TimeoutStopSec=10
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
60
configs/systemd/vpn-webui.service
Normal file
60
configs/systemd/vpn-webui.service
Normal file
|
@ -0,0 +1,60 @@
|
|||
[Unit]
|
||||
Description=VPN Gateway WebUI Service
|
||||
Documentation=https://github.com/yourusername/vpn-gateway
|
||||
After=network-online.target vpn-killswitch.service
|
||||
Wants=network-online.target
|
||||
Requires=vpn-killswitch.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=root
|
||||
Group=root
|
||||
WorkingDirectory=/opt/vpn-gateway
|
||||
|
||||
# Environment
|
||||
Environment="PATH=/opt/vpn-gateway/venv/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
|
||||
Environment="PYTHONPATH=/opt/vpn-gateway"
|
||||
Environment="FLASK_APP=app.py"
|
||||
Environment="FLASK_ENV=production"
|
||||
|
||||
# Pre-start delay to ensure network is ready
|
||||
ExecStartPre=/bin/bash -c 'sleep 5'
|
||||
|
||||
# Start command with gunicorn
|
||||
ExecStart=/opt/vpn-gateway/venv/bin/gunicorn \
|
||||
--bind 0.0.0.0:5000 \
|
||||
--workers 2 \
|
||||
--threads 4 \
|
||||
--worker-class sync \
|
||||
--worker-connections 1000 \
|
||||
--max-requests 1000 \
|
||||
--max-requests-jitter 50 \
|
||||
--timeout 120 \
|
||||
--keepalive 5 \
|
||||
--access-logfile /var/log/vpn-gateway-access.log \
|
||||
--error-logfile /var/log/vpn-gateway-error.log \
|
||||
--log-level info \
|
||||
--capture-output \
|
||||
app:app
|
||||
|
||||
# Restart policy
|
||||
Restart=always
|
||||
RestartSec=10
|
||||
StartLimitInterval=60
|
||||
StartLimitBurst=3
|
||||
|
||||
# Security settings
|
||||
NoNewPrivileges=true
|
||||
PrivateTmp=true
|
||||
|
||||
# Resource limits
|
||||
LimitNOFILE=65536
|
||||
LimitNPROC=4096
|
||||
|
||||
# Kill settings
|
||||
KillMode=mixed
|
||||
KillSignal=SIGTERM
|
||||
TimeoutStopSec=30
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
2514
create-configs-docs.sh
Executable file
2514
create-configs-docs.sh
Executable file
File diff suppressed because it is too large
Load diff
320
docs/API.md
Normal file
320
docs/API.md
Normal file
|
@ -0,0 +1,320 @@
|
|||
# API Reference
|
||||
|
||||
## Overview
|
||||
|
||||
The VPN Gateway provides a RESTful API for managing VPN connections and configuration.
|
||||
|
||||
Base URL: `http://<gateway-ip>:5000`
|
||||
|
||||
## Authentication
|
||||
|
||||
Currently, the API does not require authentication for local network access. For production use, consider implementing API keys or JWT tokens.
|
||||
|
||||
## Endpoints
|
||||
|
||||
### System Status
|
||||
|
||||
#### GET /api/status
|
||||
Get current VPN and system status.
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"connected": true,
|
||||
"provider": "mullvad",
|
||||
"server": "se-sto-wg-001",
|
||||
"ip": "185.65.134.123",
|
||||
"location": "Stockholm, Sweden",
|
||||
"uptime": "2h 34m",
|
||||
"killswitch_active": true
|
||||
}
|
||||
```
|
||||
|
||||
### Provider Management
|
||||
|
||||
#### GET /api/providers
|
||||
List available providers.
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"providers": ["mullvad", "custom", "imported"],
|
||||
"current": "mullvad"
|
||||
}
|
||||
```
|
||||
|
||||
#### POST /api/provider/{provider}
|
||||
Switch to a different provider.
|
||||
|
||||
**Parameters:**
|
||||
- `provider`: Provider name (mullvad|custom|imported)
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"provider": "custom"
|
||||
}
|
||||
```
|
||||
|
||||
### Server Management
|
||||
|
||||
#### GET /api/servers
|
||||
Get available servers for current provider.
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"servers": {
|
||||
"Sweden": {
|
||||
"Stockholm": [
|
||||
{
|
||||
"hostname": "se-sto-wg-001",
|
||||
"ipv4": "185.65.134.123",
|
||||
"type": "WireGuard",
|
||||
"provider": "Mullvad"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"provider": "mullvad"
|
||||
}
|
||||
```
|
||||
|
||||
### Connection Management
|
||||
|
||||
#### POST /api/connect
|
||||
Connect to VPN server.
|
||||
|
||||
**Request Body:**
|
||||
```json
|
||||
{
|
||||
"server": "se-sto-wg-001"
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
#### POST /api/disconnect
|
||||
Disconnect from VPN.
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "Disconnected - No internet (killswitch active)"
|
||||
}
|
||||
```
|
||||
|
||||
### Custom Server Management
|
||||
|
||||
#### POST /api/custom/add
|
||||
Add a custom WireGuard server.
|
||||
|
||||
**Request Body:**
|
||||
```json
|
||||
{
|
||||
"name": "my-vps",
|
||||
"endpoint": "1.2.3.4:51820",
|
||||
"public_key": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=",
|
||||
"private_key": "yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy=",
|
||||
"address": "10.0.0.2/32",
|
||||
"dns": "1.1.1.1,1.0.0.1",
|
||||
"allowed_ips": "0.0.0.0/0,::/0",
|
||||
"location": "Germany"
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
#### DELETE /api/custom/remove/{name}
|
||||
Remove a custom server.
|
||||
|
||||
**Parameters:**
|
||||
- `name`: Server name
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
### Import Configuration
|
||||
|
||||
#### POST /api/import
|
||||
Import a WireGuard configuration.
|
||||
|
||||
**Request Body:**
|
||||
```json
|
||||
{
|
||||
"name": "imported-config",
|
||||
"config": "[Interface]\nPrivateKey = xxx\n..."
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
### Utility
|
||||
|
||||
#### GET /api/keypair
|
||||
Generate a new WireGuard keypair.
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"private_key": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=",
|
||||
"public_key": "yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy="
|
||||
}
|
||||
```
|
||||
|
||||
#### GET /health
|
||||
Health check endpoint.
|
||||
|
||||
**Response:**
|
||||
```
|
||||
healthy
|
||||
```
|
||||
|
||||
## Error Responses
|
||||
|
||||
All endpoints may return error responses:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"error": "Error message here"
|
||||
}
|
||||
```
|
||||
|
||||
Common HTTP status codes:
|
||||
- `200`: Success
|
||||
- `400`: Bad request
|
||||
- `404`: Not found
|
||||
- `500`: Internal server error
|
||||
|
||||
## WebSocket Events (Future)
|
||||
|
||||
Planned WebSocket support for real-time updates:
|
||||
|
||||
```javascript
|
||||
const ws = new WebSocket('ws://gateway-ip:5000/ws');
|
||||
|
||||
ws.onmessage = (event) => {
|
||||
const data = JSON.parse(event.data);
|
||||
console.log('Event:', data.type, data.payload);
|
||||
};
|
||||
```
|
||||
|
||||
Events:
|
||||
- `status_change`: VPN connection status changed
|
||||
- `server_update`: Server list updated
|
||||
- `security_alert`: Security issue detected
|
||||
|
||||
## Example Usage
|
||||
|
||||
### cURL
|
||||
|
||||
```bash
|
||||
# Get status
|
||||
curl http://gateway-ip:5000/api/status
|
||||
|
||||
# Connect to server
|
||||
curl -X POST http://gateway-ip:5000/api/connect \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"server":"se-sto-wg-001"}'
|
||||
|
||||
# Add custom server
|
||||
curl -X POST http://gateway-ip:5000/api/custom/add \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"name": "my-server",
|
||||
"endpoint": "1.2.3.4:51820",
|
||||
"public_key": "xxx..."
|
||||
}'
|
||||
```
|
||||
|
||||
### Python
|
||||
|
||||
```python
|
||||
import requests
|
||||
|
||||
# API base URL
|
||||
base_url = "http://gateway-ip:5000"
|
||||
|
||||
# Get status
|
||||
response = requests.get(f"{base_url}/api/status")
|
||||
status = response.json()
|
||||
print(f"Connected: {status['connected']}")
|
||||
|
||||
# Connect to server
|
||||
response = requests.post(
|
||||
f"{base_url}/api/connect",
|
||||
json={"server": "se-sto-wg-001"}
|
||||
)
|
||||
if response.json()["success"]:
|
||||
print("Connected successfully")
|
||||
```
|
||||
|
||||
### JavaScript
|
||||
|
||||
```javascript
|
||||
// Get status
|
||||
fetch('http://gateway-ip:5000/api/status')
|
||||
.then(response => response.json())
|
||||
.then(data => console.log('Status:', data));
|
||||
|
||||
// Connect to server
|
||||
fetch('http://gateway-ip:5000/api/connect', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
server: 'se-sto-wg-001'
|
||||
})
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
console.log('Connected');
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
## Rate Limiting
|
||||
|
||||
API endpoints are rate-limited:
|
||||
- General endpoints: 10 requests/second
|
||||
- Connection endpoints: 5 requests/second
|
||||
|
||||
Headers returned:
|
||||
- `X-RateLimit-Limit`: Request limit
|
||||
- `X-RateLimit-Remaining`: Remaining requests
|
||||
- `X-RateLimit-Reset`: Reset timestamp
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
Planned API features:
|
||||
- JWT authentication
|
||||
- GraphQL endpoint
|
||||
- Metrics endpoint (Prometheus format)
|
||||
- Bulk operations
|
||||
- Configuration backup/restore
|
||||
- Traffic statistics
|
||||
- Connection history
|
241
docs/FAQ.md
Normal file
241
docs/FAQ.md
Normal file
|
@ -0,0 +1,241 @@
|
|||
# Frequently Asked Questions
|
||||
|
||||
## General Questions
|
||||
|
||||
### Q: What is the VPN Gateway?
|
||||
**A:** It's a secure VPN gateway solution that routes all network traffic through a VPN connection with a permanent killswitch to prevent leaks.
|
||||
|
||||
### Q: Which VPN providers are supported?
|
||||
**A:**
|
||||
- Mullvad VPN (commercial service)
|
||||
- Custom WireGuard servers (your own VPS)
|
||||
- Any imported WireGuard configuration
|
||||
|
||||
### Q: Can I use this with OpenVPN?
|
||||
**A:** No, this gateway only supports WireGuard protocol for better performance and security.
|
||||
|
||||
### Q: Is this free to use?
|
||||
**A:** The software is free and open source. You need to provide your own VPN service (Mullvad account or custom server).
|
||||
|
||||
## Installation
|
||||
|
||||
### Q: What are the system requirements?
|
||||
**A:**
|
||||
- LXC container or Linux system
|
||||
- Ubuntu 20.04+ or Debian 11+
|
||||
- 512MB RAM minimum
|
||||
- 1GB disk space
|
||||
- Root access
|
||||
|
||||
### Q: Can I install on a Raspberry Pi?
|
||||
**A:** Yes, as long as it runs a supported OS and has WireGuard kernel module support.
|
||||
|
||||
### Q: Does it work in Docker?
|
||||
**A:** It requires privileged mode and NET_ADMIN capability. LXC is recommended over Docker.
|
||||
|
||||
### Q: Can I install on a VPS?
|
||||
**A:** Yes, but be aware that the killswitch will block all traffic except through VPN, which might lock you out via SSH.
|
||||
|
||||
## Usage
|
||||
|
||||
### Q: No internet after disconnecting VPN?
|
||||
**A:** This is correct behavior! The killswitch blocks all internet traffic when VPN is not connected. This prevents leaks.
|
||||
|
||||
### Q: Can I disable the killswitch?
|
||||
**A:** No, the killswitch cannot be disabled through normal means. This is a security feature.
|
||||
|
||||
### Q: How do I access the WebUI?
|
||||
**A:** Navigate to `http://<container-ip>` in your browser. The WebUI is always accessible from the local network.
|
||||
|
||||
### Q: Can I use multiple VPN connections simultaneously?
|
||||
**A:** No, only one VPN connection is active at a time. You can switch between servers/providers via the WebUI.
|
||||
|
||||
## Security
|
||||
|
||||
### Q: Is this really secure?
|
||||
**A:** Yes, when properly configured:
|
||||
- Permanent killswitch prevents leaks
|
||||
- DNS leak protection enabled
|
||||
- IPv6 completely disabled
|
||||
- Continuous security monitoring
|
||||
|
||||
### Q: What about WebRTC leaks?
|
||||
**A:** WebRTC leaks are prevented at the firewall level. No direct peer connections are possible.
|
||||
|
||||
### Q: Can applications bypass the VPN?
|
||||
**A:** No, all traffic is forced through the VPN tunnel or blocked by the killswitch.
|
||||
|
||||
### Q: Is my traffic logged?
|
||||
**A:** The gateway itself doesn't log traffic. Logging depends on your VPN provider's policy.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Q: WebUI is not accessible
|
||||
**A:**
|
||||
```bash
|
||||
# Check if service is running
|
||||
sudo systemctl status vpn-webui
|
||||
|
||||
# Restart the service
|
||||
sudo systemctl restart vpn-webui
|
||||
|
||||
# Check if port is open
|
||||
sudo netstat -tlnp | grep 5000
|
||||
```
|
||||
|
||||
### Q: VPN won't connect
|
||||
**A:**
|
||||
1. Check your credentials/keys are correct
|
||||
2. Verify the server is reachable
|
||||
3. Check firewall allows outbound UDP 51820
|
||||
4. Review logs: `sudo journalctl -u vpn-webui -n 50`
|
||||
|
||||
### Q: DNS not working
|
||||
**A:**
|
||||
```bash
|
||||
# Check DNS configuration
|
||||
cat /etc/resolv.conf
|
||||
|
||||
# Test DNS resolution
|
||||
nslookup google.com
|
||||
|
||||
# Restart VPN connection
|
||||
sudo wg-quick down wg0
|
||||
sudo wg-quick up wg0
|
||||
```
|
||||
|
||||
### Q: High CPU usage
|
||||
**A:**
|
||||
- Check security monitor: `sudo systemctl status vpn-security-monitor`
|
||||
- Reduce monitoring frequency if needed
|
||||
- Check for packet loops in firewall rules
|
||||
|
||||
## Configuration
|
||||
|
||||
### Q: How do I add a custom DNS server?
|
||||
**A:** Edit the WireGuard configuration:
|
||||
```bash
|
||||
sudo nano /etc/wireguard/wg0.conf
|
||||
# Change DNS = line to your preferred servers
|
||||
```
|
||||
|
||||
### Q: Can I change the WebUI port?
|
||||
**A:** Yes, edit the systemd service:
|
||||
```bash
|
||||
sudo nano /etc/systemd/system/vpn-webui.service
|
||||
# Change --bind 0.0.0.0:5000 to your desired port
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl restart vpn-webui
|
||||
```
|
||||
|
||||
### Q: How do I backup my configuration?
|
||||
**A:**
|
||||
```bash
|
||||
sudo tar czf vpn-backup.tar.gz \
|
||||
/opt/vpn-gateway \
|
||||
/etc/wireguard \
|
||||
/etc/systemd/system/vpn-*.service
|
||||
```
|
||||
|
||||
### Q: How do I enable auto-reconnect?
|
||||
**A:** Auto-reconnect is handled by the security monitor. Ensure it's running:
|
||||
```bash
|
||||
sudo systemctl enable vpn-security-monitor
|
||||
sudo systemctl start vpn-security-monitor
|
||||
```
|
||||
|
||||
## Advanced
|
||||
|
||||
### Q: Can I use split tunneling?
|
||||
**A:** Yes, for custom servers. Modify the AllowedIPs in your WireGuard config:
|
||||
```ini
|
||||
# Only specific subnets through VPN
|
||||
AllowedIPs = 10.0.0.0/8, 172.16.0.0/12
|
||||
```
|
||||
|
||||
### Q: How do I set up failover?
|
||||
**A:** Add multiple peers in the WireGuard configuration:
|
||||
```ini
|
||||
[Peer]
|
||||
# Primary
|
||||
PublicKey = xxx...
|
||||
Endpoint = primary.example.com:51820
|
||||
|
||||
[Peer]
|
||||
# Backup
|
||||
PublicKey = yyy...
|
||||
Endpoint = backup.example.com:51820
|
||||
```
|
||||
|
||||
### Q: Can I monitor traffic statistics?
|
||||
**A:**
|
||||
```bash
|
||||
# WireGuard statistics
|
||||
wg show wg0 transfer
|
||||
|
||||
# Network statistics
|
||||
vnstat -i wg0
|
||||
|
||||
# Real-time monitoring
|
||||
iftop -i wg0
|
||||
```
|
||||
|
||||
### Q: How do I integrate with existing infrastructure?
|
||||
**A:**
|
||||
- Use as default gateway for network segments
|
||||
- Configure via DHCP options
|
||||
- Set up policy-based routing for specific clients
|
||||
|
||||
## Updates
|
||||
|
||||
### Q: How do I update the VPN Gateway?
|
||||
**A:**
|
||||
```bash
|
||||
sudo /usr/local/bin/vpn-update.sh
|
||||
```
|
||||
|
||||
### Q: Will updates break my configuration?
|
||||
**A:** No, updates preserve your configuration. Backups are created automatically.
|
||||
|
||||
### Q: How do I check for updates?
|
||||
**A:**
|
||||
```bash
|
||||
# Check current version
|
||||
cat /opt/vpn-gateway/version
|
||||
|
||||
# Check for updates
|
||||
curl -s https://raw.githubusercontent.com/yourusername/vpn-gateway/main/version
|
||||
```
|
||||
|
||||
## Support
|
||||
|
||||
### Q: Where can I get help?
|
||||
**A:**
|
||||
- GitHub Issues: https://github.com/yourusername/vpn-gateway/issues
|
||||
- Documentation: https://github.com/yourusername/vpn-gateway/wiki
|
||||
- Community Forum: [Link to forum]
|
||||
|
||||
### Q: How do I report a bug?
|
||||
**A:** Open an issue on GitHub with:
|
||||
- System information
|
||||
- Error messages
|
||||
- Steps to reproduce
|
||||
- Relevant logs
|
||||
|
||||
### Q: Can I contribute?
|
||||
**A:** Yes! Contributions are welcome:
|
||||
- Submit pull requests
|
||||
- Report bugs
|
||||
- Improve documentation
|
||||
- Share your setup
|
||||
|
||||
## Legal
|
||||
|
||||
### Q: Is this legal to use?
|
||||
**A:** Yes, but check your local laws regarding VPN usage. Some countries restrict VPN use.
|
||||
|
||||
### Q: Can I use this commercially?
|
||||
**A:** Yes, under the MIT license terms. See LICENSE file for details.
|
||||
|
||||
### Q: What about warranty?
|
||||
**A:** This software is provided "as is" without warranty. Use at your own risk.
|
339
docs/PROVIDERS.md
Normal file
339
docs/PROVIDERS.md
Normal file
|
@ -0,0 +1,339 @@
|
|||
# VPN Provider Configuration Guide
|
||||
|
||||
## Overview
|
||||
|
||||
The VPN Gateway supports three types of providers:
|
||||
1. **Mullvad VPN** - Commercial VPN service
|
||||
2. **Custom WireGuard** - Your own VPN server
|
||||
3. **Import Config** - Existing WireGuard configurations
|
||||
|
||||
## Mullvad VPN
|
||||
|
||||
### Setup
|
||||
|
||||
1. Get a Mullvad account at https://mullvad.net
|
||||
2. Note your 16-digit account number
|
||||
3. During installation, select "Mullvad" and enter your account number
|
||||
|
||||
### Features
|
||||
|
||||
- Automatic server list updates
|
||||
- 40+ countries available
|
||||
- Built-in DNS leak protection
|
||||
- No logging policy
|
||||
|
||||
### Server Selection
|
||||
|
||||
Servers are organized by:
|
||||
- **Country** (Sweden, Germany, USA, etc.)
|
||||
- **City** (Stockholm, Berlin, New York, etc.)
|
||||
- **Server** (se-sto-wg-001, de-ber-wg-002, etc.)
|
||||
|
||||
### Configuration
|
||||
|
||||
The system automatically:
|
||||
- Fetches current server list
|
||||
- Generates WireGuard keys
|
||||
- Configures DNS (100.64.0.1)
|
||||
- Sets up kill switch
|
||||
|
||||
## Custom WireGuard Server
|
||||
|
||||
### Prerequisites
|
||||
|
||||
You need:
|
||||
- A VPS or dedicated server
|
||||
- WireGuard installed on the server
|
||||
- Server public key
|
||||
- Open port (usually 51820)
|
||||
|
||||
### Server Setup (VPS Side)
|
||||
|
||||
#### 1. Install WireGuard
|
||||
```bash
|
||||
# Ubuntu/Debian
|
||||
sudo apt update
|
||||
sudo apt install wireguard
|
||||
|
||||
# CentOS/RHEL
|
||||
sudo yum install wireguard-tools
|
||||
```
|
||||
|
||||
#### 2. Generate Keys
|
||||
```bash
|
||||
cd /etc/wireguard
|
||||
wg genkey | tee server_private.key | wg pubkey > server_public.key
|
||||
```
|
||||
|
||||
#### 3. Configure Server
|
||||
```bash
|
||||
cat > /etc/wireguard/wg0.conf << EOF
|
||||
[Interface]
|
||||
PrivateKey = $(cat server_private.key)
|
||||
Address = 10.0.0.1/24
|
||||
ListenPort = 51820
|
||||
|
||||
# Enable IP forwarding
|
||||
PostUp = sysctl -w net.ipv4.ip_forward=1
|
||||
PostUp = iptables -A FORWARD -i wg0 -j ACCEPT
|
||||
PostUp = iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
|
||||
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT
|
||||
PostDown = iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
|
||||
|
||||
# Peer (VPN Gateway)
|
||||
[Peer]
|
||||
PublicKey = <GATEWAY_PUBLIC_KEY>
|
||||
AllowedIPs = 10.0.0.2/32
|
||||
EOF
|
||||
```
|
||||
|
||||
#### 4. Start WireGuard
|
||||
```bash
|
||||
sudo systemctl enable wg-quick@wg0
|
||||
sudo systemctl start wg-quick@wg0
|
||||
```
|
||||
|
||||
### Gateway Setup (Client Side)
|
||||
|
||||
During installation, provide:
|
||||
- **Endpoint**: Your server's IP:Port (e.g., 1.2.3.4:51820)
|
||||
- **Server Public Key**: From server_public.key
|
||||
- **Client IP**: Usually 10.0.0.2/32
|
||||
- **DNS**: 1.1.1.1,1.0.0.1 or your preferred DNS
|
||||
|
||||
### Adding Multiple Servers
|
||||
|
||||
Via WebUI:
|
||||
1. Go to "Custom Server" tab
|
||||
2. Click "Add New Server"
|
||||
3. Fill in server details
|
||||
4. Save configuration
|
||||
|
||||
Via API:
|
||||
```bash
|
||||
curl -X POST http://gateway-ip/api/custom/add \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"name": "my-vps-us",
|
||||
"endpoint": "us.example.com:51820",
|
||||
"public_key": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=",
|
||||
"location": "United States"
|
||||
}'
|
||||
```
|
||||
|
||||
## Import Existing Configuration
|
||||
|
||||
### Supported Formats
|
||||
|
||||
- Standard WireGuard .conf files
|
||||
- Configs from any WireGuard provider
|
||||
- Custom peer configurations
|
||||
|
||||
### Import Methods
|
||||
|
||||
#### Via WebUI
|
||||
1. Select "Import Config" tab
|
||||
2. Choose file or paste configuration
|
||||
3. Provide a name for the config
|
||||
4. Click "Import"
|
||||
|
||||
#### Via CLI
|
||||
```bash
|
||||
# Copy config to gateway
|
||||
scp myconfig.conf root@gateway-ip:/tmp/
|
||||
|
||||
# Import via API
|
||||
curl -X POST http://gateway-ip/api/import \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"name": "imported-config",
|
||||
"config": "'"$(cat /tmp/myconfig.conf)"'"
|
||||
}'
|
||||
```
|
||||
|
||||
### Automatic Modifications
|
||||
|
||||
The system automatically:
|
||||
- Adds killswitch rules if missing
|
||||
- Preserves original settings
|
||||
- Validates configuration syntax
|
||||
|
||||
### Example Configuration
|
||||
|
||||
```ini
|
||||
[Interface]
|
||||
PrivateKey = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=
|
||||
Address = 10.8.0.2/32
|
||||
DNS = 1.1.1.1
|
||||
|
||||
[Peer]
|
||||
PublicKey = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=
|
||||
AllowedIPs = 0.0.0.0/0
|
||||
Endpoint = vpn.example.com:51820
|
||||
PersistentKeepalive = 25
|
||||
```
|
||||
|
||||
## Provider Switching
|
||||
|
||||
### Via WebUI
|
||||
|
||||
1. Click on provider tabs
|
||||
2. System automatically switches backend
|
||||
3. Previous provider settings are preserved
|
||||
|
||||
### Via API
|
||||
|
||||
```bash
|
||||
# Switch to Mullvad
|
||||
curl -X POST http://gateway-ip/api/provider/mullvad
|
||||
|
||||
# Switch to Custom
|
||||
curl -X POST http://gateway-ip/api/provider/custom
|
||||
|
||||
# Switch to Imported
|
||||
curl -X POST http://gateway-ip/api/provider/imported
|
||||
```
|
||||
|
||||
## Advanced Configuration
|
||||
|
||||
### Split Tunneling
|
||||
|
||||
For custom servers, modify AllowedIPs:
|
||||
```ini
|
||||
# Route only specific subnets through VPN
|
||||
AllowedIPs = 10.0.0.0/8, 192.168.0.0/16
|
||||
|
||||
# Route everything except local network
|
||||
AllowedIPs = 0.0.0.0/1, 128.0.0.0/1
|
||||
```
|
||||
|
||||
### Multiple Peers (Failover)
|
||||
|
||||
```ini
|
||||
[Peer]
|
||||
# Primary server
|
||||
PublicKey = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=
|
||||
AllowedIPs = 0.0.0.0/0
|
||||
Endpoint = primary.example.com:51820
|
||||
|
||||
[Peer]
|
||||
# Backup server
|
||||
PublicKey = yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy=
|
||||
AllowedIPs = 0.0.0.0/0
|
||||
Endpoint = backup.example.com:51820
|
||||
```
|
||||
|
||||
### Custom DNS
|
||||
|
||||
Modify DNS in the configuration:
|
||||
```ini
|
||||
# CloudFlare
|
||||
DNS = 1.1.1.1, 1.0.0.1
|
||||
|
||||
# Quad9
|
||||
DNS = 9.9.9.9, 149.112.112.112
|
||||
|
||||
# Custom/Local
|
||||
DNS = 192.168.1.1
|
||||
```
|
||||
|
||||
## Performance Optimization
|
||||
|
||||
### MTU Settings
|
||||
|
||||
For optimal performance:
|
||||
```ini
|
||||
[Interface]
|
||||
MTU = 1420 # Default, works for most connections
|
||||
# MTU = 1380 # For problematic connections
|
||||
# MTU = 1280 # Maximum compatibility
|
||||
```
|
||||
|
||||
### Persistent Keepalive
|
||||
|
||||
Adjust based on your needs:
|
||||
```ini
|
||||
# For stable connections
|
||||
PersistentKeepalive = 25
|
||||
|
||||
# For NAT/firewall traversal
|
||||
PersistentKeepalive = 10
|
||||
|
||||
# Disable for on-demand
|
||||
# PersistentKeepalive = 0
|
||||
```
|
||||
|
||||
## Troubleshooting Providers
|
||||
|
||||
### Mullvad Issues
|
||||
|
||||
```bash
|
||||
# Check account status
|
||||
curl https://api.mullvad.net/www/accounts/<account-number>/
|
||||
|
||||
# Test server connectivity
|
||||
ping -c 1 <server-ip>
|
||||
|
||||
# Verify WireGuard keys
|
||||
wg show wg0 public-key
|
||||
```
|
||||
|
||||
### Custom Server Issues
|
||||
|
||||
```bash
|
||||
# Test connectivity
|
||||
nc -zv <server-ip> 51820
|
||||
|
||||
# Check server logs (on VPS)
|
||||
sudo journalctl -u wg-quick@wg0 -f
|
||||
|
||||
# Verify keys match
|
||||
echo "<public-key>" | base64 -d | wc -c # Should be 32
|
||||
```
|
||||
|
||||
### Import Issues
|
||||
|
||||
```bash
|
||||
# Validate config syntax
|
||||
wg-quick strip /path/to/config.conf
|
||||
|
||||
# Test config manually
|
||||
sudo wg-quick up /tmp/test.conf
|
||||
sudo wg-quick down /tmp/test.conf
|
||||
```
|
||||
|
||||
## Security Considerations
|
||||
|
||||
### Key Management
|
||||
|
||||
- Never share private keys
|
||||
- Rotate keys periodically
|
||||
- Use unique keys per device/gateway
|
||||
|
||||
### Server Hardening
|
||||
|
||||
For custom servers:
|
||||
```bash
|
||||
# Firewall rules
|
||||
ufw allow 51820/udp
|
||||
ufw allow from 10.0.0.0/24
|
||||
|
||||
# Disable password auth
|
||||
sed -i 's/PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config
|
||||
|
||||
# Enable automatic updates
|
||||
apt install unattended-upgrades
|
||||
```
|
||||
|
||||
### Monitoring
|
||||
|
||||
```bash
|
||||
# Connection status
|
||||
wg show
|
||||
|
||||
# Traffic statistics
|
||||
wg show wg0 transfer
|
||||
|
||||
# Active connections
|
||||
netstat -tunlp | grep 51820
|
||||
```
|
211
docs/QUICKSTART.md
Normal file
211
docs/QUICKSTART.md
Normal file
|
@ -0,0 +1,211 @@
|
|||
# Quick Start Guide
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- LXC Container with Ubuntu/Debian
|
||||
- Root access
|
||||
- Internet connection for initial setup
|
||||
|
||||
## Installation
|
||||
|
||||
### 1. One-Line Install
|
||||
|
||||
```bash
|
||||
curl -sSL https://raw.githubusercontent.com/yourusername/vpn-gateway/main/install.sh | bash
|
||||
```
|
||||
|
||||
### 2. Manual Install
|
||||
|
||||
```bash
|
||||
# Clone repository
|
||||
git clone https://github.com/yourusername/vpn-gateway.git
|
||||
cd vpn-gateway
|
||||
|
||||
# Run installer
|
||||
sudo ./install.sh
|
||||
```
|
||||
|
||||
## Initial Setup
|
||||
|
||||
### Step 1: Network Detection
|
||||
|
||||
The installer will auto-detect your network configuration:
|
||||
- Network interface (e.g., eth0)
|
||||
- LAN subnet (e.g., 192.168.1.0/24)
|
||||
- Container IP address
|
||||
|
||||
Confirm or modify as needed.
|
||||
|
||||
### Step 2: Choose Provider
|
||||
|
||||
Select your VPN provider:
|
||||
|
||||
#### Option 1: Mullvad VPN
|
||||
```
|
||||
Select provider [1-3]: 1
|
||||
Enter your Mullvad account number: 1234567890123456
|
||||
```
|
||||
|
||||
#### Option 2: Custom WireGuard Server
|
||||
```
|
||||
Select provider [1-3]: 2
|
||||
Server endpoint (IP:Port): 1.2.3.4:51820
|
||||
Server public key: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=
|
||||
```
|
||||
|
||||
#### Option 3: Import Configuration
|
||||
```
|
||||
Select provider [1-3]: 3
|
||||
Path to WireGuard config: /path/to/config.conf
|
||||
```
|
||||
|
||||
### Step 3: Complete Installation
|
||||
|
||||
The installer will:
|
||||
1. Install dependencies
|
||||
2. Configure killswitch
|
||||
3. Set up WebUI
|
||||
4. Start services
|
||||
|
||||
## Using the WebUI
|
||||
|
||||
### Access the Interface
|
||||
|
||||
Open your browser and navigate to:
|
||||
```
|
||||
http://<container-ip>
|
||||
```
|
||||
|
||||
### Connect to VPN
|
||||
|
||||
1. **Select Location** (Mullvad only)
|
||||
- Choose country
|
||||
- Choose city
|
||||
- Choose server
|
||||
|
||||
2. **Click Connect**
|
||||
- Connection established in ~2-5 seconds
|
||||
- Status indicator turns green
|
||||
|
||||
3. **Verify Connection**
|
||||
- Check public IP displayed
|
||||
- Verify location shown
|
||||
|
||||
### Disconnect from VPN
|
||||
|
||||
1. Click **Disconnect** button
|
||||
2. **WARNING**: No internet access after disconnect (killswitch active)
|
||||
|
||||
## Client Configuration
|
||||
|
||||
### Configure Your Devices
|
||||
|
||||
Set on each client device:
|
||||
|
||||
#### Windows
|
||||
1. Network Settings → IPv4 Properties
|
||||
2. Default Gateway: `<container-ip>`
|
||||
3. DNS Server: `<container-ip>` or `1.1.1.1`
|
||||
|
||||
#### Linux
|
||||
```bash
|
||||
# Temporary
|
||||
sudo ip route del default
|
||||
sudo ip route add default via <container-ip>
|
||||
echo "nameserver <container-ip>" | sudo tee /etc/resolv.conf
|
||||
|
||||
# Permanent (NetworkManager)
|
||||
nmcli connection modify <connection-name> ipv4.gateway <container-ip>
|
||||
nmcli connection modify <connection-name> ipv4.dns <container-ip>
|
||||
```
|
||||
|
||||
#### macOS
|
||||
1. System Preferences → Network
|
||||
2. Advanced → TCP/IP
|
||||
3. Router: `<container-ip>`
|
||||
4. DNS: `<container-ip>`
|
||||
|
||||
## Quick Commands
|
||||
|
||||
### Check Status
|
||||
```bash
|
||||
# Service status
|
||||
sudo systemctl status vpn-webui
|
||||
|
||||
# Connection status
|
||||
curl http://localhost:5000/api/status
|
||||
|
||||
# Health check
|
||||
sudo /usr/local/bin/vpn-health-check.sh
|
||||
```
|
||||
|
||||
### View Logs
|
||||
```bash
|
||||
# All logs
|
||||
sudo journalctl -u vpn-webui -u vpn-killswitch -f
|
||||
|
||||
# WebUI logs only
|
||||
sudo journalctl -u vpn-webui -f
|
||||
```
|
||||
|
||||
### Restart Services
|
||||
```bash
|
||||
sudo systemctl restart vpn-webui
|
||||
sudo systemctl restart vpn-security-monitor
|
||||
```
|
||||
|
||||
## Important Notes
|
||||
|
||||
⚠️ **Killswitch Always Active**
|
||||
- No internet without VPN connection
|
||||
- This is intentional for security
|
||||
- Local network still accessible
|
||||
|
||||
⚠️ **After Disconnect**
|
||||
- Internet blocked until reconnection
|
||||
- WebUI remains accessible
|
||||
- Connect to VPN to restore internet
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### WebUI Not Accessible
|
||||
```bash
|
||||
# Check if service is running
|
||||
sudo systemctl status vpn-webui
|
||||
|
||||
# Check if port is listening
|
||||
sudo netstat -tlnp | grep 5000
|
||||
|
||||
# Restart service
|
||||
sudo systemctl restart vpn-webui
|
||||
```
|
||||
|
||||
### No Internet After Connect
|
||||
```bash
|
||||
# Check VPN status
|
||||
sudo wg show
|
||||
|
||||
# Check killswitch
|
||||
sudo iptables -L -n -v
|
||||
|
||||
# Check DNS
|
||||
nslookup google.com
|
||||
```
|
||||
|
||||
### Can't Connect to VPN
|
||||
```bash
|
||||
# Check logs
|
||||
sudo journalctl -u vpn-webui -n 50
|
||||
|
||||
# Test killswitch
|
||||
sudo /usr/local/bin/vpn-killswitch.sh verify
|
||||
|
||||
# Manual connection test
|
||||
sudo wg-quick up wg0
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
- Read [Provider Configuration](PROVIDERS.md) for advanced setup
|
||||
- Review [Security Documentation](SECURITY.md) for security features
|
||||
- See [FAQ](FAQ.md) for common questions
|
77
docs/README.md
Normal file
77
docs/README.md
Normal file
|
@ -0,0 +1,77 @@
|
|||
# VPN Gateway Documentation
|
||||
|
||||
## Overview
|
||||
|
||||
This documentation covers the VPN Gateway multi-provider system with permanent killswitch protection.
|
||||
|
||||
## Contents
|
||||
|
||||
- [Quick Start Guide](QUICKSTART.md) - Get up and running in minutes
|
||||
- [Provider Configuration](PROVIDERS.md) - Detailed provider setup
|
||||
- [Security Documentation](SECURITY.md) - Security features and best practices
|
||||
- [API Reference](API.md) - WebUI API endpoints
|
||||
- [Troubleshooting](TROUBLESHOOTING.md) - Common issues and solutions
|
||||
- [FAQ](FAQ.md) - Frequently asked questions
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────┐
|
||||
│ Client Devices │
|
||||
└────────────┬────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────┐
|
||||
│ VPN Gateway Container │
|
||||
│ ┌─────────────────────────────┐ │
|
||||
│ │ WebUI (Port 80/5000) │ │
|
||||
│ └──────────┬──────────────────┘ │
|
||||
│ │ │
|
||||
│ ┌──────────▼──────────────────┐ │
|
||||
│ │ Flask Backend (Python) │ │
|
||||
│ └──────────┬──────────────────┘ │
|
||||
│ │ │
|
||||
│ ┌──────────▼──────────────────┐ │
|
||||
│ │ WireGuard Interface (wg0) │ │
|
||||
│ └──────────┬──────────────────┘ │
|
||||
│ │ │
|
||||
│ ┌──────────▼──────────────────┐ │
|
||||
│ │ Killswitch (iptables) │ │
|
||||
│ └──────────┬──────────────────┘ │
|
||||
└─────────────┼───────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌──────────────────┐
|
||||
│ VPN Provider │
|
||||
│ • Mullvad │
|
||||
│ • Custom Server │
|
||||
│ • Imported Config│
|
||||
└──────────────────┘
|
||||
```
|
||||
|
||||
## Key Components
|
||||
|
||||
### 1. Killswitch
|
||||
- Permanent firewall rules
|
||||
- Blocks all non-VPN traffic
|
||||
- Cannot be disabled via UI
|
||||
|
||||
### 2. WebUI
|
||||
- Modern responsive interface
|
||||
- Real-time status monitoring
|
||||
- Multi-provider support
|
||||
|
||||
### 3. Backend
|
||||
- Flask-based API
|
||||
- Provider management
|
||||
- Connection handling
|
||||
|
||||
### 4. Security Monitor
|
||||
- Continuous monitoring
|
||||
- Leak detection
|
||||
- Auto-recovery
|
||||
|
||||
## Support
|
||||
|
||||
- GitHub Issues: https://github.com/yourusername/vpn-gateway/issues
|
||||
- Documentation: https://github.com/yourusername/vpn-gateway/wiki
|
404
docs/SECURITY.md
Normal file
404
docs/SECURITY.md
Normal file
|
@ -0,0 +1,404 @@
|
|||
# Security Documentation
|
||||
|
||||
## Overview
|
||||
|
||||
The VPN Gateway implements multiple layers of security to ensure zero-leak protection and maintain privacy.
|
||||
|
||||
## Core Security Features
|
||||
|
||||
### 1. Permanent Killswitch
|
||||
|
||||
The killswitch is the primary security mechanism that prevents any traffic leaks.
|
||||
|
||||
#### Implementation
|
||||
|
||||
- **Firewall Rules**: Default DROP policy for all chains
|
||||
- **Boot Protection**: Activates before network initialization
|
||||
- **Cannot be Disabled**: No UI or API endpoint to disable
|
||||
- **Continuous Monitoring**: Verified every 10 seconds
|
||||
|
||||
#### Technical Details
|
||||
|
||||
```bash
|
||||
# Default policies
|
||||
iptables -P INPUT DROP
|
||||
iptables -P FORWARD DROP
|
||||
iptables -P OUTPUT DROP
|
||||
|
||||
# Only allowed traffic:
|
||||
# - Loopback (system operations)
|
||||
# - LAN subnet (WebUI access)
|
||||
# - Established connections
|
||||
# - VPN tunnel (when active)
|
||||
```
|
||||
|
||||
### 2. DNS Leak Protection
|
||||
|
||||
#### Mechanisms
|
||||
|
||||
1. **Forced VPN DNS**: All DNS queries routed through VPN
|
||||
2. **System DNS Override**: /etc/resolv.conf locked
|
||||
3. **IPv6 Disabled**: Prevents IPv6 DNS leaks
|
||||
4. **DNS Filtering**: Only root can make DNS queries for VPN connection
|
||||
|
||||
#### Configuration
|
||||
|
||||
```bash
|
||||
# DNS through VPN only
|
||||
iptables -A OUTPUT -p udp --dport 53 -m owner --uid-owner root -j ACCEPT
|
||||
iptables -A OUTPUT -p tcp --dport 53 -m owner --uid-owner root -j ACCEPT
|
||||
|
||||
# Block all other DNS
|
||||
iptables -A OUTPUT -p udp --dport 53 -j DROP
|
||||
iptables -A OUTPUT -p tcp --dport 53 -j DROP
|
||||
```
|
||||
|
||||
### 3. IPv6 Protection
|
||||
|
||||
Complete IPv6 blocking to prevent leaks:
|
||||
|
||||
```bash
|
||||
# IPv6 firewall
|
||||
ip6tables -P INPUT DROP
|
||||
ip6tables -P FORWARD DROP
|
||||
ip6tables -P OUTPUT DROP
|
||||
|
||||
# Kernel level
|
||||
sysctl -w net.ipv6.conf.all.disable_ipv6=1
|
||||
sysctl -w net.ipv6.conf.default.disable_ipv6=1
|
||||
```
|
||||
|
||||
### 4. Security Monitor
|
||||
|
||||
Continuous monitoring daemon that:
|
||||
- Verifies killswitch every 10 seconds
|
||||
- Detects potential leaks
|
||||
- Auto-recovers from failures
|
||||
- Logs security events
|
||||
|
||||
## Threat Model
|
||||
|
||||
### Protected Against
|
||||
|
||||
✅ **IP Leaks**
|
||||
- Killswitch blocks all non-VPN traffic
|
||||
- No traffic possible without active tunnel
|
||||
|
||||
✅ **DNS Leaks**
|
||||
- All DNS through VPN
|
||||
- System DNS locked
|
||||
- IPv6 DNS blocked
|
||||
|
||||
✅ **WebRTC Leaks**
|
||||
- Blocked at firewall level
|
||||
- No direct peer connections
|
||||
|
||||
✅ **IPv6 Leaks**
|
||||
- IPv6 completely disabled
|
||||
- Both firewall and kernel level
|
||||
|
||||
✅ **Connection Drops**
|
||||
- Killswitch remains active
|
||||
- No traffic during reconnection
|
||||
- Auto-recovery available
|
||||
|
||||
✅ **Malicious Applications**
|
||||
- Cannot bypass firewall rules
|
||||
- All traffic subject to killswitch
|
||||
|
||||
### Not Protected Against
|
||||
|
||||
❌ **Compromised Container**
|
||||
- If attacker gains root access
|
||||
- Can modify firewall rules
|
||||
|
||||
❌ **Host System Compromise**
|
||||
- Container isolation breach
|
||||
- Hypervisor vulnerabilities
|
||||
|
||||
❌ **Traffic Analysis**
|
||||
- VPN traffic patterns visible
|
||||
- Timing correlation attacks
|
||||
|
||||
❌ **VPN Provider Compromise**
|
||||
- Malicious VPN server
|
||||
- Provider logging (choose carefully)
|
||||
|
||||
## Security Best Practices
|
||||
|
||||
### 1. Installation Security
|
||||
|
||||
```bash
|
||||
# Verify installer integrity
|
||||
sha256sum install.sh
|
||||
# Compare with published hash
|
||||
|
||||
# Review script before execution
|
||||
less install.sh
|
||||
|
||||
# Run with specific version
|
||||
curl -sSL https://raw.githubusercontent.com/yourusername/vpn-gateway/v1.0.0/install.sh | bash
|
||||
```
|
||||
|
||||
### 2. Access Control
|
||||
|
||||
#### WebUI Protection
|
||||
|
||||
```nginx
|
||||
# Restrict WebUI access to LAN only
|
||||
location / {
|
||||
allow 192.168.1.0/24;
|
||||
deny all;
|
||||
# ... proxy settings
|
||||
}
|
||||
```
|
||||
|
||||
#### SSH Hardening
|
||||
|
||||
```bash
|
||||
# Disable password authentication
|
||||
PasswordAuthentication no
|
||||
|
||||
# Key-only access
|
||||
PubkeyAuthentication yes
|
||||
|
||||
# Restrict to specific IPs
|
||||
AllowUsers root@192.168.1.0/24
|
||||
```
|
||||
|
||||
### 3. Key Management
|
||||
|
||||
#### WireGuard Keys
|
||||
|
||||
```bash
|
||||
# Generate new keys periodically
|
||||
wg genkey | tee privatekey | wg pubkey > publickey
|
||||
|
||||
# Secure storage
|
||||
chmod 600 /etc/wireguard/*.key
|
||||
|
||||
# Never share private keys
|
||||
# Unique keys per gateway
|
||||
```
|
||||
|
||||
#### Rotation Schedule
|
||||
|
||||
- **Private Keys**: Every 3-6 months
|
||||
- **Preshared Keys**: Every 1-3 months
|
||||
- **API Keys**: Every 30 days
|
||||
|
||||
### 4. Monitoring
|
||||
|
||||
#### Security Logs
|
||||
|
||||
```bash
|
||||
# Monitor security events
|
||||
journalctl -u vpn-security-monitor -f
|
||||
|
||||
# Check for failures
|
||||
grep "ALERT\|ERROR" /var/log/vpn-security-monitor.log
|
||||
|
||||
# Audit firewall drops
|
||||
iptables -L -n -v | grep DROP
|
||||
```
|
||||
|
||||
#### Leak Testing
|
||||
|
||||
```bash
|
||||
# Regular leak tests
|
||||
curl https://ipleak.net/json/
|
||||
curl https://am.i.mullvad.net/json
|
||||
|
||||
# DNS leak test
|
||||
nslookup example.com
|
||||
dig example.com
|
||||
```
|
||||
|
||||
### 5. Updates
|
||||
|
||||
#### Security Updates
|
||||
|
||||
```bash
|
||||
# System updates (through VPN)
|
||||
apt update && apt upgrade
|
||||
|
||||
# VPN Gateway updates
|
||||
/usr/local/bin/vpn-update.sh
|
||||
|
||||
# Check for security advisories
|
||||
```
|
||||
|
||||
#### Automatic Updates
|
||||
|
||||
```bash
|
||||
# Enable unattended upgrades
|
||||
apt install unattended-upgrades
|
||||
dpkg-reconfigure -plow unattended-upgrades
|
||||
```
|
||||
|
||||
## Incident Response
|
||||
|
||||
### 1. Leak Detected
|
||||
|
||||
If a leak is detected:
|
||||
|
||||
1. **Immediate Action**
|
||||
```bash
|
||||
# Re-enable killswitch
|
||||
/usr/local/bin/vpn-killswitch.sh enable
|
||||
|
||||
# Disconnect VPN
|
||||
wg-quick down wg0
|
||||
```
|
||||
|
||||
2. **Investigation**
|
||||
```bash
|
||||
# Check logs
|
||||
journalctl -u vpn-security-monitor -n 100
|
||||
|
||||
# Verify firewall rules
|
||||
iptables -L -n -v
|
||||
```
|
||||
|
||||
3. **Recovery**
|
||||
```bash
|
||||
# Restart security services
|
||||
systemctl restart vpn-killswitch
|
||||
systemctl restart vpn-security-monitor
|
||||
```
|
||||
|
||||
### 2. Suspicious Activity
|
||||
|
||||
Signs of compromise:
|
||||
- Unexpected firewall rule changes
|
||||
- Unknown processes with network access
|
||||
- Unusual CPU/memory usage
|
||||
- Modified system files
|
||||
|
||||
Response:
|
||||
```bash
|
||||
# Check processes
|
||||
netstat -tulpn
|
||||
ps aux | grep -v grep | grep wg
|
||||
|
||||
# Check file integrity
|
||||
debsums -c
|
||||
find /etc -type f -mtime -1
|
||||
|
||||
# Review auth logs
|
||||
grep "Failed\|Invalid" /var/log/auth.log
|
||||
```
|
||||
|
||||
### 3. Emergency Shutdown
|
||||
|
||||
If immediate isolation needed:
|
||||
|
||||
```bash
|
||||
# Block ALL network traffic
|
||||
iptables -P INPUT DROP
|
||||
iptables -P OUTPUT DROP
|
||||
iptables -P FORWARD DROP
|
||||
iptables -F
|
||||
|
||||
# Stop services
|
||||
systemctl stop vpn-webui
|
||||
systemctl stop wg-quick@wg0
|
||||
|
||||
# Preserve evidence
|
||||
tar czf /tmp/evidence-$(date +%s).tar.gz \
|
||||
/var/log \
|
||||
/etc/wireguard \
|
||||
/opt/vpn-gateway/logs
|
||||
```
|
||||
|
||||
## Security Hardening
|
||||
|
||||
### 1. Container Hardening
|
||||
|
||||
```bash
|
||||
# Limit capabilities
|
||||
lxc config set <container> security.nesting false
|
||||
lxc config set <container> security.privileged false
|
||||
|
||||
# Resource limits
|
||||
lxc config set <container> limits.memory 512MB
|
||||
lxc config set <container> limits.cpu 1
|
||||
```
|
||||
|
||||
### 2. Network Hardening
|
||||
|
||||
```bash
|
||||
# Rate limiting
|
||||
iptables -A INPUT -p tcp --dport 5000 \
|
||||
-m conntrack --ctstate NEW \
|
||||
-m limit --limit 10/min --limit-burst 5 \
|
||||
-j ACCEPT
|
||||
|
||||
# SYN flood protection
|
||||
echo 1 > /proc/sys/net/ipv4/tcp_syncookies
|
||||
echo 2048 > /proc/sys/net/ipv4/tcp_max_syn_backlog
|
||||
```
|
||||
|
||||
### 3. Application Hardening
|
||||
|
||||
```python
|
||||
# Flask security headers
|
||||
from flask import Flask
|
||||
from flask_talisman import Talisman
|
||||
|
||||
app = Flask(__name__)
|
||||
Talisman(app,
|
||||
force_https=False, # Handle at reverse proxy
|
||||
strict_transport_security=True,
|
||||
content_security_policy={
|
||||
'default-src': "'self'"
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
## Compliance
|
||||
|
||||
### GDPR Compliance
|
||||
|
||||
- No personal data logging
|
||||
- User control over data
|
||||
- Right to deletion
|
||||
- Transparent processing
|
||||
|
||||
### Security Standards
|
||||
|
||||
- CIS Benchmarks compliance
|
||||
- NIST framework alignment
|
||||
- Zero-trust architecture
|
||||
- Defense in depth
|
||||
|
||||
## Security Checklist
|
||||
|
||||
### Daily
|
||||
- [ ] Check service status
|
||||
- [ ] Review security logs
|
||||
- [ ] Verify killswitch active
|
||||
|
||||
### Weekly
|
||||
- [ ] Run leak tests
|
||||
- [ ] Check for updates
|
||||
- [ ] Review firewall rules
|
||||
|
||||
### Monthly
|
||||
- [ ] Rotate keys
|
||||
- [ ] Audit access logs
|
||||
- [ ] Update documentation
|
||||
|
||||
### Quarterly
|
||||
- [ ] Security assessment
|
||||
- [ ] Penetration testing
|
||||
- [ ] Disaster recovery test
|
||||
|
||||
## Contact
|
||||
|
||||
For security issues:
|
||||
- **Email**: security@yourdomain.com
|
||||
- **PGP Key**: [Public key]
|
||||
- **Response Time**: < 24 hours for critical issues
|
||||
|
||||
Please report security vulnerabilities responsibly.
|
425
docs/TROUBLESHOOTING.md
Normal file
425
docs/TROUBLESHOOTING.md
Normal file
|
@ -0,0 +1,425 @@
|
|||
# Troubleshooting Guide
|
||||
|
||||
## Common Issues and Solutions
|
||||
|
||||
### Installation Issues
|
||||
|
||||
#### Problem: Installation fails with dependency errors
|
||||
```bash
|
||||
E: Unable to locate package wireguard
|
||||
```
|
||||
|
||||
**Solution:**
|
||||
```bash
|
||||
# Update package lists
|
||||
sudo apt update
|
||||
|
||||
# Enable backports (Debian)
|
||||
echo "deb http://deb.debian.org/debian $(lsb_release -cs)-backports main" | \
|
||||
sudo tee /etc/apt/sources.list.d/backports.list
|
||||
sudo apt update
|
||||
|
||||
# Install kernel headers
|
||||
sudo apt install linux-headers-$(uname -r)
|
||||
|
||||
# Retry installation
|
||||
sudo ./install.sh
|
||||
```
|
||||
|
||||
#### Problem: WireGuard module not loading
|
||||
```bash
|
||||
modprobe: FATAL: Module wireguard not found
|
||||
```
|
||||
|
||||
**Solution:**
|
||||
```bash
|
||||
# Install WireGuard kernel module
|
||||
sudo apt install wireguard-dkms
|
||||
|
||||
# Load module manually
|
||||
sudo modprobe wireguard
|
||||
|
||||
# Verify module loaded
|
||||
lsmod | grep wireguard
|
||||
```
|
||||
|
||||
### Connection Issues
|
||||
|
||||
#### Problem: VPN won't connect
|
||||
**Symptoms:** Connection timeout, no handshake
|
||||
|
||||
**Solutions:**
|
||||
|
||||
1. **Check server reachability:**
|
||||
```bash
|
||||
# Ping server (if ICMP allowed)
|
||||
ping -c 3 <server-ip>
|
||||
|
||||
# Check port connectivity
|
||||
nc -zv <server-ip> 51820
|
||||
|
||||
# Trace route
|
||||
traceroute <server-ip>
|
||||
```
|
||||
|
||||
2. **Verify credentials:**
|
||||
```bash
|
||||
# Check keys format
|
||||
wg show wg0 private-key
|
||||
wg show wg0 public-key
|
||||
|
||||
# Verify key length (should be 44 characters)
|
||||
echo "<key>" | wc -c
|
||||
```
|
||||
|
||||
3. **Check firewall:**
|
||||
```bash
|
||||
# Ensure UDP 51820 outbound is allowed
|
||||
sudo iptables -L OUTPUT -n -v | grep 51820
|
||||
|
||||
# Temporarily allow all outbound (testing only!)
|
||||
sudo iptables -I OUTPUT 1 -j ACCEPT
|
||||
```
|
||||
|
||||
4. **Review logs:**
|
||||
```bash
|
||||
# Check WebUI logs
|
||||
sudo journalctl -u vpn-webui -n 100
|
||||
|
||||
# Check WireGuard logs
|
||||
sudo dmesg | grep wireguard
|
||||
|
||||
# Enable debug logging
|
||||
echo 'module wireguard +p' | sudo tee /sys/kernel/debug/dynamic_debug/control
|
||||
```
|
||||
|
||||
#### Problem: Connection drops frequently
|
||||
**Symptoms:** Intermittent connectivity, handshake failures
|
||||
|
||||
**Solutions:**
|
||||
|
||||
1. **Adjust keepalive:**
|
||||
```bash
|
||||
# Edit WireGuard config
|
||||
sudo nano /etc/wireguard/wg0.conf
|
||||
|
||||
# Reduce keepalive interval
|
||||
PersistentKeepalive = 10 # Instead of 25
|
||||
```
|
||||
|
||||
2. **Check MTU:**
|
||||
```bash
|
||||
# Test different MTU values
|
||||
sudo ip link set dev wg0 mtu 1380
|
||||
|
||||
# Find optimal MTU
|
||||
ping -M do -s 1372 <vpn-server-ip>
|
||||
# Increase/decrease -s value until it works
|
||||
```
|
||||
|
||||
3. **Monitor connection:**
|
||||
```bash
|
||||
# Watch handshakes
|
||||
watch -n 1 'wg show wg0 latest-handshakes'
|
||||
|
||||
# Check for packet loss
|
||||
mtr <vpn-server-ip>
|
||||
```
|
||||
|
||||
### Network Issues
|
||||
|
||||
#### Problem: No internet after connecting
|
||||
**Symptoms:** VPN connected but can't browse
|
||||
|
||||
**Solutions:**
|
||||
|
||||
1. **Check routing:**
|
||||
```bash
|
||||
# Show routing table
|
||||
ip route show
|
||||
|
||||
# Verify default route through VPN
|
||||
ip route | grep default
|
||||
|
||||
# Add route manually if missing
|
||||
sudo ip route add default dev wg0
|
||||
```
|
||||
|
||||
2. **Check DNS:**
|
||||
```bash
|
||||
# Test DNS resolution
|
||||
nslookup google.com
|
||||
dig google.com
|
||||
|
||||
# Check DNS config
|
||||
cat /etc/resolv.conf
|
||||
|
||||
# Force DNS update
|
||||
echo "nameserver 1.1.1.1" | sudo tee /etc/resolv.conf
|
||||
```
|
||||
|
||||
3. **Check NAT:**
|
||||
```bash
|
||||
# Verify NAT rules
|
||||
sudo iptables -t nat -L POSTROUTING -n -v
|
||||
|
||||
# Add NAT manually if missing
|
||||
sudo iptables -t nat -A POSTROUTING -o wg0 -j MASQUERADE
|
||||
```
|
||||
|
||||
#### Problem: Local network not accessible
|
||||
**Symptoms:** Can't reach LAN devices when VPN connected
|
||||
|
||||
**Solution:**
|
||||
```bash
|
||||
# Add LAN route exception
|
||||
sudo ip route add 192.168.1.0/24 dev eth0
|
||||
|
||||
# Or modify WireGuard AllowedIPs
|
||||
# Change from: 0.0.0.0/0
|
||||
# To: 0.0.0.0/1, 128.0.0.0/1
|
||||
```
|
||||
|
||||
### WebUI Issues
|
||||
|
||||
#### Problem: WebUI not loading
|
||||
**Symptoms:** Connection refused, timeout
|
||||
|
||||
**Solutions:**
|
||||
|
||||
1. **Check service status:**
|
||||
```bash
|
||||
# Service status
|
||||
sudo systemctl status vpn-webui
|
||||
|
||||
# Restart service
|
||||
sudo systemctl restart vpn-webui
|
||||
|
||||
# Check if running
|
||||
ps aux | grep gunicorn
|
||||
```
|
||||
|
||||
2. **Check port binding:**
|
||||
```bash
|
||||
# Verify port is listening
|
||||
sudo netstat -tlnp | grep 5000
|
||||
sudo ss -tlnp | grep 5000
|
||||
|
||||
# Check for port conflicts
|
||||
sudo lsof -i :5000
|
||||
```
|
||||
|
||||
3. **Check Nginx (if used):**
|
||||
```bash
|
||||
# Test Nginx config
|
||||
sudo nginx -t
|
||||
|
||||
# Restart Nginx
|
||||
sudo systemctl restart nginx
|
||||
|
||||
# Check Nginx logs
|
||||
sudo tail -f /var/log/nginx/error.log
|
||||
```
|
||||
|
||||
#### Problem: Can't change settings
|
||||
**Symptoms:** Changes don't save, errors on submit
|
||||
|
||||
**Solutions:**
|
||||
|
||||
1. **Check permissions:**
|
||||
```bash
|
||||
# Fix ownership
|
||||
sudo chown -R root:root /opt/vpn-gateway
|
||||
|
||||
# Fix permissions
|
||||
sudo chmod 755 /opt/vpn-gateway
|
||||
sudo chmod 644 /opt/vpn-gateway/app.py
|
||||
```
|
||||
|
||||
2. **Check disk space:**
|
||||
```bash
|
||||
# Check available space
|
||||
df -h /opt/vpn-gateway
|
||||
|
||||
# Clean up if needed
|
||||
sudo journalctl --vacuum-time=7d
|
||||
sudo apt clean
|
||||
```
|
||||
|
||||
### Security Issues
|
||||
|
||||
#### Problem: Killswitch not working
|
||||
**Symptoms:** Internet accessible without VPN
|
||||
|
||||
**CRITICAL - Fix immediately:**
|
||||
|
||||
1. **Re-enable killswitch:**
|
||||
```bash
|
||||
# Force enable
|
||||
sudo /usr/local/bin/vpn-killswitch.sh enable
|
||||
|
||||
# Verify rules
|
||||
sudo iptables -L -n -v | grep DROP
|
||||
```
|
||||
|
||||
2. **Check for rule conflicts:**
|
||||
```bash
|
||||
# List all rules
|
||||
sudo iptables-save
|
||||
|
||||
# Remove conflicting rules
|
||||
sudo iptables -F OUTPUT
|
||||
sudo iptables -P OUTPUT DROP
|
||||
```
|
||||
|
||||
3. **Restart security monitor:**
|
||||
```bash
|
||||
sudo systemctl restart vpn-security-monitor
|
||||
sudo systemctl status vpn-security-monitor
|
||||
```
|
||||
|
||||
#### Problem: DNS leaks detected
|
||||
**Symptoms:** DNS queries not going through VPN
|
||||
|
||||
**Solutions:**
|
||||
|
||||
1. **Force VPN DNS:**
|
||||
```bash
|
||||
# Lock resolv.conf
|
||||
sudo chattr +i /etc/resolv.conf
|
||||
|
||||
# Disable systemd-resolved
|
||||
sudo systemctl stop systemd-resolved
|
||||
sudo systemctl disable systemd-resolved
|
||||
```
|
||||
|
||||
2. **Check firewall rules:**
|
||||
```bash
|
||||
# Block DNS except through VPN
|
||||
sudo iptables -A OUTPUT -p udp --dport 53 ! -o wg0 -j DROP
|
||||
sudo iptables -A OUTPUT -p tcp --dport 53 ! -o wg0 -j DROP
|
||||
```
|
||||
|
||||
### Performance Issues
|
||||
|
||||
#### Problem: Slow speeds
|
||||
**Symptoms:** Poor throughput, high latency
|
||||
|
||||
**Solutions:**
|
||||
|
||||
1. **Optimize MTU:**
|
||||
```bash
|
||||
# Test optimal MTU
|
||||
for mtu in 1420 1400 1380 1360; do
|
||||
sudo ip link set dev wg0 mtu $mtu
|
||||
echo "Testing MTU $mtu"
|
||||
iperf3 -c <server-ip> -t 5
|
||||
done
|
||||
```
|
||||
|
||||
2. **Check CPU usage:**
|
||||
```bash
|
||||
# Monitor CPU
|
||||
top -n 1 | grep -E "wireguard|python"
|
||||
|
||||
# Check interrupts
|
||||
watch -n 1 'cat /proc/interrupts | grep -E "eth|wg"'
|
||||
```
|
||||
|
||||
3. **Tune kernel parameters:**
|
||||
```bash
|
||||
# Optimize network stack
|
||||
cat >> /etc/sysctl.conf << EOF
|
||||
net.core.rmem_max = 134217728
|
||||
net.core.wmem_max = 134217728
|
||||
net.ipv4.tcp_rmem = 4096 87380 134217728
|
||||
net.ipv4.tcp_wmem = 4096 65536 134217728
|
||||
net.core.netdev_max_backlog = 30000
|
||||
net.ipv4.tcp_congestion_control = bbr
|
||||
EOF
|
||||
|
||||
sudo sysctl -p
|
||||
```
|
||||
|
||||
### Diagnostic Commands
|
||||
|
||||
#### Complete System Check
|
||||
```bash
|
||||
#!/bin/bash
|
||||
echo "=== VPN Gateway Diagnostics ==="
|
||||
echo ""
|
||||
echo "1. Services Status:"
|
||||
systemctl status vpn-webui --no-pager | head -n 10
|
||||
systemctl status vpn-killswitch --no-pager | head -n 10
|
||||
systemctl status vpn-security-monitor --no-pager | head -n 10
|
||||
echo ""
|
||||
echo "2. VPN Status:"
|
||||
wg show
|
||||
echo ""
|
||||
echo "3. Firewall Rules:"
|
||||
iptables -L -n | head -n 20
|
||||
echo ""
|
||||
echo "4. Network Configuration:"
|
||||
ip addr show
|
||||
ip route show
|
||||
echo ""
|
||||
echo "5. DNS Configuration:"
|
||||
cat /etc/resolv.conf
|
||||
echo ""
|
||||
echo "6. Recent Logs:"
|
||||
journalctl -u vpn-webui -n 20 --no-pager
|
||||
echo ""
|
||||
echo "7. Disk Usage:"
|
||||
df -h /opt/vpn-gateway
|
||||
echo ""
|
||||
echo "8. Memory Usage:"
|
||||
free -h
|
||||
```
|
||||
|
||||
#### Export Debug Info
|
||||
```bash
|
||||
# Create debug archive
|
||||
sudo tar czf vpn-debug-$(date +%s).tar.gz \
|
||||
/var/log/vpn-*.log \
|
||||
/var/log/syslog \
|
||||
/etc/wireguard/ \
|
||||
/opt/vpn-gateway/logs/ \
|
||||
<(iptables-save) \
|
||||
<(wg show) \
|
||||
<(ip addr) \
|
||||
<(ip route) \
|
||||
<(systemctl status vpn-*)
|
||||
```
|
||||
|
||||
## Getting Help
|
||||
|
||||
If problems persist:
|
||||
|
||||
1. **Check logs thoroughly:**
|
||||
```bash
|
||||
sudo journalctl -xe
|
||||
sudo dmesg | tail -50
|
||||
```
|
||||
|
||||
2. **Run health check:**
|
||||
```bash
|
||||
sudo /usr/local/bin/vpn-health-check.sh
|
||||
```
|
||||
|
||||
3. **Create issue on GitHub** with:
|
||||
- System info: `uname -a`
|
||||
- Service status: `systemctl status vpn-*`
|
||||
- Error messages
|
||||
- Debug archive
|
||||
|
||||
4. **Emergency recovery:**
|
||||
```bash
|
||||
# Disable killswitch (TEMPORARY!)
|
||||
sudo iptables -P INPUT ACCEPT
|
||||
sudo iptables -P OUTPUT ACCEPT
|
||||
sudo iptables -P FORWARD ACCEPT
|
||||
sudo iptables -F
|
||||
|
||||
# Reinstall
|
||||
curl -sSL https://raw.githubusercontent.com/yourusername/vpn-gateway/main/install.sh | bash
|
||||
```
|
975
frontend/index.html
Normal file
975
frontend/index.html
Normal file
|
@ -0,0 +1,975 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>VPN Gateway Control - Multi Provider</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.header {
|
||||
text-align: center;
|
||||
color: white;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 2.5em;
|
||||
margin-bottom: 10px;
|
||||
text-shadow: 2px 2px 4px rgba(0,0,0,0.2);
|
||||
}
|
||||
|
||||
.provider-selector {
|
||||
background: white;
|
||||
border-radius: 15px;
|
||||
padding: 30px;
|
||||
margin-bottom: 30px;
|
||||
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
|
||||
}
|
||||
|
||||
.provider-tabs {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-bottom: 30px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.provider-tab {
|
||||
flex: 1;
|
||||
min-width: 150px;
|
||||
padding: 15px 25px;
|
||||
border: 2px solid #e0e0e0;
|
||||
border-radius: 10px;
|
||||
background: white;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
text-align: center;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.provider-tab:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 5px 15px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.provider-tab.active {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
.main-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 30px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.main-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.card {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
backdrop-filter: blur(10px);
|
||||
border-radius: 15px;
|
||||
padding: 30px;
|
||||
box-shadow: 0 10px 40px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.status-card {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.status-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
.status-indicator {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
font-size: 1.3em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.status-dot {
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
border-radius: 50%;
|
||||
animation: pulse 2s infinite;
|
||||
}
|
||||
|
||||
.status-dot.connected {
|
||||
background: #4ade80;
|
||||
box-shadow: 0 0 10px #4ade80;
|
||||
}
|
||||
|
||||
.status-dot.disconnected {
|
||||
background: #f87171;
|
||||
box-shadow: 0 0 10px #f87171;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% { transform: scale(1); }
|
||||
50% { transform: scale(1.2); }
|
||||
}
|
||||
|
||||
.info-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
padding: 15px;
|
||||
border-radius: 10px;
|
||||
backdrop-filter: blur(5px);
|
||||
}
|
||||
|
||||
.info-label {
|
||||
font-size: 0.9em;
|
||||
opacity: 0.9;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
font-size: 1.2em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.security-notice {
|
||||
background: linear-gradient(135deg, #4ade80 0%, #22c55e 100%);
|
||||
color: white;
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
select, input, textarea {
|
||||
width: 100%;
|
||||
padding: 15px;
|
||||
border: 2px solid #e0e0e0;
|
||||
border-radius: 10px;
|
||||
font-size: 1em;
|
||||
margin-bottom: 15px;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
select:focus, input:focus, textarea:focus {
|
||||
outline: none;
|
||||
border-color: #667eea;
|
||||
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 15px 30px;
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
font-size: 1.1em;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: linear-gradient(135deg, #4ade80 0%, #22c55e 100%);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 10px 20px rgba(74, 222, 128, 0.3);
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
background: linear-gradient(135deg, #f87171 0%, #ef4444 100%);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: linear-gradient(135deg, #60a5fa 0%, #3b82f6 100%);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.custom-server-form {
|
||||
display: none;
|
||||
background: #f8f9fa;
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.custom-server-form.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.server-list {
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.server-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 15px;
|
||||
background: white;
|
||||
border-radius: 10px;
|
||||
margin-bottom: 10px;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.server-item:hover {
|
||||
box-shadow: 0 5px 15px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.import-section {
|
||||
background: #f8f9fa;
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.file-upload {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.file-upload input[type=file] {
|
||||
position: absolute;
|
||||
left: -9999px;
|
||||
}
|
||||
|
||||
.file-upload label {
|
||||
display: block;
|
||||
padding: 15px;
|
||||
background: linear-gradient(135deg, #60a5fa 0%, #3b82f6 100%);
|
||||
color: white;
|
||||
border-radius: 10px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.file-upload label:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 10px 20px rgba(96, 165, 250, 0.3);
|
||||
}
|
||||
|
||||
.tabs {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.tab {
|
||||
padding: 10px 20px;
|
||||
background: #e0e0e0;
|
||||
border-radius: 10px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.tab.active {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tab-content.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
display: none;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border: 3px solid rgba(255, 255, 255, 0.3);
|
||||
border-top-color: white;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.loading .spinner {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.modal {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
z-index: 1000;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.modal.active {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background: white;
|
||||
padding: 30px;
|
||||
border-radius: 15px;
|
||||
max-width: 500px;
|
||||
width: 90%;
|
||||
max-height: 80vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.keypair-display {
|
||||
background: #f8f9fa;
|
||||
padding: 15px;
|
||||
border-radius: 10px;
|
||||
margin: 10px 0;
|
||||
font-family: monospace;
|
||||
word-break: break-all;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.copy-btn {
|
||||
padding: 5px 10px;
|
||||
background: #667eea;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h1>🔐 VPN Gateway Control Center</h1>
|
||||
<p>Multi-Provider Support with Permanent Killswitch</p>
|
||||
</div>
|
||||
|
||||
<div class="provider-selector">
|
||||
<h2>Select VPN Provider</h2>
|
||||
<div class="provider-tabs">
|
||||
<div class="provider-tab active" data-provider="mullvad" onclick="switchProvider('mullvad')">
|
||||
<div>🌍 Mullvad</div>
|
||||
<small>Commercial VPN</small>
|
||||
</div>
|
||||
<div class="provider-tab" data-provider="custom" onclick="switchProvider('custom')">
|
||||
<div>🔧 Custom Server</div>
|
||||
<small>Own VPS/Server</small>
|
||||
</div>
|
||||
<div class="provider-tab" data-provider="imported" onclick="switchProvider('imported')">
|
||||
<div>📁 Import Config</div>
|
||||
<small>Existing WireGuard</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="main-grid">
|
||||
<div class="card status-card">
|
||||
<div class="status-header">
|
||||
<div class="status-indicator">
|
||||
<span class="status-dot disconnected" id="statusDot"></span>
|
||||
<span id="statusText">Disconnected</span>
|
||||
</div>
|
||||
<button class="btn-secondary" onclick="refreshStatus()" style="width: auto; padding: 10px 20px;">
|
||||
🔄 Refresh
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="info-grid">
|
||||
<div class="info-item">
|
||||
<div class="info-label">Provider</div>
|
||||
<div class="info-value" id="currentProvider">-</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<div class="info-label">Server</div>
|
||||
<div class="info-value" id="currentServer">-</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<div class="info-label">Public IP</div>
|
||||
<div class="info-value" id="publicIP">-</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<div class="info-label">Location</div>
|
||||
<div class="info-value" id="location">-</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="security-notice" style="margin-top: 20px;">
|
||||
<strong>🛡️ Security Status: PROTECTED</strong><br>
|
||||
✓ Killswitch permanently active<br>
|
||||
✓ No internet without VPN<br>
|
||||
✓ DNS leak protection enabled
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<!-- Mullvad Provider Section -->
|
||||
<div id="mullvad-section" class="provider-section">
|
||||
<h3>Mullvad Server Selection</h3>
|
||||
<select id="countrySelect" onchange="updateCities()">
|
||||
<option value="">Loading countries...</option>
|
||||
</select>
|
||||
|
||||
<select id="citySelect" onchange="updateServers()">
|
||||
<option value="">Select a country first</option>
|
||||
</select>
|
||||
|
||||
<select id="serverSelect">
|
||||
<option value="">Select a city first</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Custom Provider Section -->
|
||||
<div id="custom-section" class="provider-section" style="display: none;">
|
||||
<h3>Custom WireGuard Servers</h3>
|
||||
|
||||
<button class="btn-secondary" onclick="toggleCustomForm()">
|
||||
➕ Add New Server
|
||||
</button>
|
||||
|
||||
<div id="customServerForm" class="custom-server-form">
|
||||
<h4>Add Custom Server</h4>
|
||||
<input type="text" id="customName" placeholder="Server Name (e.g., my-vps)">
|
||||
<input type="text" id="customEndpoint" placeholder="Endpoint (IP:Port or domain:port)">
|
||||
<input type="text" id="customPublicKey" placeholder="Server Public Key">
|
||||
<input type="text" id="customPrivateKey" placeholder="Client Private Key (optional)">
|
||||
<input type="text" id="customAddress" placeholder="Client IP (default: 10.0.0.2/32)">
|
||||
<input type="text" id="customDNS" placeholder="DNS Servers (default: 1.1.1.1,1.0.0.1)">
|
||||
<input type="text" id="customLocation" placeholder="Location (e.g., Germany)">
|
||||
|
||||
<button class="btn-secondary" onclick="generateKeypair()">
|
||||
🔑 Generate Keypair
|
||||
</button>
|
||||
|
||||
<button class="btn-primary" onclick="addCustomServer()">
|
||||
Add Server
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="server-list" id="customServerList">
|
||||
<!-- Custom servers will be listed here -->
|
||||
</div>
|
||||
|
||||
<select id="customServerSelect">
|
||||
<option value="">Select a custom server</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Import Provider Section -->
|
||||
<div id="import-section" class="provider-section" style="display: none;">
|
||||
<h3>Import WireGuard Configuration</h3>
|
||||
|
||||
<div class="import-section">
|
||||
<input type="text" id="importName" placeholder="Configuration Name">
|
||||
|
||||
<div class="file-upload">
|
||||
<input type="file" id="configFile" accept=".conf" onchange="handleFileSelect(event)">
|
||||
<label for="configFile">📁 Choose WireGuard Config File</label>
|
||||
</div>
|
||||
|
||||
<textarea id="configContent" placeholder="Or paste your WireGuard config here..." rows="10"></textarea>
|
||||
|
||||
<button class="btn-primary" onclick="importConfig()">
|
||||
Import Configuration
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="server-list" id="importedConfigList">
|
||||
<!-- Imported configs will be listed here -->
|
||||
</div>
|
||||
|
||||
<select id="importedServerSelect">
|
||||
<option value="">Select an imported config</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 30px;">
|
||||
<button class="btn-primary" onclick="connectVPN()">
|
||||
<span>🔗 Connect</span>
|
||||
<span class="spinner"></span>
|
||||
</button>
|
||||
<button class="btn-danger" onclick="disconnectVPN()">
|
||||
<span>⛔ Disconnect</span>
|
||||
<span class="spinner"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Keypair Modal -->
|
||||
<div id="keypairModal" class="modal">
|
||||
<div class="modal-content">
|
||||
<h3>Generated WireGuard Keypair</h3>
|
||||
<p style="margin: 15px 0;">Save these keys securely!</p>
|
||||
|
||||
<label>Private Key (Keep Secret!):</label>
|
||||
<div class="keypair-display" id="generatedPrivateKey"></div>
|
||||
<button class="copy-btn" onclick="copyToClipboard('generatedPrivateKey')">Copy</button>
|
||||
|
||||
<label style="margin-top: 15px; display: block;">Public Key (Share with Server):</label>
|
||||
<div class="keypair-display" id="generatedPublicKey"></div>
|
||||
<button class="copy-btn" onclick="copyToClipboard('generatedPublicKey')">Copy</button>
|
||||
|
||||
<button class="btn-secondary" style="margin-top: 20px;" onclick="closeModal()">
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let currentProvider = 'mullvad';
|
||||
let servers = {};
|
||||
let customServers = [];
|
||||
let importedConfigs = [];
|
||||
|
||||
// Initialize
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
loadProviders();
|
||||
refreshStatus();
|
||||
setInterval(refreshStatus, 10000);
|
||||
});
|
||||
|
||||
async function loadProviders() {
|
||||
try {
|
||||
const response = await fetch('/api/providers');
|
||||
const data = await response.json();
|
||||
|
||||
if (data.current) {
|
||||
currentProvider = data.current;
|
||||
switchProvider(currentProvider);
|
||||
} else {
|
||||
loadServers();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading providers:', error);
|
||||
loadServers();
|
||||
}
|
||||
}
|
||||
|
||||
async function switchProvider(provider) {
|
||||
// Update UI
|
||||
document.querySelectorAll('.provider-tab').forEach(tab => {
|
||||
tab.classList.remove('active');
|
||||
});
|
||||
document.querySelector(`[data-provider="${provider}"]`).classList.add('active');
|
||||
|
||||
// Hide all sections
|
||||
document.querySelectorAll('.provider-section').forEach(section => {
|
||||
section.style.display = 'none';
|
||||
});
|
||||
|
||||
// Show selected section
|
||||
document.getElementById(`${provider}-section`).style.display = 'block';
|
||||
|
||||
currentProvider = provider;
|
||||
|
||||
// Switch backend provider
|
||||
try {
|
||||
await fetch(`/api/provider/${provider}`, { method: 'POST' });
|
||||
} catch (error) {
|
||||
console.error('Error switching provider:', error);
|
||||
}
|
||||
|
||||
// Load servers for new provider
|
||||
loadServers();
|
||||
}
|
||||
|
||||
async function loadServers() {
|
||||
try {
|
||||
const response = await fetch('/api/servers');
|
||||
const data = await response.json();
|
||||
|
||||
servers = data.servers;
|
||||
|
||||
if (currentProvider === 'mullvad') {
|
||||
updateMullvadSelects();
|
||||
} else if (currentProvider === 'custom') {
|
||||
updateCustomServers();
|
||||
} else if (currentProvider === 'imported') {
|
||||
updateImportedConfigs();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading servers:', error);
|
||||
}
|
||||
}
|
||||
|
||||
function updateMullvadSelects() {
|
||||
const countrySelect = document.getElementById('countrySelect');
|
||||
countrySelect.innerHTML = '<option value="">Select a country</option>';
|
||||
|
||||
Object.keys(servers).forEach(country => {
|
||||
const option = document.createElement('option');
|
||||
option.value = country;
|
||||
option.textContent = country;
|
||||
countrySelect.appendChild(option);
|
||||
});
|
||||
}
|
||||
|
||||
function updateCities() {
|
||||
const country = document.getElementById('countrySelect').value;
|
||||
const citySelect = document.getElementById('citySelect');
|
||||
const serverSelect = document.getElementById('serverSelect');
|
||||
|
||||
citySelect.innerHTML = '<option value="">Select a city</option>';
|
||||
serverSelect.innerHTML = '<option value="">Select a city first</option>';
|
||||
|
||||
if (country && servers[country]) {
|
||||
Object.keys(servers[country]).forEach(city => {
|
||||
const option = document.createElement('option');
|
||||
option.value = city;
|
||||
option.textContent = city;
|
||||
citySelect.appendChild(option);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function updateServers() {
|
||||
const country = document.getElementById('countrySelect').value;
|
||||
const city = document.getElementById('citySelect').value;
|
||||
const serverSelect = document.getElementById('serverSelect');
|
||||
|
||||
serverSelect.innerHTML = '<option value="">Select a server</option>';
|
||||
|
||||
if (country && city && servers[country][city]) {
|
||||
servers[country][city].forEach(server => {
|
||||
const option = document.createElement('option');
|
||||
option.value = server.hostname;
|
||||
option.textContent = `${server.hostname} (${server.type || 'WireGuard'})`;
|
||||
serverSelect.appendChild(option);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function updateCustomServers() {
|
||||
const customSelect = document.getElementById('customServerSelect');
|
||||
const serverList = document.getElementById('customServerList');
|
||||
|
||||
customSelect.innerHTML = '<option value="">Select a custom server</option>';
|
||||
serverList.innerHTML = '';
|
||||
|
||||
// Flatten servers structure for custom
|
||||
for (const location in servers) {
|
||||
for (const city in servers[location]) {
|
||||
servers[location][city].forEach(server => {
|
||||
// Add to select
|
||||
const option = document.createElement('option');
|
||||
option.value = server.hostname;
|
||||
option.textContent = `${server.hostname} (${location})`;
|
||||
customSelect.appendChild(option);
|
||||
|
||||
// Add to list
|
||||
const item = document.createElement('div');
|
||||
item.className = 'server-item';
|
||||
item.innerHTML = `
|
||||
<div>
|
||||
<strong>${server.hostname}</strong><br>
|
||||
<small>${server.endpoint} - ${location}</small>
|
||||
</div>
|
||||
<button class="btn-danger" style="width: auto; padding: 5px 15px;"
|
||||
onclick="removeCustomServer('${server.hostname}')">
|
||||
Remove
|
||||
</button>
|
||||
`;
|
||||
serverList.appendChild(item);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updateImportedConfigs() {
|
||||
const importedSelect = document.getElementById('importedServerSelect');
|
||||
const configList = document.getElementById('importedConfigList');
|
||||
|
||||
importedSelect.innerHTML = '<option value="">Select an imported config</option>';
|
||||
configList.innerHTML = '';
|
||||
|
||||
// Check for imported configs in servers
|
||||
if (servers['Imported'] && servers['Imported']['Configs']) {
|
||||
servers['Imported']['Configs'].forEach(config => {
|
||||
// Add to select
|
||||
const option = document.createElement('option');
|
||||
option.value = config.hostname;
|
||||
option.textContent = config.hostname;
|
||||
importedSelect.appendChild(option);
|
||||
|
||||
// Add to list
|
||||
const item = document.createElement('div');
|
||||
item.className = 'server-item';
|
||||
item.innerHTML = `
|
||||
<div>
|
||||
<strong>${config.hostname}</strong><br>
|
||||
<small>Imported Configuration</small>
|
||||
</div>
|
||||
`;
|
||||
configList.appendChild(item);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function toggleCustomForm() {
|
||||
const form = document.getElementById('customServerForm');
|
||||
form.classList.toggle('active');
|
||||
}
|
||||
|
||||
async function generateKeypair() {
|
||||
try {
|
||||
const response = await fetch('/api/keypair');
|
||||
const data = await response.json();
|
||||
|
||||
document.getElementById('customPrivateKey').value = data.private_key;
|
||||
document.getElementById('generatedPrivateKey').textContent = data.private_key;
|
||||
document.getElementById('generatedPublicKey').textContent = data.public_key;
|
||||
document.getElementById('keypairModal').classList.add('active');
|
||||
|
||||
} catch (error) {
|
||||
alert('Error generating keypair: ' + error);
|
||||
}
|
||||
}
|
||||
|
||||
async function addCustomServer() {
|
||||
const name = document.getElementById('customName').value;
|
||||
const endpoint = document.getElementById('customEndpoint').value;
|
||||
const publicKey = document.getElementById('customPublicKey').value;
|
||||
|
||||
if (!name || !endpoint || !publicKey) {
|
||||
alert('Please fill in required fields');
|
||||
return;
|
||||
}
|
||||
|
||||
const config = {
|
||||
name: name,
|
||||
endpoint: endpoint,
|
||||
public_key: publicKey,
|
||||
private_key: document.getElementById('customPrivateKey').value,
|
||||
address: document.getElementById('customAddress').value,
|
||||
dns: document.getElementById('customDNS').value,
|
||||
location: document.getElementById('customLocation').value || 'Custom'
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/custom/add', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify(config)
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
alert('Server added successfully');
|
||||
toggleCustomForm();
|
||||
// Clear form
|
||||
document.querySelectorAll('#customServerForm input').forEach(input => input.value = '');
|
||||
loadServers();
|
||||
} else {
|
||||
alert('Failed to add server');
|
||||
}
|
||||
} catch (error) {
|
||||
alert('Error adding server: ' + error);
|
||||
}
|
||||
}
|
||||
|
||||
async function removeCustomServer(name) {
|
||||
if (!confirm(`Remove server ${name}?`)) return;
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/custom/remove/${name}`, {
|
||||
method: 'DELETE'
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
loadServers();
|
||||
} else {
|
||||
alert('Failed to remove server');
|
||||
}
|
||||
} catch (error) {
|
||||
alert('Error removing server: ' + error);
|
||||
}
|
||||
}
|
||||
|
||||
function handleFileSelect(event) {
|
||||
const file = event.target.files[0];
|
||||
if (file) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = function(e) {
|
||||
document.getElementById('configContent').value = e.target.result;
|
||||
|
||||
// Try to extract name from filename
|
||||
const name = file.name.replace('.conf', '');
|
||||
document.getElementById('importName').value = name;
|
||||
};
|
||||
reader.readAsText(file);
|
||||
}
|
||||
}
|
||||
|
||||
async function importConfig() {
|
||||
const name = document.getElementById('importName').value;
|
||||
const config = document.getElementById('configContent').value;
|
||||
|
||||
if (!name || !config) {
|
||||
alert('Please provide a name and configuration');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/import', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({name: name, config: config})
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
alert('Configuration imported successfully');
|
||||
document.getElementById('importName').value = '';
|
||||
document.getElementById('configContent').value = '';
|
||||
loadServers();
|
||||
} else {
|
||||
alert('Failed to import configuration');
|
||||
}
|
||||
} catch (error) {
|
||||
alert('Error importing config: ' + error);
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshStatus() {
|
||||
try {
|
||||
const response = await fetch('/api/status');
|
||||
const data = await response.json();
|
||||
|
||||
const statusDot = document.getElementById('statusDot');
|
||||
const statusText = document.getElementById('statusText');
|
||||
|
||||
if (data.connected) {
|
||||
statusDot.className = 'status-dot connected';
|
||||
statusText.textContent = 'Connected';
|
||||
document.getElementById('currentProvider').textContent = data.provider || '-';
|
||||
document.getElementById('currentServer').textContent = data.server || '-';
|
||||
document.getElementById('publicIP').textContent = data.ip || 'Checking...';
|
||||
document.getElementById('location').textContent = data.location || '-';
|
||||
} else {
|
||||
statusDot.className = 'status-dot disconnected';
|
||||
statusText.textContent = 'Disconnected';
|
||||
document.getElementById('currentProvider').textContent = '-';
|
||||
document.getElementById('currentServer').textContent = '-';
|
||||
document.getElementById('publicIP').textContent = '-';
|
||||
document.getElementById('location').textContent = '-';
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error refreshing status:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async function connectVPN() {
|
||||
let server = null;
|
||||
|
||||
if (currentProvider === 'mullvad') {
|
||||
server = document.getElementById('serverSelect').value;
|
||||
} else if (currentProvider === 'custom') {
|
||||
server = document.getElementById('customServerSelect').value;
|
||||
} else if (currentProvider === 'imported') {
|
||||
server = document.getElementById('importedServerSelect').value;
|
||||
}
|
||||
|
||||
if (!server) {
|
||||
alert('Please select a server');
|
||||
return;
|
||||
}
|
||||
|
||||
const button = event.target.closest('button');
|
||||
button.classList.add('loading');
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/connect', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({server: server})
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
alert('Connected successfully!');
|
||||
setTimeout(refreshStatus, 2000);
|
||||
} else {
|
||||
alert('Connection failed: ' + (data.error || 'Unknown error'));
|
||||
}
|
||||
} catch (error) {
|
||||
alert('Connection error: ' + error);
|
||||
} finally {
|
||||
button.classList.remove('loading');
|
||||
}
|
||||
}
|
||||
|
||||
async function disconnectVPN() {
|
||||
const button = event.target.closest('button');
|
||||
button.classList.add('loading');
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/disconnect', {method: 'POST'});
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
alert('Disconnected - WARNING: No internet access (killswitch active)');
|
||||
setTimeout(refreshStatus, 1000);
|
||||
} else {
|
||||
alert('Disconnect failed: ' + (data.error || 'Unknown error'));
|
||||
}
|
||||
} catch (error) {
|
||||
alert('Disconnect error: ' + error);
|
||||
} finally {
|
||||
button.classList.remove('loading');
|
||||
}
|
||||
}
|
||||
|
||||
function copyToClipboard(elementId) {
|
||||
const text = document.getElementById(elementId).textContent;
|
||||
navigator.clipboard.writeText(text).then(() => {
|
||||
alert('Copied to clipboard!');
|
||||
});
|
||||
}
|
||||
|
||||
function closeModal() {
|
||||
document.getElementById('keypairModal').classList.remove('active');
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
1408
install.sh
Normal file
1408
install.sh
Normal file
File diff suppressed because it is too large
Load diff
162
scripts/health-check.sh
Executable file
162
scripts/health-check.sh
Executable file
|
@ -0,0 +1,162 @@
|
|||
#!/bin/bash
|
||||
|
||||
# VPN Gateway Health Check Script
|
||||
# Comprehensive system health monitoring
|
||||
# Version: 1.0.0
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
# Scoring
|
||||
TOTAL_SCORE=0
|
||||
MAX_SCORE=100
|
||||
ISSUES=()
|
||||
|
||||
check_mark() {
|
||||
echo -e "${GREEN}✓${NC}"
|
||||
}
|
||||
|
||||
x_mark() {
|
||||
echo -e "${RED}✗${NC}"
|
||||
}
|
||||
|
||||
warning_mark() {
|
||||
echo -e "${YELLOW}⚠${NC}"
|
||||
}
|
||||
|
||||
add_score() {
|
||||
TOTAL_SCORE=$((TOTAL_SCORE + $1))
|
||||
}
|
||||
|
||||
add_issue() {
|
||||
ISSUES+=("$1")
|
||||
}
|
||||
|
||||
echo -e "${BLUE}=== VPN Gateway Health Check ===${NC}"
|
||||
echo ""
|
||||
|
||||
# 1. Check Services
|
||||
echo -n "Checking services... "
|
||||
services_ok=true
|
||||
for service in vpn-webui vpn-killswitch vpn-security-monitor; do
|
||||
if systemctl is-active $service >/dev/null 2>&1; then
|
||||
add_score 10
|
||||
else
|
||||
services_ok=false
|
||||
add_issue "Service $service is not running"
|
||||
fi
|
||||
done
|
||||
[ "$services_ok" = true ] && check_mark || x_mark
|
||||
|
||||
# 2. Check Killswitch
|
||||
echo -n "Checking killswitch... "
|
||||
if iptables -L OUTPUT -n | grep -q "policy DROP"; then
|
||||
add_score 20
|
||||
check_mark
|
||||
else
|
||||
add_issue "Killswitch not active!"
|
||||
x_mark
|
||||
fi
|
||||
|
||||
# 3. Check VPN Connection
|
||||
echo -n "Checking VPN connection... "
|
||||
if wg show wg0 >/dev/null 2>&1; then
|
||||
add_score 15
|
||||
check_mark
|
||||
else
|
||||
add_issue "VPN not connected"
|
||||
warning_mark
|
||||
fi
|
||||
|
||||
# 4. Check for leaks
|
||||
echo -n "Checking for leaks... "
|
||||
if ! ping -c 1 -W 1 8.8.8.8 >/dev/null 2>&1; then
|
||||
if wg show wg0 >/dev/null 2>&1; then
|
||||
add_issue "VPN connected but no internet"
|
||||
warning_mark
|
||||
else
|
||||
add_score 15
|
||||
check_mark
|
||||
fi
|
||||
else
|
||||
if wg show wg0 >/dev/null 2>&1; then
|
||||
add_score 15
|
||||
check_mark
|
||||
else
|
||||
add_issue "CRITICAL: Internet accessible without VPN!"
|
||||
x_mark
|
||||
fi
|
||||
fi
|
||||
|
||||
# 5. Check DNS
|
||||
echo -n "Checking DNS configuration... "
|
||||
dns_ok=true
|
||||
while read -r dns; do
|
||||
case "$dns" in
|
||||
127.0.0.1|10.*|172.*|192.168.*|100.64.*)
|
||||
;;
|
||||
*)
|
||||
dns_ok=false
|
||||
add_issue "Public DNS detected: $dns"
|
||||
;;
|
||||
esac
|
||||
done < <(grep "^nameserver" /etc/resolv.conf | awk '{print $2}')
|
||||
if [ "$dns_ok" = true ]; then
|
||||
add_score 10
|
||||
check_mark
|
||||
else
|
||||
warning_mark
|
||||
fi
|
||||
|
||||
# 6. Check disk space
|
||||
echo -n "Checking disk space... "
|
||||
disk_usage=$(df /opt/vpn-gateway | tail -1 | awk '{print $5}' | sed 's/%//')
|
||||
if [ "$disk_usage" -lt 80 ]; then
|
||||
add_score 5
|
||||
check_mark
|
||||
elif [ "$disk_usage" -lt 90 ]; then
|
||||
add_issue "Disk usage high: ${disk_usage}%"
|
||||
warning_mark
|
||||
else
|
||||
add_issue "Critical disk usage: ${disk_usage}%"
|
||||
x_mark
|
||||
fi
|
||||
|
||||
# 7. Check WebUI accessibility
|
||||
echo -n "Checking WebUI... "
|
||||
if curl -s http://localhost:5000/api/status >/dev/null 2>&1; then
|
||||
add_score 10
|
||||
check_mark
|
||||
else
|
||||
add_issue "WebUI not accessible"
|
||||
x_mark
|
||||
fi
|
||||
|
||||
# Results
|
||||
echo ""
|
||||
echo -e "${BLUE}=== Health Score: $TOTAL_SCORE/$MAX_SCORE ===${NC}"
|
||||
echo ""
|
||||
|
||||
if [ $TOTAL_SCORE -ge 90 ]; then
|
||||
echo -e "${GREEN}System Status: EXCELLENT${NC}"
|
||||
elif [ $TOTAL_SCORE -ge 70 ]; then
|
||||
echo -e "${GREEN}System Status: GOOD${NC}"
|
||||
elif [ $TOTAL_SCORE -ge 50 ]; then
|
||||
echo -e "${YELLOW}System Status: WARNING${NC}"
|
||||
else
|
||||
echo -e "${RED}System Status: CRITICAL${NC}"
|
||||
fi
|
||||
|
||||
if [ ${#ISSUES[@]} -gt 0 ]; then
|
||||
echo ""
|
||||
echo "Issues found:"
|
||||
for issue in "${ISSUES[@]}"; do
|
||||
echo " - $issue"
|
||||
done
|
||||
fi
|
||||
|
||||
exit $((100 - TOTAL_SCORE))
|
241
scripts/killswitch.sh
Executable file
241
scripts/killswitch.sh
Executable file
|
@ -0,0 +1,241 @@
|
|||
#!/bin/bash
|
||||
|
||||
#############################################################
|
||||
# scripts/killswitch.sh #
|
||||
#############################################################
|
||||
|
||||
cat > scripts/killswitch.sh << 'EOFKILLSWITCH'
|
||||
#!/bin/bash
|
||||
|
||||
# VPN Gateway Killswitch Script
|
||||
# CRITICAL: This script ensures NO traffic leaks without VPN
|
||||
# Version: 1.0.0
|
||||
|
||||
set -e
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m'
|
||||
|
||||
# Get network configuration from install
|
||||
if [ -f /opt/vpn-gateway/network.conf ]; then
|
||||
source /opt/vpn-gateway/network.conf
|
||||
else
|
||||
# Fallback to auto-detection
|
||||
LAN_IF=$(ip route | grep default | awk '{print $5}' | head -n1)
|
||||
LAN_NET=$(ip route | grep "$LAN_IF" | grep -v default | awk '{print $1}' | head -n1)
|
||||
fi
|
||||
|
||||
# Logging
|
||||
log() {
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >> /var/log/vpn-killswitch.log
|
||||
echo -e "${GREEN}[+]${NC} $1"
|
||||
}
|
||||
|
||||
error() {
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: $1" >> /var/log/vpn-killswitch.log
|
||||
echo -e "${RED}[!]${NC} $1"
|
||||
}
|
||||
|
||||
warning() {
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] WARNING: $1" >> /var/log/vpn-killswitch.log
|
||||
echo -e "${YELLOW}[*]${NC} $1"
|
||||
}
|
||||
|
||||
enable_killswitch() {
|
||||
log "ENABLING PERMANENT KILLSWITCH - NO INTERNET WITHOUT VPN"
|
||||
|
||||
# Backup current rules (just in case)
|
||||
iptables-save > /tmp/iptables.backup.$(date +%s) 2>/dev/null || true
|
||||
|
||||
# Reset all rules
|
||||
iptables -F
|
||||
iptables -X
|
||||
iptables -t nat -F
|
||||
iptables -t nat -X
|
||||
iptables -t mangle -F
|
||||
iptables -t mangle -X
|
||||
iptables -t raw -F
|
||||
iptables -t raw -X
|
||||
|
||||
# DEFAULT POLICIES: DROP EVERYTHING
|
||||
iptables -P INPUT DROP
|
||||
iptables -P FORWARD DROP
|
||||
iptables -P OUTPUT DROP
|
||||
|
||||
log "Default policies set to DROP"
|
||||
|
||||
# CRITICAL: Allow loopback (required for system operation)
|
||||
iptables -A INPUT -i lo -j ACCEPT
|
||||
iptables -A OUTPUT -o lo -j ACCEPT
|
||||
|
||||
# Allow LAN communication (for WebUI and local clients)
|
||||
if [ -n "$LAN_IF" ] && [ -n "$LAN_NET" ]; then
|
||||
iptables -A INPUT -i $LAN_IF -s $LAN_NET -j ACCEPT
|
||||
iptables -A OUTPUT -o $LAN_IF -d $LAN_NET -j ACCEPT
|
||||
log "LAN communication allowed: $LAN_IF ($LAN_NET)"
|
||||
fi
|
||||
|
||||
# Allow DNS for initial VPN connection (root only, limited)
|
||||
iptables -A OUTPUT -p udp --dport 53 -m owner --uid-owner root -m limit --limit 10/min -j ACCEPT
|
||||
iptables -A OUTPUT -p tcp --dport 53 -m owner --uid-owner root -m limit --limit 10/min -j ACCEPT
|
||||
|
||||
# Allow DHCP client (if needed)
|
||||
iptables -A OUTPUT -p udp --sport 68 --dport 67 -j ACCEPT
|
||||
iptables -A INPUT -p udp --sport 67 --dport 68 -j ACCEPT
|
||||
|
||||
# Allow established/related connections
|
||||
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
|
||||
iptables -A OUTPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
|
||||
iptables -A FORWARD -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
|
||||
|
||||
# Allow forwarding from LAN (will only work when VPN is up)
|
||||
if [ -n "$LAN_IF" ] && [ -n "$LAN_NET" ]; then
|
||||
iptables -A FORWARD -i $LAN_IF -s $LAN_NET -j ACCEPT
|
||||
fi
|
||||
|
||||
# Log dropped packets (optional, can be verbose)
|
||||
# iptables -A INPUT -j LOG --log-prefix "INPUT-DROP: " --log-level 4
|
||||
# iptables -A OUTPUT -j LOG --log-prefix "OUTPUT-DROP: " --log-level 4
|
||||
|
||||
# IPv6: Complete blocking (unless you use IPv6 VPN)
|
||||
ip6tables -P INPUT DROP
|
||||
ip6tables -P FORWARD DROP
|
||||
ip6tables -P OUTPUT DROP
|
||||
ip6tables -A INPUT -i lo -j ACCEPT
|
||||
ip6tables -A OUTPUT -o lo -j ACCEPT
|
||||
|
||||
# Save rules for persistence
|
||||
mkdir -p /etc/iptables
|
||||
iptables-save > /etc/iptables/rules.v4
|
||||
ip6tables-save > /etc/iptables/rules.v6
|
||||
|
||||
# Ensure persistence across reboots
|
||||
if ! systemctl is-enabled netfilter-persistent >/dev/null 2>&1; then
|
||||
systemctl enable netfilter-persistent 2>/dev/null || true
|
||||
fi
|
||||
|
||||
log "KILLSWITCH ACTIVE - System is now protected"
|
||||
log "Rules saved to /etc/iptables/"
|
||||
|
||||
# Verify killswitch is working
|
||||
verify_killswitch
|
||||
}
|
||||
|
||||
disable_killswitch() {
|
||||
error "KILLSWITCH CANNOT BE DISABLED FOR SECURITY REASONS!"
|
||||
warning "This is a security feature. The killswitch must remain active."
|
||||
warning "If you really need to disable it, you must manually flush iptables rules."
|
||||
warning "This would leave your system vulnerable to leaks!"
|
||||
|
||||
# Re-enable killswitch immediately
|
||||
enable_killswitch
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
verify_killswitch() {
|
||||
log "Verifying killswitch status..."
|
||||
|
||||
# Check DROP policies
|
||||
if iptables -L -n | grep -q "Chain INPUT (policy DROP)" && \
|
||||
iptables -L -n | grep -q "Chain OUTPUT (policy DROP)" && \
|
||||
iptables -L -n | grep -q "Chain FORWARD (policy DROP)"; then
|
||||
log "✓ Killswitch policies verified: DROP"
|
||||
else
|
||||
error "✗ Killswitch policies NOT active!"
|
||||
enable_killswitch
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Test that we cannot reach internet directly
|
||||
if ping -c 1 -W 1 8.8.8.8 >/dev/null 2>&1; then
|
||||
if wg show wg0 >/dev/null 2>&1; then
|
||||
log "✓ Internet accessible (VPN is connected)"
|
||||
else
|
||||
error "✗ LEAK DETECTED: Internet accessible without VPN!"
|
||||
enable_killswitch
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
if wg show wg0 >/dev/null 2>&1; then
|
||||
warning "VPN appears connected but internet not reachable"
|
||||
else
|
||||
log "✓ Internet blocked (VPN not connected)"
|
||||
fi
|
||||
fi
|
||||
|
||||
log "Killswitch verification complete"
|
||||
}
|
||||
|
||||
status_killswitch() {
|
||||
echo -e "${GREEN}=== VPN Killswitch Status ===${NC}"
|
||||
echo ""
|
||||
|
||||
# Check policies
|
||||
echo "Firewall Policies:"
|
||||
iptables -L -n | grep "Chain.*policy" | while read line; do
|
||||
if echo "$line" | grep -q "DROP"; then
|
||||
echo -e " ${GREEN}✓${NC} $line"
|
||||
else
|
||||
echo -e " ${RED}✗${NC} $line"
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "Active Rules Summary:"
|
||||
echo " INPUT: $(iptables -L INPUT -n | grep -c ACCEPT) ACCEPT rules"
|
||||
echo " OUTPUT: $(iptables -L OUTPUT -n | grep -c ACCEPT) ACCEPT rules"
|
||||
echo " FORWARD: $(iptables -L FORWARD -n | grep -c ACCEPT) ACCEPT rules"
|
||||
|
||||
echo ""
|
||||
echo "VPN Status:"
|
||||
if wg show wg0 >/dev/null 2>&1; then
|
||||
echo -e " ${GREEN}✓${NC} WireGuard interface active"
|
||||
echo " Endpoint: $(wg show wg0 endpoints | awk '{print $2}')"
|
||||
else
|
||||
echo -e " ${YELLOW}⚠${NC} WireGuard interface not active"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Leak Test:"
|
||||
if ping -c 1 -W 1 8.8.8.8 >/dev/null 2>&1; then
|
||||
if wg show wg0 >/dev/null 2>&1; then
|
||||
echo -e " ${GREEN}✓${NC} Internet accessible via VPN"
|
||||
else
|
||||
echo -e " ${RED}✗ LEAK: Internet accessible without VPN!${NC}"
|
||||
fi
|
||||
else
|
||||
echo -e " ${GREEN}✓${NC} Internet blocked (killswitch working)"
|
||||
fi
|
||||
}
|
||||
|
||||
# Main logic
|
||||
case "${1:-enable}" in
|
||||
enable|start)
|
||||
enable_killswitch
|
||||
;;
|
||||
disable|stop)
|
||||
disable_killswitch
|
||||
;;
|
||||
verify|check)
|
||||
verify_killswitch
|
||||
;;
|
||||
status)
|
||||
status_killswitch
|
||||
;;
|
||||
restart|reload)
|
||||
enable_killswitch
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $0 {enable|disable|verify|status|restart}"
|
||||
echo " enable - Enable killswitch (default)"
|
||||
echo " disable - Disable killswitch (blocked for security)"
|
||||
echo " verify - Verify killswitch is working"
|
||||
echo " status - Show detailed status"
|
||||
echo " restart - Restart killswitch"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
258
scripts/security-monitor.sh
Executable file
258
scripts/security-monitor.sh
Executable file
|
@ -0,0 +1,258 @@
|
|||
#############################################################
|
||||
# scripts/security-monitor.sh #
|
||||
#############################################################
|
||||
|
||||
cat > scripts/security-monitor.sh << 'EOFSECMON'
|
||||
#!/bin/bash
|
||||
|
||||
# VPN Security Monitor
|
||||
# Continuous monitoring of killswitch and VPN status
|
||||
# Version: 1.0.0
|
||||
|
||||
# Configuration
|
||||
CHECK_INTERVAL=10 # seconds
|
||||
ALERT_EMAIL="" # Set email for alerts
|
||||
LOG_FILE="/var/log/vpn-security-monitor.log"
|
||||
STATE_FILE="/var/run/vpn-monitor.state"
|
||||
|
||||
# Source network config
|
||||
if [ -f /opt/vpn-gateway/network.conf ]; then
|
||||
source /opt/vpn-gateway/network.conf
|
||||
fi
|
||||
|
||||
# Logging functions
|
||||
log() {
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >> "$LOG_FILE"
|
||||
}
|
||||
|
||||
alert() {
|
||||
local message="$1"
|
||||
log "ALERT: $message"
|
||||
|
||||
# Send email alert if configured
|
||||
if [ -n "$ALERT_EMAIL" ]; then
|
||||
echo "$message" | mail -s "VPN Security Alert" "$ALERT_EMAIL" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# System notification (if available)
|
||||
if command -v notify-send >/dev/null 2>&1; then
|
||||
notify-send "VPN Security Alert" "$message" -u critical
|
||||
fi
|
||||
|
||||
# Log to syslog
|
||||
logger -t "vpn-security" -p auth.crit "$message"
|
||||
}
|
||||
|
||||
# Check functions
|
||||
check_killswitch() {
|
||||
# Verify DROP policies are active
|
||||
local policies_ok=true
|
||||
|
||||
for chain in INPUT OUTPUT FORWARD; do
|
||||
if ! iptables -L $chain -n | grep -q "policy DROP"; then
|
||||
alert "Killswitch policy missing for chain $chain!"
|
||||
policies_ok=false
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$policies_ok" = false ]; then
|
||||
log "Reactivating killswitch..."
|
||||
/usr/local/bin/vpn-killswitch.sh enable
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
check_vpn_connection() {
|
||||
if wg show wg0 >/dev/null 2>&1; then
|
||||
# VPN interface exists, check if it's actually working
|
||||
local endpoint=$(wg show wg0 endpoints | awk '{print $2}')
|
||||
|
||||
if [ -z "$endpoint" ]; then
|
||||
log "WARNING: VPN interface exists but no endpoint configured"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Check last handshake
|
||||
local last_handshake=$(wg show wg0 latest-handshakes | awk '{print $2}')
|
||||
local current_time=$(date +%s)
|
||||
|
||||
if [ -n "$last_handshake" ] && [ "$last_handshake" -ne 0 ]; then
|
||||
local time_diff=$((current_time - last_handshake))
|
||||
|
||||
# If last handshake was more than 3 minutes ago, connection might be dead
|
||||
if [ $time_diff -gt 180 ]; then
|
||||
log "WARNING: Last WireGuard handshake was ${time_diff} seconds ago"
|
||||
return 2
|
||||
fi
|
||||
fi
|
||||
|
||||
return 0
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
check_dns_leaks() {
|
||||
# Check if DNS is going through VPN
|
||||
local dns_servers=$(cat /etc/resolv.conf | grep "^nameserver" | awk '{print $2}')
|
||||
|
||||
for dns in $dns_servers; do
|
||||
# Check if DNS server is in private range or VPN provider's DNS
|
||||
case "$dns" in
|
||||
10.*|172.16.*|172.17.*|172.18.*|172.19.*|172.2*|172.30.*|172.31.*|192.168.*|100.64.*)
|
||||
# Private IP, likely VPN DNS
|
||||
;;
|
||||
127.*)
|
||||
# Localhost, check if it's a DNS proxy
|
||||
if systemctl is-active systemd-resolved >/dev/null 2>&1; then
|
||||
log "WARNING: systemd-resolved is active, checking for leaks"
|
||||
# Additional checks for systemd-resolved
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
alert "Potential DNS leak detected! Public DNS server: $dns"
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
check_leak_test() {
|
||||
# Try to reach internet without VPN
|
||||
local vpn_active=$(check_vpn_connection; echo $?)
|
||||
|
||||
if [ $vpn_active -eq 1 ]; then
|
||||
# VPN not active, internet should be blocked
|
||||
if ping -c 1 -W 1 8.8.8.8 >/dev/null 2>&1; then
|
||||
alert "CRITICAL: Internet accessible without VPN! Leak detected!"
|
||||
# Immediately re-enable killswitch
|
||||
/usr/local/bin/vpn-killswitch.sh enable
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
check_processes() {
|
||||
# Check for processes that might bypass VPN
|
||||
local suspicious_processes=$(netstat -tunp 2>/dev/null | grep -v "127.0.0.1\|::1\|$LAN_NET" | grep ESTABLISHED)
|
||||
|
||||
if [ -n "$suspicious_processes" ]; then
|
||||
log "WARNING: Detected network connections that might bypass VPN:"
|
||||
echo "$suspicious_processes" >> "$LOG_FILE"
|
||||
fi
|
||||
}
|
||||
|
||||
monitor_bandwidth() {
|
||||
# Monitor bandwidth to detect unusual activity
|
||||
if command -v vnstat >/dev/null 2>&1; then
|
||||
local current_tx=$(vnstat --oneline | cut -d';' -f9)
|
||||
local current_rx=$(vnstat --oneline | cut -d';' -f10)
|
||||
|
||||
if [ -f "$STATE_FILE" ]; then
|
||||
source "$STATE_FILE"
|
||||
|
||||
# Compare with previous values
|
||||
# Alert if sudden spike in traffic when VPN is down
|
||||
if ! check_vpn_connection && [ -n "$LAST_TX" ]; then
|
||||
# Calculate difference
|
||||
# If significant traffic when VPN is down, alert
|
||||
log "Bandwidth check: TX=$current_tx RX=$current_rx"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Save current state
|
||||
echo "LAST_TX='$current_tx'" > "$STATE_FILE"
|
||||
echo "LAST_RX='$current_rx'" >> "$STATE_FILE"
|
||||
fi
|
||||
}
|
||||
|
||||
auto_recovery() {
|
||||
local vpn_status=$(check_vpn_connection; echo $?)
|
||||
|
||||
if [ $vpn_status -eq 2 ]; then
|
||||
# Connection stale, try to reconnect
|
||||
log "Attempting auto-recovery of VPN connection..."
|
||||
|
||||
# Get last used server from config
|
||||
if [ -f /etc/wireguard/wg0.conf ]; then
|
||||
systemctl restart wg-quick@wg0 2>/dev/null || \
|
||||
wg-quick down wg0 2>/dev/null && wg-quick up wg0 2>/dev/null
|
||||
|
||||
sleep 5
|
||||
|
||||
if check_vpn_connection; then
|
||||
log "Auto-recovery successful"
|
||||
return 0
|
||||
else
|
||||
alert "Auto-recovery failed - manual intervention required"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Main monitoring loop
|
||||
main() {
|
||||
log "VPN Security Monitor started"
|
||||
log "Check interval: ${CHECK_INTERVAL}s"
|
||||
|
||||
# Initial checks
|
||||
check_killswitch
|
||||
check_vpn_connection
|
||||
check_dns_leaks
|
||||
|
||||
local error_count=0
|
||||
|
||||
while true; do
|
||||
# Run all checks
|
||||
local all_ok=true
|
||||
|
||||
if ! check_killswitch; then
|
||||
all_ok=false
|
||||
((error_count++))
|
||||
fi
|
||||
|
||||
if ! check_leak_test; then
|
||||
all_ok=false
|
||||
((error_count++))
|
||||
fi
|
||||
|
||||
if ! check_dns_leaks; then
|
||||
all_ok=false
|
||||
((error_count++))
|
||||
fi
|
||||
|
||||
check_processes
|
||||
monitor_bandwidth
|
||||
|
||||
# Auto-recovery if needed
|
||||
if [ $error_count -gt 3 ]; then
|
||||
auto_recovery
|
||||
error_count=0
|
||||
fi
|
||||
|
||||
# Status indicator
|
||||
if [ "$all_ok" = true ]; then
|
||||
echo -n "."
|
||||
error_count=0
|
||||
else
|
||||
echo -n "!"
|
||||
fi
|
||||
|
||||
sleep "$CHECK_INTERVAL"
|
||||
done
|
||||
}
|
||||
|
||||
# Signal handlers
|
||||
trap 'log "Security monitor stopped"; exit 0' SIGTERM SIGINT
|
||||
|
||||
# Run main loop
|
||||
main
|
156
scripts/uninstall.sh
Executable file
156
scripts/uninstall.sh
Executable file
|
@ -0,0 +1,156 @@
|
|||
#!/bin/bash
|
||||
|
||||
# VPN Gateway Uninstall Script
|
||||
# Completely removes VPN Gateway
|
||||
# Version: 1.0.0
|
||||
|
||||
set -e
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
INSTALL_DIR="/opt/vpn-gateway"
|
||||
|
||||
log() {
|
||||
echo -e "${GREEN}[+]${NC} $1"
|
||||
}
|
||||
|
||||
error() {
|
||||
echo -e "${RED}[!]${NC} $1"
|
||||
}
|
||||
|
||||
warning() {
|
||||
echo -e "${YELLOW}[!]${NC} $1"
|
||||
}
|
||||
|
||||
# Check root
|
||||
if [[ $EUID -ne 0 ]]; then
|
||||
error "This script must be run as root"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -e "${RED}=== VPN Gateway Uninstaller ===${NC}"
|
||||
echo ""
|
||||
warning "This will completely remove VPN Gateway and its components."
|
||||
warning "The killswitch will be DISABLED, potentially exposing your traffic!"
|
||||
echo ""
|
||||
echo "The following will be removed:"
|
||||
echo " - VPN Gateway application ($INSTALL_DIR)"
|
||||
echo " - WireGuard configurations"
|
||||
echo " - Systemd services"
|
||||
echo " - Firewall rules (killswitch)"
|
||||
echo " - Nginx configuration"
|
||||
echo ""
|
||||
read -p "Are you SURE you want to uninstall? Type 'YES' to confirm: " CONFIRM
|
||||
|
||||
if [ "$CONFIRM" != "YES" ]; then
|
||||
log "Uninstall cancelled"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Create backup just in case
|
||||
BACKUP_DIR="/root/vpn-gateway-final-backup-$(date +%Y%m%d-%H%M%S)"
|
||||
log "Creating final backup at $BACKUP_DIR..."
|
||||
mkdir -p "$BACKUP_DIR"
|
||||
|
||||
# Backup configs
|
||||
cp -r /etc/wireguard "$BACKUP_DIR/wireguard" 2>/dev/null || true
|
||||
cp -r "$INSTALL_DIR" "$BACKUP_DIR/app" 2>/dev/null || true
|
||||
iptables-save > "$BACKUP_DIR/iptables.rules" 2>/dev/null || true
|
||||
|
||||
# Stop and disable services
|
||||
log "Stopping services..."
|
||||
systemctl stop vpn-webui vpn-killswitch vpn-security-monitor 2>/dev/null || true
|
||||
systemctl disable vpn-webui vpn-killswitch vpn-security-monitor 2>/dev/null || true
|
||||
|
||||
# Stop WireGuard
|
||||
wg-quick down wg0 2>/dev/null || true
|
||||
systemctl stop wg-quick@wg0 2>/dev/null || true
|
||||
systemctl disable wg-quick@wg0 2>/dev/null || true
|
||||
|
||||
# Remove systemd services
|
||||
log "Removing systemd services..."
|
||||
rm -f /etc/systemd/system/vpn-*.service
|
||||
systemctl daemon-reload
|
||||
|
||||
# Remove application files
|
||||
log "Removing application files..."
|
||||
rm -rf "$INSTALL_DIR"
|
||||
|
||||
# Remove scripts
|
||||
log "Removing scripts..."
|
||||
rm -f /usr/local/bin/vpn-*.sh
|
||||
|
||||
# Remove Nginx configuration
|
||||
log "Removing Nginx configuration..."
|
||||
rm -f /etc/nginx/sites-enabled/vpn-gateway
|
||||
rm -f /etc/nginx/sites-available/vpn-gateway
|
||||
systemctl reload nginx 2>/dev/null || true
|
||||
|
||||
# Remove WireGuard configs (optional)
|
||||
read -p "Remove WireGuard configurations? (y/N): " -n 1 -r
|
||||
echo ""
|
||||
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||
rm -rf /etc/wireguard
|
||||
log "WireGuard configurations removed"
|
||||
fi
|
||||
|
||||
# CRITICAL: Remove killswitch
|
||||
warning "Removing killswitch - your traffic will no longer be protected!"
|
||||
read -p "Remove killswitch firewall rules? (y/N): " -n 1 -r
|
||||
echo ""
|
||||
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||
# Reset firewall to default ACCEPT policies
|
||||
iptables -P INPUT ACCEPT
|
||||
iptables -P FORWARD ACCEPT
|
||||
iptables -P OUTPUT ACCEPT
|
||||
iptables -F
|
||||
iptables -X
|
||||
iptables -t nat -F
|
||||
iptables -t nat -X
|
||||
iptables -t mangle -F
|
||||
iptables -t mangle -X
|
||||
|
||||
# IPv6
|
||||
ip6tables -P INPUT ACCEPT
|
||||
ip6tables -P FORWARD ACCEPT
|
||||
ip6tables -P OUTPUT ACCEPT
|
||||
ip6tables -F
|
||||
ip6tables -X
|
||||
|
||||
# Save clean rules
|
||||
iptables-save > /etc/iptables/rules.v4 2>/dev/null || true
|
||||
ip6tables-save > /etc/iptables/rules.v6 2>/dev/null || true
|
||||
|
||||
warning "Firewall reset to ACCEPT all - System is no longer protected!"
|
||||
else
|
||||
warning "Killswitch still active - you may have no internet access!"
|
||||
fi
|
||||
|
||||
# Remove log files
|
||||
read -p "Remove log files? (y/N): " -n 1 -r
|
||||
echo ""
|
||||
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||
rm -f /var/log/vpn-*.log
|
||||
log "Log files removed"
|
||||
fi
|
||||
|
||||
# Final cleanup
|
||||
log "Cleaning up..."
|
||||
rm -f /tmp/vpn-gateway* 2>/dev/null || true
|
||||
|
||||
echo ""
|
||||
echo -e "${GREEN}=== Uninstall Complete ===${NC}"
|
||||
echo ""
|
||||
echo "VPN Gateway has been removed."
|
||||
echo "Backup saved at: $BACKUP_DIR"
|
||||
echo ""
|
||||
warning "IMPORTANT: Your system is no longer protected by the killswitch!"
|
||||
warning "All traffic will now use your regular internet connection."
|
||||
echo ""
|
||||
echo "To reinstall, run:"
|
||||
echo " curl -sSL https://your-domain/install.sh | bash"
|
146
scripts/update.sh
Executable file
146
scripts/update.sh
Executable file
|
@ -0,0 +1,146 @@
|
|||
#############################################################
|
||||
# scripts/update.sh #
|
||||
#############################################################
|
||||
|
||||
cat > scripts/update.sh << 'EOFUPDATE'
|
||||
#!/bin/bash
|
||||
|
||||
# VPN Gateway Update Script
|
||||
# Updates the VPN Gateway installation
|
||||
# Version: 1.0.0
|
||||
|
||||
set -e
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
INSTALL_DIR="/opt/vpn-gateway"
|
||||
BACKUP_DIR="/opt/vpn-gateway-backup-$(date +%Y%m%d-%H%M%S)"
|
||||
GITHUB_REPO="https://github.com/yourusername/vpn-gateway"
|
||||
|
||||
log() {
|
||||
echo -e "${GREEN}[+]${NC} $1"
|
||||
}
|
||||
|
||||
error() {
|
||||
echo -e "${RED}[!]${NC} $1"
|
||||
exit 1
|
||||
}
|
||||
|
||||
warning() {
|
||||
echo -e "${YELLOW}[*]${NC} $1"
|
||||
}
|
||||
|
||||
# Check root
|
||||
if [[ $EUID -ne 0 ]]; then
|
||||
error "This script must be run as root"
|
||||
fi
|
||||
|
||||
echo -e "${BLUE}=== VPN Gateway Update ===${NC}"
|
||||
echo ""
|
||||
|
||||
# Check current version
|
||||
if [ -f "$INSTALL_DIR/version" ]; then
|
||||
CURRENT_VERSION=$(cat "$INSTALL_DIR/version")
|
||||
log "Current version: $CURRENT_VERSION"
|
||||
else
|
||||
warning "Version file not found"
|
||||
CURRENT_VERSION="unknown"
|
||||
fi
|
||||
|
||||
# Check for updates
|
||||
log "Checking for updates..."
|
||||
LATEST_VERSION=$(curl -s "$GITHUB_REPO/raw/main/version" 2>/dev/null || echo "")
|
||||
|
||||
if [ -z "$LATEST_VERSION" ]; then
|
||||
error "Could not fetch latest version"
|
||||
fi
|
||||
|
||||
if [ "$CURRENT_VERSION" = "$LATEST_VERSION" ]; then
|
||||
log "Already running latest version: $LATEST_VERSION"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
log "New version available: $LATEST_VERSION"
|
||||
echo ""
|
||||
read -p "Update to version $LATEST_VERSION? (y/N): " -n 1 -r
|
||||
echo ""
|
||||
|
||||
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
||||
log "Update cancelled"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Backup current installation
|
||||
log "Creating backup at $BACKUP_DIR..."
|
||||
cp -r "$INSTALL_DIR" "$BACKUP_DIR"
|
||||
|
||||
# Backup WireGuard configs
|
||||
cp -r /etc/wireguard "$BACKUP_DIR/wireguard-configs"
|
||||
|
||||
# Backup iptables rules
|
||||
iptables-save > "$BACKUP_DIR/iptables.rules"
|
||||
ip6tables-save > "$BACKUP_DIR/ip6tables.rules"
|
||||
|
||||
log "Backup complete"
|
||||
|
||||
# Stop services
|
||||
log "Stopping services..."
|
||||
systemctl stop vpn-webui vpn-security-monitor 2>/dev/null || true
|
||||
|
||||
# Download updates
|
||||
log "Downloading updates..."
|
||||
cd /tmp
|
||||
rm -rf vpn-gateway-update
|
||||
git clone "$GITHUB_REPO" vpn-gateway-update || \
|
||||
error "Failed to download updates"
|
||||
|
||||
# Update backend
|
||||
log "Updating backend..."
|
||||
cp /tmp/vpn-gateway-update/backend/app.py "$INSTALL_DIR/app.py"
|
||||
|
||||
# Update frontend
|
||||
log "Updating frontend..."
|
||||
cp /tmp/vpn-gateway-update/frontend/index.html "$INSTALL_DIR/static/index.html"
|
||||
|
||||
# Update scripts
|
||||
log "Updating scripts..."
|
||||
cp /tmp/vpn-gateway-update/scripts/*.sh /usr/local/bin/
|
||||
chmod +x /usr/local/bin/vpn-*.sh
|
||||
|
||||
# Update Python dependencies
|
||||
log "Updating dependencies..."
|
||||
source "$INSTALL_DIR/venv/bin/activate"
|
||||
pip install --upgrade -r /tmp/vpn-gateway-update/backend/requirements.txt
|
||||
|
||||
# Update version file
|
||||
echo "$LATEST_VERSION" > "$INSTALL_DIR/version"
|
||||
|
||||
# Restart services
|
||||
log "Restarting services..."
|
||||
systemctl daemon-reload
|
||||
systemctl start vpn-webui vpn-security-monitor
|
||||
|
||||
# Cleanup
|
||||
rm -rf /tmp/vpn-gateway-update
|
||||
|
||||
# Verify update
|
||||
sleep 3
|
||||
if systemctl is-active vpn-webui >/dev/null 2>&1; then
|
||||
log "Update successful!"
|
||||
log "Version $LATEST_VERSION is now running"
|
||||
echo ""
|
||||
echo -e "${GREEN}Update complete!${NC}"
|
||||
echo "Backup saved at: $BACKUP_DIR"
|
||||
else
|
||||
error "Services failed to start after update!"
|
||||
echo "Restore from backup with:"
|
||||
echo " rm -rf $INSTALL_DIR"
|
||||
echo " mv $BACKUP_DIR $INSTALL_DIR"
|
||||
echo " systemctl restart vpn-webui"
|
||||
fi
|
||||
|
1
version
Normal file
1
version
Normal file
|
@ -0,0 +1 @@
|
|||
1.0.0
|
Loading…
Add table
Add a link
Reference in a new issue