Aller au contenu

Stockage verrouillé, sauvegardes, TLS interne et IaC validée

Objectifs pédagogiques

À la fin de ce TP, vous devez être capables de :

  • déployer un stockage objet S3 avec des accès minimaux par usage et un bucket verrouillé
  • configurer des sauvegardes Velero chiffrées avec votre propre clé et indestructibles par un attaquant
  • émettre les certificats internes de la plateforme avec cert-manager, et activer TLS sur OpenBao
  • décrire une cible cloud en Terraform et la faire valider en CI avant tout déploiement
  • suivre la conformité en continu et en apporter les preuves

Prérequis

  • La plateforme de lab : VM securepay-prod, cluster k3d, ArgoCD, OpenBao et External Secrets Operator
  • Docker sur la VM (ou sur le poste) pour le stockage objet, hors du cluster
  • Les CLI aws (ou l'image amazon/aws-cli) et velero

Testé

Les comportements suivants ont été vérifiés : verrouillage d'objets en mode compliance sur SeaweedFS 4.50 (suppression d'une version refusée), droits S3 par identité et par bucket, réémission d'un certificat par cert-manager v1.21.3, détection des erreurs du module Terraform par Trivy 0.76.0, Checkov 3.4.3 et conftest 0.71.1, exceptions en ligne de Trivy et Checkov.

Contexte

Un partenaire de SecurePay a perdu ses sauvegardes, chiffrées par un rançongiciel avec le reste de son infrastructure. Le comité exige des sauvegardes immuables, une restauration prouvée, des certificats qui n'expirent plus par surprise, et une cible cloud validée avant d'être construite.

Atelier 1 : Un stockage objet hors du cluster

Les sauvegardes ne doivent pas vivre dans le cluster qu'elles protègent : vous lancez le stockage objet à côté, dans Docker.

Travail demandé

  1. Rédigez backup/seaweedfs/s3.json : une identité d'administration et une identité par usage, limitée à son bucket. Les valeurs réelles des clés viennent d'OpenBao et ne sont jamais committées.
{
  "identities": [
    {
      "name": "admin",
      "credentials": [{"accessKey": "<admin-key>", "secretKey": "<admin-secret>"}],
      "actions": ["Admin", "Read", "Write", "List", "Tagging"]
    },
    {
      "name": "velero",
      "credentials": [{"accessKey": "<velero-key>", "secretKey": "<velero-secret>"}],
      "actions": ["Read:sauvegardes", "Write:sauvegardes", "List:sauvegardes"]
    }
  ]
}
  1. Lancez SeaweedFS avec une version épinglée :
docker run -d --name seaweedfs --restart unless-stopped -p 8333:8333 \
  -v seaweedfs-data:/data -v "$PWD/backup/seaweedfs/s3.json:/etc/seaweedfs/s3.json:ro" \
  chrislusf/seaweedfs:<version> server -dir=/data -s3 -s3.config=/etc/seaweedfs/s3.json

Pourquoi SeaweedFS

MinIO Community Edition est archivé en amont depuis avril 2026, et Garage ne gère encore ni le versioning ni le verrouillage d'objets. SeaweedFS gère les deux, en modes governance et compliance.

  1. Vérifiez les droits : l'identité velero écrit dans sauvegardes, mais elle est refusée sur tout autre bucket et ne peut pas en créer. Un accès sans clé est refusé.

Critère de réussite

Chaque identité n'a que les droits de son usage, et le stockage tourne hors du cluster.

Atelier 2 : Un bucket verrouillé

Travail demandé

  1. Avec l'identité d'administration, créez le bucket avec le verrouillage d'objets (il ne peut pas être ajouté après coup), puis une rétention par défaut en mode compliance :
export S3_URL=http://<adresse>:8333
aws --endpoint-url "$S3_URL" s3api create-bucket --bucket sauvegardes --object-lock-enabled-for-bucket
aws --endpoint-url "$S3_URL" s3api get-bucket-versioning --bucket sauvegardes
aws --endpoint-url "$S3_URL" s3api put-object-lock-configuration --bucket sauvegardes \
  --object-lock-configuration \
  '{"ObjectLockEnabled":"Enabled","Rule":{"DefaultRetention":{"Mode":"COMPLIANCE","Days":7}}}'
  1. Déposez un objet, relevez sa version, puis tentez de supprimer cette version, y compris avec l'identité d'administration :
