SOPS, passez un savon à vos secrets en clair !

@sylvain_metayer

photo

Sylvain MÉTAYER

Tech Lead @Onepoint

logo

Cas d’usage

Où persister la valeur des secrets ?

Diagram
Diagram
  • Depuis 2015

  • CNCF Sandbox

  • Outil auto-suffisant

  • Types de fichiers : yaml, json, ini, .env

Exemple

file.yml
password: a secure password for SunnyTech ☀️️️ !

Structure d’un fichier

secrets.yml
password: ENC[AES256_GCM,data:K3prPdbadC91icPzyBe0+go9HBtYutskDGSNbM52knpvc/GD1t1vAIl4YRQnAw==,iv:oqlCCwSI2PCK/CGp9fMp7Uj2qKTydsSJZmrrijOoHJY=,tag:nUfQmoArPY3bnomsPuik7g==,type:str]
sops:
    kms: []
    gcp_kms: []
    azure_kv: []
    hc_vault: []
    age:
        - recipient: age16rspnzt6j2qxa2587t6umh9qykrqu2amrr0hx9xt2z7elyky3a3q4p5s7l
          enc: |
            -----BEGIN AGE ENCRYPTED FILE-----
            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSArU3JTUi95ZW1ZS0FsRUt6
            dklXOWRuY0k3clRGc2hzcW90UCtvTTRJazFjCkxMNEJYYzE4c1dMYW8wWDNwSk12
            clhCK25RbWZzc1laT0Nib1dFVnIyc0EKLS0tIHJCS3Z1Y0ZMWFp1RzdkSG8reE55
            NFhQR2VWTFcwMkhoMEpjOUxWbXVCdkEKs+47mS6jYpm5LYlq+NEIOwpqvyLtfsyS
            FmyNQh3Ob1CL/1iNaCQsZeFV/k763AIJ4JT6M2hdiQCoVNNJUBwrXw==
            -----END AGE ENCRYPTED FILE-----
    lastmodified: "2024-07-02T11:38:03Z"
    mac: ENC[AES256_GCM,data:8ox5ZXGcQggM7yeKX3EEp9GryuIyBu/K7a7JVDW/rFHPMj8akAz5w5fUUJYzIk4/9yn+rPlutQ4usa8plx4PocKTD0BrQFwi4aqdoCqNJanrr/iToPAGwBycnYOFZE3IR6Z3lfIK8wr8sXF7YJTDg2gqnkLQnWxJi38JlIR4AzM=,iv:/jFNaRm+XQhxJMCT6BDrpb6zwrukY1fWNnAP7qSBdCo=,tag:Oh8WVZ40eeMJSW/SLIEq+w==,type:str]
    pgp: []
    unencrypted_suffix: _unencrypted
    version: 3.8.1

Méthodes de chiffrement

Chiffrement avec age[1]

Exemple de clé age

$ age-keygen
# created: 2024-03-08T15:17:28+01:00
# public key: age14trf5fvhkppd8suunru9mg7dkk9r6ndzwc7tnw45yufs2j9h3u9qqwttxu
AGE-SECRET-KEY-1RM9QASYLNVDQ48HDY5ST7RUXX4LFWPR6PTUHUFG7N5RYUG0PNLLSG6D70R
1. Mais aussi : AWS KMS, GCP KMS, Azure Key Vault et PGP

Chiffrement d’un nouveau fichier

sops --age \
age14trf5fvhkppd8suunru9mg7dkk9r6ndzwc7tnw45yufs2j9h3u9qqwttxu \
secrets.yaml

Chiffrement d’un fichier existant

sops -e -i --age \
age14trf5fvhkppd8suunru9mg7dkk9r6ndzwc7tnw45yufs2j9h3u9qqwttxu \
secrets.yaml

Chiffrement partiel

sops -e -i --encrypted-suffix password --age \
age14trf5fvhkppd8suunru9mg7dkk9r6ndzwc7tnw45yufs2j9h3u9qqwttxu \
secrets.yaml

Déchiffrement

export SOPS_AGE_KEY_FILE=/path/to/my/private/key
sops -d secrets.yaml

Mise à jour

sops secrets.yaml
⚠️
Il est nécessaire d’avoir accès à la clé privée pour éditer le fichier
Si on veut autre chose que vim
EDITOR="code --wait" sops secrets.yaml
💡
--wait : Wait for the files to be closed before returning.

