Nexus (dépôts internes)

Rôle

Sonatype Nexus Repository Manager (10.100.2.178) est le miroir/proxy d'artefacts central de toute la flotte Indio : paquets RPM (Rocky/EPEL/CRB + une douzaine de dépôts tiers), registres Docker, tarballs internes, paquets Python/npm. C'est la brique qui permet à des VM en zones sans sortie Internet directe (DMZ, SOC) de récupérer tout ce dont elles ont besoin via un chemin réseau interne unique, exposé par HAProxy sous repo.infra.infra.

Comme Guacamole, Nexus est géré entièrement hors IaC : aucun projet Terraform ou Ansible dédié dans /root — c'est une brique d'infrastructure à part entière, configurée uniquement via son UI/API. Le RPM indio-baseline-config (voir baseline-config.md) consomme Nexus mais ne le configure pas.

Architecture

Exposition réseau

  • Accès direct : 10.100.2.178:8081 (UI/API Nexus, zone admin).
  • Accès flotte : via la VIP HAProxy repo.infra.infra (10.100.2.130) — terminaison SSL côté HAProxy, backend Nexus en clair. Le service repo (port 443) proxifie tout le chemin /repository/<nom>/..., ce qui couvre la totalité des dépôts à accès "path-based" ci-dessous en une seule règle.
  • 5 registres Docker à port HAProxy dédié (8083-8087) : nécessaire car containerd/kubelet (cluster Kubernetes) gèrent mal le mode path-based de Nexus pour le format docker — voir haproxy.md pour la configuration des frontends.
flowchart LR
    subgraph Clients
        FLEET["Flotte Rocky<br/>(indio.repo, template doré)"]
        K8S["Cluster Kubernetes<br/>(containerd)"]
        DMZ["Proxies DMZ<br/>(Squid, unbound)"]
    end

    FLEET -->|HTTPS 443, path-based| VIP["VIP HAProxy<br/>repo.infra.infra"]
    DMZ -->|HTTPS 443, path-based<br/>ne peut pas sortir via sa propre VIP| VIP
    K8S -->|HTTPS 8083-8087, 1 port/registre| VIP
    VIP --> NEXUS["Nexus 10.100.2.178:8081"]

    NEXUS -->|proxy| UP1["Upstreams publics<br/>(Rocky, EPEL, Docker Hub, GHCR, Quay...)"]
    NEXUS --> HOSTED[("Dépôts hosted<br/>(indio-custom, tarballs internes...)")]

Les 25 dépôts (vérifiés en direct le 2026-07-19)

Écart avec la documentation historique

Un souvenir daté du rebuild SOC (2026-07-07) documentait 10 dépôts créés à l'époque, dont elastic-9x et cassandra-41x — une correction ultérieure (07-08) notait que elastic-9x n'était en fait jamais vraiment utilisé (paquets Elastic servis directement via le Squid whitelisté). L'état actuel ne contient ni elastic-9x ni cassandra-41x : à la place, un dépôt nommé elk (même remoteUrl, artifacts.elastic.co) est présent — probablement recréé/renommé depuis sans qu'aucun souvenir ne documente ce changement. Le code/l'état réel fait foi ; cette page reflète les 25 dépôts constatés aujourd'hui, pas l'historique.

Paquets système (yum, proxy vers un upstream public)

Dépôt Upstream Usage
rocky download.rockylinux.org Miroir Rocky Linux (BaseOS/AppStream/Extras/CRB, consommés via des sous-chemins précis, voir indio.repo)
epel dl.fedoraproject.org EPEL 9
postgresql download.postgresql.org Dépôt PGDG (voir postgresql.md)
corretto yum.corretto.aws JDK Amazon Corretto (repodata sous x86_64/, pas à la racine)
syslog-ng download.copr.fedorainfracloud.org COPR czanik/syslog-ng412 (voir syslogng.md) — hôte de téléchargement, pas le frontend web copr.fedorainfracloud.org (protégé par anti-bot, inutilisable en remoteUrl)
docker-ce download.docker.com Paquets docker-ce/containerd (RPM, distinct des registres Docker ci-dessous)
zabbix repo.zabbix.com/zabbix/8.0/unstable/rhel/9/ Créé le 2026-07-16 pour affranchir les 4 VM proxy DMZ de la boucle sur leur propre VIP Squid — bénéficie à toute la flotte (voir zabbix.md)
kubernetes-rpm pkgs.k8s.io/core:/stable:/v1.33/rpm/ kubeadm/kubelet/kubectl (voir k8s.md)
elk artifacts.elastic.co Paquets Elastic (voir écart ci-dessus)

