Aller au contenu

Gestion des distributions

Un guide conceptuel de la manière dont Repod modélise les distributions, pourquoi cette distinction est importante, et comment les paquets circulent entre elles.


Qu'est-ce qu'une distribution ?

Une distribution dans Repod représente une version cible de système d'exploitation. Lorsque vous uploadez ou importez un paquet, vous l'affectez à une distribution spécifique. Seuls les clients configurés pour cette distribution verront le paquet.

Le concept correspond directement à la notion de cible du gestionnaire de paquets natif :

Une distribution APT est un codename (jammy, noble, bookworm). Il apparaît dans l'entrée sources.list :

deb http://repo.example.com/repos jammy main

En coulisses, reprepro gère une arborescence de répertoires sous /repos/dists/jammy/ contenant InRelease, Release, et l'index Packages, servie par depot-apt.

Une distribution RPM est un composant de chemin (almalinux9, fedora, rocky9). Il apparaît dans le fichier .repo :

baseurl=http://repo.example.com:8080/repos/almalinux9/x86_64/

En coulisses, createrepo_c gère une arborescence de répertoires sous /repos/rpm/almalinux9/x86_64/ contenant repodata/repomd.xml et les fichiers RPM, servie par depot-rpm.

Une distribution Alpine est également un composant de chemin (alpine3.20). Elle apparaît dans /etc/apk/repositories :

http://repo.example.com/apk/alpine3.20/main

En coulisses, apk index gère une arborescence de répertoires sous /repos/apk/alpine3.20/main/x86_64/ contenant APKINDEX.tar.gz et les fichiers .apk, servie par depot-apt sous /apk/.


Pourquoi les distributions sont importantes

Compatibilité binaire

