Skip to content

Accès FTP

Spektrum héberge un service FTP/FTPS dédié pour ses clients qui ont besoin de déposer des médias (images, documents) consommés par leurs applications. Le cas d'usage typique: un ERP ou un service externe pousse des fichiers en FTP, et l'application Spektrum les récupère pour les intégrer.

Architecture

Pour les mêmes raisons d'isolation que le reste de l'infrastructure (cf. Cloud Layout), chaque environnement (staging, production) a sa propre VM FTP dédiée avec une IP publique propre.

ServeurHostnameIP publiqueSous-réseau
FTP-Stagingftp.staging.spektrum-suisse.ch84.234.27.226internal
FTP-Prodftp.prod.spektrum-suisse.ch83.228.249.248production

Pourquoi pas derrière le bastion NGINX ?

Le protocole FTP ne peut pas être reverse-proxifié par NGINX (ni par NPM). FTP n'a pas de header Host et négocie IP+port dans la connexion de contrôle pour le mode passif. Donc chaque serveur FTP est exposé directement sur sa propre IP, sans passer par les bastions HTTP/S.

Stack technique

ComposantChoix
OSUbuntu 24.04 LTS
Containerdelfer/alpine-ftp-server (vsftpd Alpine)
ModeFTPS Explicit (TLS sur port 21 via AUTH SSL)
CertLet's Encrypt via certbot HTTP-01 standalone
Mode connexionPassive uniquement, plage 21000-21100
Renouvellementcertbot.timer + deploy hook qui restart le container
TODO DNS Challenge régler l'issue

Ports

PortUsageSG / UFW
21/tcpFTP control (TLS négocié dessus via AUTH)Ingress public
21000-21100/tcpFTPS passive dataIngress public
80/tcpCert Let's Encrypt (renouvellement HTTP-01)Ingress public

Port 80 ouvert sans risque

Le port 80 reste ouvert en permanence pour les renouvellements certbot, mais rien n'écoute dessus entre les renouvellements. La connexion est refusée par le kernel - pas de surface d'attaque.

Stockage - 1 volume par client

Chaque client qui consomme le FTP a son propre volume cloud Infomaniak rattaché à la VM FTP, monté directement sur son dossier de données. Cela permet:

  • D'isoler les données entre clients (un client ne peut pas remplir l'espace d'un autre)
  • D'agrandir/restaurer indépendamment chaque volume
  • De détacher proprement le volume si on déplace un client ailleurs

Layout