aws --endpoint-url "$S3_URL" s3api put-object --bucket sauvegardes --key test.txt --body test.txt
aws --endpoint-url "$S3_URL" s3api get-object-retention --bucket sauvegardes --key test.txt --version-id "$VERSION"
aws --endpoint-url "$S3_URL" s3api delete-object --bucket sauvegardes --key test.txt --version-id "$VERSION"
aws --endpoint-url "$S3_URL" s3api delete-object --bucket sauvegardes --key test.txt
aws --endpoint-url "$S3_URL" s3api list-object-versions --bucket sauvegardes

Mode compliance

En mode compliance, personne ne peut supprimer une version avant l'échéance, pas même l'administrateur. Gardez une rétention courte pendant le TP.

Ce que vous devez observer

  • Le versioning est activé automatiquement à la création du bucket verrouillé
  • La suppression d'une version verrouillée est refusée (AccessDenied)
  • Une suppression simple ne pose qu'un marqueur de suppression : la version d'origine reste listée et récupérable

Critère de réussite

La preuve du refus de suppression est rangée dans docs/preuves/.

Atelier 3 : Velero vers le bucket verrouillé

Travail demandé

  1. Rangez dans OpenBao les clés de l'identité velero et une clé de dépôt propre à votre plateforme, puis exposez-les par External Secrets dans le namespace velero (Secrets velero-s3 et velero-repo-credentials).

La clé de dépôt par défaut

Par défaut, Velero chiffre tous ses dépôts de sauvegarde de fichiers avec une clé statique commune : quiconque accède au bucket peut lire les données. Les manifests sauvegardés, Secrets Kubernetes compris, sont en outre déposés tels quels dans le bucket. Définissez votre propre clé et restreignez l'accès au bucket.

  1. Déployez Velero (chart vmware-tanzu/velero) avec le plugin AWS, un emplacement de sauvegarde vers SeaweedFS et la sauvegarde des volumes par fichiers. Vérifiez les clés exactes avec helm show values pour votre version :
initContainers:
  - name: velero-plugin-for-aws
    image: velero/velero-plugin-for-aws:<version>
    volumeMounts:
      - mountPath: /target
        name: plugins
configuration:
  backupStorageLocation:
    - name: default
      provider: aws
      bucket: sauvegardes
      config:
        region: us-east-1
        s3ForcePathStyle: "true"
        s3Url: http://<adresse>:8333
credentials:
  useSecret: true
  existingSecret: velero-s3
deployNodeAgent: true
  1. Planifiez une sauvegarde horaire de securepay-prod, avec une durée de vie au moins égale à la rétention du verrou :
velero schedule create securepay-prod-horaire --schedule="0 * * * *" \
  --include-namespaces securepay-prod --default-volumes-to-fs-backup --ttl 168h
velero backup create test-manuel --include-namespaces securepay-prod --default-volumes-to-fs-backup --wait
velero backup describe test-manuel
  1. Tentez de supprimer un objet de sauvegarde avec l'identité velero : le verrou doit l'empêcher.

Critère de réussite

Les sauvegardes sont planifiées, chiffrées avec votre propre clé, et aucune identité ne peut les détruire avant échéance. La restauration est chronométrée dans le TP de Security Chaos Engineering.

Atelier 4 : Une autorité de certification interne

Travail demandé

  1. Déployez cert-manager (chart jetstack/cert-manager, crds.enabled=true) par ArgoCD, puis la chaîne autosigné → autorité interne :
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata: {name: selfsigned}
spec: {selfSigned: {}}
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata: {name: securepay-ca, namespace: cert-manager}
spec:
  isCA: true
  commonName: SecurePay Internal CA
  secretName: securepay-ca
  issuerRef: {name: selfsigned, kind: ClusterIssuer}
---
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata: {name: securepay-ca}
spec: {ca: {secretName: securepay-ca}}
  1. Émettez les certificats de l'ingress (api.securepay.local, argocd.securepay.local, grafana.securepay.local) et celui d'OpenBao de production (openbao-prod.openbao-prod.svc).

  2. Activez TLS sur OpenBao :

global:
  tlsDisable: false
server:
  extraVolumes:
    - type: secret
      name: openbao-prod-tls
  standalone:
    config: |
      listener "tcp" {
        address         = "[::]:8200"
        cluster_address = "[::]:8201"
        tls_cert_file   = "/openbao/userconfig/openbao-prod-tls/tls.crt"
        tls_key_file    = "/openbao/userconfig/openbao-prod-tls/tls.key"
      }
      # storage et audit inchangés
  1. Mettez à jour le SecretStore d'External Secrets (https://, avec l'autorité interne en confiance) et la CLI (BAO_CACERT).

  2. Ajoutez les alertes d'expiration et de certificat non prêt (voir le TP de Security Chaos Engineering), puis notez dans le calendrier d'exploitation la date d'expiration de l'autorité elle-même.