Éxecution dans un processus

$ sops exec-env 01-sops-file.yaml 'echo $password'
a secure password for SunnyTech ☀️️️ !

Gestion des destinataires

With sops cli
sops -r -i --add-age PUB_KEY,PUB_KEY2 secrets.yaml
sops -r -i --rm-age PUB_KEY secrets.yaml
.sops.yaml
creation_rules:
  - path_regex: .*\.?dev\.yaml
    encrypted_suffix: 'password'
    key_groups:
      - age:
          - age1pcutm2ytcnn5t2zpj4dvvqhzxw5dcj0gfqplevtev7h8j9hf397s22uacq
          - age1248xnnj285mc759u43dv99fzlkgpf82khtm7jzepet70hnz33u2sln6v5a
  - path_regex: .*\.?prod\.yaml
    key_groups:
      - age:
          - age1pcutm2ytcnn5t2zpj4dvvqhzxw5dcj0gfqplevtev7h8j9hf397s22uacq

Support des documents YAML

tls.k8s_secret.yaml
---
apiVersion: v1
kind: Secret
data:
    tls.crt: mon-certificat-preprod
    tls.key: ma-cle-preprod
metadata:
    name: preprod-tls
type: kubernetes.io/tls
---
apiVersion: v1
kind: Secret
data:
    tls.crt: mon-certificat-prod
    tls.key: ma-cle-prod
metadata:
    name: prod-tls
type: kubernetes.io/tls

Support des documents YAML

tls.k8s_secret.yaml
apiVersion: v1
kind: Secret
data:
    tls.crt: ENC[AES256_GCM,data:yhqqbp+xcYdotBdMsAReHqjK/mRz1A==,iv:aDQatfjwRABdtBk9CYrPl12TP1d8/f0OaIi+1o7aVQo=,tag:xyBNiHJ2ktENTpgBS955cA==,type:str]
    tls.key: ENC[AES256_GCM,data:KfYfpul+BEKr6HUttLc=,iv:1tzQf72XEXNHRepaS4WKmQjvF2XQezDtvuqxqJVT3yw=,tag:g73pHvj31Ha7JFSNEAVPFw==,type:str]
metadata:
    name: preprod-tls
type: kubernetes.io/tls
sops:
    kms: []
    gcp_kms: []
    azure_kv: []
    hc_vault: []
    age:
        - recipient: age16rspnzt6j2qxa2587t6umh9qykrqu2amrr0hx9xt2z7elyky3a3q4p5s7l
          enc: |
            -----BEGIN AGE ENCRYPTED FILE-----
            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBJVE1peU1zb2xGSlpiVVFP
            cXZEWnYzZ1gwNzFwdWdYanRISTgwM0tickRjClJ0QlBEUjhGb253aHQ5ek9vNDlC
            UGZSK2RNSjZhbEpvczh1Q0ZRdldLb0UKLS0tIGRnNmplSG5kaWE0SXRma3dFTEEx
            YXBkN3hoTDBhNFlnL0E3QXJzWWsvNVkK5KWG0DaTK1t57H9hJxdNoujIoL+Mrij5
            EhgjkrMfqbz+KmWcvnYwJs1nmthscEDyGGMUXQ2Pc3GpqahDSTJtGg==
            -----END AGE ENCRYPTED FILE-----
    lastmodified: "2024-07-04T13:48:20Z"
    mac: ENC[AES256_GCM,data:HHfd96ET1M+W42NWq0L3Pia69qEB5B6WHgKD5FIGIVwpG/gpPSxDorOFA1lBqdHpkFExH5bvRnBOl72ZIkMs5MwVK9jm+YkIRsd5NpgiaXHQjEGsBuE1IqHrbGC4Q5qON4VtseFqL03StgCWCIpBMEo6VZp6XI++ByuWdkPSkeA=,iv:ly6mCmTfk1ND0NutIA5aLEF/iwQj4ooKcmEPzPRD++0=,tag:11Sv7MJ4ZNOWLXCtABOsZQ==,type:str]
    pgp: []
    encrypted_regex: ^(data|stringData)$
    version: 3.9.0