/                                              ← rootfs (sda1, ~20G)
└── data/ftp/                                  ← rootfs
    ├── docker-compose.yml                     ← rootfs
    ├── .env (chmod 600, contient les users + mots de passe)
    ├── certs/{fullchain,privkey}.pem          ← rootfs (copiés depuis Let's Encrypt)
    └── data/
        ├── the-swiss-collector/               ← /dev/sdb (volume dédié client A)
        ├── hopital-vs/                        ← /dev/sdc (volume dédié client B)
        └── client-suivant/                    ← /dev/sdd (volume dédié client C)

Persistance

Les volumes sont montés via LABEL dans /etc/fstab:

LABEL=tsc-ftp-prod   /data/ftp/data/the-swiss-collector  ext4  defaults,nofail  0  2
LABEL=hvs-ftp-prod   /data/ftp/data/hopital-vs           ext4  defaults,nofail  0  2

Ordre du chown

Toujours faire le chown APRÈS le mount. Sinon, les permissions sont sur le dossier sous-jacent du rootfs, pas sur le filesystem du volume - et elles disparaissent au montage.

Authentification

Les utilisateurs FTP sont déclarés dans le fichier .env du serveur (chmod 600):

env
FTP_ADDRESS=ftp.spektrum-suisse.ch
FTP_USERS=the-swiss-collector|<password>|/home/vsftpd/the-swiss-collector hopitalvs|<password>|/home/vsftpd/hopital-vs

Format: username|password|home_path, séparés par des espaces. Chaque user est confiné en chroot dans son home_path, qui correspond au volume monté pour ce client.

Mot de passe par client

Génère un mot de passe robuste pour chaque client:

bash
openssl rand -base64 24

Ne jamais réutiliser un mot de passe entre staging et production.

Ajouter un nouveau client

  1. Créer / attacher un volume dans Manager Infomaniak Public Cloud (taille selon besoin)
  2. Sur la VM FTP, formater et monter:
    bash
    sudo mkfs.ext4 -L <client>-ftp-<env> /dev/sdX
    sudo mkdir -p /data/ftp/data/<client>
    sudo mount /dev/sdX /data/ftp/data/<client>
    echo "LABEL=<client>-ftp-<env>  /data/ftp/data/<client>  ext4  defaults,nofail  0  2" | sudo tee -a /etc/fstab
    sudo mount -a
  3. Ajouter le user dans /data/ftp/.env:
    env
    FTP_USERS=...précédents... <client>|<password>|/home/vsftpd/<client>
  4. Mettre à jour le docker-compose.yml pour binder le nouveau volume:
    yaml
    volumes:
      - /data/ftp/data/<client>:/home/vsftpd/<client>
  5. Redémarrer:
    bash
    docker compose up -d
  6. Communiquer au client: hostname, port (21), user, password, mode FTPS Explicit + Passif.

Connexion administrateur (SSH)

L'accès admin à la VM FTP se fait via le serveur Bastion correspondant, comme toute autre VM

Username + clé privée

Les accès sont les mêmes que partout: username LDAP + clé privée. Cf. Accès.

Renouvellement automatique du certificat

Le hook /etc/letsencrypt/renewal-hooks/deploy/ftp.sh recopie les certs renouvelés et redémarre le container:

bash
#!/bin/bash
cp /etc/letsencrypt/live/<hostname>/fullchain.pem /data/ftp/certs/
cp /etc/letsencrypt/live/<hostname>/privkey.pem  /data/ftp/certs/
docker restart ftp-server

Le timer certbot.timer tourne 2x par jour et déclenche le renouvellement quand il reste <30 jours sur le cert. Vérification:

bash
sudo systemctl list-timers certbot.timer
sudo certbot renew --dry-run

Côté application qui consomme le FTP

Côté NopCommerce (et autres clients .NET), utiliser FluentFTP avec:

csharp
new FtpClient(host, new NetworkCredential(user, pass), port: 21) {
    Config = {
        EncryptionMode         = FtpEncryptionMode.Explicit,
        DataConnectionEncryption = true,
        DataConnectionType     = FtpDataConnectionType.AutoPassive,
        ConnectTimeout         = 15_000,
        DataConnectionConnectTimeout = 15_000,
        SocketKeepAlive        = true
    }
};

Mode passif obligatoire

En mode actif, le serveur FTP essaie de se connecter au client sur un port qu'il a annoncé - bloqué par tous les NAT modernes. Toujours AutoPassive.

Test de connexion (debug)

Depuis n'importe quel poste:

bash
curl -v --ssl-reqd \
  --user "<user>:<password>" \
  ftp://<hostname>/

Si tu vois 230 Login successful + un listing → tout fonctionne.

Sur Windows / cmd.exe

Utilise des guillemets doubles ("foo:bar"), pas simples - cmd.exe ne traite pas les ' comme délimiteurs de chaîne et te file un user/pass invalide:

cmd
curl -v --ssl-reqd --user "client:password" ftp://ftp.spektrum-suisse.ch/

Migration depuis un ancien serveur FTP

Voir rsync via ProxyJump - le pattern réutilisable pour transférer les données existantes vers le nouveau serveur sans recopier via le FTP lui-même (beaucoup plus rapide).

Contributors

The avatar of contributor named as Clément Favre Clément Favre

Changelog