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à !