GUIDE
Installation et configuration
Déployer Aegis Vetis en production : pré-requis, secrets, TLS, OIDC et enrôlement de cluster.
- Mis à jour
- 2026-05-09
Installation et configuration
Ce guide couvre un déploiement Aegis Vetis en environnement client. Pour une démo en 15 minutes sur cluster local, suivre le Quick start. Pour un déploiement sans Internet, suivre Air-gap.
0. Vos credentials Aegis Vetis
Aegis Vetis distribue ses images container et son chart Helm depuis
registry.aegis-vetis.io (registre Harbor souverain hébergé en
France). Votre licence donne automatiquement accès à ce registre.
À la finalisation de votre commande, vous recevez par email à l’adresse de facturation :
- un token Harbor robot au format
robot$aegis-vetis+client-pull, périmètrepulluniquement sur le projetaegis-vetis; - la clé publique Cosign servant à vérifier la signature des images, également téléchargeable publiquement à https://aegis-vetis.io/cosign.pub ;
- un récapitulatif des limites contractuelles (nombre de clusters supervisables, durée de validité du token).
Conservez le token dans votre coffre-fort (Bitwarden / 1Password /
HashiCorp Vault). Il n’expire pas par défaut. Si vous suspectez une
fuite, écrivez à security@aegis-vetis.io — nous régénérons sous
24 h ouvrées.
1. Pré-requis
Cluster Kubernetes
| Élément | Minimum | Recommandé production |
|---|---|---|
| Version Kubernetes | 1.28 | 1.30+ |
| Distributions testées | k3s, rke2, kubeadm, kind | k3s, rke2, OpenShift (best effort) |
| StorageClass par défaut | requise | RWO + support snapshots |
| Ressources Control Plane | 4 vCPU / 4 Gi | 8 vCPU / 16 Gi |
| Ressources agent cluster | 0,5 vCPU / 512 Mi | 1 vCPU / 1 Gi |
⚠️ StorageClass par défaut : indispensable. Le chart ne définit pas explicitement de
storageClassNamesur les PVC Postgres + reports et compte sur la SC par défaut du cluster. Vérification :kubectl get sc # Une ligne doit afficher (default) # Si aucune : kubectl annotate sc <nom> \ # storageclass.kubernetes.io/is-default-class=trueSans default, les PVC restent
Pendingindéfiniment ethelm installtimeout avecpod has unbound immediate PersistentVolumeClaims.
Outillage côté admin
kubectlconfiguré sur le cluster cible.helm≥ 3.14.- Recommandé :
cert-manager≥ 1.13 pour TLS automatique. - Recommandé : un Ingress Controller (ingress-nginx, Traefik, Contour…).
- Optionnel : Prometheus operator (CRDs
monitoring.coreos.com/v1) si vous activezserviceMonitor.enabled: true.
Réseau
Aegis Vetis distingue deux flux :
- UI ↔ utilisateur : HTTPS public ou LAN, exposé par Ingress.
- Agent ↔ Control Plane : HTTPS sortant depuis chaque cluster client vers l’URL publique du Control Plane (port 443 par défaut).
Aucun flux entrant n’est requis depuis le Control Plane vers les clusters clients. Voir Architecture §7 pour le détail des modes de connectivité.
2. Schéma de déploiement
Le chart aegis-vetis (Control Plane) déploie :
aegis-api— REST API (Gin), JWT/OIDC, exposée par Ingress.aegis-worker— jobs asynchrones (scoring, génération PDF).aegis-frontend— SPA Vue 3, servie par nginx.aegis-postgres— PostgreSQL 16 embarqué (option : externe).aegis-operator— réconciliation des CRDs côté Control Plane.
Le chart aegis-vetis-cluster (agent) déploie sur chaque cluster client :
aegis-agent— heartbeat, scrape desPolicyReport, scans.kyverno(subchart) — moteur d’admission.kubescape+trivy—CronJobde scan posture et vulnérabilités.
3. Installation du Control Plane
3.1 Se connecter à Harbor + récupérer le chart
Avec votre token Harbor (cf. §0) :
export HARBOR_TOKEN='<le-token-reçu-par-email>'
VERSION=0.1.0 # adapter à la dernière release annoncée par email
helm registry login registry.aegis-vetis.io \
-u 'robot$aegis-vetis+client-pull' \
-p "$HARBOR_TOKEN"
helm pull oci://registry.aegis-vetis.io/aegis-vetis/aegis-vetis \
--version "$VERSION" --untar
# → ./aegis-vetis/ (chart extrait)
Quotez le username avec des simples quotes : le
$fait partie du nom du robot Harbor, pas une variable shell.
3.2 Vérifier la signature des images (recommandé)
Toutes les images Aegis Vetis sont signées avec une clé Cosign dont la publique est servie sur la landing :
curl -fsSL https://aegis-vetis.io/cosign.pub -o cosign.pub
for IMAGE in api worker agent frontend operator; do
cosign verify --key cosign.pub \
"registry.aegis-vetis.io/aegis-vetis/${IMAGE}:${VERSION}"
done
Une sortie Verified OK par image confirme que l’artefact provient
bien d’Aegis Vetis. N’importe quelle autre sortie : ne déployez pas,
contactez security@aegis-vetis.io.
3.3 Pull secret Kubernetes pour Harbor
Pour que vos pods puissent pull les images au runtime :
kubectl create namespace aegis-vetis
kubectl create secret docker-registry aegis-vetis-pull \
--namespace aegis-vetis \
--docker-server=registry.aegis-vetis.io \
--docker-username='robot$aegis-vetis+client-pull' \
--docker-password="$HARBOR_TOKEN" \
--docker-email='ops@your-company.tld'
Pour un déploiement air-gap (site sans accès Internet), suivre plutôt Air-gap — vous téléchargerez un bundle tarball self-contained à importer dans votre propre Harbor interne.
3.4 Préparer les Secrets cluster
Trois secrets sont requis avant le helm install (en plus du
pull secret Harbor de §3.3) :
# 1. Clés JWT (RS256) — pour la signature des tokens d'authentification UI
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 \
-out jwt-private.pem
openssl rsa -in jwt-private.pem -pubout -out jwt-public.pem
kubectl create secret generic aegis-jwt -n aegis-vetis \
--from-file=jwt-private.pem \
--from-file=jwt-public.pem
# 2. Clés Cosign (ECDSA P-256) — pour signer les rapports PDF générés
# par le backend. C'est VOTRE clé runtime, distincte de la clé Aegis
# Vetis qui signe les images upstream (cf. §3.2).
openssl ecparam -name prime256v1 -genkey -noout -out cosign.key
openssl ec -in cosign.key -pubout -out cosign.pub
kubectl create secret generic aegis-cosign -n aegis-vetis \
--from-file=cosign.key \
--from-file=cosign.pub
# 3. Mot de passe Postgres
kubectl create secret generic aegis-postgres -n aegis-vetis \
--from-literal=password="$(openssl rand -base64 32)"
Conservez
jwt-*.pemetcosign.*dans un coffre-fort hors cluster. Ils sont nécessaires pour vérifier les rapports PDF historiques et pour rotater les secrets — voir Conformité §6.
3.5 values-prod.yaml
# ──────────────────────────────────────────────────────────────
# Stratégie de registry
#
# Ne PAS définir `global.imageRegistry: registry.aegis-vetis.io/aegis-vetis` :
# le chart concatène `<registry>/<image.repository>` et chaque composant
# a déjà `aegis-vetis/` dans son `repository`. Résultat : path doublé
# vers `…/aegis-vetis/aegis-vetis/api:0.3.0` (NotFound).
#
# Par ailleurs, l'image Postgres officielle n'est PAS miroirée dans
# Harbor (elle pull depuis Docker Hub). Un global registry forcerait
# Postgres à passer par Harbor → également NotFound.
#
# Solution : laisser `global.imageRegistry` vide et préciser le path
# complet sur chaque composant Aegis. Postgres garde son default
# `postgres:16-alpine` (Docker Hub public, pas besoin de credentials).
# ──────────────────────────────────────────────────────────────
global:
imageRegistry: ""
imagePullSecrets:
- name: aegis-vetis-pull
api:
replicas: 1
image:
repository: registry.aegis-vetis.io/aegis-vetis/api
config:
seedDev: false # pas d'admin auto-créé ; voir §3.7 ci-dessous
# ⚠️ `auth` est un sibling de `config`, pas nested dessous.
auth:
accessTTL: 15m
refreshTTL: 168h
jwtKeys:
existingSecret: aegis-jwt # Secret avec keys jwt-private.pem + jwt-public.pem
oidc:
enabled: false # break-glass local au premier deploy ; activable §5
worker:
replicas: 1
image:
repository: registry.aegis-vetis.io/aegis-vetis/worker
reportsPersistence:
enabled: true
size: 5Gi
frontend:
replicas: 1
image:
repository: registry.aegis-vetis.io/aegis-vetis/frontend
operator:
enabled: true
image:
repository: registry.aegis-vetis.io/aegis-vetis/operator
cosign:
enabled: true
# ⚠️ `existingSecret` est un map, pas une string.
existingSecret:
name: aegis-cosign
postgres:
embedded:
enabled: true
auth:
passwordExistingSecret:
name: aegis-postgres
key: password
persistence:
enabled: true
size: 20Gi
# storageClassName vide → utilise la StorageClass par défaut du cluster.
# ⚠️ Vérifier qu'une SC est marquée default : `kubectl get sc` doit
# afficher (default) sur une ligne. Sinon le PVC reste Pending.
# Ingress public — bloc TOP-LEVEL (pas sous api/frontend).
ingress:
enabled: true
className: nginx
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod # cert-manager ClusterIssuer
nginx.ingress.kubernetes.io/proxy-body-size: 50m
hosts:
- host: aegis.example.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: aegis-tls
hosts:
- aegis.example.com
serviceMonitor:
enabled: true # nécessite la CRD monitoring.coreos.com/v1
3.6 Installer
helm install aegis ./aegis-vetis \
-n aegis-vetis \
-f values-prod.yaml \
--wait --timeout=10m
Vérification :
kubectl -n aegis-vetis get pods
kubectl -n aegis-vetis port-forward svc/aegis-api 8080:80
curl -sf http://localhost:8080/healthz # → ok
curl -sf http://localhost:8080/readyz # → ok (DB + migrations)
3.7 Créer le premier administrateur
api.config.seedDev: false (sain en production) signifie que le chart
ne crée pas automatiquement de compte. Deux chemins :
Voie A — aegisctl admin create-user (recommandée, automatisée)
aegisctl admin create-user \
--kube-context <ctx> \
--namespace aegis-vetis \
--email admin@example.com \
--role admin
# → prompts for password, hashes via argon2id, INSERTs in users table,
# writes an audit event 'user.create'.
Disponibilité : le sous-commande
adminest livrée avecaegisctlà partir de v0.4.0. Sur v0.3.x utilisez la Voie B.
Voie B — INSERT SQL direct + hash argon2id généré localement
Le backend ne reconnaît que des hashs argon2id au format PHC
avec les paramètres m=65536, t=3, p=4, key=32, salt=16 (voir
backend/internal/auth/password.go). htpasswd/bcrypt ne marche pas.
pip install --user --break-system-packages argon2-cffi
python3 - <<'PYEOF'
import getpass, secrets, base64
from argon2.low_level import hash_secret_raw, Type
pw = getpass.getpass("Admin password: ")
salt = secrets.token_bytes(16)
h = hash_secret_raw(pw.encode(), salt, 3, 65536, 4, 32, Type.ID)
b64s = base64.b64encode(salt).rstrip(b'=').decode()
b64h = base64.b64encode(h).rstrip(b'=').decode()
print(f"\nargon2id hash:\n$argon2id$v=19$m=65536,t=3,p=4${b64s}${b64h}")
PYEOF
Puis insérer l’utilisateur :
HASH='<le hash ci-dessus, single-quoted>'
ADMIN_EMAIL='admin@example.com'
kubectl -n aegis-vetis exec aegis-aegis-vetis-postgres-0 -- \
psql -U aegis -d aegis -c "
INSERT INTO tenants (name) VALUES ('default') ON CONFLICT DO NOTHING;
INSERT INTO users (tenant_id, email, password_hash, role)
SELECT id, '$ADMIN_EMAIL', '$HASH', 'admin'
FROM tenants WHERE name='default'
ON CONFLICT (tenant_id, email)
DO UPDATE SET password_hash = EXCLUDED.password_hash;
"
# → INSERT 0 1 (ou UPDATE 1 si vous le rejouez)
Smoke test :
curl -sS -X POST https://aegis.example.com/api/v1/auth/login \
-H 'Content-Type: application/json' \
-d "{\"email\":\"$ADMIN_EMAIL\",\"password\":\"<votre password>\"}"
# → {"access_token":"eyJ...","refresh_token":"..."}
4. TLS
Avec cert-manager pré-installé, l’Ingress provisionne le certificat automatiquement. Sans cert-manager, fournir le certificat à la main :
kubectl create secret tls aegis-tls -n aegis-vetis \
--cert=tls.crt --key=tls.key
Et dans values-prod.yaml, l’Ingress référence le Secret existant :
ingress:
tls:
- secretName: aegis-tls
hosts:
- aegis.example.com
5. OIDC (Keycloak, Azure AD, Okta…)
api:
auth:
oidc:
enabled: true
issuer: https://keycloak.example.com/realms/corp
clientID: aegis-vetis
clientSecretExistingSecret:
name: aegis-oidc
key: clientSecret
groupsClaim: groups
roleMapping:
admin: ["aegis-admins"]
operator: ["aegis-operators", "platform-team"]
viewer: ["aegis-readers"]
Les utilisateurs sont créés à la première connexion OIDC, leur rôle calculé d’après les groupes mappés. L’authentification locale reste disponible pour le compte break-glass créé en §3.7.
6. PostgreSQL externe
Pour la production, externalisez la base :
postgres:
embedded:
enabled: false
external:
enabled: true
host: pg.example.internal
port: 5432
database: aegis
sslMode: require
auth:
username: aegis
passwordExistingSecret:
name: aegis-postgres-external
key: password
Pré-requis sur la base : PostgreSQL ≥ 14, extension citext activée,
1 schéma dédié aegis avec privilèges DDL pour les migrations.
7. Enrôler un cluster
Depuis l’UI : Clusters → New cluster. Le Control Plane génère un token bootstrap à usage unique, TTL 24 h. La commande complète à exécuter sur le cluster cible est affichée — adaptez-la pour pull depuis Harbor :
# Pull du chart cluster depuis Harbor
helm pull oci://registry.aegis-vetis.io/aegis-vetis/aegis-vetis-cluster \
--version 0.3.0 --untar --untardir /tmp/
# Pull secret + values
kubectl create namespace aegis-system
kubectl -n aegis-system create secret docker-registry aegis-vetis-pull \
--docker-server=registry.aegis-vetis.io \
--docker-username='robot$aegis-vetis+client-pull' \
--docker-password="$HARBOR_TOKEN" \
--docker-email='ops@your-company.tld'
cat > values-cluster.yaml <<EOF
controlPlaneURL: https://aegis.example.com
clusterName: <managed-cluster-name>
bootstrapToken: <TOKEN-from-UI>
global:
imageRegistry: ""
imagePullSecrets:
- name: aegis-vetis-pull
agent:
image:
repository: registry.aegis-vetis.io/aegis-vetis/agent
operator:
image:
repository: registry.aegis-vetis.io/aegis-vetis/operator
EOF
helm dependency update /tmp/aegis-vetis-cluster
helm install aegis-cluster /tmp/aegis-vetis-cluster \
-n aegis-system \
-f values-cluster.yaml \
--wait --timeout=5m
Vérification : le cluster apparaît Active dans l’UI sous 60 s. Le
token bootstrap est consommé à la première connexion et remplacé en
mémoire par un token long-terme stocké dans un Secret namespace-scoped.
Cas particulier — auto-enrôlement. Si le cluster qui héberge le Control Plane s’enrôle lui-même, utiliser l’URL Service interne (évite le passage par l’Ingress public) :
controlPlaneURL: http://aegis-aegis-vetis-api.aegis-vetis.svc.cluster.local:8080 tlsInsecure: true # HTTP plain sur le Service ClusterIP
Pour révoquer un cluster : Clusters → … → Revoke côté UI, puis
helm uninstall aegis-cluster -n aegis-system côté cluster.
8. Mises à jour
helm repo update
helm upgrade aegis aegis-vetis/aegis-vetis \
-n aegis-vetis \
-f values-prod.yaml \
--reset-then-reuse-values
--reset-then-reuse-values est important : --reuse-values seul ignore
les nouveaux paramètres ajoutés dans la version cible.
Les migrations PostgreSQL sont exécutées par un Job pré-install/upgrade.
En cas d’échec, le Job reste visible (kubectl logs job/aegis-migrate)
et le rollback est non-destructif côté schéma — les migrations sont
backwards-compatibles sur une release majeure.
Côté agents cluster : la mise à jour est déclenchable depuis l’UI
(Clusters → Upgrade agent) ou par helm upgrade aegis-cluster
manuel.
9. Désinstallation
helm uninstall aegis -n aegis-vetis
kubectl delete namespace aegis-vetis
Les PVC
postgresetreportssont supprimés avec le namespace. Si vous voulez conserver les rapports historiques, sauvegardez le PVCaegis-reportsavant de purger le namespace.
10. Annexe — valeurs essentielles
| Clé | Type | Défaut | Effet |
|---|---|---|---|
api.replicas | int | 1 | Réplicas API. 2+ pour HA |
worker.replicas | int | 1 | Worker jobs PDF + scoring |
ingress.enabled | bool | false | Active l’Ingress public (top-level, pas sous api/frontend) |
api.config.seedDev | bool | false | Seed admin auto en dev. Toujours false en prod (voir §3.7) |
cosign.enabled | bool | true | Signature des rapports PDF |
postgres.embedded.enabled | bool | true | Base embarquée |
postgres.external.enabled | bool | false | Base externe (override embarquée) |
worker.reportsPersistence.size | string | 5Gi | PVC stockage des PDF |
global.imageRegistry | string | "" | Préfixe registry global. Laisser vide ; mettre le path complet par composant (voir §3.5) |
airGap | bool | false | Mode air-gap (désactive sortants) |
La référence complète des valeurs est livrée avec le chart :
helm show values aegis-vetis/aegis-vetis > values-reference.yaml