Dépôts internes (yum/raw, hosted — paquets maison, poussés par upload)

Dépôt Format Usage
indio-custom yum hosted Paquets internes génériques, notamment indio-baseline-config (voir baseline-config.md) — non signé (gpgcheck=0)
strangebee-rpm yum hosted RPM TheHive/Cortex (StrangeBee)
grafana-rpm yum hosted RPM Grafana — contournement du blocage Fastly de rpm.grafana.com
kafka-tarball raw hosted Tarball Apache Kafka
k8s-manifests raw hosted Manifests Kubernetes internes
glpi-tarball raw hosted Archive GLPI
ocs-tarball raw hosted Archive OCS Inventory
ocs-agent-rpm raw hosted Agent Unix OCS Inventory (tarball, malgré le nom — consommé par le rôle Ansible ocs_agent, voir baseline-config.md)

Registres Docker

Dépôt Upstream Port HAProxy Détail
dockerhub registry-1.docker.io 443 (path-based) Créé le 07-07, historique, pathEnabled=true
docker-proxy registry-1.docker.io 8083 Port dédié pour containerd/kubelet (node, nginx, alpine, kaniko)
k8s-proxy registry.k8s.io 8084 kube-apiserver, coredns, pause, etcd
ghcr-proxy ghcr.io 8085 ArgoCD, External Secrets
quay-proxy quay.io 8086 Prometheus, MetalLB, Calico/Tigera
docker-internal — (hosted) 8087 Images internes/custom, forceBasicAuth=true

Gestionnaires de paquets applicatifs

Dépôt Format Upstream
pypi pypi proxy pypi.org
npm npm proxy registry.npmjs.org

Provisioning Terraform

Non applicable. Aucun projet /root/nexus n'existe — vérifié explicitement (absence du dossier). Nexus est une VM/service géré hors IaC, au même titre que Guacamole.

Configuration Ansible

Non applicable côté serveur (aucun rôle ne configure Nexus lui-même). Côté client, en revanche, plusieurs projets Ansible consomment Nexus :

Procédure manuelle

Nexus étant piloté à 100% hors code, voici le mode opératoire réel (API REST, admin/mot de passe géré via le mécanisme de secrets Indio — jamais la valeur en clair ici) pour les opérations courantes, tel que pratiqué historiquement sur ce serveur :

1. Créer un dépôt yum proxy :

curl -sk -u admin:'<mot de passe>' -X POST \
  https://10.100.2.178:8081/service/rest/v1/repositories/yum/proxy \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "<nom>",
    "online": true,
    "storage": {"blobStoreName": "default", "strictContentTypeValidation": true},
    "proxy": {"remoteUrl": "https://<upstream>", "contentMaxAge": 1440, "metadataMaxAge": 1440},
    "httpClient": {"blocked": false, "autoBlock": true},
    "yum": {"repodataDepth": 0}
  }'

2. Créer un dépôt hosted (interne, upload manuel) : même endpoint, .../yum/hosted (ou raw/hosted, docker/hosted), avec "storage": {"writePolicy": "ALLOW", ...}.

3. Uploader un paquet dans un dépôt hosted :

curl -sk -u admin:'<mot de passe>' -X POST \
  "https://10.100.2.178:8081/service/rest/v1/components?repository=indio-custom" \
  -F "yum.asset=@mon-paquet.rpm" -F "yum.directory=/"

Piège : régénération asynchrone des métadonnées

Après un upload dans un dépôt yum hosted, la régénération de repodata/repomd.xml est asynchrone — un dnf install lancé immédiatement après peut encore résoudre l'ancienne version. Aucune tâche Nexus dédiée ne permet de forcer la régénération via l'API v1 publique (POST /service/rest/v1/tasks refuse, HTTP 405). Attendre quelques secondes avant de tester, ou vérifier directement le contenu de primary.xml.gz plutôt que le simple code HTTP 200 de repomd.xml.

4. Créer un registre Docker à port dédié (nécessaire pour containerd/kubelet) : créer le dépôt docker/proxy ou docker/hosted avec "docker": {"httpPort": <port>, "v1Enabled": false, "forceBasicAuth": <bool>}, puis exposer ce port côté HAProxy avec un health-check GET /v2/ + expect status 401jamais le health-check générique (option httpchk seul), qui reçoit un 400 "Not a docker request" de Nexus sur ce type de connecteur et fait passer le backend DOWN à tort.

