Aujourd’hui nous allons voir comment utiliser des secrets sops “en mode gitops” avec Flux.

Ceci permettra de stocker les secrets à l’intérieur d’un dépôt git sans en compromettre la sécurité. Pour cela ces secrets seront chiffrés avant d’être commités dans le dépôt.

Outillage

Pour gérer le chiffrement et le déchiffrement des secrets, nous nous appuyons sur les deux outils suivants :

  • age, est un outil de chiffrement moderne comparable à gpg mais avec un format de clés plus simple à manipuler.

  • sops, abréviation de Secret OPerationS, est un outil de la fondation Mozilla permettant de piloter le chiffrement ou déchiffrement de fichiers complets ou de sous-parties de fichiers à l’aide de différents formats de clés : AWS KMS, GCP KMS, Azure Key Vault, age, and PGP.

Afin de se simplifier la vie, nous utilisons l’image Docker xian310/encryption-toolbox qui contient déjà les deux outils.

Génération d’une paire de clé age

La première étape consiste à générer des clés avec age-keygen, par exemple :

docker run -ti xian310/encryption-toolbox age-keygen > playground-cluster-key.txt

Le fichier généré contient une clé privé et une clé publique :

$ cat playground-cluster-key.txt 
# created: 2022-10-25T20:39:09Z
# public key: age16wx32x2p2n20df5q74sq2e4smtdpk248tt67uqw6xjdg7p7xgeqsqrgm5u
AGE-SECRET-KEY-1Y0HJ40LHPF6ZHQMQ9MXVDNJPCW8WLYWRGSNY09WFM9VPEXZWVPUQLHKM3W

Attention à ne jamais commiter ce fichier dans le dépôt git, il permettrait à n’importe qui de déchiffrer vos données confidentielles.

La clé publique va permettre de chiffrer les données et la clé privée sera nécessaire pour les déchiffrer.

Configuration du comportement de sops

Dans le dossier ciblé par la Kustomization Flux, commençons par créer un fichier nommé .sops.yaml dont l’objectif est double :

  • définir les clés age à utiliser pour le chiffrement/déchiffrement en fonction du type de fichier,
  • définir les portions de fichiers qui doivent être chiffrées.

Le fichier .sops.yaml ressemble à ceci :

creation_rules:
- path_regex: .*.yaml
  encrypted_regex: '^(data|stringData)$'
  age: age16wx32x2p2n20df5q74sq2e4smtdpk248tt67uqw6xjdg7p7xgeqsqrgm5u
- path_regex: .*.env
  age: age16wx32x2p2n20df5q74sq2e4smtdpk248tt67uqw6xjdg7p7xgeqsqrgm5u

Chiffrement des secrets

Il faut tout d’abord exporter la variable SOPS_AGE_KEY_FILE avec comme valeur le chemin vers le fichier contenant la clé privée générée précédement, par exemple :

export SOPS_AGE_KEY_FILE=~/path/to/playground-cluster-key.txt

Chiffrement d’un fichier de type env

Dans ce type de fichier chaque ligne correspond à un nom de variable et à une valeur associée sous la forme :

CLE=valeur

Pour créer un fichier contenant les secrets au format env, il suffit de mentionner l’extension .env :

sops encrypted/env-secret.env

Ceci ouvre un éditeur dans lequel nous remplaçons les lignes proposées en exemple par les lignes suivantes :

username=bob
password="321 aZeRtY!!!"

Enregistrer le fichier. Si on regarde maintenant le contenu du fichier on remarque que son contenu a bien été chiffré :

