article:virt-manager_plusieurs_vm_yunohost_vers_une_seule_ip:accueil
Ceci est une ancienne révision du document !
Architecture Proxy Sécurisé C++ sur Virt-Manager
Objectif : Créer une infrastructure virtualisée où des VMs Yunohost sont isolées dans un réseau privé, protégées par une VM Gateway (Debian) exécutant un Reverse Proxy personnalisé en C++.
Contexte : Freebox Pop (IP Fixe V4 Full Stack), PC Hôte Linux avec virt-manager.
1. Architecture Réseau
Topologie Clé : La VM Yunohost est invisible depuis Internet. Seul le Proxy sur la Gateway est exposé.
+-----------------------+ INTERNET (IP Fixe) +---------------------------+
| UTILISATEUR | | FREEBOX POP |
| (Navigateur) | | (IP Publique Fixe) |
+----------+------------+ +-------------+-------------+
| |
| 1. Requêtes HTTP/HTTPS (Port 80/443) |
| |
v v
+--------------------------------------------------------------------------------------------+
| SERVEUR PHYSIQUE (Hôte KVM) |
| (PC avec virt-manager) |
| |
| +----------------------+ +----------------------+ +------------------+ |
| | | | RÉSEAU VIRTUEL | | VM YUNOHOST | |
| | VM GATEWAY (Debian) | <------> | (reseau-yunohost) | <------> | (Service A) | |
| | | | (192.168.100.0/24) | | | |
| | +--------------+ | | (Bridge virtuel) | | IP: 192.168.100.10| |
| | | eth0 (WAN) |----+----------+----------------------+----------+--> eth0 (LAN) | |
| | | 192.168.1.50 | | | | | | |
| | +--------------+ | | | | | |
| | | | | | | |
| | +--------------+ | | | | | |
| | | eth1 (LAN) |----+----------+--------------------+----------+--> eth0 (LAN) | |
| | | 192.168.100.1| | | | | | |
| | +--------------+ | | | | | |
| | | | | | | |
| | [Services]: | | | | [Services]: | |
| | - Reverse Proxy C++ | | | | - Web/Mail | |
| | - Pare-feu (NAT) | | | | - Apps | |
| | - Interface Web | | | | | |
| +----------------------+ +----------------------+ +------------------+ |
+--------------------------------------------------------------------------------------------+
Configuration des Adresses IP
| Composant | Interface | Réseau | Adresse IP | Rôle |
|---|---|---|---|---|
| Freebox | LAN | 192.168.1.0/24 | 192.168.1.254 | Passerelle Physique |
| VM Gateway | eth0 (WAN) | 192.168.1.0/24 | 192.168.1.50 (Statique) | Cible de la DMZ |
| VM Gateway | eth1 (LAN) | 192.168.100.0/24 | 192.168.100.1 | Gateway Interne |
| VM Yunohost | eth0 | 192.168.100.0/24 | 192.168.100.10 | Service Caché |
Attention : La Freebox doit avoir l'IP
192.168.1.50 configurée en DMZ.
2. Configuration Virt-Manager
Étape A : Créer le réseau interne
Dans l'interface virt-manager :
- Ouvrez "Détails de l'hôte" > onglet "Réseaux virtuels".
- Cliquez sur + pour ajouter un réseau.
- Nom :
reseau-yunohost. - IPv4 :
192.168.100.0/24. - Mode : NAT (par défaut).
- Activer le DHCP : Oui (plage
192.168.100.10à192.168.100.254).
Étape B : Configurer la VM Gateway (Debian)
Éditez les détails de la VM Debian :
- Interface 1 (WAN) :
- Source : Bridge vers l'interface physique de votre PC (ex:
eth0ouenp3s0). - Modèle :
virtio.
- Source : Bridge vers l'interface physique de votre PC (ex:
- Interface 2 (LAN) :
- Source : réseau virtuel nommé
reseau-yunohost. - Modèle :
virtio.
- Source : réseau virtuel nommé
Étape C : Configurer la VM Yunohost
- Éditez les détails de la VM Yunohost.
- Interface :
- Source : réseau virtuel
reseau-yunohost. - Ne pas ajouter d'interface WAN.
- Source : réseau virtuel
3. Script de Configuration Gateway (Bash)
À exécuter sur la VM Debian (Gateway) après installation. Ce script active le routage, le NAT et le pare-feu.
#!/bin/bash
# ==========================================
# CONFIGURATION DE LA GATEWAY DEBIAN
# ==========================================
# --- 1. Configuration des Interfaces (À adapter) ---
WAN_IF="eth0" # Interface vers la Box/Internet (Bridge physique)
LAN_IF="eth1" # Interface vers le réseau 'reseau-yunhost' (192.168.100.x)
# Vérification simple
if ! ip link show $WAN_IF &>/dev/null; then
echo "ERREUR: Interface WAN ($WAN_IF) introuvable."
exit 1
fi
if ! ip link show $LAN_IF &>/dev/null; then
echo "ERREUR: Interface LAN ($LAN_IF) introuvable."
exit 1
fi
echo "[+] Interfaces détectées : WAN=$WAN_IF, LAN=$LAN_IF"
# --- 2. Activation du Forwarding IP (Routeur) ---
echo "[+] Activation du forwarding IP..."
echo "net.ipv4.ip_forward=1" | sudo tee -a /etc/sysctl.conf
sudo sysctl -p
# --- 3. Configuration du Pare-feu (iptables) ---
echo "[+] Configuration des règles iptables..."
# Effacer les règles existantes
sudo iptables -F
sudo iptables -t nat -F
sudo iptables -X
sudo iptables -t nat -X
# Politiques par défaut
sudo iptables -P INPUT DROP
sudo iptables -P FORWARD DROP
sudo iptables -P OUTPUT ACCEPT
# --- Règles INPUT (Trafic vers la Gateway) ---
sudo iptables -A INPUT -i lo -j ACCEPT
sudo iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 22 -j ACCEPT # SSH
sudo iptables -A INPUT -i $WAN_IF -p tcp --dport 80 -j ACCEPT # HTTP
sudo iptables -A INPUT -i $WAN_IF -p tcp --dport 443 -j ACCEPT # HTTPS
# --- Règles FORWARD (Trafic transitant) ---
sudo iptables -A FORWARD -i $LAN_IF -o $WAN_IF -j ACCEPT # Sortie VMs
sudo iptables -A FORWARD -i $WAN_IF -o $LAN_IF -m state --state ESTABLISHED,RELATED -j ACCEPT
sudo iptables -A FORWARD -i $WAN_IF -o $LAN_IF -p tcp --dport 80 -j ACCEPT # Proxy In
sudo iptables -A FORWARD -i $WAN_IF -o $LAN_IF -p tcp --dport 443 -j ACCEPT # Proxy In
# --- Règles NAT (Masquerade) ---
sudo iptables -t nat -A POSTROUTING -o $WAN_IF -j MASQUERADE
# --- 4. Persistance ---
echo "[+] Sauvegarde des règles..."
sudo apt install -y iptables-persistent
sudo netfilter-persistent save
echo "=========================================="
echo "CONFIGURATION TERMINÉE !"
echo "=========================================="
4. Source C++ (Proxy Manager)
Code complet incluant le serveur HTTP (interface web), le moteur de proxy et la gestion de config.
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>
#include <thread>
#include <mutex>
#include <cstring>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <regex>
#include <ctime>
struct VMConfig {
std::string id;
std::string name;
std::string domain;
std::string ip;
int port;
bool enabled;
};
class ConfigManager {
private:
std::string config_path;
std::vector<VMConfig> vms;
std::string admin_password;
std::mutex mtx;
public:
ConfigManager(std::string path = "config.json") : config_path(path) { load_config(); }
void load_config() {
std::lock_guard<std::mutex> lock(mtx);
std::ifstream file(config_path);
if (!file.is_open()) return;
std::stringstream buffer; buffer << file.rdbuf();
parse_json(buffer.str());
}
void save_config() {
std::lock_guard<std::mutex> lock(mtx);
std::ofstream file(config_path);
file << "{\n \"vm_list\": [\n";
for (size_t i = 0; i < vms.size(); i++) {
file << " {\"id\": \"" << vms[i].id << "\", \"name\": \"" << vms[i].name << "\", ";
file << "\"domain\": \"" << vms[i].domain << "\", \"ip\": \"" << vms[i].ip << "\", ";
file << "\"port\": " << vms[i].port << ", \"enabled\": " << (vms[i].enabled ? "true" : "false") << "}";
if (i < vms.size() - 1) file << ",";
file << "\n";
}
file << " ]\n}";
}
void parse_json(const std::string& content) {
vms.clear();
std::regex vm_block(R"(\{\s*"id"\s*:\s*"([^"]+)".*?"name"\s*:\s*"([^"]+)".*?"domain"\s*:\s*"([^"]+)".*?"ip"\s*:\s*"([^"]+)".*?"port"\s*:\s*(\d+).*?"enabled"\s*:\s*(true|false)\s*\})", std::regex::dotall);
auto begin = std::sregex_iterator(content.begin(), content.end(), vm_block);
auto end = std::sregex_iterator();
for (auto i = begin; i != end; ++i) {
vms.push_back({(*i)[1], (*i)[2], (*i)[3], (*i)[4], std::stoi((*i)[5]), (*i)[6]=="true"});
}
}
void add_vm(const std::string& id, const std::string& name, const std::string& domain, const std::string& ip, int port) {
std::lock_guard<std::mutex> lock(mtx);
vms.push_back({id, name, domain, ip, port, true});
save_config();
}
void remove_vm(const std::string& id) {
std::lock_guard<std::mutex> lock(mtx);
vms.erase(std::remove_if(vms.begin(), vms.end(), [&id](const VMConfig& vm) { return vm.id == id; }), vms.end());
save_config();
}
VMConfig* find_by_domain(const std::string& domain) {
std::lock_guard<std::mutex> lock(mtx);
for (auto& vm : vms) if (vm.domain == domain && vm.enabled) return &vm;
return nullptr;
}
std::vector<VMConfig> get_all_vms() { std::lock_guard<std::mutex> lock(mtx); return vms; }
};
class HTTPServer {
private:
int server_fd;
ConfigManager& config;
std::string generate_html() {
std::stringstream html;
html << "<!DOCTYPE html><html><head><title>Proxy Manager</title><style>body{font-family:Arial;} table{border-collapse:collapse;width:100%;} th,td{border:1px solid #ddd;padding:8px;} th{background:#4CAF50;color:white;}</style></head><body><h1>Proxy Manager</h1><form method='POST' action='/add'>ID: <input name='id'> Nom: <input name='name'> Domaine: <input name='domain'> IP: <input name='ip'> Port: <input name='port' value='80'> <button type='submit'>Ajouter</button></form><h3>Liste</h3><table><tr><th>ID</th><th>Nom</th><th>Domaine</th><th>IP</th><th>Action</th></tr>";
for (const auto& vm : config.get_all_vms()) {
html << "<tr><td>" << vm.id << "</td><td>" << vm.name << "</td><td>" << vm.domain << "</td><td>" << vm.ip << "</td><td><form method='POST' action='/remove' style='display:inline;'><input type='hidden' name='id' value='" << vm.id << "'><button type='submit'>Supprimer</button></form></td></tr>";
}
html << "</table></body></html>";
return html.str();
}
std::string get_param(const std::string& data, const std::string& param) {
std::string search = param + "="; size_t pos = data.find(search);
if (pos == std::string::npos) return "";
pos += search.length(); size_t end = data.find('&', pos);
return data.substr(pos, end == std::string::npos ? data.length() - pos : end - pos);
}
public:
HTTPServer(ConfigManager& cfg, int port = 8080) : config(cfg) {
server_fd = socket(AF_INET, SOCK_STREAM, 0);
int opt = 1; setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
sockaddr_in addr{}; addr.sin_family = AF_INET; addr.sin_addr.s_addr = INADDR_ANY; addr.sin_port = htons(port);
bind(server_fd, (sockaddr*)&addr, sizeof(addr)); listen(server_fd, 10);
std::cout << "[+] Interface Web sur port " << port << std::endl;
}
void handle_request(int client_fd) {
char buffer[8192] = {0}; read(client_fd, buffer, sizeof(buffer));
std::string request(buffer), response;
if (request.find("POST /add") != std::string::npos) {
std::string data = request.substr(request.find("\r\n\r\n") + 4);
config.add_vm(get_param(data, "id"), get_param(data, "name"), get_param(data, "domain"), get_param(data, "ip"), std::stoi(get_param(data, "port")));
response = "HTTP/1.1 302 Found\r\nLocation: /\r\n\r\n";
} else if (request.find("POST /remove") != std::string::npos) {
std::string data = request.substr(request.find("\r\n\r\n") + 4);
config.remove_vm(get_param(data, "id"));
response = "HTTP/1.1 302 Found\r\nLocation: /\r\n\r\n";
} else {
response = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n" + generate_html();
}
write(client_fd, response.c_str(), response.length()); close(client_fd);
}
void run() { while(true) { sockaddr_in client_addr{}; socklen_t len = sizeof(client_addr); int cfd = accept(server_fd, (sockaddr*)&client_addr, &len); if(cfd>=0) std::thread(&HTTPServer::handle_request, this, cfd).detach(); } }
};
class ReverseProxy {
private:
ConfigManager& config;
int create_backend(const std::string& ip, int port) {
int sock = socket(AF_INET, SOCK_STREAM, 0);
sockaddr_in srv{}; srv.sin_family = AF_INET; srv.sin_port = htons(port); inet_pton(AF_INET, ip.c_str(), &srv.sin_addr);
if (connect(sock, (sockaddr*)&srv, sizeof(srv)) < 0) { close(sock); return -1; }
return sock;
}
public:
ReverseProxy(ConfigManager& cfg, int port = 80) : config(cfg) {
int lfd = socket(AF_INET, SOCK_STREAM, 0);
int opt = 1; setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
sockaddr_in addr{}; addr.sin_family = AF_INET; addr.sin_addr.s_addr = INADDR_ANY; addr.sin_port = htons(port);
bind(lfd, (sockaddr*)&addr, sizeof(addr)); listen(lfd, 100);
std::cout << "[+] Proxy sur port " << port << std::endl;
while(true) {
sockaddr_in caddr{}; socklen_t clen = sizeof(caddr); int cfd = accept(lfd, (sockaddr*)&caddr, &clen);
if(cfd < 0) continue;
std::thread([this, cfd]() {
char buf[8192] = {0}; int bytes = recv(cfd, buf, sizeof(buf)-1, 0);
if(bytes > 0) {
buf[bytes] = '\0'; std::string req(buf);
std::regex host_r(R"(Host:\s*([^\r\n]+))"); std::smatch m; std::string host = "localhost";
if(std::regex_search(req, m, host_r)) { host = m[1].str(); size_t p = host.find(':'); if(p!=std::string::npos) host = host.substr(0,p); }
VMConfig* vm = config.find_by_domain(host);
if(vm) {
int bfd = create_backend(vm->ip, vm->port);
if(bfd >= 0) {
std::string mod_req = req; size_t eh = mod_req.find("\r\n\r\n");
if(eh != std::string::npos) mod_req.insert(eh, "X-Forwarded-For: client\r\nX-Real-IP: client\r\n");
send(bfd, mod_req.c_str(), mod_req.length(), 0);
char bbuf[4096];
while(true) {
fd_set fds; FD_ZERO(&fds); FD_SET(cfd, &fds); FD_SET(bfd, &fds);
struct timeval tv{10,0}; int act = select(std::max(cfd,bfd)+1, &fds, NULL, NULL, &tv);
if(act<0) break;
if(FD_ISSET(cfd, &fds)) { int r = recv(cfd, bbuf, sizeof(bbuf), 0); if(r<=0) break; send(bfd, bbuf, r, 0); }
if(FD_ISSET(bfd, &fds)) { int r = recv(bfd, bbuf, sizeof(bbuf), 0); if(r<=0) break; send(cfd, bbuf, r, 0); }
}
close(bfd);
} else { std::string err = "HTTP/1.1 502 Bad Gateway\r\n\r\n"; send(cfd, err.c_str(), err.length(), 0); }
} else { std::string err = "HTTP/1.1 404 Not Found\r\n\r\n"; send(cfd, err.c_str(), err.length(), 0); }
}
close(cfd);
}).detach();
}
}
};
int main() {
ConfigManager cfg("config.json");
std::thread t1([&cfg](){ ReverseProxy p(cfg, 80); p.run(); });
std::thread t2([&cfg](){ HTTPServer w(cfg, 8080); w.run(); });
t1.join(); t2.join();
return 0;
}
5. Makefile
CXX = g++
CXXFLAGS = -std=c++17 -Wall -pthread -O2
TARGET = proxy_manager
all: $(TARGET)
$(TARGET): main.cpp
$(CXX) $(CXXFLAGS) -o $@ $<
clean:
rm -f $(TARGET)
run: $(TARGET)
sudo ./$(TARGET)
6. Fichier de Configuration (config.json)
Créez ce fichier manuellement ou laissez le programme le créer au premier lancement.
{
"vm_list": []
}
7. Instructions de Déploiement
- Préparer la VM Gateway (Debian) :
- Installer les dépendances :
sudo apt update && sudo apt install -y build-essential net-tools iptables-persistent. - Créer les fichiers
main.cpp,Makefile,config.json. - Lancer le script Bash
setup_gateway.sh(copié ci-dessus) pour configurer le réseau.
- Installer les dépendances :
- Compiler le Proxy :
make
- Lancer le Proxy :
sudo ./proxy_manager
- Configurer les VMs Yunohost :
- Assurez-vous qu'elles sont sur le réseau
reseau-yunohost. - Accédez à l'interface web du proxy :
http://192.168.1.50:8080. - Ajoutez vos VMs via l'interface (ex: Domaine:
monsite.com, IP:192.168.100.10).
- Assurez-vous qu'elles sont sur le réseau
- Test Final :
- Depuis l'extérieur (4G), accédez à
http://. - Le trafic doit passer par la Gateway, puis être routé vers la VM Yunohost.
- Depuis l'extérieur (4G), accédez à
article/virt-manager_plusieurs_vm_yunohost_vers_une_seule_ip/accueil.1778601796.txt.gz · Dernière modification : de estro
