Outils pour utilisateurs

Outils du site


article:virt-manager_plusieurs_vm_yunohost_vers_une_seule_ip:accueil

Ceci est une ancienne révision du document !


Documentation : Proxy Sécurisé C++ sur Virt-Manager

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 :

  1. Ouvrez "Détails de l'hôte" > onglet "Réseaux virtuels".
  2. Cliquez sur + pour ajouter un réseau.
  3. Nom : reseau-yunohost.
  4. IPv4 : 192.168.100.0/24.
  5. Mode : NAT (par défaut).
  6. 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: eth0 ou enp3s0).
    • Modèle : virtio.
  • Interface 2 (LAN) :
    • Source : réseau virtuel nommé reseau-yunohost.
    • Modèle : virtio.

É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.

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

  1. 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.
  2. Compiler le Proxy :
    • make
  3. Lancer le Proxy :
    • sudo ./proxy_manager
  4. 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).
  5. Test Final :
    • Depuis l'extérieur (4G), accédez à http://.
    • Le trafic doit passer par la Gateway, puis être routé vers la VM Yunohost.
article/virt-manager_plusieurs_vm_yunohost_vers_une_seule_ip/accueil.1778601796.txt.gz · Dernière modification : de estro

Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki