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
aptpeuvent ê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.1dans.envpour 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¶
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 :
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 :
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"
- "[email protected]"
- "--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¶
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 :
8.3 Paramètres applicatifs¶
Dans l'interface web : Paramètres > Général > app_url → https://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 :
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¶
9.4 Vérification dans les outils de développement du navigateur¶
- Ouvrez
https://repo.example.comdans Chrome/Firefox. - Ouvrez les DevTools → onglet Network → cliquez sur la première requête.
- Vérifiez dans Response Headers :
strict-transport-security: max-age=31536000; includeSubDomainsx-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 :
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 |