Un .deb construit pour Ubuntu 22.04 (jammy) peut ne pas s'installer correctement sur Ubuntu 24.04 (noble) — version de glibc différente, Python par défaut différent, ABI de bibliothèque SSL différente. La même règle s'applique entre distributions basées sur RPM (AlmaLinux 8 vs. 9) et versions Alpine (changements d'ABI musl entre alpine3.18 et alpine3.21). Garder les distributions séparées empêche des paquets non compatibles d'atteindre le mauvais système d'exploitation.

Précision des CVE

Grype utilise l'ID de distribution (almalinux:9, rockylinux:9, opensuse/leap:15.6, etc., configuré par codename dans services/distributions_rpm.py) pour filtrer les avis CVE. Une vulnérabilité peut être corrigée dans AlmaLinux 9 mais pas dans AlmaLinux 8 — sans le bon contexte de distribution, Grype pourrait retourner des faux positifs ou manquer des avis pertinents.

Pour Ubuntu/Debian, Grype établit une corrélation avec les Ubuntu Security Notices (USN) et les entrées du Debian Security Tracker par version, ce qui nécessite de la même manière que le codename soit connu.

Contrôle du déploiement progressif

Les distributions servent de paliers d'étagement. Un motif courant :

Développement → QA → Production
(focal)          (jammy)   (noble)

La promotion de paquet déplace un binaire entre distributions sans re-upload ni re-scan (la décision CVE est préservée). Voir Promouvoir des paquets.


Distributions prises en charge

Lesquelles sont actives dépend de REPO_FORMAT (apt, rpm, apk, both, ou all) — voir Démarrage rapide — Étape 2.

Codename OS Architecture
jammy Ubuntu 22.04 LTS amd64
noble Ubuntu 24.04 LTS amd64
focal Ubuntu 20.04 LTS amd64
bookworm Debian 12 amd64
Codename OS Architecture ID de distro Grype
almalinux8 AlmaLinux 8 x86_64 almalinux:8
almalinux9 AlmaLinux 9 x86_64 almalinux:9
rocky8 Rocky Linux 8 x86_64 rockylinux:8
rocky9 Rocky Linux 9 x86_64 rockylinux:9
centos-stream9 CentOS Stream 9 x86_64 centos:9
oraclelinux8 Oracle Linux 8 x86_64 oraclelinux:8
fedora Fedora 42 x86_64 fedora:42
opensuse-leap-15.6 openSUSE Leap 15.6 x86_64 opensuse/leap:15.6
opensuse-tumbleweed openSUSE Tumbleweed x86_64 opensuse/tumbleweed:latest
Codename OS Architecture
alpine3.18 Alpine Linux 3.18 x86_64
alpine3.19 Alpine Linux 3.19 x86_64
alpine3.20 Alpine Linux 3.20 x86_64
alpine3.21 Alpine Linux 3.21 x86_64

Organisation du système de fichiers des dépôts

Comprendre la disposition sur disque aide au dépannage ou à l'intégration avec d'autres outils. Les trois arborescences vivent côte à côte sous /repos/ dans le même conteneur backend — lesquelles existent dépend de REPO_FORMAT.

/repos/
├── conf/
│   └── distributions           ← configuration de reprepro
├── db/                         ← base de données interne de reprepro
├── dists/
│   ├── jammy/
│   │   ├── InRelease           ← index signé GPG
│   │   ├── Release
│   │   ├── Release.gpg
│   │   └── main/
│   │       ├── binary-amd64/
│   │       │   ├── Packages
│   │       │   ├── Packages.gz
│   │       │   └── Packages.xz
│   │       └── Contents-amd64.gz
│   ├── noble/
│   ├── focal/
│   └── bookworm/
└── pool/
    └── main/
        └── n/nginx/
            └── nginx_1.24.0-1_amd64.deb

Le répertoire pool/ est partagé entre les distributions. Un binaire de paquet est stocké une seule fois ; la commande includedeb de reprepro crée l'entrée d'index qui le rend visible dans une distribution spécifique.

/repos/rpm/
├── almalinux9/
│   └── x86_64/
│       ├── repodata/
│       │   ├── repomd.xml          ← index principal
│       │   ├── repomd.xml.asc      ← signature GPG détachée
│       │   ├── primary.xml.gz      ← métadonnées de paquets
│       │   ├── filelists.xml.gz
│       │   └── other.xml.gz
│       └── nginx-1.24.0-1.el9.ngx.x86_64.rpm
├── rocky9/
│   └── x86_64/
└── fedora/
    └── x86_64/

Chaque distribution RPM possède sa propre arborescence complète. Contrairement à APT, les paquets RPM ne sont pas dédupliqués entre distributions — chaque distribution conserve sa propre copie du binaire.

/repos/apk/
├── alpine3.20/
│   └── main/
│       └── x86_64/
│           ├── APKINDEX.tar.gz     ← index de paquets signé
│           └── mypackage-1.0.0-r0.apk
└── alpine3.21/
    └── main/
        └── x86_64/

Comme RPM, chaque distribution Alpine conserve sa propre copie de chaque binaire de paquet — il n'y a pas de pool partagé.


Promouvoir des paquets

La promotion déplace un paquet d'une distribution vers une autre sans re-upload ni re-scan. L'enregistrement de décision CVE et sa justification sont préservés.

# Promouvoir nginx de jammy (dev/QA) vers noble (production)
curl -X POST -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  http://localhost:8000/api/v1/distributions/promote \
  -d '{"package":"nginx","from_dist":"jammy","to_dist":"noble"}'

Ce qui se passe en interne :

  1. Repod copie le binaire .deb dans pool/ (ou crée un lien dur).
  2. reprepro includedeb <to_dist> <path> ajoute le paquet à l'index de la distribution cible.
  3. reprepro re-signe le fichier InRelease pour la distribution cible.
  1. Repod copie le fichier .rpm vers le répertoire de la distribution cible.
  2. createrepo_c --update régénère l'index repodata/.
  3. La signature GPG repomd.xml.asc est régénérée.
  1. Repod copie le fichier .apk vers le répertoire de la distribution cible.
  2. apk index régénère et re-signe APKINDEX.tar.gz.

Rôle requis

La promotion requiert le rôle maintainer ou admin.


Migrer des paquets entre distributions

La migration copie tous les paquets d'une distribution vers une autre. Utile lors de la mise à niveau de votre base de système d'exploitation sur l'ensemble de votre infrastructure.

# Migrer tous les paquets de focal vers jammy
curl -X POST -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  http://localhost:8000/api/v1/distributions/migrate \
  -d '{"from_dist":"focal","to_dist":"jammy"}'

La distribution source n'est pas supprimée

Après la migration, les paquets existent dans les deux distributions. Retirez les paquets de la distribution source manuellement si nécessaire.


Filtres de contenu (listes allow/deny)

Les filtres de contenu restreignent quels paquets peuvent entrer dans une distribution — un équivalent léger des filtres de Content View de Katello, scopé aux noms de paquets (correspondance exacte, wildcard glob, ou regex). Une distribution sans filtres se comporte exactement comme avant (ouverte à tout paquet) — les filtres sont strictement opt-in, aucun déploiement existant ne change de comportement en mettant à niveau.

Quand un filtre s'applique

Un filtre est évalué avant que tout fichier ne touche le disque — à l'upload, à l'import depuis Internet, et à la promotion/migration vers la distribution cible. Un paquet rejeté n'est jamais écrit dans pool/, jamais indexé, et n'atteint jamais l'arborescence Packages/repomd.xml/APKINDEX de la distribution. Ceci diffère du workflow de revue CVE (pending_review) : un filtre est un verrou de politique binaire, pas une file d'approbation humaine.

Repod n'a pas de « Library » non filtrée à la Katello se trouvant derrière une « Content View » publiée — l'upload/import valide et publie déjà en une seule étape. Les filtres s'attachent donc directement à la distribution qu'ils protègent, pas à un objet de vue composable séparé.

Ordre d'évaluation

Si une distribution possède des règles allow, un paquet doit correspondre à au moins l'une d'entre elles, ou il est rejeté. Ensuite, indépendamment du résultat de l'allow, toute règle deny correspondante l'emportedeny a toujours le dernier mot.

aucune règle          → autorisé
des règles allow existent,
  aucune correspondance → rejeté
correspondance allow,
  correspondance deny aussi → rejeté (deny l'emporte)
correspondance allow,
  aucune correspondance deny → autorisé
aucune règle allow,
  correspondance deny        → rejeté
aucune règle allow,
  aucune correspondance deny → autorisé

Gérer les filtres

# Bloquer un paquet spécifique par nom exact
curl -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  http://localhost:8000/api/v1/distributions/jammy/filters \
  -d '{"rule_type":"deny","match_type":"exact","pattern":"telnet","description":"Protocole non sécurisé, banni par la politique"}'

# Bloquer toute la famille de paquets -dev avec un glob
curl -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  http://localhost:8000/api/v1/distributions/jammy/filters \
  -d '{"rule_type":"deny","match_type":"glob","pattern":"*-dbg"}'

# Restreindre une distribution de production à une allowlist explicite (regex)
curl -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  http://localhost:8000/api/v1/distributions/noble/filters \
  -d '{"rule_type":"allow","match_type":"regex","pattern":"^(nginx|openssl|curl|ca-certificates)$"}'

# Lister les règles actuelles
curl -H "Authorization: Bearer $TOKEN" http://localhost:8000/api/v1/distributions/jammy/filters

# Supprimer une règle
curl -X DELETE -H "Authorization: Bearer $TOKEN" \
  http://localhost:8000/api/v1/distributions/jammy/filters/{rule_id}

Rôle requis

Ajouter, supprimer ou balayer des filtres requiert le rôle admin — la lecture des règles actuelles ne requiert que l'accès à la distribution (le même RBAC que GET /distributions/{codename}/packages).

Prévisualiser avant de valider

Testez ce qu'une règle ferait — contre un nom de paquet existant ou hypothétique — sans rien sauvegarder :

curl -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  http://localhost:8000/api/v1/distributions/jammy/filters/preview \
  -d '{"name":"telnet"}'
# → {"allowed": false, "reason": "'telnet' blocked by rule deny/exact 'telnet'", "matched_rule": {...}}

Ce même endpoint est ce qu'appelle le bloc « Tester un paquet » de l'interface, dans l'onglet Distributions → (sélectionner une distribution) → Filtres, afin que vous puissiez valider l'effet d'une règle avant de l'ajouter, ou comprendre pourquoi un upload réel a été rejeté.

Les filtres ne sont jamais rétroactifs par eux-mêmes

Ajouter une règle à une distribution qui contient déjà des paquets correspondants ne retire rien automatiquement — un filtre est une politique tournée vers l'avenir, pas une purge silencieuse. Pour réconcilier le contenu existant avec les règles actuelles, exécutez un balayage explicite :

# Exécution à blanc (par défaut) — liste ce qui serait retiré, ne change rien
curl -X POST -H "Authorization: Bearer $TOKEN" \
  "http://localhost:8000/api/v1/distributions/jammy/filters/sweep"

# Application — retire réellement les paquets non conformes de CETTE distribution uniquement
curl -X POST -H "Authorization: Bearer $TOKEN" \
  "http://localhost:8000/api/v1/distributions/jammy/filters/sweep?dry_run=false"

L'étape d'application ne touche jamais que la seule distribution balayée — un paquet retiré de jammy à cause du filtre de jammy reste intact dans noble ou toute autre distribution.


Initialiser les distributions

Les distributions sont initialisées automatiquement au premier démarrage pour chaque format activé par REPO_FORMAT. Si vous avez besoin de les réinitialiser (par exemple après une restauration de sauvegarde sans les arborescences de distribution) :

curl -X POST -H "Authorization: Bearer $TOKEN" \
  http://localhost:8000/api/v1/distributions/init

Ajouter des distributions non prises en charge

Les listes de distributions sont définies dans le code source du backend :

  • backend/services/distributions_apt.py — codenames APT
  • backend/services/distributions_rpm.py — codenames RPM + ID de distro Grype
  • backend/services/distributions_apk.py — codenames APK

Ajouter une nouvelle distribution requiert :

  1. Modifier le fichier distributions_*.py concerné pour ajouter le nouveau codename / les métadonnées.
  2. Pour APT, ajouter également le codename à conf/distributions.
  3. Reconstruire l'image backend : docker compose build backend-api.
  4. Appeler POST /api/v1/distributions/init pour créer la structure sur disque.

Ceci est délibérément verrouillé derrière un changement de code pour prévenir une prolifération accidentelle de distributions en production.


Support des architectures

La version actuelle ne prend en charge que amd64 (APT) / x86_64 (RPM, APK). Le support ARM (arm64 / aarch64) est prévu pour une version future — l'endpoint d'upload rejette actuellement les paquets aux architectures non prises en charge.