Skip to content




sops setup

https://fluxcd.io/flux/guides/mozilla-sops/

In GitOps, all the declarative changes including secrets must be placed on a repository. SOPS can provide encryption for the GitOps repository files.

The official document above explains the steps to setup SOPS using a separate git repository, but here I will setup SOPS on the same GitOps repository managing the cluster.

steps

  • install gpg and sops
  • generate gpg key

install gpg and sops

GPG should be available from the distribution package.

SOPS should be installed separately.

https://github.com/getsops/sops

curl -LO https://github.com/getsops/sops/releases/download/v3.9.4/sops-v3.9.4.linux.amd64
mv sops-v3.9.4.linux.amd64 ~/.local/bin/sops
chmod u+x ~/.local/bin/sops
sops --version

generate gpg key

export KEY_NAME="lab-hlv3.lab.blink-1x52.net"
export KEY_COMMENT="flux secrets"

gpg --batch --full-generate-key <<EOF
%no-protection
Key-Type: 1
Key-Length: 4096
Subkey-Type: 1
Subkey-Length: 4096
Expire-Date: 0
Name-Comment: ${KEY_COMMENT}
Name-Real: ${KEY_NAME}
EOF

retrieve the fingerprint of the generated gpg key

gpg --list-secret-keys "${KEY_NAME}"

export public and private keypair as secret on the kubernetes cluster

Find the fingerprint from the second row of the "sec:" output and export it.

Here is the example output from the flux document.

sec   rsa4096 2020-09-06 [SC]
      1F3D1CED2F865F5E59CA564553241F147E7C5FA4
export KEY_FP=1F3D1CED2F865F5E59CA564553241F147E7C5FA4

And generate the secret.

# create gpg keypair secret on flux-system
gpg --export-secret-keys --armor "${KEY_FP}" |
kubectl create secret generic sops-gpg \
  --namespace=flux-system \
  --from-file=sops.asc=/dev/stdin

# confirm
kubectl get secret sops-gpg -n flux-system

upload gpg public key to the gitops repo

# on gitops repo
mkdir -p sops/lab-hlv3
gpg --export --armor "${KEY_FP}" > sops/lab-hlv3/.sops.pub.asc

delete local gpg keypair

Delete the generated keypair to remove private key off host.

gpg --delete-secret-keys "${KEY_FP}"

import public key for encryption

Import gpg public key on any host that needs to encrypt gitops manifest file.

# on gitops repo
gpg --import sops/lab-hlv3/.sops.pub.asc

add sops configuration file to the sops directory

# on gitops repo
cat <<EOF > sops/lab-hlv3/.sops.yaml
creation_rules:
  - path_regex: .*.yaml
    encrypted_regex: ^(data|stringData)$
    pgp: ${KEY_FP}
EOF

set flux kustomization on SOPS directory

This is similar to the other flux kustomizations created to setup separate layers of infrastructure and apps kustomizations. It is using the same sourceRef which is the very GitOps repository flux was bootstrapped with. The main difference is the .spec.decryption section specifying the provider and the gpg keypair to use (secret generated from keypair export earlier).

cat <<'EOF' > clusters/lab-hlv3/sops.yaml
---
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: sops
  namespace: flux-system
spec:
  decryption:
    provider: sops
    secretRef:
      name: sops-gpg
  interval: 1m0s
  path: ./sops/lab-hlv3
  prune: true
  sourceRef:
    kind: GitRepository
    name: flux-system
EOF

Wait for the reconciliation and you will see the new flux kustomization "sops" in the list.

$ flux get ks
NAME                    REVISION                SUSPENDED       READY   MESSAGE
apps                    main@sha1:1b76dba2      False           False   dependency 'flux-system/infra-configs' revision is not up to date
flux-system             main@sha1:de9c4295      False           True    Applied revision: main@sha1:de9c4295
infra-configs           main@sha1:1b76dba2      False           False   dependency 'flux-system/infra-controllers' is not ready
infra-controllers       main@sha1:de9c4295      False           True    Applied revision: main@sha1:de9c4295
sops                    main@sha1:de9c4295      False           True    Applied revision: main@sha1:de9c4295

demo on secret encryption

Let's create a dummy secret in the placeholder namespace.

# on gitops repo
mkdir sops/lab-hlv3/placeholder

# create a secret yaml file
kubectl -n placeholder create secret generic dummy \
--from-literal=user=username \
--from-literal=password=passwordforusername \
--dry-run=client \
-o yaml > sops/lab-hlv3/placeholder/dummy.yaml


# encrypt
# chdir where sops configuration file is placed at
cd sops/lab-hlv3
sops --encrypt --in-place placeholder/dummy.yaml

