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'imageamazon/aws-cli) etvelero
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é¶
- 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"]
}
]
}
- 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.
- Vérifiez les droits : l'identité
veleroécrit danssauvegardes, 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é¶
- 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}}}'
- 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é¶
- Rangez dans OpenBao les clés de l'identité
veleroet une clé de dépôt propre à votre plateforme, puis exposez-les par External Secrets dans le namespacevelero(Secretsvelero-s3etvelero-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.
- 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 avechelm show valuespour 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
- 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
- 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é¶
- 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}}
-
É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). -
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
-
Mettez à jour le
SecretStored'External Secrets (https://, avec l'autorité interne en confiance) et la CLI (BAO_CACERT). -
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é¶
- 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]
}
- É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])
}
- 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
-
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.
-
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
...
}
- Ajoutez les jobs
iac:validate,iac:trivy,iac:checkovetiac:conftestà la CI desecurepay-platform, avec des images épinglées. Aucunterraform applyn'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 plusAVD-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é¶
-
Déployez Trivy Operator, limité aux namespaces applicatifs, et consultez ses rapports d'audit de configuration et de conformité.
-
Consultez les
PolicyReportde Kyverno sur les ressources existantes. -
Mettez à jour
docs/conformite.mdavec 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 ?