Critère de réussite

Plus aucun service de la plateforme n'est servi en HTTP clair, et chaque certificat est surveillé.

Atelier 5 : Une cible cloud validée avant apply

Travail demandé

  1. Partez de ce module volontairement fautif, iac/terraform/main.tf :
resource "aws_s3_bucket" "sauvegardes" {
  bucket = "securepay-sauvegardes"
}

resource "aws_security_group" "db" {
  name        = "securepay-db"
  description = "Acces a la base de paiements"

  ingress {
    description = "PostgreSQL"
    from_port   = 5432
    to_port     = 5432
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

resource "aws_db_instance" "paiements" {
  identifier                  = "securepay-paiements"
  engine                      = "postgres"
  instance_class              = "db.t4g.micro"
  allocated_storage           = 20
  username                    = "securepay"
  manage_master_user_password = true
  publicly_accessible         = true
  storage_encrypted           = false
  skip_final_snapshot         = true
  vpc_security_group_ids      = [aws_security_group.db.id]
}
  1. Écrivez la politique maison iac/policy/pssi.rego (attention : conftest représente chaque bloc Terraform par une liste, d'où le [_]) :
package main

import rego.v1

deny contains msg if {
    some name
    db := input.resource.aws_db_instance[name][_]
    not db.storage_encrypted == true
    msg := sprintf("PSSI-CHIF-01 : la base %s doit être chiffrée au repos", [name])
}

deny contains msg if {
    some name
    db := input.resource.aws_db_instance[name][_]
    db.publicly_accessible == true
    msg := sprintf("PSSI-RES-02 : la base %s ne doit pas être exposée publiquement", [name])
}
  1. Lancez les trois analyses et relevez les constats :
trivy config --severity HIGH,CRITICAL --exit-code 1 iac/terraform
checkov -d iac/terraform --framework terraform --compact --quiet
conftest test --policy iac/policy iac/terraform/*.tf
  1. Corrigez le module jusqu'à ce que les trois passent : blocage de l'accès public, chiffrement par clé KMS, versioning et verrouillage du bucket, base chiffrée, privée, protégée contre la suppression, groupe de sécurité limité au réseau applicatif.

  2. Pour tout écart que vous assumez, écrivez une exception au plus près de la ressource, justifiée et reliée à un ADR :

#trivy:ignore:AWS-0180
resource "aws_db_instance" "paiements" {
  #checkov:skip=CKV_AWS_17:justification, voir docs/adr/0007
  ...
}
  1. Ajoutez les jobs iac:validate, iac:trivy, iac:checkov et iac:conftest à la CI de securepay-platform, avec des images épinglées. Aucun terraform apply n'est exigé.

Ce que vous devez observer

  • Sur le module de départ : 7 constats HIGH pour Trivy, 18 contrôles en échec pour Checkov, et les deux règles PSSI en échec pour conftest
  • Les identifiants de Trivy ont la forme AWS-0080 (et non plus AVD-AWS-0080)

Critère de réussite

La MR qui introduit une base publique ou non chiffrée est bloquée par la CI.

Atelier 6 : Conformité continue et preuves

Travail demandé

  1. Déployez Trivy Operator, limité aux namespaces applicatifs, et consultez ses rapports d'audit de configuration et de conformité.

  2. Consultez les PolicyReport de Kyverno sur les ressources existantes.

  3. Mettez à jour docs/conformite.md avec les contrôles de ce TP : exigence, contrôle, outil, preuve, statut.

Critère de réussite

Chaque nouvelle exigence a son contrôle automatisé et sa preuve horodatée.

Questions de synthèse

  • Pourquoi une sauvegarde accessible avec les identifiants de production ne protège-t-elle pas d'un rançongiciel ?
  • Quelle différence entre les modes governance et compliance, et lequel choisir pour des sauvegardes ?
  • Que contient une sauvegarde Velero, et pourquoi la clé de dépôt par défaut est-elle un risque ?
  • Pourquoi valider l'IaC avant le terraform apply, et que n'apportent pas les scanners génériques ?
  • Comment éviter qu'un certificat interne n'expire par surprise ?