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