---
apiVersion: v1
kind: Secret
data:
    tls.crt: ENC[AES256_GCM,data:B+1GVfvEkYztUW84XXxMi1/oww==,iv:k9vqw2WQSd5U9pdc7heIwC0yGwJ8cpzy2BTjHdUtf9U=,tag:OoNSaCedDck26hnCG4+YZg==,type:str]
    tls.key: ENC[AES256_GCM,data:+WELpbjtuVS8EmI=,iv:1GtWQW/xv40E1ud0IMvQwJgndECWZVjfFvjApuD6AG8=,tag:kyZDmcIaic10bhJkAc8goQ==,type:str]
metadata:
    name: prod-tls
type: kubernetes.io/tls
sops:
    kms: []
    gcp_kms: []
    azure_kv: []
    hc_vault: []
    age:
        - recipient: age16rspnzt6j2qxa2587t6umh9qykrqu2amrr0hx9xt2z7elyky3a3q4p5s7l
          enc: |
            -----BEGIN AGE ENCRYPTED FILE-----
            YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBJVE1peU1zb2xGSlpiVVFP
            cXZEWnYzZ1gwNzFwdWdYanRISTgwM0tickRjClJ0QlBEUjhGb253aHQ5ek9vNDlC
            UGZSK2RNSjZhbEpvczh1Q0ZRdldLb0UKLS0tIGRnNmplSG5kaWE0SXRma3dFTEEx
            YXBkN3hoTDBhNFlnL0E3QXJzWWsvNVkK5KWG0DaTK1t57H9hJxdNoujIoL+Mrij5
            EhgjkrMfqbz+KmWcvnYwJs1nmthscEDyGGMUXQ2Pc3GpqahDSTJtGg==
            -----END AGE ENCRYPTED FILE-----
    lastmodified: "2024-07-04T13:48:20Z"
    mac: ENC[AES256_GCM,data:HHfd96ET1M+W42NWq0L3Pia69qEB5B6WHgKD5FIGIVwpG/gpPSxDorOFA1lBqdHpkFExH5bvRnBOl72ZIkMs5MwVK9jm+YkIRsd5NpgiaXHQjEGsBuE1IqHrbGC4Q5qON4VtseFqL03StgCWCIpBMEo6VZp6XI++ByuWdkPSkeA=,iv:ly6mCmTfk1ND0NutIA5aLEF/iwQj4ooKcmEPzPRD++0=,tag:11Sv7MJ4ZNOWLXCtABOsZQ==,type:str]
    pgp: []
    encrypted_regex: ^(data|stringData)$
    version: 3.9.0

Démo

./demo.sh

Pas très lisible tout ça ?

$ git diff 34526..3933
diff --git a/code/recipients/example-diff-secrets.yaml b/code/recipients/example-diff-secrets.yaml
index be346d4..7210f74 100644
--- a/code/recipients/example-diff-secrets.yaml
+++ b/code/recipients/example-diff-secrets.yaml
@@ -1,21 +1,21 @@
-password: ENC[AES256_GCM,data:EuHISKWakII=,iv:1i5ZMWseSJpcZoPdkdhNHaytlapMD5J4MmYJDSjPetA=,tag:SleSVvjR9arBbVIpUeFC5Q==,type:str]
+password: ENC[AES256_GCM,data:RlVe+IAXalyz75NeVqsS64gqRQ==,iv:dq/Yd+25rH2a9pXHYMQ4Qs1qxDPcO5JCIL1TlT+yxG4=,tag:Es0gSsnabjyx4UcHiN91eA==,type:str]
[...]

Pas très lisible tout ça ?

$ git config diff.sopsdiffer.textconv "sops -d"
$ cat .gitattributes
code/recipients/example-diff-secrets.yaml diff=sopsdiffer
$ git diff 34526..3933
diff --git a/code/recipients/example-diff-secrets.yaml b/code/recipients/example-diff-secrets.yaml
index be346d4..7210f74 100644
--- a/code/recipients/example-diff-secrets.yaml
+++ b/code/recipients/example-diff-secrets.yaml
@@ -1 +1 @@
-password: password
+password: ThisIsARealPassword

Conclusion

Diagram
Terraform
Ansible
ArgoCD
Kustomize
FluxCD

Et plus encore !

Slides
Slides
Vos retours
Vos retours