# Manage Kubernetes secrets with Mozilla SOPS In order to store secrets safely in a public or private Git repository, you can use Mozilla's [SOPS](https://github.com/mozilla/sops) CLI to encrypt Kubernetes secrets with OpenPGP, AWS KMS, GCP KMS and Azure Key Vault. ## Prerequisites To follow this guide you'll need a Kubernetes cluster with the GitOps toolkit controllers installed on it. Please see the [get started guide](../get-started/index.md) or the [installation guide](installation.md). Install [gnupg](https://www.gnupg.org/) and [SOPS](https://github.com/mozilla/sops): ```sh brew install gnupg sops ``` ## Generate a GPG key Generate a GPG/OpenPGP key with no passphrase (`%no-protection`): ```sh export KEY_NAME="cluster0.yourdomain.com" export KEY_COMMENT="flux secrets" gpg --batch --full-generate-key < ./clusters/cluster0/.sops.pub.asc ``` Check the file contents to ensure it's the public key before adding it to the repo and committing. ```sh git add ./clusters/cluster0/.sops.pub.asc git commit -am 'Share GPG public key for secrets generation' ``` Team members can then import this key when they pull the Git repository: ```sh gpg --import ./clusters/cluster0/.sops.pub.asc ``` !!! hint The public key is sufficient for creating brand new files. The secret key is required for decrypting and editing existing files because SOPS computes a MAC on all values. When using solely the public key to add or remove a field, the whole file should be deleted and recreated. ## Configure the Git directory for encryption Write a [SOPS config file](https://github.com/mozilla/sops#using-sops-yaml-conf-to-select-kms-pgp-for-new-files) to the specific cluster or namespace directory used to store encrypted objects with this particular GPG key's fingerprint. ```yaml cat < ./clusters/cluster0/.sops.yaml creation_rules: - path_regex: .*.yaml encrypted_regex: ^(data|stringData)$ pgp: ${KEY_FP} EOF ``` This config applies recursively to all sub-directories. Multiple directories can use separate SOPS configs. Contributors using the `sops` CLI to create and encrypt files won't have to worry about specifying the proper key for the target cluster or namespace. `encrypted_regex` helps encrypt the `data` and `stringData` fields for Secrets. You may wish to add other fields if you are encrypting other types of Objects. !!! hint Note that you should encrypt only the `data` or `stringData` section. Encrypting the Kubernetes secret metadata, kind or apiVersion is not supported by kustomize-controller. ## Encrypt secrets Generate a Kubernetes secret manifest with kubectl: ```sh kubectl -n default create secret generic basic-auth \ --from-literal=user=admin \ --from-literal=password=change-me \ --dry-run=client \ -o yaml > basic-auth.yaml ``` Encrypt the secret with SOPS using your GPG key: ```sh sops --encrypt --in-place basic-auth.yaml ``` You can now commit the encrypted secret to your Git repository. !!! hint Note that you shouldn't apply the encrypted secrets onto the cluster with kubectl. SOPS encrypted secrets are designed to be consumed by kustomize-controller. ### Using various cloud providers When using AWS/GCP KMS, you don't have to include the gpg `secretRef` under `spec.provider` (you can skip the `--decryption-secret` flag when running `flux create kustomization`), instead you'll have to bind an IAM Role with access to the KMS keys to the `kustomize-controller` service account of the `flux-system` namespace for kustomize-controller to be able to fetch keys from KMS. #### AWS Enabled the [IAM OIDC provider](https://eksctl.io/usage/iamserviceaccounts/) on your EKS cluster: ```sh eksctl utils associate-iam-oidc-provider --cluster= ``` Create an IAM Role with access to AWS KMS e.g.: ```json { "Version": "2012-10-17", "Statement": [ { "Action": [ "kms:Encrypt", "kms:Decrypt", "kms:ReEncrypt*", "kms:GenerateDataKey*", "kms:DescribeKey" ], "Effect": "Allow", "Resource": "arn:aws:kms:eu-west-1:XXXXX209540:key/4f581f5b-7f78-45e9-a543-83a7022e8105" } ] } ``` Bind the IAM role to the `kustomize-controller` service account: ```sh eksctl create iamserviceaccount \ --override-existing-serviceaccounts \ --name=kustomize-controller \ --namespace=flux-system \ --attach-policy-arn= \ --cluster= ``` Restart kustomize-controller for the binding to take effect: ```sh kubectl -n flux-system rollout restart deployment/kustomize-controller ``` #### Azure When using Azure Key Vault you need to authenticate kustomize-controller either with [aad-pod-identity](../use-cases/azure.md#aad-pod-identity) or by passing [Service Principal credentials as environment variables](https://github.com/mozilla/sops#encrypting-using-azure-key-vault). Create the Azure Key-Vault: ```sh export VAULT_NAME="fluxcd-$(uuidgen | tr -d - | head -c 16)" export KEY_NAME="sops-cluster0" az keyvault create --name "${VAULT_NAME}" az keyvault key create --name "${KEY_NAME}" \ --vault-name "${VAULT_NAME}" --protection software \ --ops encrypt decrypt az keyvault key show --name "${KEY_NAME}" \ --vault-name "${VAULT_NAME}" \ --query key.kid ``` If using AAD Pod-Identity, create an identity within Azure to bind against, then create an `AzureIdentity` object to match: ```yaml # Create an identity in Azure and assign it a role to access Key Vault (note: the identity's resourceGroup should match the desired Key Vault): # az identity create -n sops-akv-decryptor # az role assignment create --role "Key Vault Crypto User" --assignee-object-id "$(az identity show -n sops-akv-decryptor -o tsv --query principalId)" # Fetch the clientID and resourceID to configure the AzureIdentity spec below: # az identity show -n sops-akv-decryptor -otsv --query clientId # az identity show -n sops-akv-decryptor -otsv --query resourceId --- apiVersion: aadpodidentity.k8s.io/v1 kind: AzureIdentity metadata: name: sops-akv-decryptor # kustomize-controller label will match this name namespace: flux-system spec: clientID: 58027844-6b86-424b-9888-b5ae2dc28b4f resourceID: /subscriptions/8c69185e-55f9-4d00-8e71-a1b1bb1386a1/resourcegroups/stealthybox/providers/Microsoft.ManagedIdentity/userAssignedIdentities/sops-akv-decryptor type: 0 # user-managed identity ``` [Customize your Flux Manifests](../guides/installation.md#customize-flux-manifests) so that kustomize-controller has the proper credentials. Patch the kustomize-controller Pod template so that the label matches the `AzureIdentity` name. Additionally, the SOPS specific environment variable `AZURE_AUTH_METHOD=msi` to activate the proper auth method within kustomize-controller. ```yaml apiVersion: apps/v1 kind: Deployment metadata: name: kustomize-controller namespace: flux-system spec: template: metadata: labels: aadpodidbinding: sops-akv-decryptor # match the AzureIdentity name spec: containers: - name: manager env: - name: AZURE_AUTH_METHOD value: msi ``` Alternatively, if using a Service Principal stored in a K8s Secret, patch the Pod's envFrom to reference the `AZURE_TENANT_ID`/`AZURE_CLIENT_ID`/`AZURE_CLIENT_SECRET` fields from your Secret. ```yaml apiVersion: apps/v1 kind: Deployment metadata: name: kustomize-controller namespace: flux-system spec: template: spec: containers: - name: manager envFrom: - secretRef: name: sops-akv-decryptor-service-principal ``` At this point, kustomize-controller is now authorized to decrypt values in SOPS encrypted files from your Sources via the related Key Vault. See Mozilla's guide to [Encrypting Using Azure Key Vault](https://github.com/mozilla/sops#encrypting-using-azure-key-vault) to get started committing encrypted files to your Git Repository or other Sources. #### Google Cloud Please ensure that the GKE cluster has Workload Identity enabled. 1. Create a service account with the role `Cloud KMS CryptoKey Encrypter/Decrypter`. 2. Create an IAM policy binding between the GCP service account to the `kustomize-controller` service account of the `flux-system`. 3. Annotate the `kustomize-controller` service account in the `flux-system` with the GCP service account. ```sh kubectl annotate serviceaccount kustomize-controller \ --namespace flux-system \ iam.gke.io/gcp-service-account=@project-id.iam.gserviceaccount.com ``` ## GitOps workflow A cluster admin should create the Kubernetes secret with the PGP keys on each cluster and add the GitRepository/Kustomization manifests to the fleet repository. Git repository manifest: ```yaml apiVersion: source.toolkit.fluxcd.io/v1beta1 kind: GitRepository metadata: name: my-secrets namespace: flux-system spec: interval: 1m url: https://github.com/my-org/my-secrets ``` Kustomization manifest: ```yaml apiVersion: kustomize.toolkit.fluxcd.io/v1beta1 kind: Kustomization metadata: name: my-secrets namespace: flux-system spec: interval: 10m0s sourceRef: kind: GitRepository name: my-secrets path: ./ prune: true decryption: provider: sops secretRef: name: sops-gpg ``` !!! hint You can generate the above manifests using `flux create --export > manifest.yaml`. Assuming a team member wants to deploy an application that needs to connect to a database using a username and password, they'll be doing the following: * create a Kubernetes Secret manifest locally with the db credentials e.g. `db-auth.yaml` * encrypt the secret `data` field with sops * create a Kubernetes Deployment manifest for the app e.g. `app-deployment.yaml` * add the Secret to the Deployment manifest as a [volume mount or env var](https://kubernetes.io/docs/concepts/configuration/secret/#using-secrets) * commit the manifests `db-auth.yaml` and `app-deployment.yaml` to a Git repository that's being synced by the GitOps toolkit controllers Once the manifests have been pushed to the Git repository, the following happens: * source-controller pulls the changes from Git * kustomize-controller loads the GPG keys from the `sops-pgp` secret * kustomize-controller decrypts the Kubernetes secrets with SOPS and applies them on the cluster * kubelet creates the pods and mounts the secret as a volume or env variable inside the app container