From 266fd4222b698ab5b7398b81e6efe044bdac7f4a Mon Sep 17 00:00:00 2001 From: Charles-Antoine Dolbeau Date: Wed, 3 Jun 2026 15:28:42 +0200 Subject: [PATCH 1/3] feat(resources): update VTOM default resource requests/limits MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Align server, agent and apiserver resource defaults with Absyss official prerequisites: - server: CPU 50m→250m req / RAM 128Mi→512Mi req / RAM 1Gi→2Gi limit - agent: CPU 20m→100m req / CPU 200m→500m limit / RAM 64Mi→256Mi req / RAM 256Mi→2Gi limit - apiserver: CPU 20m→500m req / CPU 500m→2000m limit / RAM 1Gi req / RAM 4Gi limit Bumps chart version to 0.2.4. --- charts/visual-tom/Chart.yaml | 2 +- charts/visual-tom/values.yaml | 22 +++++++++++----------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/charts/visual-tom/Chart.yaml b/charts/visual-tom/Chart.yaml index b82f61f..879ff5e 100644 --- a/charts/visual-tom/Chart.yaml +++ b/charts/visual-tom/Chart.yaml @@ -7,7 +7,7 @@ description: > type: application # Chart version (follows SemVer). Increment on every chart change. -version: 0.2.3 +version: 0.2.4 # Reference application version (VTOM). ITC, ITM and MFT versions are defined in values.yaml. appVersion: "7.3.2c" diff --git a/charts/visual-tom/values.yaml b/charts/visual-tom/values.yaml index 49e2220..c400a34 100644 --- a/charts/visual-tom/values.yaml +++ b/charts/visual-tom/values.yaml @@ -116,29 +116,29 @@ vtom: # Resources vtom-server serverResources: requests: - cpu: "50m" - memory: "128Mi" + cpu: "250m" + memory: "512Mi" limits: cpu: "1000m" - memory: "1Gi" + memory: "2Gi" # Resources vtom-apiserver apiserverResources: requests: - cpu: "20m" - memory: "600Mi" - limits: cpu: "500m" - memory: "1536Mi" + memory: "1Gi" + limits: + cpu: "2000m" + memory: "4Gi" # Resources vtom-agent agentResources: requests: - cpu: "20m" - memory: "64Mi" - limits: - cpu: "200m" + cpu: "100m" memory: "256Mi" + limits: + cpu: "500m" + memory: "2Gi" # Ingress vtom-apiserver (web interface) ingress: From 97b8647708a8ceb6bd40ab966a977d385d64eb82 Mon Sep 17 00:00:00 2001 From: Charles-Antoine Dolbeau Date: Wed, 3 Jun 2026 16:53:22 +0200 Subject: [PATCH 2/3] feat(security): CIS Benchmark Level 1 + PSS restricted compliance (v0.2.5) - Add runAsGroup and fsGroup to securityContext values (configurable, null-safe for OpenShift) - Set runAsGroup: 1000 / fsGroup: 1000 in all cloud profiles (uid/gid confirmed from running image) - Add readOnlyRootFilesystem: true on db-proxy sidecar (socat / cloud-sql-proxy) - Document security compliance (PSS restricted, CIS L1) in README.md and README-fr.md --- charts/visual-tom/Chart.yaml | 2 +- charts/visual-tom/README-fr.md | 556 ++++++++++++++++++ charts/visual-tom/README.md | 556 ++++++++++++++++++ charts/visual-tom/templates/_helpers.tpl | 1 + .../templates/vtom/deployment-agent.yaml | 5 +- .../templates/vtom/deployment-apiserver.yaml | 5 +- .../templates/vtom/deployment-server.yaml | 5 +- charts/visual-tom/values-aws.yaml | 4 +- charts/visual-tom/values-azure.yaml | 4 +- charts/visual-tom/values-gcp.yaml | 4 +- charts/visual-tom/values-onpremise.yaml | 6 +- charts/visual-tom/values.yaml | 17 +- 12 files changed, 1149 insertions(+), 16 deletions(-) create mode 100644 charts/visual-tom/README-fr.md create mode 100644 charts/visual-tom/README.md diff --git a/charts/visual-tom/Chart.yaml b/charts/visual-tom/Chart.yaml index 879ff5e..ab8601c 100644 --- a/charts/visual-tom/Chart.yaml +++ b/charts/visual-tom/Chart.yaml @@ -7,7 +7,7 @@ description: > type: application # Chart version (follows SemVer). Increment on every chart change. -version: 0.2.4 +version: 0.2.5 # Reference application version (VTOM). ITC, ITM and MFT versions are defined in values.yaml. appVersion: "7.3.2c" diff --git a/charts/visual-tom/README-fr.md b/charts/visual-tom/README-fr.md new file mode 100644 index 0000000..664fbf6 --- /dev/null +++ b/charts/visual-tom/README-fr.md @@ -0,0 +1,556 @@ +# Visual TOM — Chart Helm Kubernetes +[![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](LICENSE)  +[![en](https://img.shields.io/badge/lang-en-red.svg)](README.md) + +Ce dépôt fournit un chart Helm pour déployer **Visual TOM (Absyss)** et ses produits associés sur Kubernetes : + +- **VTOM** — Ordonnanceur (core) +- **ITC** — Visual TOM User Portal +- **ITM** — Visual IT Messenger +- **MFT** — Visual TOM Managed File Transfer + +Cibles validées : + +| Environnement | Statut | +|---|---| +| Azure AKS | ✅ Validé | +| GCP GKE (Autopilot) | ✅ Validé | +| AWS EKS | ⚠️ Implémenté, non testé en production | +| On-premise / Minikube | ✅ Testé localement | + +# Disclaimer + +Ce chart Helm est fourni par Absyss SAS comme un **déploiement de référence** de Visual TOM sur Kubernetes. Il est conçu comme un **point de départ** qui doit être adapté par chaque client à son infrastructure, à ses exigences de sécurité et à ses contraintes opérationnelles (topologie réseau, gestion des secrets, classes de stockage, contrôleur Ingress, politiques RBAC, etc.). + +Le chart est distribué **tel quel**, sans garantie d'aucune sorte. Absyss SAS ne peut être tenu responsable des dommages résultant de son utilisation ou des adaptations effectuées par le client. + +Les contrats de support Visual TOM standard ne couvrent pas le chart Helm lui-même ni les adaptations Kubernetes côté client. Des jours de consulting peuvent être demandés pour accompagner le déploiement, la personnalisation ou la résolution de problèmes. + +# Prérequis + +## Tous les environnements +- **Visual TOM** ≥ 7.3.2c (versions inférieures non testées avec ce chart) +- **Kubernetes** ≥ 1.28 +- **Helm** ≥ 3.8 (requis pour l'installation depuis le registry OCI) +- **PostgreSQL 17** accessible depuis le cluster (VNet, VPC ou réseau local) +- Un **contrôleur Ingress** installé — **Traefik** est utilisé par défaut, nginx est également supporté +- **cert-manager** installé avec un `ClusterIssuer` configuré si vous utilisez Let's Encrypt (option TLS par défaut). L'installation et la configuration de cert-manager sont à la charge du client — le chart se contente de créer les ressources `Certificate`. Si vous fournissez vos propres certificats TLS (`tls.provider: secret`), cert-manager n'est pas nécessaire +- **ExternalDNS** (optionnel) — pour la gestion automatique des enregistrements DNS du service LoadBalancer. Sans ExternalDNS, les enregistrements DNS doivent être créés manuellement. Voir `vtom.serverService.hostname` dans les values +- Une **licence VTOM** valide et un accès à un registry d'images + +> **Accès réseau privé / VPN :** les ressources type WireGuard, Private Endpoint, Cloud SQL Auth Proxy, etc. ne font **pas** partie de ce chart. Elles sont à provisionner en amont via votre IaC (Terraform, Bicep, etc.). + +## Prérequis cloud-spécifiques + +### Azure (AKS) +- ACR (Azure Container Registry) avec les images VTOM/ITC/ITM/MFT chargées +- Azure Key Vault avec les secrets créés (voir section Azure ci-dessous) +- User-Assigned Managed Identity avec accès Key Vault (`Key Vault Secrets User`) +- External Secrets Operator installé dans le cluster + +### AWS (EKS) +- ECR ou autre registry avec les images chargées +- AWS Secrets Manager avec les secrets créés +- IAM Role avec accès Secrets Manager, associé au ServiceAccount via IRSA +- External Secrets Operator installé dans le cluster + +### GCP (GKE) +- Artifact Registry ou GCR avec les images chargées +- GCP Secret Manager avec les secrets créés +- GCP Service Account avec accès Secret Manager, lié via Workload Identity +- External Secrets Operator installé dans le cluster +- Cloud SQL Auth Proxy activé dans les valeurs (préconfiguré dans `values-gcp.yaml`) + +### On-premise +- Registry Docker local ou images chargées manuellement (`docker load`) +- PostgreSQL accessible depuis les pods +- Secrets Kubernetes créés manuellement (voir section on-premise ci-dessous) +- Une **implémentation LoadBalancer** telle que [MetalLB](https://metallb.universe.tf/) — requise pour qu'une IP soit assignée aux Services `vtom-server` et `mft-sftp`. Sans cela, les Services `type: LoadBalancer` restent indéfiniment en état ``. Sur les clusters bare-metal ou locaux (RKE2, kubeadm, Minikube), ceci n'est pas fourni par défaut + +# Produits + +Le chart déploie 4 produits Visual TOM, chacun activable indépendamment via son toggle `.enabled`. Tous se partagent les paramètres globaux (registry, namespace, base de données, exposition réseau, NetworkPolicy). + +## VTOM — Ordonnanceur + +Le cœur de Visual TOM. Trois composants déployés en pods distincts, partageant la même image. + +| Composant | Rôle | Service | PVC | Mémoire (limite) | +|---|---|---|---|---| +| `vtom-server` | Moteur d'ordonnancement | LoadBalancer (5 ports natifs VTOM) | 5 Gi | 1 Gi | +| `vtom-apiserver` | API REST + interface web | ClusterIP + Ingress HTTPS | 2 Gi | 1.5 Gi | +| `vtom-agent` | Exécution des jobs Kubernetes | (aucun) | 10 Gi | 256 Mi | + +**Paramètres typiques :** +- `vtom.image.tag` — version VTOM (ex. `7.3.2c`) +- `vtom.ingress.host` — FQDN apiserver, ex. `vtom.mycompany.com` +- `vtom.serverService.hostname` — FQDN client VTOM Desktop (géré par ExternalDNS) +- `vtom.serverService.loadBalancerIP` — IP statique pour survivre aux reprovisions du LB +- `vtom.timezone` — fuseau horaire partagé (défaut `Europe/Paris`) +- `vtom.{server,apiserver,agent}Resources` — CPU/mémoire par composant +- `vtom.{server,apiserver,agent}Pvc.size` — tailles de stockage + +**Licence :** `vtom.license.secretName` (défaut `vtom-license-secret`) — partagée avec ITC et ITM par défaut. + +**Base :** DB nommée `vtom`, secret `vtom-db-secret` (clés `TOM_SGBD_USER` + `TOM_SGBD_PASSWORD` — mot de passe **chiffré format VTOM**, cf. [Format des secrets](#format-des-secrets)). + +**Désactiver :** `vtom.enabled: false` (utile si vous déployez uniquement ITC/ITM/MFT contre un serveur VTOM externe). + +## ITC — Visual TOM User Portal + +Portail utilisateur web pour piloter VTOM (visualisation, supervision, drag & drop). + +| Composant | Rôle | Service | PVC | Mémoire (limite) | +|---|---|---|---|---| +| `itc` | Portail web utilisateur | ClusterIP + Ingress HTTPS | 2 Gi | 1 Gi | + +**Paramètres typiques :** +- `itc.image.tag` — version ITC (**requise** si `itc.enabled=true`) +- `itc.ingress.host` — FQDN ITC, ex. `vitc.mycompany.com` +- `itc.resources`, `itc.pvc.size` + +**Licence :** par défaut réutilise la licence VTOM (`itc.license.secretName: vtom-license-secret`). + +**Base :** DB nommée `ITCockpits`, secret `itc-db-secret` (clés `ITDB_USER` + `ITDB_PASSWORD` — mot de passe **en clair**). + +**Désactiver :** `itc.enabled: false`. + +## ITM — Visual IT Messenger + +Service de messagerie / notification (envoi de courriels, alertes intégrées au workflow VTOM). + +| Composant | Rôle | Service | PVC | Mémoire (limite) | +|---|---|---|---|---| +| `itm` | Messenger | ClusterIP + Ingress HTTPS | 2 Gi | 1 Gi | + +**Paramètres typiques :** +- `itm.image.tag` — version ITM (**requise** si `itm.enabled=true`) +- `itm.ingress.host` — FQDN ITM, ex. `vitm.mycompany.com` +- `itm.resources`, `itm.pvc.size` + +**Licence :** par défaut réutilise la licence VTOM (`itm.license.secretName: vtom-license-secret`). + +**Base :** DB nommée `ITMessenger`, secret `itm-db-secret` (clés `ITDB_USER` + `ITDB_PASSWORD` — mot de passe **en clair**). + +**Désactiver :** `itm.enabled: false`. + +## MFT — Visual TOM Managed File Transfer + +Transferts de fichiers managés : serveur SFTP entrant + connecteurs sortants vers backends externes (NFS, S3, Azure Blob, FTP, SFTP). **Pas de base de données ni de licence séparée.** + +| Composant | Rôle | Services | PVC | Mémoire (limite) | +|---|---|---|---|---| +| `vtom-mft` | Portail HTTPS + serveur SFTP | `mft` (ClusterIP) + `mft-sftp` (LoadBalancer) + Ingress HTTPS | 1 Gi | 1 Gi | + +**Ports exposés :** +- `30034` — portail HTTPS (TLS auto-signé côté pod) +- `30022` — SFTP (accès clients externes via LoadBalancer) + +**Paramètres typiques :** +- `mft.image.tag` — version MFT (**requise** si `mft.enabled=true`) +- `mft.ingress.host` — FQDN portail web, ex. `mft.mycompany.com` +- `mft.sftpService.hostname` — FQDN SFTP (géré par ExternalDNS) +- `mft.sftpService.loadBalancerIP` — IP statique du LB SFTP +- `mft.sftpService.loadBalancerSourceRanges` — **toujours renseigner en production** pour restreindre l'accès SFTP par IP +- `mft.externalEgress` — règles NetworkPolicy de sortie vers les backends de stockage (NFS, S3, FTP, SFTP) +- `mft.pvcSeed.enabled` — init container qui prépare la structure du PVC au premier démarrage + +**Désactiver :** `mft.enabled: false`. + +# Installation + +## Depuis le registry public OCI (recommandé) + +Le chart est publié en tant qu'artefact OCI sur GitHub Container Registry. Aucune authentification requise. + +**Étape 1 — Récupérer le chart en local** : +```bash +helm pull oci://ghcr.io/absysslab/visual-tom --version 0.1.0 --untar +``` +Cette commande télécharge le chart et le dépaquette dans un dossier `visual-tom/` contenant `Chart.yaml`, `values.yaml`, tous les `values-.yaml`, le `values-client-template.yaml` et les `templates/`. + +**Étape 2 — Préparer votre fichier de valeurs client** : +```bash +cp visual-tom/values-client-template.yaml values-monentreprise.yaml +# Éditer values-monentreprise.yaml et remplir les lignes marquées « # TODO » +``` + +**Étape 3 — Installer** : +```bash +helm install visual-tom ./visual-tom \ + -f visual-tom/values-azure.yaml \ + -f values-monentreprise.yaml \ + --namespace vtom --create-namespace +``` + +Remplacer `values-azure.yaml` par `values-aws.yaml`, `values-gcp.yaml` ou `values-onpremise.yaml` selon votre cible. + +> **Mises à jour** : pour passer à une version ultérieure (ex. 0.1.1), relancer l'étape 1 avec `--version 0.1.1`, vérifier les changements éventuels dans les `values-.yaml`, puis `helm upgrade visual-tom ./visual-tom -f ... -f values-monentreprise.yaml --namespace vtom`. + +## Depuis les sources + +```bash +git clone https://github.com/AbsyssLab/vtom-helm.git +cd vtom-helm + +helm install visual-tom ./charts/visual-tom \ + -f ./charts/visual-tom/values-azure.yaml \ + -f values-monentreprise.yaml \ + --namespace vtom --create-namespace +``` + +# Configuration + +## Superposition des fichiers de valeurs + +Helm fusionne les fichiers de valeurs dans l'ordre indiqué sur la ligne de commande. Chaque fichier suivant écrase les valeurs du précédent : + +``` +values.yaml (defaults internes, chargé automatiquement) + + +values-.yaml (remplace les defaults par les valeurs cloud-spécifiques) + + +values-monentreprise.yaml (remplace par VOS valeurs spécifiques) + = +configuration finale déployée +``` + +## Étapes + +1. **Copier** `values-client-template.yaml` → `values-monentreprise.yaml` +2. **Remplir** toutes les lignes marquées `# TODO` +3. **Ne pas modifier** `values.yaml` ni les fichiers `values-.yaml` + +## Exposition réseau + +Par défaut, **tout est privé en production**. Le chart impose un Load Balancer interne pour `vtom.serverService` (VTOM Desktop) et `mft.sftpService` (SFTP) via des annotations spécifiques à chaque cloud, déjà configurées dans les fichiers `values-.yaml`. + +| Composant | Défaut | Override (public, tests) | Configuré dans | +|---|---|---|---| +| PostgreSQL | Endpoint privé (Private Endpoint, Private IP, VPC peering) | FQDN public | `database.host` — ce chart | +| vtom-server (VTOM Desktop) | LB interne — **imposé par le chart** | LB public | `vtom.serverService.annotations` — ce chart | +| MFT SFTP | LB interne — **imposé par le chart** | LB public | `mft.sftpService.annotations` — ce chart | +| Interfaces web (Ingress) | LB interne — **recommandé côté client** | LB public | Paramètres du contrôleur Ingress — **hors du chart** | + +**Annotations LB interne par cloud** (déjà configurées dans `values-.yaml`) : + +| Cloud | Annotations | +|---|---| +| Azure | `service.beta.kubernetes.io/azure-load-balancer-internal: "true"` | +| AWS | `aws-load-balancer-scheme: "internal"` (+ `aws-load-balancer-type: "external"`, `nlb-target-type: "ip"`) | +| GCP | `networking.gke.io/load-balancer-type: "Internal"` | +| On-premise | aucune — nécessite une implémentation LoadBalancer (ex. [MetalLB](https://metallb.universe.tf/)) | + +**Exposer publiquement (tests uniquement)** — surcharger les annotations dans `values-monentreprise.yaml` : +```yaml +vtom: + serverService: + annotations: {} # Désactive le LB interne + loadBalancerSourceRanges: + - "203.0.113.0/24" # Restreindre aux IPs autorisées +``` + +**Restreindre l'accès au LB interne par IP** (production) : +```yaml +vtom: + serverService: + loadBalancerSourceRanges: + - "10.0.0.0/8" # Réseau interne (VNet/VPC + VPN clients) +``` + +## Format des secrets + +Les conventions suivantes s'appliquent à **toutes les infrastructures** (Azure Key Vault, AWS Secrets Manager, GCP Secret Manager, secrets Kubernetes natifs) : + +| Produit | Utilisateur DB | Mot de passe DB | +|---|---|---| +| **VTOM** | Texte brut | **Chiffré format VTOM** (bcrypt Absyss) — utiliser l'outil de chiffrement fourni par Absyss | +| **ITC** | Texte brut | **En clair** (ITC ne supporte pas le chiffrement VTOM) | +| **ITM** | Texte brut | **En clair** (ITM ne supporte pas le chiffrement VTOM) | + +> **Important :** ne **jamais** stocker le mot de passe VTOM en clair. Le format attendu est le hash bcrypt généré par l'outil Absyss. À l'inverse, les mots de passe ITC et ITM doivent rester en clair — toute tentative de chiffrement entraînera un échec de connexion. + +## Configuration par environnement + +### Azure (AKS) + +**Valeurs obligatoires :** + +| Paramètre | Description | Exemple | +|---|---|---| +| `global.imageRegistry` | Nom de l'ACR | `monacr.azurecr.io` | +| `vtom.image.tag` | Version VTOM | `7.3.2c` | +| `itc.image.tag` | Version ITC | `7.3.2c` | +| `itm.image.tag` | Version ITM | `7.3.2c` | +| `vtom.ingress.host` | Domaine VTOM | `vtom.monentreprise.com` | +| `itc.ingress.host` | Domaine ITC | `vitc.monentreprise.com` | +| `itm.ingress.host` | Domaine ITM | `vitm.monentreprise.com` | +| `database.host` | FQDN PostgreSQL | `vtom-pg.postgres.database.azure.com` | +| `secrets.azure.keyVaultUrl` | URL du Key Vault | `https://mon-kv.vault.azure.net` | +| `secrets.azure.tenantId` | ID du tenant Azure AD | `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx` | +| `serviceAccount.azure.clientId` | Client ID de la Managed Identity | `yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy` | + +**Secrets à créer dans Azure Key Vault :** + +| Nom du secret | Contenu | Format | +|---|---|---| +| `vtom-db-user` | Utilisateur PostgreSQL pour VTOM | Texte brut | +| `vtom-db-password` | Mot de passe VTOM pour PostgreSQL | **Chiffré format VTOM** (bcrypt Absyss) | +| `vtom-license-register` | Contenu du fichier `license.register` | Texte brut | +| `itc-db-user` | Utilisateur PostgreSQL pour ITC | Texte brut | +| `itc-db-password` | Mot de passe ITC pour PostgreSQL | **En clair** | +| `itm-db-user` | Utilisateur PostgreSQL pour ITM | Texte brut | +| `itm-db-password` | Mot de passe ITM pour PostgreSQL | **En clair** | + +> **PostgreSQL — privé vs public :** sur Azure, utilisez de préférence un **Private Endpoint** sur le serveur PostgreSQL flexible (FQDN `.private.postgres.database.azure.com`). Le FQDN public Azure (`.postgres.database.azure.com`) ne doit être utilisé qu'en test, avec firewall PostgreSQL restreint à votre VNet. + +### AWS (EKS) + +**Valeurs obligatoires :** + +| Paramètre | Description | Exemple | +|---|---|---| +| `global.imageRegistry` | Registry ECR | `123456789.dkr.ecr.eu-west-1.amazonaws.com` | +| `vtom.image.tag` | Version VTOM | `7.3.2c` | +| `itc.image.tag` | Version ITC | `7.3.2c` | +| `itm.image.tag` | Version ITM | `7.3.2c` | +| `vtom.ingress.host` | Domaine VTOM | `vtom.monentreprise.com` | +| `itc.ingress.host` | Domaine ITC | `vitc.monentreprise.com` | +| `itm.ingress.host` | Domaine ITM | `vitm.monentreprise.com` | +| `database.host` | Endpoint RDS | `vtom.xxxx.eu-west-1.rds.amazonaws.com` | +| `secrets.aws.region` | Région AWS | `eu-west-1` | +| `serviceAccount.aws.roleArn` | ARN du rôle IAM | `arn:aws:iam::123456789012:role/vtom-role` | + +**Secrets à créer dans AWS Secrets Manager :** + +| Nom du secret | Contenu | Format | +|---|---|---| +| `vtom/db-user` | Utilisateur PostgreSQL pour VTOM | Texte brut | +| `vtom/db-password` | Mot de passe VTOM | **Chiffré format VTOM** | +| `vtom/license-register` | Fichier `license.register` | Texte brut | +| `vtom/itc-db-user` | Utilisateur PostgreSQL pour ITC | Texte brut | +| `vtom/itc-db-password` | Mot de passe ITC | **En clair** | +| `vtom/itm-db-user` | Utilisateur PostgreSQL pour ITM | Texte brut | +| `vtom/itm-db-password` | Mot de passe ITM | **En clair** | + +### GCP (GKE) + +**Valeurs obligatoires :** + +| Paramètre | Description | Exemple | +|---|---|---| +| `global.imageRegistry` | Artifact Registry | `europe-west1-docker.pkg.dev/mon-projet/vtom` | +| `vtom.image.tag` | Version VTOM | `7.3.2c` | +| `itc.image.tag` | Version ITC | `7.3.2c` | +| `itm.image.tag` | Version ITM | `7.3.2c` | +| `vtom.ingress.host` | Domaine VTOM | `vtom.monentreprise.com` | +| `itc.ingress.host` | Domaine ITC | `vitc.monentreprise.com` | +| `itm.ingress.host` | Domaine ITM | `vitm.monentreprise.com` | +| `dbProxy.cloudsqlProxy.instanceConnectionName` | Instance Cloud SQL | `mon-projet:europe-west1:vtom-postgres` | +| `secrets.gcp.projectId` | ID du projet GCP | `mon-projet-gcp` | +| `serviceAccount.gcp.serviceAccount` | GSA liée au KSA | `vtom@mon-projet.iam.gserviceaccount.com` | + +**Secrets à créer dans GCP Secret Manager :** + +| Nom du secret | Contenu | Format | +|---|---|---| +| `vtom-db-user` | Utilisateur PostgreSQL pour VTOM | Texte brut | +| `vtom-db-password` | Mot de passe VTOM | **Chiffré format VTOM** | +| `vtom-license-register` | Fichier `license.register` | Texte brut | +| `itc-db-user` | Utilisateur PostgreSQL pour ITC | Texte brut | +| `itc-db-password` | Mot de passe ITC | **En clair** | +| `itm-db-user` | Utilisateur PostgreSQL pour ITM | Texte brut | +| `itm-db-password` | Mot de passe ITM | **En clair** | + +### On-premise / RKE2 / Minikube + +**Valeurs obligatoires :** + +| Paramètre | Description | Exemple | +|---|---|---| +| `global.imageRegistry` | Registry local | `registry.monentreprise.com` | +| `vtom.image.tag` | Version VTOM | `7.3.2c` | +| `itc.image.tag` | Version ITC | `7.3.2c` | +| `itm.image.tag` | Version ITM | `7.3.2c` | +| `vtom.ingress.host` | Domaine VTOM | `vtom.monentreprise.local` | +| `itc.ingress.host` | Domaine ITC | `vitc.monentreprise.local` | +| `itm.ingress.host` | Domaine ITM | `vitm.monentreprise.local` | +| `database.host` | Hostname/IP PostgreSQL | `192.168.1.50` | + +**Secrets Kubernetes à créer manuellement avant le déploiement :** + +```bash +kubectl create namespace vtom + +# Utilisateur + mot de passe VTOM (mot de passe CHIFFRÉ au format VTOM — bcrypt Absyss) +kubectl create secret generic vtom-db-secret \ + --from-literal=TOM_SGBD_USER='' \ + --from-literal=TOM_SGBD_PASSWORD='' \ + -n vtom + +# Licence VTOM/ITC/ITM (fichier fourni par Absyss) +kubectl create secret generic vtom-license-secret \ + --from-file=license.register=/chemin/vers/license.register \ + -n vtom + +# Utilisateur + mot de passe ITC — EN CLAIR +kubectl create secret generic itc-db-secret \ + --from-literal=ITDB_USER='' \ + --from-literal=ITDB_PASSWORD='' \ + -n vtom + +# Utilisateur + mot de passe ITM — EN CLAIR +kubectl create secret generic itm-db-secret \ + --from-literal=ITDB_USER='' \ + --from-literal=ITDB_PASSWORD='' \ + -n vtom +``` + +## NetworkPolicy — Health checks Load Balancer + +Pour que les Load Balancers cloud puissent vérifier la santé des pods (probes), les CIDRs d'origine des probes doivent être explicitement autorisés dans la NetworkPolicy. Configurez `networkPolicy.lbHealthCheckCidrs` selon votre cloud : + +| Cloud | Valeur recommandée | +|---|---| +| Azure | `["168.63.129.16/32"]` | +| AWS | CIDR du VPC (ex. `["172.31.0.0/16"]`) — ne **pas** utiliser `["0.0.0.0/0"]` en production | +| GCP | `["130.211.0.0/22", "35.191.0.0/16"]` (Internal LB utilise les deux ranges) | +| On-premise | `[]` (pas de probe cloud) | + +Les fichiers `values-.yaml` fournissent déjà la bonne valeur par défaut. + +# Architecture + +![Architecture VTOM sur Kubernetes](architecture.png) + +# Mise à jour + +```bash +# Via OCI +helm upgrade visual-tom oci://ghcr.io/absysslab/visual-tom \ + --version 0.1.1 \ + -f values-azure.yaml \ + -f values-monentreprise.yaml \ + --namespace vtom + +# Depuis les sources +helm upgrade visual-tom ./charts/visual-tom \ + -f ./charts/visual-tom/values-azure.yaml \ + -f values-monentreprise.yaml \ + --namespace vtom +``` + +# Désinstallation + +```bash +helm uninstall visual-tom -n vtom +``` + +Par défaut, **les PersistentVolumeClaims (PVC) et les PersistentVolumes (PV) sous-jacents sont conservés** (`reclaimPolicy: Retain`). Cela protège vos données (clés de chiffrement, logs, journaux, configuration) contre une suppression accidentelle. + +> ⚠️ **AVERTISSEMENT — Perte de données irréversible** +> +> Les commandes ci-dessous suppriment définitivement **toutes les données VTOM, ITC, ITM et MFT**. Sur Azure / AWS / GCP, le disque cloud sous-jacent est également supprimé. **Aucune restauration n'est possible sans sauvegarde préalable**. +> +> N'exécuter qu'après avoir : +> - Effectué un dump complet de PostgreSQL +> - Sauvegardé les fichiers de configuration et journaux des PVC +> - Confirmé que ces données ne sont plus nécessaires + +```bash +# 1. Supprimer les PVC (libère les PV qui passent en état "Released") +kubectl delete pvc --all -n vtom + +# 2. Supprimer les PV libérés (perte définitive des disques sous-jacents) +kubectl get pv | grep "vtom/" | awk '{print $1}' | xargs -r kubectl delete pv + +# 3. (Optionnel) Supprimer le namespace +kubectl delete namespace vtom +``` + +# Vérification du déploiement + +```bash +kubectl get pods -n vtom +kubectl get ingress -n vtom +kubectl get svc -n vtom +helm status visual-tom -n vtom +``` + +# Résolution de problèmes + +**Les pods restent en `Pending` :** +```bash +kubectl describe pod -n vtom +# Vérifier les events — souvent un problème de PVC ou de ressources insuffisantes +``` + +**Les pods restent en `Init:0/1` ou `Init:Error` :** +```bash +kubectl logs -n vtom -c wait-for-db +# Le proxy DB ne démarre pas ou la DB est inaccessible +``` + +**Les secrets ESO ne se synchronisent pas :** +```bash +kubectl get externalsecret -n vtom +kubectl describe externalsecret vtom-db-secret -n vtom +# Vérifier les permissions de la Managed Identity / IAM Role / GSA sur le coffre de secrets +``` + +# Sécurité + +## Conformité + +Ce chart est conforme aux référentiels suivants : + +- **Kubernetes Pod Security Standards (PSS) — profil `restricted`** (tous les pods) +- **CIS Kubernetes Benchmark — Level 1** (tous les contrôles applicables) + +### Contrôles en place + +| Contrôle | Détail | +|---|---| +| `runAsNonRoot: true` | Tous les pods | +| `runAsUser` / `runAsGroup` non nuls | uid=1000 / gid=1000 (vtom), uid=10001 (ITC, ITM, MFT) | +| `fsGroup` non nul | 1000 sur les composants VTOM core ; 10001 sur ITC, ITM, MFT | +| `allowPrivilegeEscalation: false` | Tous les conteneurs, y compris les init containers et sidecars | +| `capabilities.drop: ["ALL"]` | Tous les conteneurs, y compris les init containers et sidecars | +| `seccompProfile: RuntimeDefault` | Tous les pods | +| `readOnlyRootFilesystem: true` | Init containers et sidecar proxy DB | +| `automountServiceAccountToken: false` | Tous les pods | +| NetworkPolicies — deny par défaut | Ingress et egress bloqués par défaut ; règles d'autorisation explicites par composant | +| Secrets via `secretKeyRef` | Aucune donnée sensible dans les ConfigMaps ou variables d'environnement | +| `defaultMode: 0440` sur les volumes secrets | Fichiers de licence montés en lecture seule | + +### Exception documentée (CIS Level 2) + +`readOnlyRootFilesystem` n'est pas activé sur les conteneurs applicatifs principaux (vtom-server, vtom-agent, vtom-apiserver, ITC, ITM). Ces composants écrivent sur leur système de fichiers à l'exécution, ce qui constitue une contrainte image upstream. Ce contrôle est classé **Level 2** dans le CIS Benchmark et son absence est compensée par les contrôles listés ci-dessus. + +### Pod Security Admission + +Pour appliquer le profil PSS `restricted` au niveau du namespace, ajouter le label suivant à la création du namespace : + +```bash +kubectl label namespace vtom \ + pod-security.kubernetes.io/enforce=restricted \ + pod-security.kubernetes.io/warn=restricted +``` + +**Le certificat TLS n'est pas émis :** +```bash +kubectl get certificate -n vtom +kubectl describe certificate vtom-tls-cert -n vtom +# Vérifier que le ClusterIssuer est prêt et que le domaine est accessible depuis internet +``` + +**Le Load Balancer reste `` ou les probes échouent :** +```bash +kubectl describe svc vtom-server -n vtom +# Côté NetworkPolicy : vérifier networkPolicy.lbHealthCheckCidrs (cf. section dédiée) +# Côté cloud : vérifier annotations et quotas LB +``` + +# Licence + +Ce projet est sous licence Apache 2.0. Voir le fichier [LICENSE](LICENSE) pour plus de détails. diff --git a/charts/visual-tom/README.md b/charts/visual-tom/README.md new file mode 100644 index 0000000..3e01b8f --- /dev/null +++ b/charts/visual-tom/README.md @@ -0,0 +1,556 @@ +# Visual TOM — Kubernetes Helm Chart +[![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](LICENSE)  +[![fr](https://img.shields.io/badge/lang-fr-yellow.svg)](README-fr.md) + +This repository provides a Helm chart to deploy **Visual TOM (Absyss)** and its related products on Kubernetes: + +- **VTOM** — Scheduler (core) +- **ITC** — Visual TOM User Portal +- **ITM** — Visual IT Messenger +- **MFT** — Visual TOM Managed File Transfer + +Validated targets: + +| Environment | Status | +|---|---| +| Azure AKS | ✅ Validated | +| GCP GKE (Autopilot) | ✅ Validated | +| AWS EKS | ⚠️ Implemented, not tested in production | +| On-premise / Minikube | ✅ Tested locally | + +# Disclaimer + +This Helm chart is provided by Absyss SAS as a **reference deployment** of Visual TOM on Kubernetes. It is designed as a **starting point** that must be adapted by each client to match their specific infrastructure, security requirements and operational constraints (network topology, secrets management, storage classes, ingress controller, RBAC policies, etc.). + +The chart is distributed **as-is**, without warranty of any kind. Absyss SAS is not liable for damages resulting from its use or from client-side adaptations. + +Standard Visual TOM support contracts do not cover the Helm chart itself or client-side Kubernetes adaptations. Consulting days can be requested to assist with deployment, customization or troubleshooting. + +# Prerequisites + +## All environments +- **Visual TOM** ≥ 7.3.2c (earlier versions are not tested with this chart) +- **Kubernetes** ≥ 1.28 +- **Helm** ≥ 3.8 (required to install from the OCI registry) +- **PostgreSQL 17** reachable from the cluster (VNet, VPC or local network) +- An **Ingress controller** installed — **Traefik** is used by default, nginx is also supported +- **cert-manager** installed with a `ClusterIssuer` configured if you use Let's Encrypt (default TLS option). Installing and configuring cert-manager is the client's responsibility — the chart only creates the `Certificate` resources. If you provide your own TLS certificates (`tls.provider: secret`), cert-manager is not required +- **ExternalDNS** (optional) — for automatic DNS record management of the LoadBalancer service. Without ExternalDNS, DNS records must be created manually. See `vtom.serverService.hostname` in the values +- A valid **VTOM license** and access to an image registry + +> **Private network access / VPN:** resources such as WireGuard, Private Endpoints, Cloud SQL Auth Proxy, etc. are **not** part of this chart. They must be provisioned upstream via your IaC (Terraform, Bicep, etc.). + +## Cloud-specific prerequisites + +### Azure (AKS) +- ACR (Azure Container Registry) with the VTOM/ITC/ITM/MFT images loaded +- Azure Key Vault with the secrets created (see Azure section below) +- User-Assigned Managed Identity with Key Vault access (`Key Vault Secrets User`) +- External Secrets Operator installed in the cluster + +### AWS (EKS) +- ECR or another registry with the images loaded +- AWS Secrets Manager with the secrets created +- IAM Role with Secrets Manager access, associated with the ServiceAccount via IRSA +- External Secrets Operator installed in the cluster + +### GCP (GKE) +- Artifact Registry or GCR with the images loaded +- GCP Secret Manager with the secrets created +- GCP Service Account with Secret Manager access, linked via Workload Identity +- External Secrets Operator installed in the cluster +- Cloud SQL Auth Proxy enabled in the values (preconfigured in `values-gcp.yaml`) + +### On-premise +- Local Docker registry or images loaded manually (`docker load`) +- PostgreSQL reachable from the pods +- Kubernetes secrets created manually (see on-premise section below) +- A **LoadBalancer implementation** such as [MetalLB](https://metallb.universe.tf/) — required to assign an IP to `vtom-server` and `mft-sftp` Services. Without it, `type: LoadBalancer` Services remain in `` state indefinitely. On bare-metal or local clusters (RKE2, kubeadm, Minikube), this is not provided by default + +# Products + +The chart deploys 4 Visual TOM products, each independently toggleable via its `.enabled` flag. They all share global settings (registry, namespace, database, network exposure, NetworkPolicy). + +## VTOM — Scheduler + +The core of Visual TOM. Three components deployed as separate pods, sharing the same image. + +| Component | Role | Service | PVC | Memory (limit) | +|---|---|---|---|---| +| `vtom-server` | Scheduling engine | LoadBalancer (5 native VTOM ports) | 5 Gi | 1 Gi | +| `vtom-apiserver` | REST API + web interface | ClusterIP + HTTPS Ingress | 2 Gi | 1.5 Gi | +| `vtom-agent` | Kubernetes job executor | (none) | 10 Gi | 256 Mi | + +**Typical parameters:** +- `vtom.image.tag` — VTOM version (e.g. `7.3.2c`) +- `vtom.ingress.host` — apiserver FQDN, e.g. `vtom.mycompany.com` +- `vtom.serverService.hostname` — VTOM Desktop client FQDN (managed by ExternalDNS) +- `vtom.serverService.loadBalancerIP` — static IP to survive LB reprovisioning +- `vtom.timezone` — shared timezone (default `Europe/Paris`) +- `vtom.{server,apiserver,agent}Resources` — CPU/memory per component +- `vtom.{server,apiserver,agent}Pvc.size` — storage sizes + +**License:** `vtom.license.secretName` (default `vtom-license-secret`) — shared with ITC and ITM by default. + +**Database:** DB named `vtom`, secret `vtom-db-secret` (keys `TOM_SGBD_USER` + `TOM_SGBD_PASSWORD` — password **VTOM-encrypted**, see [Secret formats](#secret-formats)). + +**Disable:** `vtom.enabled: false` (useful if you only deploy ITC/ITM/MFT against an external VTOM server). + +## ITC — Visual TOM User Portal + +Web user portal to operate VTOM (visualization, monitoring, drag & drop). + +| Component | Role | Service | PVC | Memory (limit) | +|---|---|---|---|---| +| `itc` | Web user portal | ClusterIP + HTTPS Ingress | 2 Gi | 1 Gi | + +**Typical parameters:** +- `itc.image.tag` — ITC version (**required** if `itc.enabled=true`) +- `itc.ingress.host` — ITC FQDN, e.g. `vitc.mycompany.com` +- `itc.resources`, `itc.pvc.size` + +**License:** by default reuses the VTOM license (`itc.license.secretName: vtom-license-secret`). + +**Database:** DB named `ITCockpits`, secret `itc-db-secret` (keys `ITDB_USER` + `ITDB_PASSWORD` — password **plain text**). + +**Disable:** `itc.enabled: false`. + +## ITM — Visual IT Messenger + +Messaging / notification service (email sending, alerts integrated into the VTOM workflow). + +| Component | Role | Service | PVC | Memory (limit) | +|---|---|---|---|---| +| `itm` | Messenger | ClusterIP + HTTPS Ingress | 2 Gi | 1 Gi | + +**Typical parameters:** +- `itm.image.tag` — ITM version (**required** if `itm.enabled=true`) +- `itm.ingress.host` — ITM FQDN, e.g. `vitm.mycompany.com` +- `itm.resources`, `itm.pvc.size` + +**License:** by default reuses the VTOM license (`itm.license.secretName: vtom-license-secret`). + +**Database:** DB named `ITMessenger`, secret `itm-db-secret` (keys `ITDB_USER` + `ITDB_PASSWORD` — password **plain text**). + +**Disable:** `itm.enabled: false`. + +## MFT — Visual TOM Managed File Transfer + +Managed file transfers: inbound SFTP server + outbound connectors to external backends (NFS, S3, Azure Blob, FTP, SFTP). **No database, no separate license.** + +| Component | Role | Services | PVC | Memory (limit) | +|---|---|---|---|---| +| `vtom-mft` | HTTPS portal + SFTP server | `mft` (ClusterIP) + `mft-sftp` (LoadBalancer) + HTTPS Ingress | 1 Gi | 1 Gi | + +**Exposed ports:** +- `30034` — HTTPS portal (self-signed TLS served by the pod) +- `30022` — SFTP (external client access via LoadBalancer) + +**Typical parameters:** +- `mft.image.tag` — MFT version (**required** if `mft.enabled=true`) +- `mft.ingress.host` — web portal FQDN, e.g. `mft.mycompany.com` +- `mft.sftpService.hostname` — SFTP FQDN (managed by ExternalDNS) +- `mft.sftpService.loadBalancerIP` — static IP of the SFTP LB +- `mft.sftpService.loadBalancerSourceRanges` — **always set in production** to restrict SFTP access by IP +- `mft.externalEgress` — egress NetworkPolicy rules to storage backends (NFS, S3, FTP, SFTP) +- `mft.pvcSeed.enabled` — init container that prepares the PVC structure on first startup + +**Disable:** `mft.enabled: false`. + +# Installation + +## From the public OCI registry (recommended) + +The chart is published as an OCI artifact on GitHub Container Registry. No authentication required. + +**Step 1 — Pull the chart locally**: +```bash +helm pull oci://ghcr.io/absysslab/visual-tom --version 0.1.0 --untar +``` +This downloads the chart and extracts it into a `visual-tom/` directory containing `Chart.yaml`, `values.yaml`, all `values-.yaml`, the `values-client-template.yaml` and the `templates/`. + +**Step 2 — Prepare your client values file**: +```bash +cp visual-tom/values-client-template.yaml values-mycompany.yaml +# Edit values-mycompany.yaml and fill in the lines marked "# TODO" +``` + +**Step 3 — Install**: +```bash +helm install visual-tom ./visual-tom \ + -f visual-tom/values-azure.yaml \ + -f values-mycompany.yaml \ + --namespace vtom --create-namespace +``` + +Replace `values-azure.yaml` with `values-aws.yaml`, `values-gcp.yaml` or `values-onpremise.yaml` according to your target. + +> **Upgrades**: to move to a later version (e.g. 0.1.1), re-run step 1 with `--version 0.1.1`, review any changes in the `values-.yaml`, then `helm upgrade visual-tom ./visual-tom -f ... -f values-mycompany.yaml --namespace vtom`. + +## From sources + +```bash +git clone https://github.com/AbsyssLab/vtom-helm.git +cd vtom-helm + +helm install visual-tom ./charts/visual-tom \ + -f ./charts/visual-tom/values-azure.yaml \ + -f values-mycompany.yaml \ + --namespace vtom --create-namespace +``` + +# Configuration + +## Values files layering + +Helm merges values files in the order they appear on the command line. Each subsequent file overrides the previous one: + +``` +values.yaml (internal defaults, loaded automatically) + + +values-.yaml (overrides defaults with cloud-specific values) + + +values-mycompany.yaml (overrides with YOUR specific values) + = +final deployed configuration +``` + +## Steps + +1. **Copy** `values-client-template.yaml` → `values-mycompany.yaml` +2. **Fill in** all lines marked `# TODO` +3. **Do not modify** `values.yaml` or any `values-.yaml` file + +## Network exposure + +By default, **everything is private in production**. The chart enforces an internal Load Balancer for `vtom.serverService` (VTOM Desktop) and `mft.sftpService` (SFTP) via cloud-specific annotations, already configured in the `values-.yaml` files. + +| Component | Default | Override (public, tests) | Configured in | +|---|---|---|---| +| PostgreSQL | Private endpoint (Private Endpoint, Private IP, VPC peering) | Public FQDN | `database.host` — this chart | +| vtom-server (VTOM Desktop) | Internal LB — **enforced by the chart** | Public LB | `vtom.serverService.annotations` — this chart | +| MFT SFTP | Internal LB — **enforced by the chart** | Public LB | `mft.sftpService.annotations` — this chart | +| Web interfaces (Ingress) | Internal LB — **recommended on the client side** | Public LB | Ingress controller settings — **out of scope of the chart** | + +**Internal LB annotations per cloud** (already configured in `values-.yaml`): + +| Cloud | Annotations | +|---|---| +| Azure | `service.beta.kubernetes.io/azure-load-balancer-internal: "true"` | +| AWS | `aws-load-balancer-scheme: "internal"` (+ `aws-load-balancer-type: "external"`, `nlb-target-type: "ip"`) | +| GCP | `networking.gke.io/load-balancer-type: "Internal"` | +| On-premise | none — requires a LoadBalancer implementation (e.g. [MetalLB](https://metallb.universe.tf/)) | + +**Expose publicly (tests only)** — override the annotations in `values-mycompany.yaml`: +```yaml +vtom: + serverService: + annotations: {} # Disables the internal LB + loadBalancerSourceRanges: + - "203.0.113.0/24" # Restrict to authorized IPs +``` + +**Restrict access to the internal LB by IP** (production): +```yaml +vtom: + serverService: + loadBalancerSourceRanges: + - "10.0.0.0/8" # Internal network (VNet/VPC + client VPN) +``` + +## Secret formats + +The following conventions apply to **all infrastructures** (Azure Key Vault, AWS Secrets Manager, GCP Secret Manager, native Kubernetes secrets): + +| Product | DB user | DB password | +|---|---|---| +| **VTOM** | Plain text | **VTOM-encrypted** (Absyss bcrypt) — use the encryption tool provided by Absyss | +| **ITC** | Plain text | **Plain text** (ITC does not support VTOM encryption) | +| **ITM** | Plain text | **Plain text** (ITM does not support VTOM encryption) | + +> **Important:** never store the VTOM password in plain text. The expected format is the bcrypt hash generated by the Absyss tool. Conversely, ITC and ITM passwords must remain plain text — any encryption attempt will cause connection failures. + +## Configuration per environment + +### Azure (AKS) + +**Required values:** + +| Parameter | Description | Example | +|---|---|---| +| `global.imageRegistry` | ACR name | `myacr.azurecr.io` | +| `vtom.image.tag` | VTOM version | `7.3.2c` | +| `itc.image.tag` | ITC version | `7.3.2c` | +| `itm.image.tag` | ITM version | `7.3.2c` | +| `vtom.ingress.host` | VTOM domain | `vtom.mycompany.com` | +| `itc.ingress.host` | ITC domain | `vitc.mycompany.com` | +| `itm.ingress.host` | ITM domain | `vitm.mycompany.com` | +| `database.host` | PostgreSQL FQDN | `vtom-pg.postgres.database.azure.com` | +| `secrets.azure.keyVaultUrl` | Key Vault URL | `https://my-kv.vault.azure.net` | +| `secrets.azure.tenantId` | Azure AD tenant ID | `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx` | +| `serviceAccount.azure.clientId` | Managed Identity client ID | `yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy` | + +**Secrets to create in Azure Key Vault:** + +| Secret name | Content | Format | +|---|---|---| +| `vtom-db-user` | PostgreSQL user for VTOM | Plain text | +| `vtom-db-password` | VTOM password for PostgreSQL | **VTOM-encrypted** (Absyss bcrypt) | +| `vtom-license-register` | Content of the `license.register` file | Plain text | +| `itc-db-user` | PostgreSQL user for ITC | Plain text | +| `itc-db-password` | ITC password for PostgreSQL | **Plain text** | +| `itm-db-user` | PostgreSQL user for ITM | Plain text | +| `itm-db-password` | ITM password for PostgreSQL | **Plain text** | + +> **PostgreSQL — private vs public:** on Azure, prefer a **Private Endpoint** on the PostgreSQL Flexible Server (FQDN `.private.postgres.database.azure.com`). The Azure public FQDN (`.postgres.database.azure.com`) should only be used in test, with the PostgreSQL firewall restricted to your VNet. + +### AWS (EKS) + +**Required values:** + +| Parameter | Description | Example | +|---|---|---| +| `global.imageRegistry` | ECR registry | `123456789.dkr.ecr.eu-west-1.amazonaws.com` | +| `vtom.image.tag` | VTOM version | `7.3.2c` | +| `itc.image.tag` | ITC version | `7.3.2c` | +| `itm.image.tag` | ITM version | `7.3.2c` | +| `vtom.ingress.host` | VTOM domain | `vtom.mycompany.com` | +| `itc.ingress.host` | ITC domain | `vitc.mycompany.com` | +| `itm.ingress.host` | ITM domain | `vitm.mycompany.com` | +| `database.host` | RDS endpoint | `vtom.xxxx.eu-west-1.rds.amazonaws.com` | +| `secrets.aws.region` | AWS region | `eu-west-1` | +| `serviceAccount.aws.roleArn` | IAM Role ARN | `arn:aws:iam::123456789012:role/vtom-role` | + +**Secrets to create in AWS Secrets Manager:** + +| Secret name | Content | Format | +|---|---|---| +| `vtom/db-user` | PostgreSQL user for VTOM | Plain text | +| `vtom/db-password` | VTOM password | **VTOM-encrypted** | +| `vtom/license-register` | `license.register` file | Plain text | +| `vtom/itc-db-user` | PostgreSQL user for ITC | Plain text | +| `vtom/itc-db-password` | ITC password | **Plain text** | +| `vtom/itm-db-user` | PostgreSQL user for ITM | Plain text | +| `vtom/itm-db-password` | ITM password | **Plain text** | + +### GCP (GKE) + +**Required values:** + +| Parameter | Description | Example | +|---|---|---| +| `global.imageRegistry` | Artifact Registry | `europe-west1-docker.pkg.dev/my-project/vtom` | +| `vtom.image.tag` | VTOM version | `7.3.2c` | +| `itc.image.tag` | ITC version | `7.3.2c` | +| `itm.image.tag` | ITM version | `7.3.2c` | +| `vtom.ingress.host` | VTOM domain | `vtom.mycompany.com` | +| `itc.ingress.host` | ITC domain | `vitc.mycompany.com` | +| `itm.ingress.host` | ITM domain | `vitm.mycompany.com` | +| `dbProxy.cloudsqlProxy.instanceConnectionName` | Cloud SQL instance | `my-project:europe-west1:vtom-postgres` | +| `secrets.gcp.projectId` | GCP project ID | `my-gcp-project` | +| `serviceAccount.gcp.serviceAccount` | GSA linked to the KSA | `vtom@my-project.iam.gserviceaccount.com` | + +**Secrets to create in GCP Secret Manager:** + +| Secret name | Content | Format | +|---|---|---| +| `vtom-db-user` | PostgreSQL user for VTOM | Plain text | +| `vtom-db-password` | VTOM password | **VTOM-encrypted** | +| `vtom-license-register` | `license.register` file | Plain text | +| `itc-db-user` | PostgreSQL user for ITC | Plain text | +| `itc-db-password` | ITC password | **Plain text** | +| `itm-db-user` | PostgreSQL user for ITM | Plain text | +| `itm-db-password` | ITM password | **Plain text** | + +### On-premise / RKE2 / Minikube + +**Required values:** + +| Parameter | Description | Example | +|---|---|---| +| `global.imageRegistry` | Local registry | `registry.mycompany.com` | +| `vtom.image.tag` | VTOM version | `7.3.2c` | +| `itc.image.tag` | ITC version | `7.3.2c` | +| `itm.image.tag` | ITM version | `7.3.2c` | +| `vtom.ingress.host` | VTOM domain | `vtom.mycompany.local` | +| `itc.ingress.host` | ITC domain | `vitc.mycompany.local` | +| `itm.ingress.host` | ITM domain | `vitm.mycompany.local` | +| `database.host` | PostgreSQL hostname/IP | `192.168.1.50` | + +**Kubernetes secrets to create manually before deployment:** + +```bash +kubectl create namespace vtom + +# VTOM user + password (password VTOM-ENCRYPTED — Absyss bcrypt) +kubectl create secret generic vtom-db-secret \ + --from-literal=TOM_SGBD_USER='' \ + --from-literal=TOM_SGBD_PASSWORD='' \ + -n vtom + +# VTOM/ITC/ITM license (file provided by Absyss) +kubectl create secret generic vtom-license-secret \ + --from-file=license.register=/path/to/license.register \ + -n vtom + +# ITC user + password — PLAIN TEXT +kubectl create secret generic itc-db-secret \ + --from-literal=ITDB_USER='' \ + --from-literal=ITDB_PASSWORD='' \ + -n vtom + +# ITM user + password — PLAIN TEXT +kubectl create secret generic itm-db-secret \ + --from-literal=ITDB_USER='' \ + --from-literal=ITDB_PASSWORD='' \ + -n vtom +``` + +## NetworkPolicy — Load Balancer health checks + +For cloud Load Balancers to be able to probe pod health, the source CIDRs of the probes must be explicitly allowed in the NetworkPolicy. Configure `networkPolicy.lbHealthCheckCidrs` according to your cloud: + +| Cloud | Recommended value | +|---|---| +| Azure | `["168.63.129.16/32"]` | +| AWS | VPC CIDR (e.g. `["172.31.0.0/16"]`) — do **not** use `["0.0.0.0/0"]` in production | +| GCP | `["130.211.0.0/22", "35.191.0.0/16"]` (Internal LB uses both ranges) | +| On-premise | `[]` (no cloud probe) | + +The `values-.yaml` files already provide the correct default value. + +# Architecture + +![VTOM on Kubernetes architecture](architecture.png) + +# Upgrade + +```bash +# Via OCI +helm upgrade visual-tom oci://ghcr.io/absysslab/visual-tom \ + --version 0.1.1 \ + -f values-azure.yaml \ + -f values-mycompany.yaml \ + --namespace vtom + +# From sources +helm upgrade visual-tom ./charts/visual-tom \ + -f ./charts/visual-tom/values-azure.yaml \ + -f values-mycompany.yaml \ + --namespace vtom +``` + +# Uninstallation + +```bash +helm uninstall visual-tom -n vtom +``` + +By default, **PersistentVolumeClaims (PVCs) and the underlying PersistentVolumes (PVs) are retained** (`reclaimPolicy: Retain`). This protects your data (encryption keys, application logs, audit logs, configuration) against accidental deletion. + +> ⚠️ **WARNING — Irreversible data loss** +> +> The commands below permanently delete **all VTOM, ITC, ITM and MFT data**. On Azure / AWS / GCP, the underlying cloud disk is also deleted. **No restoration is possible without a prior backup**. +> +> Only run after you have: +> - Taken a complete PostgreSQL dump +> - Backed up the configuration files and logs from the PVCs +> - Confirmed that this data is no longer needed + +```bash +# 1. Delete the PVCs (releases the PVs, which transition to "Released" state) +kubectl delete pvc --all -n vtom + +# 2. Delete the released PVs (permanent loss of the underlying disks) +kubectl get pv | grep "vtom/" | awk '{print $1}' | xargs -r kubectl delete pv + +# 3. (Optional) Delete the namespace +kubectl delete namespace vtom +``` + +# Verifying the deployment + +```bash +kubectl get pods -n vtom +kubectl get ingress -n vtom +kubectl get svc -n vtom +helm status visual-tom -n vtom +``` + +# Troubleshooting + +**Pods stay in `Pending`:** +```bash +kubectl describe pod -n vtom +# Check the events — typically a PVC issue or insufficient resources +``` + +**Pods stay in `Init:0/1` or `Init:Error`:** +```bash +kubectl logs -n vtom -c wait-for-db +# The DB proxy is not starting or the DB is unreachable +``` + +**ESO secrets are not syncing:** +```bash +kubectl get externalsecret -n vtom +kubectl describe externalsecret vtom-db-secret -n vtom +# Check the permissions of the Managed Identity / IAM Role / GSA on the secret store +``` + +# Security + +## Compliance + +This chart is compliant with: + +- **Kubernetes Pod Security Standards (PSS) — `restricted` profile** (all pods) +- **CIS Kubernetes Benchmark — Level 1** (all applicable controls) + +### Controls in place + +| Control | Detail | +|---|---| +| `runAsNonRoot: true` | All pods | +| `runAsUser` / `runAsGroup` non-zero | uid=1000 / gid=1000 (vtom), uid=10001 (ITC, ITM, MFT) | +| `fsGroup` non-zero | 1000 on VTOM core; 10001 on ITC, ITM, MFT | +| `allowPrivilegeEscalation: false` | All containers, including init containers and sidecars | +| `capabilities.drop: ["ALL"]` | All containers, including init containers and sidecars | +| `seccompProfile: RuntimeDefault` | All pods | +| `readOnlyRootFilesystem: true` | Init containers and DB proxy sidecar | +| `automountServiceAccountToken: false` | All pods | +| NetworkPolicies — default deny | Ingress and egress blocked by default; explicit allow rules per component | +| Secrets via `secretKeyRef` | No credentials in ConfigMaps or environment variables | +| Secret volume `defaultMode: 0440` | License files mounted read-only | + +### Known exception (CIS Level 2) + +`readOnlyRootFilesystem` is not set on the main application containers (vtom-server, vtom-agent, vtom-apiserver, ITC, ITM). These components write to their filesystem at runtime, which is an upstream image constraint. This control is classified **Level 2** in the CIS Benchmark and its absence is compensated by the controls listed above. + +### Pod Security Admission + +To enforce the `restricted` PSS profile at the namespace level, add the following label when creating the namespace: + +```bash +kubectl label namespace vtom \ + pod-security.kubernetes.io/enforce=restricted \ + pod-security.kubernetes.io/warn=restricted +``` + +**The TLS certificate is not being issued:** +```bash +kubectl get certificate -n vtom +kubectl describe certificate vtom-tls-cert -n vtom +# Check that the ClusterIssuer is ready and the domain is reachable from the internet +``` + +**The Load Balancer stays `` or probes are failing:** +```bash +kubectl describe svc vtom-server -n vtom +# NetworkPolicy side: check networkPolicy.lbHealthCheckCidrs (see dedicated section) +# Cloud side: check annotations and LB quotas +``` + +# License + +This project is licensed under the Apache 2.0 License — see the [LICENSE](LICENSE) file for details. diff --git a/charts/visual-tom/templates/_helpers.tpl b/charts/visual-tom/templates/_helpers.tpl index b9b6b8f..cac95c0 100644 --- a/charts/visual-tom/templates/_helpers.tpl +++ b/charts/visual-tom/templates/_helpers.tpl @@ -212,6 +212,7 @@ Rendered only when dbProxy.enabled=true. runAsNonRoot: true runAsUser: 1000 allowPrivilegeEscalation: false + readOnlyRootFilesystem: true capabilities: drop: ["ALL"] {{- end }} diff --git a/charts/visual-tom/templates/vtom/deployment-agent.yaml b/charts/visual-tom/templates/vtom/deployment-agent.yaml index 4379d51..f59b4f2 100644 --- a/charts/visual-tom/templates/vtom/deployment-agent.yaml +++ b/charts/visual-tom/templates/vtom/deployment-agent.yaml @@ -37,7 +37,10 @@ spec: {{- if .Values.securityContext.runAsUser }} runAsUser: {{ .Values.securityContext.runAsUser }} {{- end }} - fsGroup: 0 + {{- if .Values.securityContext.runAsGroup }} + runAsGroup: {{ .Values.securityContext.runAsGroup }} + {{- end }} + fsGroup: {{ .Values.securityContext.fsGroup | default 0 }} seccompProfile: type: RuntimeDefault containers: diff --git a/charts/visual-tom/templates/vtom/deployment-apiserver.yaml b/charts/visual-tom/templates/vtom/deployment-apiserver.yaml index 9b938b8..4f6c3b1 100644 --- a/charts/visual-tom/templates/vtom/deployment-apiserver.yaml +++ b/charts/visual-tom/templates/vtom/deployment-apiserver.yaml @@ -40,7 +40,10 @@ spec: {{- if .Values.securityContext.runAsUser }} runAsUser: {{ .Values.securityContext.runAsUser }} {{- end }} - fsGroup: 0 + {{- if .Values.securityContext.runAsGroup }} + runAsGroup: {{ .Values.securityContext.runAsGroup }} + {{- end }} + fsGroup: {{ .Values.securityContext.fsGroup | default 0 }} seccompProfile: type: RuntimeDefault initContainers: diff --git a/charts/visual-tom/templates/vtom/deployment-server.yaml b/charts/visual-tom/templates/vtom/deployment-server.yaml index d87194a..c2357a3 100644 --- a/charts/visual-tom/templates/vtom/deployment-server.yaml +++ b/charts/visual-tom/templates/vtom/deployment-server.yaml @@ -42,7 +42,10 @@ spec: {{- if .Values.securityContext.runAsUser }} runAsUser: {{ .Values.securityContext.runAsUser }} {{- end }} - fsGroup: 0 + {{- if .Values.securityContext.runAsGroup }} + runAsGroup: {{ .Values.securityContext.runAsGroup }} + {{- end }} + fsGroup: {{ .Values.securityContext.fsGroup | default 0 }} seccompProfile: type: RuntimeDefault initContainers: diff --git a/charts/visual-tom/values-aws.yaml b/charts/visual-tom/values-aws.yaml index 6e045b5..0e0634b 100644 --- a/charts/visual-tom/values-aws.yaml +++ b/charts/visual-tom/values-aws.yaml @@ -66,7 +66,9 @@ tls: clusterIssuer: letsencrypt-prod securityContext: - runAsUser: 1000 # UID of the vtom user in Absyss images + runAsUser: 1000 # uid=1000(vtom) — confirmed from running image (kubectl exec id) + runAsGroup: 1000 # gid=1000(vtom) — confirmed from running image + fsGroup: 1000 networkPolicy: enabled: true diff --git a/charts/visual-tom/values-azure.yaml b/charts/visual-tom/values-azure.yaml index 9853199..1c7a7ec 100644 --- a/charts/visual-tom/values-azure.yaml +++ b/charts/visual-tom/values-azure.yaml @@ -72,7 +72,9 @@ tls: clusterIssuer: letsencrypt-prod securityContext: - runAsUser: 1000 # UID of the vtom user in Absyss images + runAsUser: 1000 # uid=1000(vtom) — confirmed from running image (kubectl exec id) + runAsGroup: 1000 # gid=1000(vtom) — confirmed from running image + fsGroup: 1000 networkPolicy: enabled: true diff --git a/charts/visual-tom/values-gcp.yaml b/charts/visual-tom/values-gcp.yaml index cc4863d..4670109 100644 --- a/charts/visual-tom/values-gcp.yaml +++ b/charts/visual-tom/values-gcp.yaml @@ -66,7 +66,9 @@ tls: clusterIssuer: letsencrypt-prod securityContext: - runAsUser: 1000 # UID of the vtom user in Absyss images + runAsUser: 1000 # uid=1000(vtom) — confirmed from running image (kubectl exec id) + runAsGroup: 1000 # gid=1000(vtom) — confirmed from running image + fsGroup: 1000 networkPolicy: enabled: true diff --git a/charts/visual-tom/values-onpremise.yaml b/charts/visual-tom/values-onpremise.yaml index 37f0abd..2ff272d 100644 --- a/charts/visual-tom/values-onpremise.yaml +++ b/charts/visual-tom/values-onpremise.yaml @@ -37,8 +37,10 @@ tls: clusterIssuer: letsencrypt-prod securityContext: - runAsUser: 1000 # UID of the vtom user in Absyss images - # Leave null on OpenShift (nonroot SCC manages the UID) + runAsUser: 1000 # uid=1000(vtom) — confirmed from running image (kubectl exec id) + runAsGroup: 1000 # gid=1000(vtom) — confirmed from running image + fsGroup: 1000 + # Leave all three null on OpenShift (nonroot SCC manages the UID/GID automatically) networkPolicy: enabled: true diff --git a/charts/visual-tom/values.yaml b/charts/visual-tom/values.yaml index c400a34..cc76d34 100644 --- a/charts/visual-tom/values.yaml +++ b/charts/visual-tom/values.yaml @@ -532,16 +532,19 @@ networkPolicy: # ----------------------------------------------------------------------------- # Pod security context — applies to VTOM components only (server, apiserver, agent). -# runAsUser: numeric UID required on standard K8s when the image uses a symbolic -# user (e.g.: USER vtom) — K8s cannot verify runAsNonRoot without a numeric UID. -# Leave null on OpenShift: the nonroot SCC assigns the UID automatically from the -# namespace range, avoiding conflicts with a hardcoded UID. -# Recommended value for VTOM images (server, apiserver, agent): 1000 (set in values-.yaml). -# ITC, ITM and MFT run under their own hardcoded uid 10001 (absyss) in their templates -# and ignore this value. +# runAsUser / runAsGroup: numeric UID/GID required on standard K8s when the image uses +# a symbolic user (e.g.: USER vtom) — K8s cannot verify runAsNonRoot without a numeric UID. +# fsGroup: controls volume ownership on mounted PVCs. +# Leave all three null on OpenShift: the nonroot SCC assigns the UID automatically from +# the namespace range, avoiding conflicts with a hardcoded value. +# Confirmed values for VTOM images (server, apiserver, agent): uid=1000 gid=1000 (set in +# values-.yaml). ITC, ITM and MFT run under their own hardcoded uid/gid 10001 and +# ignore these values. # ----------------------------------------------------------------------------- securityContext: runAsUser: null + runAsGroup: null + fsGroup: null # ----------------------------------------------------------------------------- # PodDisruptionBudget From 996940bd2d8d5f6fdd0c0e05932fbf509904cbfd Mon Sep 17 00:00:00 2001 From: Charles-Antoine Dolbeau Date: Wed, 3 Jun 2026 16:57:07 +0200 Subject: [PATCH 3/3] fix: move security section to root READMEs (not charts/visual-tom) --- README-fr.md | 39 +++ README.md | 39 +++ charts/visual-tom/README-fr.md | 556 --------------------------------- charts/visual-tom/README.md | 556 --------------------------------- 4 files changed, 78 insertions(+), 1112 deletions(-) delete mode 100644 charts/visual-tom/README-fr.md delete mode 100644 charts/visual-tom/README.md diff --git a/README-fr.md b/README-fr.md index 410d96e..1a6d61b 100644 --- a/README-fr.md +++ b/README-fr.md @@ -511,6 +511,45 @@ kubectl describe svc vtom-server -n vtom # Côté cloud : vérifier annotations et quotas LB ``` +# Sécurité + +## Conformité + +Ce chart est conforme aux référentiels suivants : + +- **Kubernetes Pod Security Standards (PSS) — profil `restricted`** (tous les pods) +- **CIS Kubernetes Benchmark — Level 1** (tous les contrôles applicables) + +### Contrôles en place + +| Contrôle | Détail | +|---|---| +| `runAsNonRoot: true` | Tous les pods | +| `runAsUser` / `runAsGroup` non nuls | uid=1000 / gid=1000 (vtom), uid=10001 (ITC, ITM, MFT) | +| `fsGroup` non nul | 1000 sur les composants VTOM core ; 10001 sur ITC, ITM, MFT | +| `allowPrivilegeEscalation: false` | Tous les conteneurs, y compris les init containers et sidecars | +| `capabilities.drop: ["ALL"]` | Tous les conteneurs, y compris les init containers et sidecars | +| `seccompProfile: RuntimeDefault` | Tous les pods | +| `readOnlyRootFilesystem: true` | Init containers et sidecar proxy DB | +| `automountServiceAccountToken: false` | Tous les pods | +| NetworkPolicies — deny par défaut | Ingress et egress bloqués par défaut ; règles d'autorisation explicites par composant | +| Secrets via `secretKeyRef` | Aucune donnée sensible dans les ConfigMaps ou variables d'environnement | +| `defaultMode: 0440` sur les volumes secrets | Fichiers de licence montés en lecture seule | + +### Exception documentée (CIS Level 2) + +`readOnlyRootFilesystem` n'est pas activé sur les conteneurs applicatifs principaux (vtom-server, vtom-agent, vtom-apiserver, ITC, ITM). Ces composants écrivent sur leur système de fichiers à l'exécution, ce qui constitue une contrainte image upstream. Ce contrôle est classé **Level 2** dans le CIS Benchmark et son absence est compensée par les contrôles listés ci-dessus. + +### Pod Security Admission + +Pour appliquer le profil PSS `restricted` au niveau du namespace, ajouter le label suivant à la création du namespace : + +```bash +kubectl label namespace vtom \ + pod-security.kubernetes.io/enforce=restricted \ + pod-security.kubernetes.io/warn=restricted +``` + # Licence Ce projet est sous licence Apache 2.0. Voir le fichier [LICENSE](LICENSE) pour plus de détails. diff --git a/README.md b/README.md index 00e45c4..a579375 100644 --- a/README.md +++ b/README.md @@ -511,6 +511,45 @@ kubectl describe svc vtom-server -n vtom # Cloud side: check annotations and LB quotas ``` +# Security + +## Compliance + +This chart is compliant with: + +- **Kubernetes Pod Security Standards (PSS) — `restricted` profile** (all pods) +- **CIS Kubernetes Benchmark — Level 1** (all applicable controls) + +### Controls in place + +| Control | Detail | +|---|---| +| `runAsNonRoot: true` | All pods | +| `runAsUser` / `runAsGroup` non-zero | uid=1000 / gid=1000 (vtom), uid=10001 (ITC, ITM, MFT) | +| `fsGroup` non-zero | 1000 on VTOM core; 10001 on ITC, ITM, MFT | +| `allowPrivilegeEscalation: false` | All containers, including init containers and sidecars | +| `capabilities.drop: ["ALL"]` | All containers, including init containers and sidecars | +| `seccompProfile: RuntimeDefault` | All pods | +| `readOnlyRootFilesystem: true` | Init containers and DB proxy sidecar | +| `automountServiceAccountToken: false` | All pods | +| NetworkPolicies — default deny | Ingress and egress blocked by default; explicit allow rules per component | +| Secrets via `secretKeyRef` | No credentials in ConfigMaps or environment variables | +| Secret volume `defaultMode: 0440` | License files mounted read-only | + +### Known exception (CIS Level 2) + +`readOnlyRootFilesystem` is not set on the main application containers (vtom-server, vtom-agent, vtom-apiserver, ITC, ITM). These components write to their filesystem at runtime, which is an upstream image constraint. This control is classified **Level 2** in the CIS Benchmark and its absence is compensated by the controls listed above. + +### Pod Security Admission + +To enforce the `restricted` PSS profile at the namespace level, add the following label when creating the namespace: + +```bash +kubectl label namespace vtom \ + pod-security.kubernetes.io/enforce=restricted \ + pod-security.kubernetes.io/warn=restricted +``` + # License This project is licensed under the Apache 2.0 License — see the [LICENSE](LICENSE) file for details. diff --git a/charts/visual-tom/README-fr.md b/charts/visual-tom/README-fr.md deleted file mode 100644 index 664fbf6..0000000 --- a/charts/visual-tom/README-fr.md +++ /dev/null @@ -1,556 +0,0 @@ -# Visual TOM — Chart Helm Kubernetes -[![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](LICENSE)  -[![en](https://img.shields.io/badge/lang-en-red.svg)](README.md) - -Ce dépôt fournit un chart Helm pour déployer **Visual TOM (Absyss)** et ses produits associés sur Kubernetes : - -- **VTOM** — Ordonnanceur (core) -- **ITC** — Visual TOM User Portal -- **ITM** — Visual IT Messenger -- **MFT** — Visual TOM Managed File Transfer - -Cibles validées : - -| Environnement | Statut | -|---|---| -| Azure AKS | ✅ Validé | -| GCP GKE (Autopilot) | ✅ Validé | -| AWS EKS | ⚠️ Implémenté, non testé en production | -| On-premise / Minikube | ✅ Testé localement | - -# Disclaimer - -Ce chart Helm est fourni par Absyss SAS comme un **déploiement de référence** de Visual TOM sur Kubernetes. Il est conçu comme un **point de départ** qui doit être adapté par chaque client à son infrastructure, à ses exigences de sécurité et à ses contraintes opérationnelles (topologie réseau, gestion des secrets, classes de stockage, contrôleur Ingress, politiques RBAC, etc.). - -Le chart est distribué **tel quel**, sans garantie d'aucune sorte. Absyss SAS ne peut être tenu responsable des dommages résultant de son utilisation ou des adaptations effectuées par le client. - -Les contrats de support Visual TOM standard ne couvrent pas le chart Helm lui-même ni les adaptations Kubernetes côté client. Des jours de consulting peuvent être demandés pour accompagner le déploiement, la personnalisation ou la résolution de problèmes. - -# Prérequis - -## Tous les environnements -- **Visual TOM** ≥ 7.3.2c (versions inférieures non testées avec ce chart) -- **Kubernetes** ≥ 1.28 -- **Helm** ≥ 3.8 (requis pour l'installation depuis le registry OCI) -- **PostgreSQL 17** accessible depuis le cluster (VNet, VPC ou réseau local) -- Un **contrôleur Ingress** installé — **Traefik** est utilisé par défaut, nginx est également supporté -- **cert-manager** installé avec un `ClusterIssuer` configuré si vous utilisez Let's Encrypt (option TLS par défaut). L'installation et la configuration de cert-manager sont à la charge du client — le chart se contente de créer les ressources `Certificate`. Si vous fournissez vos propres certificats TLS (`tls.provider: secret`), cert-manager n'est pas nécessaire -- **ExternalDNS** (optionnel) — pour la gestion automatique des enregistrements DNS du service LoadBalancer. Sans ExternalDNS, les enregistrements DNS doivent être créés manuellement. Voir `vtom.serverService.hostname` dans les values -- Une **licence VTOM** valide et un accès à un registry d'images - -> **Accès réseau privé / VPN :** les ressources type WireGuard, Private Endpoint, Cloud SQL Auth Proxy, etc. ne font **pas** partie de ce chart. Elles sont à provisionner en amont via votre IaC (Terraform, Bicep, etc.). - -## Prérequis cloud-spécifiques - -### Azure (AKS) -- ACR (Azure Container Registry) avec les images VTOM/ITC/ITM/MFT chargées -- Azure Key Vault avec les secrets créés (voir section Azure ci-dessous) -- User-Assigned Managed Identity avec accès Key Vault (`Key Vault Secrets User`) -- External Secrets Operator installé dans le cluster - -### AWS (EKS) -- ECR ou autre registry avec les images chargées -- AWS Secrets Manager avec les secrets créés -- IAM Role avec accès Secrets Manager, associé au ServiceAccount via IRSA -- External Secrets Operator installé dans le cluster - -### GCP (GKE) -- Artifact Registry ou GCR avec les images chargées -- GCP Secret Manager avec les secrets créés -- GCP Service Account avec accès Secret Manager, lié via Workload Identity -- External Secrets Operator installé dans le cluster -- Cloud SQL Auth Proxy activé dans les valeurs (préconfiguré dans `values-gcp.yaml`) - -### On-premise -- Registry Docker local ou images chargées manuellement (`docker load`) -- PostgreSQL accessible depuis les pods -- Secrets Kubernetes créés manuellement (voir section on-premise ci-dessous) -- Une **implémentation LoadBalancer** telle que [MetalLB](https://metallb.universe.tf/) — requise pour qu'une IP soit assignée aux Services `vtom-server` et `mft-sftp`. Sans cela, les Services `type: LoadBalancer` restent indéfiniment en état ``. Sur les clusters bare-metal ou locaux (RKE2, kubeadm, Minikube), ceci n'est pas fourni par défaut - -# Produits - -Le chart déploie 4 produits Visual TOM, chacun activable indépendamment via son toggle `.enabled`. Tous se partagent les paramètres globaux (registry, namespace, base de données, exposition réseau, NetworkPolicy). - -## VTOM — Ordonnanceur - -Le cœur de Visual TOM. Trois composants déployés en pods distincts, partageant la même image. - -| Composant | Rôle | Service | PVC | Mémoire (limite) | -|---|---|---|---|---| -| `vtom-server` | Moteur d'ordonnancement | LoadBalancer (5 ports natifs VTOM) | 5 Gi | 1 Gi | -| `vtom-apiserver` | API REST + interface web | ClusterIP + Ingress HTTPS | 2 Gi | 1.5 Gi | -| `vtom-agent` | Exécution des jobs Kubernetes | (aucun) | 10 Gi | 256 Mi | - -**Paramètres typiques :** -- `vtom.image.tag` — version VTOM (ex. `7.3.2c`) -- `vtom.ingress.host` — FQDN apiserver, ex. `vtom.mycompany.com` -- `vtom.serverService.hostname` — FQDN client VTOM Desktop (géré par ExternalDNS) -- `vtom.serverService.loadBalancerIP` — IP statique pour survivre aux reprovisions du LB -- `vtom.timezone` — fuseau horaire partagé (défaut `Europe/Paris`) -- `vtom.{server,apiserver,agent}Resources` — CPU/mémoire par composant -- `vtom.{server,apiserver,agent}Pvc.size` — tailles de stockage - -**Licence :** `vtom.license.secretName` (défaut `vtom-license-secret`) — partagée avec ITC et ITM par défaut. - -**Base :** DB nommée `vtom`, secret `vtom-db-secret` (clés `TOM_SGBD_USER` + `TOM_SGBD_PASSWORD` — mot de passe **chiffré format VTOM**, cf. [Format des secrets](#format-des-secrets)). - -**Désactiver :** `vtom.enabled: false` (utile si vous déployez uniquement ITC/ITM/MFT contre un serveur VTOM externe). - -## ITC — Visual TOM User Portal - -Portail utilisateur web pour piloter VTOM (visualisation, supervision, drag & drop). - -| Composant | Rôle | Service | PVC | Mémoire (limite) | -|---|---|---|---|---| -| `itc` | Portail web utilisateur | ClusterIP + Ingress HTTPS | 2 Gi | 1 Gi | - -**Paramètres typiques :** -- `itc.image.tag` — version ITC (**requise** si `itc.enabled=true`) -- `itc.ingress.host` — FQDN ITC, ex. `vitc.mycompany.com` -- `itc.resources`, `itc.pvc.size` - -**Licence :** par défaut réutilise la licence VTOM (`itc.license.secretName: vtom-license-secret`). - -**Base :** DB nommée `ITCockpits`, secret `itc-db-secret` (clés `ITDB_USER` + `ITDB_PASSWORD` — mot de passe **en clair**). - -**Désactiver :** `itc.enabled: false`. - -## ITM — Visual IT Messenger - -Service de messagerie / notification (envoi de courriels, alertes intégrées au workflow VTOM). - -| Composant | Rôle | Service | PVC | Mémoire (limite) | -|---|---|---|---|---| -| `itm` | Messenger | ClusterIP + Ingress HTTPS | 2 Gi | 1 Gi | - -**Paramètres typiques :** -- `itm.image.tag` — version ITM (**requise** si `itm.enabled=true`) -- `itm.ingress.host` — FQDN ITM, ex. `vitm.mycompany.com` -- `itm.resources`, `itm.pvc.size` - -**Licence :** par défaut réutilise la licence VTOM (`itm.license.secretName: vtom-license-secret`). - -**Base :** DB nommée `ITMessenger`, secret `itm-db-secret` (clés `ITDB_USER` + `ITDB_PASSWORD` — mot de passe **en clair**). - -**Désactiver :** `itm.enabled: false`. - -## MFT — Visual TOM Managed File Transfer - -Transferts de fichiers managés : serveur SFTP entrant + connecteurs sortants vers backends externes (NFS, S3, Azure Blob, FTP, SFTP). **Pas de base de données ni de licence séparée.** - -| Composant | Rôle | Services | PVC | Mémoire (limite) | -|---|---|---|---|---| -| `vtom-mft` | Portail HTTPS + serveur SFTP | `mft` (ClusterIP) + `mft-sftp` (LoadBalancer) + Ingress HTTPS | 1 Gi | 1 Gi | - -**Ports exposés :** -- `30034` — portail HTTPS (TLS auto-signé côté pod) -- `30022` — SFTP (accès clients externes via LoadBalancer) - -**Paramètres typiques :** -- `mft.image.tag` — version MFT (**requise** si `mft.enabled=true`) -- `mft.ingress.host` — FQDN portail web, ex. `mft.mycompany.com` -- `mft.sftpService.hostname` — FQDN SFTP (géré par ExternalDNS) -- `mft.sftpService.loadBalancerIP` — IP statique du LB SFTP -- `mft.sftpService.loadBalancerSourceRanges` — **toujours renseigner en production** pour restreindre l'accès SFTP par IP -- `mft.externalEgress` — règles NetworkPolicy de sortie vers les backends de stockage (NFS, S3, FTP, SFTP) -- `mft.pvcSeed.enabled` — init container qui prépare la structure du PVC au premier démarrage - -**Désactiver :** `mft.enabled: false`. - -# Installation - -## Depuis le registry public OCI (recommandé) - -Le chart est publié en tant qu'artefact OCI sur GitHub Container Registry. Aucune authentification requise. - -**Étape 1 — Récupérer le chart en local** : -```bash -helm pull oci://ghcr.io/absysslab/visual-tom --version 0.1.0 --untar -``` -Cette commande télécharge le chart et le dépaquette dans un dossier `visual-tom/` contenant `Chart.yaml`, `values.yaml`, tous les `values-.yaml`, le `values-client-template.yaml` et les `templates/`. - -**Étape 2 — Préparer votre fichier de valeurs client** : -```bash -cp visual-tom/values-client-template.yaml values-monentreprise.yaml -# Éditer values-monentreprise.yaml et remplir les lignes marquées « # TODO » -``` - -**Étape 3 — Installer** : -```bash -helm install visual-tom ./visual-tom \ - -f visual-tom/values-azure.yaml \ - -f values-monentreprise.yaml \ - --namespace vtom --create-namespace -``` - -Remplacer `values-azure.yaml` par `values-aws.yaml`, `values-gcp.yaml` ou `values-onpremise.yaml` selon votre cible. - -> **Mises à jour** : pour passer à une version ultérieure (ex. 0.1.1), relancer l'étape 1 avec `--version 0.1.1`, vérifier les changements éventuels dans les `values-.yaml`, puis `helm upgrade visual-tom ./visual-tom -f ... -f values-monentreprise.yaml --namespace vtom`. - -## Depuis les sources - -```bash -git clone https://github.com/AbsyssLab/vtom-helm.git -cd vtom-helm - -helm install visual-tom ./charts/visual-tom \ - -f ./charts/visual-tom/values-azure.yaml \ - -f values-monentreprise.yaml \ - --namespace vtom --create-namespace -``` - -# Configuration - -## Superposition des fichiers de valeurs - -Helm fusionne les fichiers de valeurs dans l'ordre indiqué sur la ligne de commande. Chaque fichier suivant écrase les valeurs du précédent : - -``` -values.yaml (defaults internes, chargé automatiquement) - + -values-.yaml (remplace les defaults par les valeurs cloud-spécifiques) - + -values-monentreprise.yaml (remplace par VOS valeurs spécifiques) - = -configuration finale déployée -``` - -## Étapes - -1. **Copier** `values-client-template.yaml` → `values-monentreprise.yaml` -2. **Remplir** toutes les lignes marquées `# TODO` -3. **Ne pas modifier** `values.yaml` ni les fichiers `values-.yaml` - -## Exposition réseau - -Par défaut, **tout est privé en production**. Le chart impose un Load Balancer interne pour `vtom.serverService` (VTOM Desktop) et `mft.sftpService` (SFTP) via des annotations spécifiques à chaque cloud, déjà configurées dans les fichiers `values-.yaml`. - -| Composant | Défaut | Override (public, tests) | Configuré dans | -|---|---|---|---| -| PostgreSQL | Endpoint privé (Private Endpoint, Private IP, VPC peering) | FQDN public | `database.host` — ce chart | -| vtom-server (VTOM Desktop) | LB interne — **imposé par le chart** | LB public | `vtom.serverService.annotations` — ce chart | -| MFT SFTP | LB interne — **imposé par le chart** | LB public | `mft.sftpService.annotations` — ce chart | -| Interfaces web (Ingress) | LB interne — **recommandé côté client** | LB public | Paramètres du contrôleur Ingress — **hors du chart** | - -**Annotations LB interne par cloud** (déjà configurées dans `values-.yaml`) : - -| Cloud | Annotations | -|---|---| -| Azure | `service.beta.kubernetes.io/azure-load-balancer-internal: "true"` | -| AWS | `aws-load-balancer-scheme: "internal"` (+ `aws-load-balancer-type: "external"`, `nlb-target-type: "ip"`) | -| GCP | `networking.gke.io/load-balancer-type: "Internal"` | -| On-premise | aucune — nécessite une implémentation LoadBalancer (ex. [MetalLB](https://metallb.universe.tf/)) | - -**Exposer publiquement (tests uniquement)** — surcharger les annotations dans `values-monentreprise.yaml` : -```yaml -vtom: - serverService: - annotations: {} # Désactive le LB interne - loadBalancerSourceRanges: - - "203.0.113.0/24" # Restreindre aux IPs autorisées -``` - -**Restreindre l'accès au LB interne par IP** (production) : -```yaml -vtom: - serverService: - loadBalancerSourceRanges: - - "10.0.0.0/8" # Réseau interne (VNet/VPC + VPN clients) -``` - -## Format des secrets - -Les conventions suivantes s'appliquent à **toutes les infrastructures** (Azure Key Vault, AWS Secrets Manager, GCP Secret Manager, secrets Kubernetes natifs) : - -| Produit | Utilisateur DB | Mot de passe DB | -|---|---|---| -| **VTOM** | Texte brut | **Chiffré format VTOM** (bcrypt Absyss) — utiliser l'outil de chiffrement fourni par Absyss | -| **ITC** | Texte brut | **En clair** (ITC ne supporte pas le chiffrement VTOM) | -| **ITM** | Texte brut | **En clair** (ITM ne supporte pas le chiffrement VTOM) | - -> **Important :** ne **jamais** stocker le mot de passe VTOM en clair. Le format attendu est le hash bcrypt généré par l'outil Absyss. À l'inverse, les mots de passe ITC et ITM doivent rester en clair — toute tentative de chiffrement entraînera un échec de connexion. - -## Configuration par environnement - -### Azure (AKS) - -**Valeurs obligatoires :** - -| Paramètre | Description | Exemple | -|---|---|---| -| `global.imageRegistry` | Nom de l'ACR | `monacr.azurecr.io` | -| `vtom.image.tag` | Version VTOM | `7.3.2c` | -| `itc.image.tag` | Version ITC | `7.3.2c` | -| `itm.image.tag` | Version ITM | `7.3.2c` | -| `vtom.ingress.host` | Domaine VTOM | `vtom.monentreprise.com` | -| `itc.ingress.host` | Domaine ITC | `vitc.monentreprise.com` | -| `itm.ingress.host` | Domaine ITM | `vitm.monentreprise.com` | -| `database.host` | FQDN PostgreSQL | `vtom-pg.postgres.database.azure.com` | -| `secrets.azure.keyVaultUrl` | URL du Key Vault | `https://mon-kv.vault.azure.net` | -| `secrets.azure.tenantId` | ID du tenant Azure AD | `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx` | -| `serviceAccount.azure.clientId` | Client ID de la Managed Identity | `yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy` | - -**Secrets à créer dans Azure Key Vault :** - -| Nom du secret | Contenu | Format | -|---|---|---| -| `vtom-db-user` | Utilisateur PostgreSQL pour VTOM | Texte brut | -| `vtom-db-password` | Mot de passe VTOM pour PostgreSQL | **Chiffré format VTOM** (bcrypt Absyss) | -| `vtom-license-register` | Contenu du fichier `license.register` | Texte brut | -| `itc-db-user` | Utilisateur PostgreSQL pour ITC | Texte brut | -| `itc-db-password` | Mot de passe ITC pour PostgreSQL | **En clair** | -| `itm-db-user` | Utilisateur PostgreSQL pour ITM | Texte brut | -| `itm-db-password` | Mot de passe ITM pour PostgreSQL | **En clair** | - -> **PostgreSQL — privé vs public :** sur Azure, utilisez de préférence un **Private Endpoint** sur le serveur PostgreSQL flexible (FQDN `.private.postgres.database.azure.com`). Le FQDN public Azure (`.postgres.database.azure.com`) ne doit être utilisé qu'en test, avec firewall PostgreSQL restreint à votre VNet. - -### AWS (EKS) - -**Valeurs obligatoires :** - -| Paramètre | Description | Exemple | -|---|---|---| -| `global.imageRegistry` | Registry ECR | `123456789.dkr.ecr.eu-west-1.amazonaws.com` | -| `vtom.image.tag` | Version VTOM | `7.3.2c` | -| `itc.image.tag` | Version ITC | `7.3.2c` | -| `itm.image.tag` | Version ITM | `7.3.2c` | -| `vtom.ingress.host` | Domaine VTOM | `vtom.monentreprise.com` | -| `itc.ingress.host` | Domaine ITC | `vitc.monentreprise.com` | -| `itm.ingress.host` | Domaine ITM | `vitm.monentreprise.com` | -| `database.host` | Endpoint RDS | `vtom.xxxx.eu-west-1.rds.amazonaws.com` | -| `secrets.aws.region` | Région AWS | `eu-west-1` | -| `serviceAccount.aws.roleArn` | ARN du rôle IAM | `arn:aws:iam::123456789012:role/vtom-role` | - -**Secrets à créer dans AWS Secrets Manager :** - -| Nom du secret | Contenu | Format | -|---|---|---| -| `vtom/db-user` | Utilisateur PostgreSQL pour VTOM | Texte brut | -| `vtom/db-password` | Mot de passe VTOM | **Chiffré format VTOM** | -| `vtom/license-register` | Fichier `license.register` | Texte brut | -| `vtom/itc-db-user` | Utilisateur PostgreSQL pour ITC | Texte brut | -| `vtom/itc-db-password` | Mot de passe ITC | **En clair** | -| `vtom/itm-db-user` | Utilisateur PostgreSQL pour ITM | Texte brut | -| `vtom/itm-db-password` | Mot de passe ITM | **En clair** | - -### GCP (GKE) - -**Valeurs obligatoires :** - -| Paramètre | Description | Exemple | -|---|---|---| -| `global.imageRegistry` | Artifact Registry | `europe-west1-docker.pkg.dev/mon-projet/vtom` | -| `vtom.image.tag` | Version VTOM | `7.3.2c` | -| `itc.image.tag` | Version ITC | `7.3.2c` | -| `itm.image.tag` | Version ITM | `7.3.2c` | -| `vtom.ingress.host` | Domaine VTOM | `vtom.monentreprise.com` | -| `itc.ingress.host` | Domaine ITC | `vitc.monentreprise.com` | -| `itm.ingress.host` | Domaine ITM | `vitm.monentreprise.com` | -| `dbProxy.cloudsqlProxy.instanceConnectionName` | Instance Cloud SQL | `mon-projet:europe-west1:vtom-postgres` | -| `secrets.gcp.projectId` | ID du projet GCP | `mon-projet-gcp` | -| `serviceAccount.gcp.serviceAccount` | GSA liée au KSA | `vtom@mon-projet.iam.gserviceaccount.com` | - -**Secrets à créer dans GCP Secret Manager :** - -| Nom du secret | Contenu | Format | -|---|---|---| -| `vtom-db-user` | Utilisateur PostgreSQL pour VTOM | Texte brut | -| `vtom-db-password` | Mot de passe VTOM | **Chiffré format VTOM** | -| `vtom-license-register` | Fichier `license.register` | Texte brut | -| `itc-db-user` | Utilisateur PostgreSQL pour ITC | Texte brut | -| `itc-db-password` | Mot de passe ITC | **En clair** | -| `itm-db-user` | Utilisateur PostgreSQL pour ITM | Texte brut | -| `itm-db-password` | Mot de passe ITM | **En clair** | - -### On-premise / RKE2 / Minikube - -**Valeurs obligatoires :** - -| Paramètre | Description | Exemple | -|---|---|---| -| `global.imageRegistry` | Registry local | `registry.monentreprise.com` | -| `vtom.image.tag` | Version VTOM | `7.3.2c` | -| `itc.image.tag` | Version ITC | `7.3.2c` | -| `itm.image.tag` | Version ITM | `7.3.2c` | -| `vtom.ingress.host` | Domaine VTOM | `vtom.monentreprise.local` | -| `itc.ingress.host` | Domaine ITC | `vitc.monentreprise.local` | -| `itm.ingress.host` | Domaine ITM | `vitm.monentreprise.local` | -| `database.host` | Hostname/IP PostgreSQL | `192.168.1.50` | - -**Secrets Kubernetes à créer manuellement avant le déploiement :** - -```bash -kubectl create namespace vtom - -# Utilisateur + mot de passe VTOM (mot de passe CHIFFRÉ au format VTOM — bcrypt Absyss) -kubectl create secret generic vtom-db-secret \ - --from-literal=TOM_SGBD_USER='' \ - --from-literal=TOM_SGBD_PASSWORD='' \ - -n vtom - -# Licence VTOM/ITC/ITM (fichier fourni par Absyss) -kubectl create secret generic vtom-license-secret \ - --from-file=license.register=/chemin/vers/license.register \ - -n vtom - -# Utilisateur + mot de passe ITC — EN CLAIR -kubectl create secret generic itc-db-secret \ - --from-literal=ITDB_USER='' \ - --from-literal=ITDB_PASSWORD='' \ - -n vtom - -# Utilisateur + mot de passe ITM — EN CLAIR -kubectl create secret generic itm-db-secret \ - --from-literal=ITDB_USER='' \ - --from-literal=ITDB_PASSWORD='' \ - -n vtom -``` - -## NetworkPolicy — Health checks Load Balancer - -Pour que les Load Balancers cloud puissent vérifier la santé des pods (probes), les CIDRs d'origine des probes doivent être explicitement autorisés dans la NetworkPolicy. Configurez `networkPolicy.lbHealthCheckCidrs` selon votre cloud : - -| Cloud | Valeur recommandée | -|---|---| -| Azure | `["168.63.129.16/32"]` | -| AWS | CIDR du VPC (ex. `["172.31.0.0/16"]`) — ne **pas** utiliser `["0.0.0.0/0"]` en production | -| GCP | `["130.211.0.0/22", "35.191.0.0/16"]` (Internal LB utilise les deux ranges) | -| On-premise | `[]` (pas de probe cloud) | - -Les fichiers `values-.yaml` fournissent déjà la bonne valeur par défaut. - -# Architecture - -![Architecture VTOM sur Kubernetes](architecture.png) - -# Mise à jour - -```bash -# Via OCI -helm upgrade visual-tom oci://ghcr.io/absysslab/visual-tom \ - --version 0.1.1 \ - -f values-azure.yaml \ - -f values-monentreprise.yaml \ - --namespace vtom - -# Depuis les sources -helm upgrade visual-tom ./charts/visual-tom \ - -f ./charts/visual-tom/values-azure.yaml \ - -f values-monentreprise.yaml \ - --namespace vtom -``` - -# Désinstallation - -```bash -helm uninstall visual-tom -n vtom -``` - -Par défaut, **les PersistentVolumeClaims (PVC) et les PersistentVolumes (PV) sous-jacents sont conservés** (`reclaimPolicy: Retain`). Cela protège vos données (clés de chiffrement, logs, journaux, configuration) contre une suppression accidentelle. - -> ⚠️ **AVERTISSEMENT — Perte de données irréversible** -> -> Les commandes ci-dessous suppriment définitivement **toutes les données VTOM, ITC, ITM et MFT**. Sur Azure / AWS / GCP, le disque cloud sous-jacent est également supprimé. **Aucune restauration n'est possible sans sauvegarde préalable**. -> -> N'exécuter qu'après avoir : -> - Effectué un dump complet de PostgreSQL -> - Sauvegardé les fichiers de configuration et journaux des PVC -> - Confirmé que ces données ne sont plus nécessaires - -```bash -# 1. Supprimer les PVC (libère les PV qui passent en état "Released") -kubectl delete pvc --all -n vtom - -# 2. Supprimer les PV libérés (perte définitive des disques sous-jacents) -kubectl get pv | grep "vtom/" | awk '{print $1}' | xargs -r kubectl delete pv - -# 3. (Optionnel) Supprimer le namespace -kubectl delete namespace vtom -``` - -# Vérification du déploiement - -```bash -kubectl get pods -n vtom -kubectl get ingress -n vtom -kubectl get svc -n vtom -helm status visual-tom -n vtom -``` - -# Résolution de problèmes - -**Les pods restent en `Pending` :** -```bash -kubectl describe pod -n vtom -# Vérifier les events — souvent un problème de PVC ou de ressources insuffisantes -``` - -**Les pods restent en `Init:0/1` ou `Init:Error` :** -```bash -kubectl logs -n vtom -c wait-for-db -# Le proxy DB ne démarre pas ou la DB est inaccessible -``` - -**Les secrets ESO ne se synchronisent pas :** -```bash -kubectl get externalsecret -n vtom -kubectl describe externalsecret vtom-db-secret -n vtom -# Vérifier les permissions de la Managed Identity / IAM Role / GSA sur le coffre de secrets -``` - -# Sécurité - -## Conformité - -Ce chart est conforme aux référentiels suivants : - -- **Kubernetes Pod Security Standards (PSS) — profil `restricted`** (tous les pods) -- **CIS Kubernetes Benchmark — Level 1** (tous les contrôles applicables) - -### Contrôles en place - -| Contrôle | Détail | -|---|---| -| `runAsNonRoot: true` | Tous les pods | -| `runAsUser` / `runAsGroup` non nuls | uid=1000 / gid=1000 (vtom), uid=10001 (ITC, ITM, MFT) | -| `fsGroup` non nul | 1000 sur les composants VTOM core ; 10001 sur ITC, ITM, MFT | -| `allowPrivilegeEscalation: false` | Tous les conteneurs, y compris les init containers et sidecars | -| `capabilities.drop: ["ALL"]` | Tous les conteneurs, y compris les init containers et sidecars | -| `seccompProfile: RuntimeDefault` | Tous les pods | -| `readOnlyRootFilesystem: true` | Init containers et sidecar proxy DB | -| `automountServiceAccountToken: false` | Tous les pods | -| NetworkPolicies — deny par défaut | Ingress et egress bloqués par défaut ; règles d'autorisation explicites par composant | -| Secrets via `secretKeyRef` | Aucune donnée sensible dans les ConfigMaps ou variables d'environnement | -| `defaultMode: 0440` sur les volumes secrets | Fichiers de licence montés en lecture seule | - -### Exception documentée (CIS Level 2) - -`readOnlyRootFilesystem` n'est pas activé sur les conteneurs applicatifs principaux (vtom-server, vtom-agent, vtom-apiserver, ITC, ITM). Ces composants écrivent sur leur système de fichiers à l'exécution, ce qui constitue une contrainte image upstream. Ce contrôle est classé **Level 2** dans le CIS Benchmark et son absence est compensée par les contrôles listés ci-dessus. - -### Pod Security Admission - -Pour appliquer le profil PSS `restricted` au niveau du namespace, ajouter le label suivant à la création du namespace : - -```bash -kubectl label namespace vtom \ - pod-security.kubernetes.io/enforce=restricted \ - pod-security.kubernetes.io/warn=restricted -``` - -**Le certificat TLS n'est pas émis :** -```bash -kubectl get certificate -n vtom -kubectl describe certificate vtom-tls-cert -n vtom -# Vérifier que le ClusterIssuer est prêt et que le domaine est accessible depuis internet -``` - -**Le Load Balancer reste `` ou les probes échouent :** -```bash -kubectl describe svc vtom-server -n vtom -# Côté NetworkPolicy : vérifier networkPolicy.lbHealthCheckCidrs (cf. section dédiée) -# Côté cloud : vérifier annotations et quotas LB -``` - -# Licence - -Ce projet est sous licence Apache 2.0. Voir le fichier [LICENSE](LICENSE) pour plus de détails. diff --git a/charts/visual-tom/README.md b/charts/visual-tom/README.md deleted file mode 100644 index 3e01b8f..0000000 --- a/charts/visual-tom/README.md +++ /dev/null @@ -1,556 +0,0 @@ -# Visual TOM — Kubernetes Helm Chart -[![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](LICENSE)  -[![fr](https://img.shields.io/badge/lang-fr-yellow.svg)](README-fr.md) - -This repository provides a Helm chart to deploy **Visual TOM (Absyss)** and its related products on Kubernetes: - -- **VTOM** — Scheduler (core) -- **ITC** — Visual TOM User Portal -- **ITM** — Visual IT Messenger -- **MFT** — Visual TOM Managed File Transfer - -Validated targets: - -| Environment | Status | -|---|---| -| Azure AKS | ✅ Validated | -| GCP GKE (Autopilot) | ✅ Validated | -| AWS EKS | ⚠️ Implemented, not tested in production | -| On-premise / Minikube | ✅ Tested locally | - -# Disclaimer - -This Helm chart is provided by Absyss SAS as a **reference deployment** of Visual TOM on Kubernetes. It is designed as a **starting point** that must be adapted by each client to match their specific infrastructure, security requirements and operational constraints (network topology, secrets management, storage classes, ingress controller, RBAC policies, etc.). - -The chart is distributed **as-is**, without warranty of any kind. Absyss SAS is not liable for damages resulting from its use or from client-side adaptations. - -Standard Visual TOM support contracts do not cover the Helm chart itself or client-side Kubernetes adaptations. Consulting days can be requested to assist with deployment, customization or troubleshooting. - -# Prerequisites - -## All environments -- **Visual TOM** ≥ 7.3.2c (earlier versions are not tested with this chart) -- **Kubernetes** ≥ 1.28 -- **Helm** ≥ 3.8 (required to install from the OCI registry) -- **PostgreSQL 17** reachable from the cluster (VNet, VPC or local network) -- An **Ingress controller** installed — **Traefik** is used by default, nginx is also supported -- **cert-manager** installed with a `ClusterIssuer` configured if you use Let's Encrypt (default TLS option). Installing and configuring cert-manager is the client's responsibility — the chart only creates the `Certificate` resources. If you provide your own TLS certificates (`tls.provider: secret`), cert-manager is not required -- **ExternalDNS** (optional) — for automatic DNS record management of the LoadBalancer service. Without ExternalDNS, DNS records must be created manually. See `vtom.serverService.hostname` in the values -- A valid **VTOM license** and access to an image registry - -> **Private network access / VPN:** resources such as WireGuard, Private Endpoints, Cloud SQL Auth Proxy, etc. are **not** part of this chart. They must be provisioned upstream via your IaC (Terraform, Bicep, etc.). - -## Cloud-specific prerequisites - -### Azure (AKS) -- ACR (Azure Container Registry) with the VTOM/ITC/ITM/MFT images loaded -- Azure Key Vault with the secrets created (see Azure section below) -- User-Assigned Managed Identity with Key Vault access (`Key Vault Secrets User`) -- External Secrets Operator installed in the cluster - -### AWS (EKS) -- ECR or another registry with the images loaded -- AWS Secrets Manager with the secrets created -- IAM Role with Secrets Manager access, associated with the ServiceAccount via IRSA -- External Secrets Operator installed in the cluster - -### GCP (GKE) -- Artifact Registry or GCR with the images loaded -- GCP Secret Manager with the secrets created -- GCP Service Account with Secret Manager access, linked via Workload Identity -- External Secrets Operator installed in the cluster -- Cloud SQL Auth Proxy enabled in the values (preconfigured in `values-gcp.yaml`) - -### On-premise -- Local Docker registry or images loaded manually (`docker load`) -- PostgreSQL reachable from the pods -- Kubernetes secrets created manually (see on-premise section below) -- A **LoadBalancer implementation** such as [MetalLB](https://metallb.universe.tf/) — required to assign an IP to `vtom-server` and `mft-sftp` Services. Without it, `type: LoadBalancer` Services remain in `` state indefinitely. On bare-metal or local clusters (RKE2, kubeadm, Minikube), this is not provided by default - -# Products - -The chart deploys 4 Visual TOM products, each independently toggleable via its `.enabled` flag. They all share global settings (registry, namespace, database, network exposure, NetworkPolicy). - -## VTOM — Scheduler - -The core of Visual TOM. Three components deployed as separate pods, sharing the same image. - -| Component | Role | Service | PVC | Memory (limit) | -|---|---|---|---|---| -| `vtom-server` | Scheduling engine | LoadBalancer (5 native VTOM ports) | 5 Gi | 1 Gi | -| `vtom-apiserver` | REST API + web interface | ClusterIP + HTTPS Ingress | 2 Gi | 1.5 Gi | -| `vtom-agent` | Kubernetes job executor | (none) | 10 Gi | 256 Mi | - -**Typical parameters:** -- `vtom.image.tag` — VTOM version (e.g. `7.3.2c`) -- `vtom.ingress.host` — apiserver FQDN, e.g. `vtom.mycompany.com` -- `vtom.serverService.hostname` — VTOM Desktop client FQDN (managed by ExternalDNS) -- `vtom.serverService.loadBalancerIP` — static IP to survive LB reprovisioning -- `vtom.timezone` — shared timezone (default `Europe/Paris`) -- `vtom.{server,apiserver,agent}Resources` — CPU/memory per component -- `vtom.{server,apiserver,agent}Pvc.size` — storage sizes - -**License:** `vtom.license.secretName` (default `vtom-license-secret`) — shared with ITC and ITM by default. - -**Database:** DB named `vtom`, secret `vtom-db-secret` (keys `TOM_SGBD_USER` + `TOM_SGBD_PASSWORD` — password **VTOM-encrypted**, see [Secret formats](#secret-formats)). - -**Disable:** `vtom.enabled: false` (useful if you only deploy ITC/ITM/MFT against an external VTOM server). - -## ITC — Visual TOM User Portal - -Web user portal to operate VTOM (visualization, monitoring, drag & drop). - -| Component | Role | Service | PVC | Memory (limit) | -|---|---|---|---|---| -| `itc` | Web user portal | ClusterIP + HTTPS Ingress | 2 Gi | 1 Gi | - -**Typical parameters:** -- `itc.image.tag` — ITC version (**required** if `itc.enabled=true`) -- `itc.ingress.host` — ITC FQDN, e.g. `vitc.mycompany.com` -- `itc.resources`, `itc.pvc.size` - -**License:** by default reuses the VTOM license (`itc.license.secretName: vtom-license-secret`). - -**Database:** DB named `ITCockpits`, secret `itc-db-secret` (keys `ITDB_USER` + `ITDB_PASSWORD` — password **plain text**). - -**Disable:** `itc.enabled: false`. - -## ITM — Visual IT Messenger - -Messaging / notification service (email sending, alerts integrated into the VTOM workflow). - -| Component | Role | Service | PVC | Memory (limit) | -|---|---|---|---|---| -| `itm` | Messenger | ClusterIP + HTTPS Ingress | 2 Gi | 1 Gi | - -**Typical parameters:** -- `itm.image.tag` — ITM version (**required** if `itm.enabled=true`) -- `itm.ingress.host` — ITM FQDN, e.g. `vitm.mycompany.com` -- `itm.resources`, `itm.pvc.size` - -**License:** by default reuses the VTOM license (`itm.license.secretName: vtom-license-secret`). - -**Database:** DB named `ITMessenger`, secret `itm-db-secret` (keys `ITDB_USER` + `ITDB_PASSWORD` — password **plain text**). - -**Disable:** `itm.enabled: false`. - -## MFT — Visual TOM Managed File Transfer - -Managed file transfers: inbound SFTP server + outbound connectors to external backends (NFS, S3, Azure Blob, FTP, SFTP). **No database, no separate license.** - -| Component | Role | Services | PVC | Memory (limit) | -|---|---|---|---|---| -| `vtom-mft` | HTTPS portal + SFTP server | `mft` (ClusterIP) + `mft-sftp` (LoadBalancer) + HTTPS Ingress | 1 Gi | 1 Gi | - -**Exposed ports:** -- `30034` — HTTPS portal (self-signed TLS served by the pod) -- `30022` — SFTP (external client access via LoadBalancer) - -**Typical parameters:** -- `mft.image.tag` — MFT version (**required** if `mft.enabled=true`) -- `mft.ingress.host` — web portal FQDN, e.g. `mft.mycompany.com` -- `mft.sftpService.hostname` — SFTP FQDN (managed by ExternalDNS) -- `mft.sftpService.loadBalancerIP` — static IP of the SFTP LB -- `mft.sftpService.loadBalancerSourceRanges` — **always set in production** to restrict SFTP access by IP -- `mft.externalEgress` — egress NetworkPolicy rules to storage backends (NFS, S3, FTP, SFTP) -- `mft.pvcSeed.enabled` — init container that prepares the PVC structure on first startup - -**Disable:** `mft.enabled: false`. - -# Installation - -## From the public OCI registry (recommended) - -The chart is published as an OCI artifact on GitHub Container Registry. No authentication required. - -**Step 1 — Pull the chart locally**: -```bash -helm pull oci://ghcr.io/absysslab/visual-tom --version 0.1.0 --untar -``` -This downloads the chart and extracts it into a `visual-tom/` directory containing `Chart.yaml`, `values.yaml`, all `values-.yaml`, the `values-client-template.yaml` and the `templates/`. - -**Step 2 — Prepare your client values file**: -```bash -cp visual-tom/values-client-template.yaml values-mycompany.yaml -# Edit values-mycompany.yaml and fill in the lines marked "# TODO" -``` - -**Step 3 — Install**: -```bash -helm install visual-tom ./visual-tom \ - -f visual-tom/values-azure.yaml \ - -f values-mycompany.yaml \ - --namespace vtom --create-namespace -``` - -Replace `values-azure.yaml` with `values-aws.yaml`, `values-gcp.yaml` or `values-onpremise.yaml` according to your target. - -> **Upgrades**: to move to a later version (e.g. 0.1.1), re-run step 1 with `--version 0.1.1`, review any changes in the `values-.yaml`, then `helm upgrade visual-tom ./visual-tom -f ... -f values-mycompany.yaml --namespace vtom`. - -## From sources - -```bash -git clone https://github.com/AbsyssLab/vtom-helm.git -cd vtom-helm - -helm install visual-tom ./charts/visual-tom \ - -f ./charts/visual-tom/values-azure.yaml \ - -f values-mycompany.yaml \ - --namespace vtom --create-namespace -``` - -# Configuration - -## Values files layering - -Helm merges values files in the order they appear on the command line. Each subsequent file overrides the previous one: - -``` -values.yaml (internal defaults, loaded automatically) - + -values-.yaml (overrides defaults with cloud-specific values) - + -values-mycompany.yaml (overrides with YOUR specific values) - = -final deployed configuration -``` - -## Steps - -1. **Copy** `values-client-template.yaml` → `values-mycompany.yaml` -2. **Fill in** all lines marked `# TODO` -3. **Do not modify** `values.yaml` or any `values-.yaml` file - -## Network exposure - -By default, **everything is private in production**. The chart enforces an internal Load Balancer for `vtom.serverService` (VTOM Desktop) and `mft.sftpService` (SFTP) via cloud-specific annotations, already configured in the `values-.yaml` files. - -| Component | Default | Override (public, tests) | Configured in | -|---|---|---|---| -| PostgreSQL | Private endpoint (Private Endpoint, Private IP, VPC peering) | Public FQDN | `database.host` — this chart | -| vtom-server (VTOM Desktop) | Internal LB — **enforced by the chart** | Public LB | `vtom.serverService.annotations` — this chart | -| MFT SFTP | Internal LB — **enforced by the chart** | Public LB | `mft.sftpService.annotations` — this chart | -| Web interfaces (Ingress) | Internal LB — **recommended on the client side** | Public LB | Ingress controller settings — **out of scope of the chart** | - -**Internal LB annotations per cloud** (already configured in `values-.yaml`): - -| Cloud | Annotations | -|---|---| -| Azure | `service.beta.kubernetes.io/azure-load-balancer-internal: "true"` | -| AWS | `aws-load-balancer-scheme: "internal"` (+ `aws-load-balancer-type: "external"`, `nlb-target-type: "ip"`) | -| GCP | `networking.gke.io/load-balancer-type: "Internal"` | -| On-premise | none — requires a LoadBalancer implementation (e.g. [MetalLB](https://metallb.universe.tf/)) | - -**Expose publicly (tests only)** — override the annotations in `values-mycompany.yaml`: -```yaml -vtom: - serverService: - annotations: {} # Disables the internal LB - loadBalancerSourceRanges: - - "203.0.113.0/24" # Restrict to authorized IPs -``` - -**Restrict access to the internal LB by IP** (production): -```yaml -vtom: - serverService: - loadBalancerSourceRanges: - - "10.0.0.0/8" # Internal network (VNet/VPC + client VPN) -``` - -## Secret formats - -The following conventions apply to **all infrastructures** (Azure Key Vault, AWS Secrets Manager, GCP Secret Manager, native Kubernetes secrets): - -| Product | DB user | DB password | -|---|---|---| -| **VTOM** | Plain text | **VTOM-encrypted** (Absyss bcrypt) — use the encryption tool provided by Absyss | -| **ITC** | Plain text | **Plain text** (ITC does not support VTOM encryption) | -| **ITM** | Plain text | **Plain text** (ITM does not support VTOM encryption) | - -> **Important:** never store the VTOM password in plain text. The expected format is the bcrypt hash generated by the Absyss tool. Conversely, ITC and ITM passwords must remain plain text — any encryption attempt will cause connection failures. - -## Configuration per environment - -### Azure (AKS) - -**Required values:** - -| Parameter | Description | Example | -|---|---|---| -| `global.imageRegistry` | ACR name | `myacr.azurecr.io` | -| `vtom.image.tag` | VTOM version | `7.3.2c` | -| `itc.image.tag` | ITC version | `7.3.2c` | -| `itm.image.tag` | ITM version | `7.3.2c` | -| `vtom.ingress.host` | VTOM domain | `vtom.mycompany.com` | -| `itc.ingress.host` | ITC domain | `vitc.mycompany.com` | -| `itm.ingress.host` | ITM domain | `vitm.mycompany.com` | -| `database.host` | PostgreSQL FQDN | `vtom-pg.postgres.database.azure.com` | -| `secrets.azure.keyVaultUrl` | Key Vault URL | `https://my-kv.vault.azure.net` | -| `secrets.azure.tenantId` | Azure AD tenant ID | `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx` | -| `serviceAccount.azure.clientId` | Managed Identity client ID | `yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy` | - -**Secrets to create in Azure Key Vault:** - -| Secret name | Content | Format | -|---|---|---| -| `vtom-db-user` | PostgreSQL user for VTOM | Plain text | -| `vtom-db-password` | VTOM password for PostgreSQL | **VTOM-encrypted** (Absyss bcrypt) | -| `vtom-license-register` | Content of the `license.register` file | Plain text | -| `itc-db-user` | PostgreSQL user for ITC | Plain text | -| `itc-db-password` | ITC password for PostgreSQL | **Plain text** | -| `itm-db-user` | PostgreSQL user for ITM | Plain text | -| `itm-db-password` | ITM password for PostgreSQL | **Plain text** | - -> **PostgreSQL — private vs public:** on Azure, prefer a **Private Endpoint** on the PostgreSQL Flexible Server (FQDN `.private.postgres.database.azure.com`). The Azure public FQDN (`.postgres.database.azure.com`) should only be used in test, with the PostgreSQL firewall restricted to your VNet. - -### AWS (EKS) - -**Required values:** - -| Parameter | Description | Example | -|---|---|---| -| `global.imageRegistry` | ECR registry | `123456789.dkr.ecr.eu-west-1.amazonaws.com` | -| `vtom.image.tag` | VTOM version | `7.3.2c` | -| `itc.image.tag` | ITC version | `7.3.2c` | -| `itm.image.tag` | ITM version | `7.3.2c` | -| `vtom.ingress.host` | VTOM domain | `vtom.mycompany.com` | -| `itc.ingress.host` | ITC domain | `vitc.mycompany.com` | -| `itm.ingress.host` | ITM domain | `vitm.mycompany.com` | -| `database.host` | RDS endpoint | `vtom.xxxx.eu-west-1.rds.amazonaws.com` | -| `secrets.aws.region` | AWS region | `eu-west-1` | -| `serviceAccount.aws.roleArn` | IAM Role ARN | `arn:aws:iam::123456789012:role/vtom-role` | - -**Secrets to create in AWS Secrets Manager:** - -| Secret name | Content | Format | -|---|---|---| -| `vtom/db-user` | PostgreSQL user for VTOM | Plain text | -| `vtom/db-password` | VTOM password | **VTOM-encrypted** | -| `vtom/license-register` | `license.register` file | Plain text | -| `vtom/itc-db-user` | PostgreSQL user for ITC | Plain text | -| `vtom/itc-db-password` | ITC password | **Plain text** | -| `vtom/itm-db-user` | PostgreSQL user for ITM | Plain text | -| `vtom/itm-db-password` | ITM password | **Plain text** | - -### GCP (GKE) - -**Required values:** - -| Parameter | Description | Example | -|---|---|---| -| `global.imageRegistry` | Artifact Registry | `europe-west1-docker.pkg.dev/my-project/vtom` | -| `vtom.image.tag` | VTOM version | `7.3.2c` | -| `itc.image.tag` | ITC version | `7.3.2c` | -| `itm.image.tag` | ITM version | `7.3.2c` | -| `vtom.ingress.host` | VTOM domain | `vtom.mycompany.com` | -| `itc.ingress.host` | ITC domain | `vitc.mycompany.com` | -| `itm.ingress.host` | ITM domain | `vitm.mycompany.com` | -| `dbProxy.cloudsqlProxy.instanceConnectionName` | Cloud SQL instance | `my-project:europe-west1:vtom-postgres` | -| `secrets.gcp.projectId` | GCP project ID | `my-gcp-project` | -| `serviceAccount.gcp.serviceAccount` | GSA linked to the KSA | `vtom@my-project.iam.gserviceaccount.com` | - -**Secrets to create in GCP Secret Manager:** - -| Secret name | Content | Format | -|---|---|---| -| `vtom-db-user` | PostgreSQL user for VTOM | Plain text | -| `vtom-db-password` | VTOM password | **VTOM-encrypted** | -| `vtom-license-register` | `license.register` file | Plain text | -| `itc-db-user` | PostgreSQL user for ITC | Plain text | -| `itc-db-password` | ITC password | **Plain text** | -| `itm-db-user` | PostgreSQL user for ITM | Plain text | -| `itm-db-password` | ITM password | **Plain text** | - -### On-premise / RKE2 / Minikube - -**Required values:** - -| Parameter | Description | Example | -|---|---|---| -| `global.imageRegistry` | Local registry | `registry.mycompany.com` | -| `vtom.image.tag` | VTOM version | `7.3.2c` | -| `itc.image.tag` | ITC version | `7.3.2c` | -| `itm.image.tag` | ITM version | `7.3.2c` | -| `vtom.ingress.host` | VTOM domain | `vtom.mycompany.local` | -| `itc.ingress.host` | ITC domain | `vitc.mycompany.local` | -| `itm.ingress.host` | ITM domain | `vitm.mycompany.local` | -| `database.host` | PostgreSQL hostname/IP | `192.168.1.50` | - -**Kubernetes secrets to create manually before deployment:** - -```bash -kubectl create namespace vtom - -# VTOM user + password (password VTOM-ENCRYPTED — Absyss bcrypt) -kubectl create secret generic vtom-db-secret \ - --from-literal=TOM_SGBD_USER='' \ - --from-literal=TOM_SGBD_PASSWORD='' \ - -n vtom - -# VTOM/ITC/ITM license (file provided by Absyss) -kubectl create secret generic vtom-license-secret \ - --from-file=license.register=/path/to/license.register \ - -n vtom - -# ITC user + password — PLAIN TEXT -kubectl create secret generic itc-db-secret \ - --from-literal=ITDB_USER='' \ - --from-literal=ITDB_PASSWORD='' \ - -n vtom - -# ITM user + password — PLAIN TEXT -kubectl create secret generic itm-db-secret \ - --from-literal=ITDB_USER='' \ - --from-literal=ITDB_PASSWORD='' \ - -n vtom -``` - -## NetworkPolicy — Load Balancer health checks - -For cloud Load Balancers to be able to probe pod health, the source CIDRs of the probes must be explicitly allowed in the NetworkPolicy. Configure `networkPolicy.lbHealthCheckCidrs` according to your cloud: - -| Cloud | Recommended value | -|---|---| -| Azure | `["168.63.129.16/32"]` | -| AWS | VPC CIDR (e.g. `["172.31.0.0/16"]`) — do **not** use `["0.0.0.0/0"]` in production | -| GCP | `["130.211.0.0/22", "35.191.0.0/16"]` (Internal LB uses both ranges) | -| On-premise | `[]` (no cloud probe) | - -The `values-.yaml` files already provide the correct default value. - -# Architecture - -![VTOM on Kubernetes architecture](architecture.png) - -# Upgrade - -```bash -# Via OCI -helm upgrade visual-tom oci://ghcr.io/absysslab/visual-tom \ - --version 0.1.1 \ - -f values-azure.yaml \ - -f values-mycompany.yaml \ - --namespace vtom - -# From sources -helm upgrade visual-tom ./charts/visual-tom \ - -f ./charts/visual-tom/values-azure.yaml \ - -f values-mycompany.yaml \ - --namespace vtom -``` - -# Uninstallation - -```bash -helm uninstall visual-tom -n vtom -``` - -By default, **PersistentVolumeClaims (PVCs) and the underlying PersistentVolumes (PVs) are retained** (`reclaimPolicy: Retain`). This protects your data (encryption keys, application logs, audit logs, configuration) against accidental deletion. - -> ⚠️ **WARNING — Irreversible data loss** -> -> The commands below permanently delete **all VTOM, ITC, ITM and MFT data**. On Azure / AWS / GCP, the underlying cloud disk is also deleted. **No restoration is possible without a prior backup**. -> -> Only run after you have: -> - Taken a complete PostgreSQL dump -> - Backed up the configuration files and logs from the PVCs -> - Confirmed that this data is no longer needed - -```bash -# 1. Delete the PVCs (releases the PVs, which transition to "Released" state) -kubectl delete pvc --all -n vtom - -# 2. Delete the released PVs (permanent loss of the underlying disks) -kubectl get pv | grep "vtom/" | awk '{print $1}' | xargs -r kubectl delete pv - -# 3. (Optional) Delete the namespace -kubectl delete namespace vtom -``` - -# Verifying the deployment - -```bash -kubectl get pods -n vtom -kubectl get ingress -n vtom -kubectl get svc -n vtom -helm status visual-tom -n vtom -``` - -# Troubleshooting - -**Pods stay in `Pending`:** -```bash -kubectl describe pod -n vtom -# Check the events — typically a PVC issue or insufficient resources -``` - -**Pods stay in `Init:0/1` or `Init:Error`:** -```bash -kubectl logs -n vtom -c wait-for-db -# The DB proxy is not starting or the DB is unreachable -``` - -**ESO secrets are not syncing:** -```bash -kubectl get externalsecret -n vtom -kubectl describe externalsecret vtom-db-secret -n vtom -# Check the permissions of the Managed Identity / IAM Role / GSA on the secret store -``` - -# Security - -## Compliance - -This chart is compliant with: - -- **Kubernetes Pod Security Standards (PSS) — `restricted` profile** (all pods) -- **CIS Kubernetes Benchmark — Level 1** (all applicable controls) - -### Controls in place - -| Control | Detail | -|---|---| -| `runAsNonRoot: true` | All pods | -| `runAsUser` / `runAsGroup` non-zero | uid=1000 / gid=1000 (vtom), uid=10001 (ITC, ITM, MFT) | -| `fsGroup` non-zero | 1000 on VTOM core; 10001 on ITC, ITM, MFT | -| `allowPrivilegeEscalation: false` | All containers, including init containers and sidecars | -| `capabilities.drop: ["ALL"]` | All containers, including init containers and sidecars | -| `seccompProfile: RuntimeDefault` | All pods | -| `readOnlyRootFilesystem: true` | Init containers and DB proxy sidecar | -| `automountServiceAccountToken: false` | All pods | -| NetworkPolicies — default deny | Ingress and egress blocked by default; explicit allow rules per component | -| Secrets via `secretKeyRef` | No credentials in ConfigMaps or environment variables | -| Secret volume `defaultMode: 0440` | License files mounted read-only | - -### Known exception (CIS Level 2) - -`readOnlyRootFilesystem` is not set on the main application containers (vtom-server, vtom-agent, vtom-apiserver, ITC, ITM). These components write to their filesystem at runtime, which is an upstream image constraint. This control is classified **Level 2** in the CIS Benchmark and its absence is compensated by the controls listed above. - -### Pod Security Admission - -To enforce the `restricted` PSS profile at the namespace level, add the following label when creating the namespace: - -```bash -kubectl label namespace vtom \ - pod-security.kubernetes.io/enforce=restricted \ - pod-security.kubernetes.io/warn=restricted -``` - -**The TLS certificate is not being issued:** -```bash -kubectl get certificate -n vtom -kubectl describe certificate vtom-tls-cert -n vtom -# Check that the ClusterIssuer is ready and the domain is reachable from the internet -``` - -**The Load Balancer stays `` or probes are failing:** -```bash -kubectl describe svc vtom-server -n vtom -# NetworkPolicy side: check networkPolicy.lbHealthCheckCidrs (see dedicated section) -# Cloud side: check annotations and LB quotas -``` - -# License - -This project is licensed under the Apache 2.0 License — see the [LICENSE](LICENSE) file for details.