Skip to content

Reverse Proxy et TLS pour APT Repo Manager

Ce guide explique comment placer APT Repo Manager derrière un reverse proxy afin d'activer HTTPS, renforcer la sécurité et exposer l'application sur les ports standard (80/443).


1. Pourquoi un reverse proxy ?

APT Repo Manager expose trois ports en HTTP brut par défaut :

Service Port Description
Frontend 3003 Interface React (SPA)
Backend API 8000 API FastAPI appelée par le browser
Dépôt APT 80 Servi par Nginx interne

Sans reverse proxy, l'application tourne en HTTP clair. Cela implique :

  • Les identifiants et tokens d'API transitent en clair sur le réseau.
  • Les navigateurs modernes refusent ou avertissent pour les sites HTTP.
  • Il est impossible d'activer HSTS (HTTP Strict Transport Security).
  • Les clients apt peuvent être victimes d'attaques de type man-in-the-middle.

Avec un reverse proxy TLS, vous obtenez :

  • Chiffrement de bout en bout (TLS 1.2/1.3).
  • HSTS pour forcer HTTPS dans les navigateurs.
  • Exposition sur les ports standard 80/443 (pas de numéro de port dans l'URL).
  • Gestion centralisée des certificats (Let's Encrypt ou CA interne).
  • Possibilité de filtrer, limiter le débit et journaliser les requêtes.

2. Architecture cible

                        ┌──────────────────────────────────────┐
                        │           Reverse Proxy              │
Internet ──── :443 ───► │  /         → frontend  :3003         │
                        │  /api/     → backend   :8000         │
         ──── :80  ───► │  apt.*     → apt-repo  :80 (HTTP OK) │
                        └──────────────────────────────────────┘
  • Le frontend et l'API sont servis en HTTPS sur le même domaine (repo.example.com).
  • Le dépôt APT peut rester en HTTP (les paquets sont signés par GPG) ou être migré en HTTPS si les clients apt le supportent.
  • En production, BIND_HOST=127.0.0.1 dans .env pour que les services n'écoutent que sur l'interface loopback.

3. Prérequis et DNS

Prérequis système

  • Un serveur Linux avec Docker et Docker Compose installés.
  • Les ports 80 et 443 ouverts dans le pare-feu.
  • Un nom de domaine pointant vers l'IP publique du serveur.

Configuration DNS recommandée

Enregistrement Valeur Usage
repo.example.com A → IP_SERVEUR Frontend + API
apt.example.com A → IP_SERVEUR Dépôt APT (optionnel)

Si vous utilisez un seul domaine avec des chemins (/api/), un seul enregistrement A suffit.

Ports à ouvrir (pare-feu)

# ufw
ufw allow 80/tcp
ufw allow 443/tcp

# iptables
iptables -A INPUT -p tcp --dport 80 -j ACCEPT
iptables -A INPUT -p tcp --dport 443 -j ACCEPT

4. Option A : Nginx + Let's Encrypt (recommandé)

4.1 Installation

apt update
apt install nginx certbot python3-certbot-nginx

4.2 Obtention du certificat

certbot --nginx -d repo.example.com
# Si vous utilisez un sous-domaine séparé pour apt :
certbot --nginx -d repo.example.com -d apt.example.com

Certbot modifie automatiquement la configuration Nginx et configure le renouvellement automatique.

4.3 Configuration Nginx complète

Créez le fichier /etc/nginx/sites-available/repod :

# /etc/nginx/sites-available/repod

# Redirection HTTP → HTTPS (frontend + API)
server {
    listen 80;
    server_name repo.example.com;
    return 301 https://$host$request_uri;
}

# Frontend + API en HTTPS
server {
    listen 443 ssl http2;
    server_name repo.example.com;

    ssl_certificate     /etc/letsencrypt/live/repo.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/repo.example.com/privkey.pem;

    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
    ssl_prefer_server_ciphers off;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 1d;

    # HSTS (1 an — ne pas activer sans avoir testé HTTPS au préalable)
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

    # Frontend (React SPA)
    location / {
        proxy_pass http://127.0.0.1:3003;
        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;
    }

    # Backend API
    location /api/ {
        rewrite ^/api/(.*) /$1 break;
        proxy_pass http://127.0.0.1:8000;
        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;
        proxy_read_timeout 300s;     # scans CVE potentiellement longs
        client_max_body_size 512m;   # upload de paquets .deb volumineux
    }
}

# Dépôt APT (HTTP, les clients apt n'ont généralement pas de CA custom)
server {
    listen 80;
    server_name apt.example.com;

    location / {
        proxy_pass http://127.0.0.1:80;
        proxy_set_header Host $host;
    }
}

4.4 Activation

ln -s /etc/nginx/sites-available/repod /etc/nginx/sites-enabled/repod
nginx -t          # vérification de la syntaxe
systemctl reload nginx

4.5 Variante : domaine unique avec chemin /api/

Si vous ne souhaitez pas de sous-domaine séparé pour l'API, utilisez le bloc location /api/ ci-dessus et mettez à jour REACT_APP_API_URL :

REACT_APP_API_URL=https://repo.example.com/api

5. Option B : Nginx + certificat interne / auto-signé

Utile pour un intranet ou un environnement de développement sans accès Internet public.

5.1 Génération du certificat auto-signé

openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
  -keyout /etc/ssl/private/repod.key \
  -out /etc/ssl/certs/repod.crt \
  -subj "/CN=repo.example.com"

Pour un certificat avec Subject Alternative Names (recommandé) :

openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
  -keyout /etc/ssl/private/repod.key \
  -out /etc/ssl/certs/repod.crt \
  -subj "/CN=repo.example.com" \
  -addext "subjectAltName=DNS:repo.example.com,IP:192.168.1.100"

5.2 Adaptation de la configuration Nginx

Remplacez les lignes ssl_certificate / ssl_certificate_key par :

ssl_certificate     /etc/ssl/certs/repod.crt;
ssl_certificate_key /etc/ssl/private/repod.key;

Supprimez ou commentez le bloc HSTS si le certificat n'est pas reconnu publiquement.

5.3 Distribution du certificat aux clients

Pour que les navigateurs et les clients apt acceptent le certificat :

# Sur chaque machine cliente (Ubuntu/Debian)
cp repod.crt /usr/local/share/ca-certificates/repod.crt
update-ca-certificates

# Pour apt spécifiquement
echo 'Acquire::https::repo.example.com::CaInfo "/etc/ssl/certs/repod.crt";' \
  > /etc/apt/apt.conf.d/99repod-tls

6. Option C : Traefik (Docker-native)

Traefik s'intègre nativement à Docker et obtient automatiquement des certificats Let's Encrypt via des labels sur les conteneurs.

6.1 Service Traefik dans docker-compose.yaml

services:
  traefik:
    image: traefik:v3.0
    command:
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.websecure.address=:443"
      - "--certificatesresolvers.letsencrypt.acme.tlschallenge=true"
      - "--certificatesresolvers.letsencrypt.acme.email=admin@example.com"
      - "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock:ro"
      - "./letsencrypt:/letsencrypt"
    restart: unless-stopped

6.2 Labels sur les services repod

  frontend:
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.repod-frontend.rule=Host(`repo.example.com`)"
      - "traefik.http.routers.repod-frontend.entrypoints=websecure"
      - "traefik.http.routers.repod-frontend.tls.certresolver=letsencrypt"
      - "traefik.http.services.repod-frontend.loadbalancer.server.port=3003"

  backend:
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.repod-api.rule=Host(`repo.example.com`) && PathPrefix(`/api/`)"
      - "traefik.http.routers.repod-api.entrypoints=websecure"
      - "traefik.http.routers.repod-api.tls.certresolver=letsencrypt"
      - "traefik.http.routers.repod-api.middlewares=strip-api-prefix"
      - "traefik.http.middlewares.strip-api-prefix.stripprefix.prefixes=/api"
      - "traefik.http.services.repod-api.loadbalancer.server.port=8000"

6.3 Avantages et inconvénients

Avantage Inconvénient
Zéro configuration manuelle des certificats Traefik doit avoir accès à la socket Docker
Renouvellement automatique Labels verbeux pour les règles complexes
Dashboard intégré Moins adapté aux déploiements hors Docker

7. Option D : Caddy (configuration minimale)

Caddy gère automatiquement HTTPS sans configuration supplémentaire.

7.1 Installation

apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' \
  | gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' \
  | tee /etc/apt/sources.list.d/caddy-stable.list
apt update && apt install caddy

7.2 Caddyfile

# /etc/caddy/Caddyfile

repo.example.com {
    # Frontend
    reverse_proxy / http://127.0.0.1:3003

    # Backend API (strip le préfixe /api)
    handle /api/* {
        uri strip_prefix /api
        reverse_proxy http://127.0.0.1:8000 {
            header_up X-Forwarded-Proto {scheme}
            transport http {
                read_timeout 300s
            }
        }
    }

    # Taille maximale des uploads (paquets .deb)
    request_body {
        max_size 512MB
    }
}

apt.example.com {
    reverse_proxy http://127.0.0.1:80
}

7.3 Démarrage

systemctl enable --now caddy
caddy reload --config /etc/caddy/Caddyfile

Caddy obtient et renouvelle automatiquement les certificats Let's Encrypt. Aucune action supplémentaire n'est requise.


8. Mise à jour de la configuration repod après activation HTTPS

Après avoir activé le reverse proxy, plusieurs variables d'environnement doivent être mises à jour.

8.1 Fichier .env (racine du projet)

# Restreindre l'écoute à l'interface loopback (le RP gère l'exposition externe)
BIND_HOST=127.0.0.1

# URLs publiques (utilisées par le build frontend)
PUBLIC_URL=https://repo.example.com
REACT_APP_API_URL=https://repo.example.com/api
REACT_APP_REPO_URL=http://apt.example.com

8.2 Fichier backend.env

# Autoriser uniquement les requêtes provenant du nouveau domaine HTTPS
CORS_ORIGINS=https://repo.example.com

# Faire confiance uniquement au reverse proxy local
TRUSTED_PROXIES=127.0.0.1

Pour Traefik dans Docker (réseau bridge), ajoutez le sous-réseau Docker :

TRUSTED_PROXIES=127.0.0.1,172.16.0.0/12

8.3 Paramètres applicatifs

Dans l'interface web : Paramètres > Général > app_urlhttps://repo.example.com

8.4 Reconstruction du frontend

Le frontend React est compilé avec les variables d'environnement au moment du build. Après toute modification de REACT_APP_* ou PUBLIC_URL, reconstruisez l'image :

docker compose build frontend
docker compose up -d frontend

9. Vérification et tests

9.1 Vérification SSL

# Test de la chaîne de certificats
curl -vI https://repo.example.com 2>&1 | grep -E "SSL|TLS|certificate|issuer|expire"

# Vérification avec openssl
openssl s_client -connect repo.example.com:443 -servername repo.example.com < /dev/null \
  | openssl x509 -noout -dates -subject -issuer

9.2 Test de l'API

# Vérification que l'API répond via HTTPS
curl -s https://repo.example.com/api/health | jq .

# Vérification du header HSTS
curl -sI https://repo.example.com | grep -i strict-transport

9.3 Test de redirection HTTP → HTTPS

curl -I http://repo.example.com
# Attendu : HTTP/1.1 301 Moved Permanently + Location: https://...

9.4 Vérification dans les outils de développement du navigateur

  1. Ouvrez https://repo.example.com dans Chrome/Firefox.
  2. Ouvrez les DevTools → onglet Network → cliquez sur la première requête.
  3. Vérifiez dans Response Headers :
  4. strict-transport-security: max-age=31536000; includeSubDomains
  5. x-forwarded-proto: https (si visible dans les logs backend)

9.5 Test des clients apt

# Ajoutez le dépôt et testez
echo "deb http://apt.example.com/ubuntu focal main" \
  > /etc/apt/sources.list.d/repod.list
apt update

9.6 Qualité SSL (optionnel)

Testez votre configuration sur https://www.ssllabs.com/ssltest/ pour obtenir une note A+.


10. Renouvellement des certificats

10.1 Let's Encrypt (Certbot)

Certbot configure automatiquement un timer systemd ou une cron pour le renouvellement :

# Vérifier le timer systemd
systemctl status certbot.timer

# Test de renouvellement à sec
certbot renew --dry-run

# Renouvellement forcé (si nécessaire)
certbot renew --force-renewal

La cron par défaut ajoutée par certbot :

0 0,12 * * * certbot renew --quiet

Après renouvellement, Nginx recharge automatiquement la configuration si le hook post-deploy est en place (certbot l'ajoute normalement).

10.2 Traefik

Traefik gère le renouvellement automatiquement. Aucune action manuelle n'est requise. Les certificats sont stockés dans ./letsencrypt/acme.json.

10.3 Caddy

Caddy renouvelle automatiquement les certificats en arrière-plan. Aucune action manuelle n'est requise.

10.4 Certificats internes / auto-signés

Pour un certificat auto-signé avec une durée de validité de 1 an, planifiez une tâche cron pour le renouvellement :

# Exemple de script de renouvellement
cat > /usr/local/bin/renew-repod-cert.sh << 'EOF'
#!/bin/bash
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
  -keyout /etc/ssl/private/repod.key \
  -out /etc/ssl/certs/repod.crt \
  -subj "/CN=repo.example.com"
systemctl reload nginx
EOF
chmod +x /usr/local/bin/renew-repod-cert.sh

# Cron annuelle (330 jours pour anticiper l'expiration)
echo "0 3 1 1 * root /usr/local/bin/renew-repod-cert.sh" \
  > /etc/cron.d/repod-cert-renewal

Récapitulatif des actions post-déploiement

Action Commande / Fichier
Restreindre l'écoute BIND_HOST=127.0.0.1 dans .env
Mettre à jour les URLs PUBLIC_URL, REACT_APP_API_URL dans .env
Mettre à jour CORS CORS_ORIGINS=https://... dans backend.env
Configurer les proxies de confiance TRUSTED_PROXIES=127.0.0.1 dans backend.env
Mettre à jour app_url Paramètres > Général dans l'interface web
Reconstruire le frontend docker compose build frontend && docker compose up -d frontend
Vérifier HSTS DevTools navigateur → Response Headers