5. Purger d'anciennes versions d'un composant interne :

curl -sk -u admin:'<mot de passe>' -X DELETE \
  "https://10.100.2.178:8081/service/rest/v1/components/<id-composant>"

Procédure de déploiement

Non applicable au sens "redéployer depuis zéro" — pas de code. La procédure manuelle ci-dessus couvre l'intégralité du cycle de vie opérationnel (créer un dépôt, y pousser un paquet, l'exposer). Côté consommation, un client type suit ce chemin : dépôt Nexus créé → exposé via repo.infra.infra (règle DFW X-to-repo déjà existante à vérifier avant d'en créer une nouvelle, voir nsx.md) → fichier .repo/config containerd déployé côté client avec proxy=_none_ si le proxy Squid global est actif (voir baseline-config.md).

Contrôle de santé / Vérification

# Liste complète des dépôts (lecture seule)
curl -sk -u admin:'<mot de passe>' http://10.100.2.178:8081/service/rest/v1/repositories

# Test d'un dépôt yum (doit répondre 200)
curl -sk -o /dev/null -w '%{http_code}\n' \
  https://repo.infra.infra/repository/<nom>/repodata/repomd.xml

# Test d'un registre Docker à port dédié (401 attendu = fonctionnel, PAS 400)
curl -sk -o /dev/null -w '%{http_code}\n' https://repo.infra.infra:8083/v2/

Vérifié en direct le 2026-07-19 : 25 dépôts listés via l'API, tous online. Comptage par type — 14 yum (9 proxy + 5 hosted dont indio-custom/strangebee-rpm/grafana-rpm), 5 raw hosted, 6 docker (1 path-based + 4 proxy à port dédié + 1 hosted), 1 pypi, 1 npm — total 25 confirmé par comptage direct (len(repositories)).

Points d'attention

  • Aucune sauvegarde/versionnage de la configuration Nexus elle-même (dette technique) : toute la configuration (25 dépôts, réglages proxy, credentials upstream) n'existe que dans la base Nexus, sans export/IaC — une perte de cette VM impliquerait une reconstruction manuelle complète. Cette page sert de meilleur inventaire de référence disponible à ce jour, mais n'est pas un substitut à un vrai mécanisme de sauvegarde.
  • Single point of failure : un seul serveur Nexus, pas de cluster/HA. Toute panne bloque simultanément les mises à jour système de toute la flotte, les images Docker du cluster Kubernetes, et l'installation de tout logiciel tiers.
  • Rate-limit anonyme Docker Hub sur docker-proxy/dockerhub : Nexus a un circuit-breaker (httpClient.autoBlock) qui réagit aux rejets 429 de Docker Hub (limite anonyme ~100 pulls/6h, partagée par toute l'IP de sortie du lab). Un compte Docker Hub authentifié a été configuré sur docker-proxy après avoir rencontré ce problème en conditions réelles — les registres k8s-proxy/ghcr-proxy/quay-proxy n'ont pas de limite anonyme équivalente connue.
  • Health-check HAProxy générique incompatible avec les connecteurs Docker : option httpchk seul (sans GET /v2/ + expect status 401) fait passer un backend Docker Nexus fonctionnel pour DOWN — déjà documenté et corrigé pour les 5 registres Docker, à reproduire pour tout futur dépôt Docker exposé de la même façon.
  • proxy=_none_ obligatoire sur chaque section .repo cliente dès qu'un proxy Squid global est actif (RPM indio-baseline-config) — sinon le proxy intercepte aussi le trafic vers Nexus et le casse (domaine non whitelisté et/ou zone non routable depuis DMZ). Voir baseline-config.md.
  • Piège de diagnostic : forcer un en-tête Host sans port dans un test curl contre un registre Docker à port dédié fait répondre à Nexus un WWW-Authenticate realm= sans port, laissant croire à tort à un bug d'authentification — toujours laisser le client déduire le Host complet (avec port) comme le ferait un vrai client Docker/containerd.
  • Les identifiants admin Nexus (admin/mot de passe fleet-wide) restent la seule voie d'administration — pas de compte de service dédié en lecture seule pour l'automatisation (contrairement à d'autres briques comme Grafana/Zabbix qui ont des tokens scoping).