Episode 0x0A: Sealed Secrets

Table of Contents

NOTE: Many commands in this post make use of specific constants tied to my own setup. Make sure to tailor these to your own needs. These examples should serve as a guide, not as direct instructions to copy and paste.

NOTE: Check out the final code at homelab repo on my Github account.

Introduction

As explored in the previous episode, we’ve adopted the GitOps methodology. This approach posits that our Git repository serves as the authoritative source for our declarative deployment strategy. However, a important question arises: can we securely store secrets within Git?

Kubernetes secrets, in their default form, are not particularly secure, as they are only base64-encoded data. As a result, pushing them directly to Git poses a security risk. Yet, there exists a solution: encrypting these secrets before committing them to Git. This process is manageable using a tool that can decrypt them automatically back into Kubernetes secrets. One such tool is Bitnami Sealed Secrets. Sealed Secrets are designed to be safely stored inside a public repository, and they can only be decrypted by the controller residing within the target cluster.

Among the various methods for managing secrets in a Kubernetes cluster, HashiCorp Vault stands out as a powerful option. It, however, is more complex to set up and operate. As a future enhancement, I might consider migrating to Vault, but for now, we will stick with Sealed Secrets due to its simplicity in usage.

Installation

We will utilize Argo CD to install applications in our cluster. Argo CD supports deploying apps from a Helm repository. Let’s begin by creating an apps.yaml file, in which we declare the applications we plan to install in our cluster, adding one in each episode. Our first addition will be Sealed Secrets.

apiVersion: v1
kind: List
items:
  - apiVersion: argoproj.io/v1alpha1
    kind: Application
    metadata:
      name: sealed-secrets
      namespace: argocd
    spec:
      project: default
      source:
        repoURL: https://charts.bitnami.com/bitnami
        chart: sealed-secrets
        targetRevision: 2.3.0
        helm:
          releaseName: sealed-secrets
          parameters:
            - name: fullnameOverride
              value: sealed-secrets-controller
      destination:
        server: https://kubernetes.default.svc
        namespace: kube-system
      syncPolicy:
        automated:
          prune: true
          selfHeal: true

The application manifest is straightforward. Notably, the syncPolicy is configured to be automated, meaning that a sync is triggered upon detecting changes in the source. Additionally, the selfHeal setting ensures that any manual changes made to the deployment will be automatically reverted, thereby maintaining the declared state within the namespace.

Since Application is a Kubernetes Custom Resource Definition (CRD) introduced by Argo CD in our cluster, we can apply the above configuration using kubectl:

kubectl apply -f apps.yaml

This command will create the Sealed Secrets application. Log in to the Argo CD interface to verify the application’s status:

Argo

Kubeseal CLI

We can create sealed secrets using the kubeseal CLI. To install kubeseal, follow the instructions in its documentation.

NOTE: The kubeseal CLI attempts to access the controller named sealed-secrets-controller. This is why we employ the fullnameOverride parameter when installing sealed secrets. Additionally, kubeseal assumes the controller is installed in the kube-system namespace by default, which explains the specified namespace in our deployment. With these configurations, we can use kubeseal without additional parameters.

Now, let’s create our first sealed secret:

kubectl create secret generic test-secret --dry-run=client --from-literal="key=mysecret" --output=yaml | kubeseal -o yaml

The above command creates a secret in dry-run mode (i.e., it outputs the secret without actually creating it) and then kubeseal is used to encrypt it. The resulting output will be:

---
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
  creationTimestamp: null
  name: test-secret
  namespace: default
spec:
  encryptedData:
    key: AgANV/MahwI... # trimmed for brevity
  template:
    metadata:
      creationTimestamp: null
      name: test-secret
      namespace: default

This sealed secret can be safely stored in our public repository. Setting up and using Sealed Secrets is truly that straightforward!

In the next episode, we will install another essential component in our cluster: Object Storage. Stay tuned! 🪣 🚀