One of the most annoying aspects of using Sealed Secrets is their encryption design. They are tightly coupled to the namespace and name of the target Secret, meaning any changes to either require re-encrypting the Sealed Secret. This rigidity complicates workflows, especially when you need to duplicate or promote Secrets across environments.
It breaks the ideal of DRY (Don’t Repeat Yourself) in infrastructure management and adds friction to automation pipelines.
Fortunately, one of the most widespread tools in the Kubernetes ecosystem – Kyverno – offers a flexible and declarative way to overcome this limitation.
Kyverno is the Swiss Army Knife of Kubernetes: a versatile, all-in-one tool that helps you enforce policies, mutate configurations, and generate resources with just a few lines of YAML. Whether you need to keep your cluster secure, automate repetitive tasks, or ensure consistency, Kyverno has a tool ready to tackle the job.
It works by watching the Kubernetes API server for resource changes and then applying user-defined policies in real time. These policies can validate resources to ensure they meet certain criteria, mutate them by adding or modifying fields, or generate new resources automatically.
Kyverno uses a simple manifest to define these rules, making it easy to enforce best practices, enhance security, and automate repetitive tasks – all without writing complex code or running extra controllers.
Sealing cluster-scoped secrets may sound convenient, but it comes with significant security risks. Unlike namespace-scoped ones (which limit where a secret can be decrypted), cluster-scoped secrets can be decrypted in any namespace across the cluster. This broader access increases significantly the attack surface because anyone who can apply resources in any namespace could potentially decrypt and access the secret.
That’s why cluster-scoped secrets are generally not considered safe for most use cases, especially in multi-tenant or highly regulated environments.
Suppose you have a Sealed Secret in the default namespace that you want to replicate to all the others without manually decrypting and re-encrypting it:
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
creationTimestamp: null
name: my-secret
namespace: default
spec:
encryptedData:
password: AgAptWTolExtJPF1qE/o7fLurMLpQtcd+zyHtnFeM6ovhuVYbxoOGaLeb+ZscRywyoMMwKK7z5l/c+o3YPiJYq1loSgUI7ebJfGvHpM4I3sTcixa3iw0vuLXf+EGhTQ3nOfNDP14hhWS4LhNNGxSZQV2l0pcGl5OjYqlTLG6byrl2fBGSAcHult56oPWTdeQm2boLjdIAYd/dXFlqFyuzwDwH87GLEBNqMCr0Ev5CntEn/8LU/m67ejyGLmZzCLZW/jirwrdioAgZMqAHpj2u4kCC0rCr6qRso7ZZe5b66ix3r/9ef8h1ol63na/94JsJ0mvUF06WqC8NZOuyCZuq4OTdgr7YVeNyUlxMnQA8q4pfBOAJmCTqoguBZuF3NwkrP8zMwpODCXbHl5CGPaatBgbZ29XCtMe3XWwanwRe5Amx9hAKJPqyfUwIfZemRmCjWFzuqUhiB1cQscrmI4DP4QQspPDFfnNiZ2SwnrA/3QQbZErsRjEYyFnDJVAwW1PKNJOpBSZjC43ydxP14hKD7mv82KWXTviNOzcybtMUW1DUYLnIpIDur6pYz/6JvwSG6bnrCdrYJ1ranDKv6gwDA0HPmOFCJLytrjH11UQqY1RjFKSAr1VhLZYwvgSE+e3d6I7UWhaxHlcG/lPYPM4xXWOID7sKf91gE/LdUlpAibOMxlhwdlczEehysF2a1Balb1SYCwENzKDIg==
username: AgBHWXWXkgmDE4Sky5R/+kge+skrPQj051mjcJX0MDGgE62q4qq1Ah/B9Zg/Rne56O8XpbbfLnbxOS/Ol8nVCsyT9AL66tjDUDlbFDsCSSvH6Mrk2PhZHl5b4Ys0LqBuPScT+f6+rDjh0Cn/Gor8v2OOJB+uZNaWhkhMTvQtuflzs/zmjRnjxl9hi9YfDgRNVIN1v0D5D/TzB3ug81hMbAQY81vwZixV60NBavCUE0gt8vZYZfqU4BvAgE3gACWkMNWZtDnt/uoqimqC7bOqe7nPpENAYkGNppRi5Dq2n6yKS2UyZ45sjgfHrLJpz7TAbiSbc06MTyhJMJMf6TaQcwdJZysISy1G/XWZr1AgY+1eJyxAJu1jCjbx0RFFjDg6se2zoq1hJfPd/jbuI9rNpk3AvAXm1CfiL+rrcpE8TPhrnAZkGFduBns3BenpY47JlgYwpYYLprLMyFl33zGc8UKTsFwS3KR9Nn1fVmihYLKX6HCeSTGcVIyEtxDQRFYFBSDXPhOpOzk+yaxRdCs64C7KD0F03QPXwbcPvMsABIqjSO5Sn4ESj/hu3IaJS1ZhY9+E2dbb3Wqu7aQfgUC3Kpf4DKsfjykq4jb6IQa4swWet6PtSEXJjDrQMLrHcKYgOLkWB+AX/hoqZ5ZbwfAgDp+H75sd4UIMgBZO16Fd+nO/4A5hX8PXkaTCggF+nmxZr6qhhCJgSw==
template:
metadata:
creationTimestamp: null
name: my-secret
namespace: default
type: Opaque
Since Sealed Secrets are bound to their namespace and name, a direct copy won’t work out of the box. But with Kyverno, you can create a policy that watches for the unsealed Secret once it’s created and automatically generates a duplicate in the target namespace.
You can do that by writing a ClusterPolicy:
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: sync-secrets
spec:
rules:
- name: sync-image-pull-secret
match:
resources:
kinds:
- Namespace
generate:
apiVersion: v1
kind: Secret
name: my-secret
namespace: "{{request.object.metadata.name}}"
synchronize: true
clone:
namespace: default
name: my-secret
Kyverno will automatically duplicate the Secret named my-secret
from the default namespace into every newly created namespace in your cluster. Whenever a new namespace appears, Kyverno generates a copy of my-secret inside it.
You can then play around with the match section to ensure the policy only targets specific namespaces or resources based on labels, names, or other criteria. For example, you might want to exclude system namespaces like kube-system
or default
itself, or only sync secrets to namespaces labeled for a particular team or environment.
match:
resources:
kinds:
- Namespace
exclude:
names:
- kube-system
- default
By leveraging Kyverno’s powerful policy engine, you can automate and simplify this task, ensuring secrets are consistently and securely propagated across namespaces. With flexible matching and synchronization options, Kyverno helps maintain a single source of truth, reduces manual overhead, and enhances the security posture of your Kubernetes clusters.
Interested in further reading about monitoring Kubernetes? You might find this article useful!
Did you find this article interesting? Does it match your skill set? Programming is at the heart of how we develop customized solutions. In fact, we’re currently hiring for roles just like this and others here at Würth Phoenix.