$ cat secrets.env 
username=ENC[AES256_GCM,data:LHLi,iv:xUsB41CMbdNplOX52NIvm0ayKzIZDHV3xPs9el5EPCA=,tag:5wtLmKbclqf4huleRSA8+g==,type:str]
password=ENC[AES256_GCM,data:lm8rgy8VafGO7M3YOyIn,iv:RgHp0U9vMxjq9428z+uawS6SxyVGJj7G3mbHIxr98rs=,tag:sa5kwvTG4nCOQT+/7Zx9QA==,type:str]
sops_age__list_0__map_recipient=age16wx32x2p2n20df5q74sq2e4smtdpk248tt67uqw6xjdg7p7xgeqsqrgm5u
sops_lastmodified=2023-01-06T07:26:28Z
sops_unencrypted_suffix=_unencrypted
sops_age__list_0__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBWdHJzMXdKYmt1WGpIUklD\ncytad05LcThCRjMzUlhjQnFySUhDN2dBUmxrCkhSUkQzY1hVUzlpUVFjZVVNQ0Y3\nN1VMZHJDTW9ubDB5MTVHUGIxK2RZQ00KLS0tIHhyZy9icVFYTnZIYmcya0EvUDVV\neUxTUGFrMVBvUUpVSHVObEZBMDBUNmcKvHFodm8IJXcvONPcVpNyEIxcyKQCXEc0\nSZzc18RaGOFfbrERLacsqnXhvDirRRCnEnlQ6NTysqG9zgfTavbI+w==\n-----END AGE ENCRYPTED FILE-----\n
sops_mac=ENC[AES256_GCM,data:ZKdV+UNjQkLufkbiudGz/2qcgZey88rwXS/0TXTPFFxRWYbCCZYNQyPFSpTpOPhenRIBGiCucKKyEyC+chx0rOo8YC4Fw+2DPPyFist0Fm8MWO2FsUV/fVUCnHVj5H7nrjbt3RCmapMTXFV5bfrVHGanmqctUCvzT+eoulc5k+M=,iv:ufOMax3BAHOenSxl3ihxm5TZ/RUgQdaRcp6tbjn2NYI=,tag:yp4l6nr7Cn5xxiL2OtxvKQ==,type:str]
sops_version=3.7.3

Chiffrement d’un secret kubernetes

Pour créer un fichier contenant un secret Kubernetes au format yaml, il suffit de mentionner l’extension .yaml :

sops encrypted/yaml-secret.yaml

Copier le contenu en clair de la ressource Secret à chiffrer et enregistrer. Seules les valeurs contenues dans les sections data et stringData seront chiffrées.

Déclaration des secrets

Nous utilisons Kustomize pour le déploiement de nos ressources. La structure de notre dossier Kustomize ressemble maintenant à ça :

└── playground
    ├── kustomization.yaml
    ├── encrypted
    │   ├── env-secret.env
    │   └── yaml-secret.yaml
    └── .sops.yaml

Pour que les secrets puissent être créés sur le cluster on les ajoute dans notre Kustomization Kustomize :

  • en référençant directement le fichier contenant le secret Kubernetes chiffré dans la section resources,
  • en demandant la création d’un secret Kubernetes pour le fichier d’env chiffré dans la section secretGenerator.

Contenu du fichier kustomization.yaml :

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
- encrypted/yaml-secret.yaml

secretGenerator:
- name: test-env-secret
  namespace: flux-system
  envs:
  - encrypted/env-secret.env
  options:
    disableNameSuffixHash: true

Déchiffrement des secrets

Pour que Flux puisse déchiffrer les secrets il faut qu’il ait accès à la clé de déchiffrement (clé privée). Pour cela, cette clé privée doit au préalable être injectée sous la forme d’un secret Kubernetes à l’aide de la commande :

cat playground-cluster-key.txt | kubectl create secret generic sops-age \
  --namespace=flux-system \
  --from-file=age.agekey=/dev/stdin

On vérifie la création du secret sops-age :

$ kubectl -n flux-system get secret sops-age
NAME       TYPE     DATA   AGE
sops-age   Opaque   1      2h

Ensuite le nom de ce secret doit être spécifié dans la section spec.decryption de la Kustomization Flux qui chargera les fichiers à déchiffrer, par exemple :

apiVersion: kustomize.toolkit.fluxcd.io/v1beta2
kind: Kustomization
metadata:
  name: playground
  namespace: flux-system
spec:
  decryption:
    provider: sops
    secretRef:
      name: sops-age
  interval: 1m0s
  path: ./playground
  prune: true
  sourceRef:
    kind: GitRepository
    name: flux-system

Flux sera ainsi capable de déchiffrer les secrets.

Attention : il est important de placer la section spec.decryption dans le bon fichier de Kustomization Flux, c’est à dire celui qui va cibler les fichiers qui doivent être déchiffrés.

A noter : Flux et Kustomize utilisent tous les deux le terme “kustomization” pour des ressources qui ne représentent pas la même chose et dont les objectifs sont différents, ce qui peut entraîner une certaine confusion au départ :

Et voilà !