Accueil Aegis Vetis

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ètre pull uniquement sur le projet aegis-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émentMinimumRecommandé production
Version Kubernetes1.281.30+
Distributions testéesk3s, rke2, kubeadm, kindk3s, rke2, OpenShift (best effort)
StorageClass par défautrequiseRWO + support snapshots
Ressources Control Plane4 vCPU / 4 Gi8 vCPU / 16 Gi
Ressources agent cluster0,5 vCPU / 512 Mi1 vCPU / 1 Gi

⚠️ StorageClass par défaut : indispensable. Le chart ne définit pas explicitement de storageClassName sur 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=true

Sans default, les PVC restent Pending indéfiniment et helm install timeout avec pod has unbound immediate PersistentVolumeClaims.

Outillage côté admin

  • kubectl configuré 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 activez serviceMonitor.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 des PolicyReport, scans.
  • kyverno (subchart) — moteur d’admission.
  • kubescape + trivyCronJob de 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-*.pem et cosign.* 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 admin est livrée avec aegisctl à 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 postgres et reports sont supprimés avec le namespace. Si vous voulez conserver les rapports historiques, sauvegardez le PVC aegis-reports avant de purger le namespace.

10. Annexe — valeurs essentielles

CléTypeDéfautEffet
api.replicasint1Réplicas API. 2+ pour HA
worker.replicasint1Worker jobs PDF + scoring
ingress.enabledboolfalseActive l’Ingress public (top-level, pas sous api/frontend)
api.config.seedDevboolfalseSeed admin auto en dev. Toujours false en prod (voir §3.7)
cosign.enabledbooltrueSignature des rapports PDF
postgres.embedded.enabledbooltrueBase embarquée
postgres.external.enabledboolfalseBase externe (override embarquée)
worker.reportsPersistence.sizestring5GiPVC stockage des PDF
global.imageRegistrystring""Préfixe registry global. Laisser vide ; mettre le path complet par composant (voir §3.5)
airGapboolfalseMode 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

11. Aller plus loin