# commit and push

Here is the content of the yaml file, before and after the sops encryption.

# before
$ cat sops/lab-hlv3/placeholder/dummy.yaml
apiVersion: v1
data:
  password: cGFzc3dvcmRmb3J1c2VybmFtZQ==
  user: dXNlcm5hbWU=
kind: Secret
metadata:
  creationTimestamp: null
  name: basic-auth
  namespace: default

# after the encryption
$ cat sops/lab-hlv3/placeholder/dummy.yaml
apiVersion: v1
data:
    password: ENC[AES256_GCM,data:DWjyV2ooXZc2z+jBvzWfKrzz2oU8sSa/evt3rQ==,iv:m243VqNqgMgITXuygRuDIKDPDb8g1dbXT6+nkDL960E=,tag:ThXtGSft4AWcWvZrkFjaQA==,type:str]
    user: ENC[AES256_GCM,data:yFbRgz3holhq1jZh,iv:T2KWIc84ntnbWF69AUsqlCNVh0iI8o0jdPy7Lwmy6oc=,tag:mi+n1lTCYiDpYIrdbC4Ntw==,type:str]
kind: Secret
metadata:
    creationTimestamp: null
    name: basic-auth
    namespace: default
sops:
    kms: []
    gcp_kms: []
    azure_kv: []
    hc_vault: []
    age: []
    lastmodified: "2025-03-27T08:14:27Z"
    mac: ENC[AES256_GCM,data:npyzMmL71epRGsqTIDAyCMpiIYYR+qJPpC05Fd8XRxnaPGKbZ3z9rzRJIb/f0ei0J7qyi5f2SKmkYvGpN2DIdiV10Gake59LohUG909XTUuTdPS1Cr85/ygUN20AY3PaUJ82wTpIO1eM8rJfW7SJSrvlcXo81h/QTQtMMyF1w8U=,iv:d98j1wrTz6PVQrHDC5quS9ldGrRrIuYAqwu8KdAcp8Q=,tag:vVg3ceWRgzdtMPbPBaPE+Q==,type:str]
    pgp:
        - created_at: "2025-03-27T08:14:27Z"
          enc: |-
            -----BEGIN PGP MESSAGE-----

            hQIMA1ZiUHDvVyFzARAAlb55Oj8tQw+/UoqQq+cPFOxn4Fkor3ffeFQgHyMUXiuc
            sTSM1plrY6VqHBSszKr7qHdj8FKw/KdgSxlNvo8C8bYtgoo0SICLnUSnLwdP7co6
            bEWJ9b+gFeqUgXbs+ugJbwQPy1QgyRjU9hS2YILg5WyOO4Yxs0K6ov6Aq8QEQxnM
            6WLKr+7rAlsZvZiXz4ylQPw5motoHD5NJnc/S20nWKQXoNPQglH8nF2NK9ormQ1w
            XD2cqzkF20CEHiMKe/9pdyuz9U3n53giu8y1UtCmEO6dZvuWLa8Vw/Pme+8RQ7s4
            O4P55VQvCSCwrWByl9ZRDRUhOzz+gLAz9zSAyksokN49A43B7XUprmVZsUOokGa/
            hZoDRnq5PBlWzVIUJ6iQgXrce/JlWKPXpTr/pGOp5LhJwn4PhBeQNrOKMzO/Il1W
            /BfgwCGwdpdvNGBxNCjkHa2zCeqdjfgSQfdjggV5VxpPJbSd/BTOyHwlOj4rLdAS
            4rghIGB+2fRxgzj7MWpZdYBqfz+UE1jdFQg9zcJGwuLKvnUZNmhIuuGFj7odWGI4
            cHwyek9iW5x9WtPCnDV3XsLHevpuUqrm/9TmM/Vv13PvuL734bkw4Wn4m6F0FM3o
            54s6SKQobGuTgJwtVhMYCbqvykGiq9b9CBtIX+ot/dpuB25OExmOPOfM2+GM8HfU
            aAEJAhDeX2/rkXQMr12PWjj0rsTJ41p+6aZgcsiY0QjgRDH5z0wfA1jtK1suEz/z
            w074qr7/2JEqpjJSrjwxum6MvG2ve04Rh5sWmV1f8tgyOKj6fjyj2xXGaCaSUA8c
            yZznilTPD8zc
            =7/av
            -----END PGP MESSAGE-----
          fp: FF44D91FBED62563A999B5E0A9697E97CDDD9973
    encrypted_regex: ^(data|stringData)$
    version: 3.9.4

Once flux reconciliation is complete for sops ks, run kubectl get secret dummy -n placeholder -o yaml and you can confirm the same content before the encryption is on the cluster.