In this tutorial, you will learn how to deploy and encrypt generic Kubernetes Secrets using the Sealed Secrets Controller.
What Sealed Secrets allows you to do is:
- Store
encryptedsecrets in aGitrepository (even inpublicones). - Apply
GitOpsprinciples forKubernetes Secretsas well (Section 15 - Continuous Delivery using GitOps gives you more practical examples on this topic).
The Sealed Secrets Controller creates generic (classic) Kubernetes secrets in your DOKS cluster, from sealed secrets manifests. Sealed secrets decryption happens server side only, so as long as the DOKS cluster is secured (etcd database, RBAC properly set), everything should be safe.
There are two components involved:
- A client side utility called
kubeseal, used forencryptinggenericKubernetessecrets. ThekubesealCLI usesasymmetric cryptoto encrypt secrets thatonlytheSealed Secrets Controllercandecrypt. - A server side component called
Sealed Secrets Controllerwhich runs on yourDOKScluster, and takes care ofdecryptingsealed secrets objects for applications to use.
The real benefit comes when you use Sealed Secrets in a GitOps flow. After you commit the sealed secret manifest to your applications Git repository, the Continuous Delivery system (e.g. Flux CD) is notified about the change, and creates a Sealed Secret resource in your DOKS cluster. Then the Sealed Secrets Controller kicks in, and decrypts your sealed secret object back to the original Kubernetes secret. Next, applications can consume the secret as usual.
Compared to other solutions like Vault, Sealed Secrets lacks the following features:
Multiplestorage backend support (likeConsul,S3,Filesystem,SQL databases, etc).Dynamic Secrets: Sealed Secrets cannot create application credentials ondemandfor accessing other systems, likeS3compatible storage (e.g.DO Spaces), andautomatically revoke credentialslater on, when theleaseexpires.Leasingandrenewalof secrets: Sealed Secrets doesn't provide aclient APIforrenewing leases, nor does it provide aleaseassociated to eachsecret.Revokingold keys/secrets: Sealed Secrets canrotatethe encryption keyautomatically, but it's quite limited in this regard. Old keys and secrets are not revoked automatically - you have to manually revoke the old key(s) and re-seal everything again.Pluggablearchitecture which extends existing functionality like, settingACLsviaidentity based access controlplugins (Okta,AWS, etc).
Although Vault is more feature capable, it comes with a tradeoff: increased complexity and costs in terms of maintenance. Where Sealed Secrets really shines is: simplicity and low maintenance overhead and costs.
For enterprise grade production or HIPAA compliant systems, Vault is definitely one of the best candidates. For small projects and development environments, Sealed Secrets will suffice in most of the cases.
After finishing this tutorial, you will be able to:
Createand deploy sealedKubernetessecrets to yourDOKScluster.Manageandupdatesealed secrets.Configuresealed secretsscope.
- Introduction
- Prerequisites
- Step 1 - Installing the Sealed Secrets Controller
- Step 2 - Encrypting a Kubernetes Secret
- Step 3 - Managing Sealed Secrets
- Step 4 - Sealed Secrets Controller Private Key Backup
- Security Best Practices
- Conclusion
To complete this tutorial, you will need:
- A Git client, to clone the
Starter Kitrepository. - Kubeseal, for encrypting secrets and
Sealed Secrets Controllerinteraction. - Helm, for managing
Sealed Secrets Controllerreleases and upgrades. - Kubectl, for
Kubernetesinteraction.
In this step, you will learn how to deploy the Sealed Secrets Controller using Helm. The chart of interest is called sealed-secrets and it's provided by the bitnami-labs repository.
First, clone the Starter Kit Git repository, and change directory to your local copy:
git clone https://github.com/digitalocean/Kubernetes-Starter-Kit-Developers.git
cd Kubernetes-Starter-Kit-DevelopersThen, add the sealed secrets bitnami-labs repository for Helm:
helm repo add sealed-secrets https://bitnami-labs.github.io/sealed-secretsNext, update the sealed-secrets chart repository:
helm repo update sealed-secretsNext, search the sealed-secrets repository for available charts to install:
helm search repo sealed-secretsThe output looks similar to:
NAME CHART VERSION APP VERSION DESCRIPTION
sealed-secrets/sealed-secrets 2.4.0 v0.18.1 Helm chart for the sealed-secrets controller.
Now, open and inspect the 06-kubernetes-secrets/assets/manifests/sealed-secrets-values-v2.4.0.yaml file provided in the Starter kit repository, using an editor of your choice (preferably with YAML lint support). You can use VS Code, for example:
code 06-kubernetes-secrets/assets/manifests/sealed-secrets-values-v2.4.0.yamlNext, install the sealed-secrets/sealed-secrets chart, using Helm (notice that a dedicated sealed-secrets namespace is created as well):
HELM_CHART_VERSION="2.4.0"
helm install sealed-secrets-controller sealed-secrets/sealed-secrets --version "${HELM_CHART_VERSION}" \
--namespace sealed-secrets \
--create-namespace \
-f "06-kubernetes-secrets/assets/manifests/sealed-secrets-values-v${HELM_CHART_VERSION}.yaml"Notes:
- A
specificversion for theHelmchart is used. In this case2.4.0is picked, which maps to the0.18.1version of the application. It’s good practice in general, to lock on a specific version. This helps to have predictable results, and allows versioning control viaGit. - You will want to
restrictaccess to the sealed-secretsnamespacefor other users that have access to yourDOKScluster, to preventunauthorizedaccess to theprivate key(e.g. useRBACpolicies).
Next, list the deployment status for Sealed Secrets controller (the STATUS column value should be deployed):
helm ls -n sealed-secretsThe output looks similar to:
NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION
sealed-secrets-controller sealed-secrets 1 2021-10-04 18:25:03.594564 +0300 EEST deployed sealed-secrets-2.4.0 v0.18.1
Finally, inspect the Kubernetes resources created by the Sealed Secrets Helm deployment:
kubectl get all -n sealed-secretsThe output looks similar to (notice the status of the sealed-secrets-controller pod and service - must be UP and Running):
NAME READY STATUS RESTARTS AGE
pod/sealed-secrets-controller-7b649d967c-mrpqq 1/1 Running 0 2m19s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/sealed-secrets-controller ClusterIP 10.245.105.164 <none> 8080/TCP 2m20s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/sealed-secrets-controller 1/1 1 1 2m20s
NAME DESIRED CURRENT READY AGE
replicaset.apps/sealed-secrets-controller-7b649d967c 1 1 1 2m20s
In the next step you will learn how to seal your secrets. Only your DOKS cluster can decrypt the sealed secrets, because it's the only one having the private key.
In this step, you will learn how to encrypt your generic Kubernetes secret, using kubeseal CLI. Then, you will deploy it to your DOKS cluster and see how the Sealed Secrets controller decrypts it for your applications to use.
Suppose that you need to seal a generic secret for your application, saved in the following file: your-app-secret.yaml. Notice the your-data field which is base64 encoded (it's vulnerable to attacks, because it can be very easily decoded using free tools):
apiVersion: v1
data:
your-data: ZXh0cmFFbnZWYXJzOgogICAgRElHSVRBTE9DRUFOX1RPS0VOOg== # base64 encoded application data
kind: Secret
metadata:
name: your-appFirst, you need to fetch the public key from the Sealed Secrets Controller (performed only once per cluster, and on each fresh install):
kubeseal --fetch-cert --controller-namespace=sealed-secrets > pub-sealed-secrets.pemNotes:
- If you deploy the
Sealed Secretscontroller to another namespace (defaults tokube-system), you need to specify to thekubesealCLI the namespace, via the--controller-namespaceflag. - The
public keycan besafelystored in aGitrepository for example, or even given to the world. The encryption mechanism used by theSealed Secretscontroller cannot be reversed without theprivate key(stored in yourDOKScluster only).
Next, create a sealed file from the Kubernetes secret, using the pub-sealed-secrets.pem key:
kubeseal --format=yaml \
--cert=pub-sealed-secrets.pem \
--secret-file your-app-secret.yaml \
--sealed-secret-file your-app-sealed.yamlThe file content looks similar to (notice the your-data field which is encrypted now, using a Bitnami SealedSecret object):
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
creationTimestamp: null
name: your-app
namespace: default
spec:
encryptedData:
your-data: AgCFNTLd+KD2IGZo3YWbRgPsK1dEhxT3NwSCU2Inl8A6phhTwMxKSu82fu0LGf/AoYCB35xrdPl0sCwwB4HSXRZMl2WbL6HrA0DQNB1ov8DnnAVM+6TZFCKePkf9yqVIekr4VojhPYAvkXq8TEAxYslQ0ppNg6AlduUZbcfZgSDkMUBfaczjwb69BV8kBf5YXMRmfGtL3mh5CZA6AAK0Q9cFwT/gWEZQU7M1BOoMXUJrHG9p6hboqzyEIWg535j+14tNy1srAx6oaQeEKOW9fr7C6IZr8VOe2wRtHFWZGjCL3ulzFeNu5GG0FmFm/bdB7rFYUnUIrb2RShi1xvyNpaNDF+1BDuZgpyDPVO8crCc+r2ozDnkTo/sJhNdLDuYgIzoQU7g1yP4U6gYDTE+1zUK/b1Q+X2eTFwHQoli/IRSv5eP/EAVTU60QJklwza8qfHE9UjpsxgcrZnaxdXZz90NahoGPtdJkweoPd0/CIoaugx4QxbxaZ67nBgsVYAnikqc9pVs9VmX/Si24aA6oZbtmGzkc4b80yi+9ln7x/7/B0XmyLNLS2Sz0lnqVUN8sfvjmehpEBDjdErekSlQJ4xWEQQ9agdxz7WCSCgPJVnwA6B3GsnL5dleMObk7eGUj9DNMv4ETrvx/ZaS4bpjwS2TL9S5n9a6vx6my3VC3tLA5QAW+GBIfRD7/CwyGZnTJHtW5f6jlDWYS62LbFJKfI9hb8foR/XLvBhgxuiwfj7SjjAzpyAgq
template:
data: null
metadata:
creationTimestamp: null
name: your-app
namespace: defaultNote:
If you don't specify a namespace, the default one is assumed (use kubeseal --namespace flag, to change targeted namespace). Default scope used by kubeseal is strict - please refer to scopes in Security Best Practices.
Next, you can delete the Kubernetes secret file, because it's not needed anymore:
rm -f your-app-secret.yamlFinally, deploy the sealed secret to your cluster:
kubectl apply -f your-app-sealed.yamlCheck that the Sealed Secrets Controller decrypted your Kubernetes secret in the default namespace:
kubectl get secretsThe output looks similar to:
NAME TYPE DATA AGE
your-app Opaque 1 31s
Inspect the secret:
kubectl get secret your-app -o yamlThe output looks similar to (your-data key value should be decrypted to the original base64 encoded value):
apiVersion: v1
data:
your-data: ZXh0cmFFbnZWYXJzOgogICAgRElHSVRBTE9DRUFOX1RPS0VOOg==
kind: Secret
metadata:
creationTimestamp: "2021-10-05T08:34:07Z"
name: your-app
namespace: default
ownerReferences:
- apiVersion: bitnami.com/v1alpha1
controller: true
kind: SealedSecret
name: your-app
uid: f6475e74-78eb-4c6a-9f19-9d9ceee231d0
resourceVersion: "235947"
uid: 7b7d2fee-c48a-4b4c-8f16-2e58d25da804
type: OpaqueIf you want SealedSecret controller to take management of an existing Secret (i.e. overwrite it when unsealing a SealedSecret with the same name and namespace), then you have to annotate that Secret with the annotation sealedsecrets.bitnami.com/managed: "true" ahead applying Step 2 - Encrypting a Kubernetes Secret.
If you want to add or update existing sealed secrets without having the cleartext for the other items, you can just copy&paste the new encrypted data items and merge it into an existing sealed secret.
You must take care of sealing the updated items with a compatible name and namespace (see note about scopes above).
You can use the --merge-into command to update an existing sealed secrets if you don't want to copy&paste:
echo -n bar | kubectl create secret generic mysecret --dry-run=client --from-file=foo=/dev/stdin -o json \
| kubeseal --controller-namespace=sealed-secrets > mysealedsecret.json
echo -n baz | kubectl create secret generic mysecret --dry-run=client --from-file=bar=/dev/stdin -o json \
| kubeseal --controller-namespace=sealed-secrets --merge-into mysealedsecret.jsonIf using VS Code there's an extension that allows you to use the GUI mode to perform the above operations - Kubeseal for vscode.
If you want to perform a manual backup of the private and public keys, you can do so via:
kubectl get secret -n sealed-secrets -l sealedsecrets.bitnami.com/sealed-secrets-key -o yaml > master.keyThen, store the master.key file somewhere safe. To restore from a backup after some disaster, just put that secrets back before starting the controller - or if the controller was already started, replace the newly-created secrets and restart the controller:
kubectl apply -f master.key
kubectl delete pod -n sealed-secrets -l name=sealed-secrets-controllerBest approach is to perform regular backups for example, as you already learned in Section 6 - Set up Backup and Restore. Velero or Trilio helps you to restore the Sealed Secrets controller state in case of a disaster as well (without the need to fetch the master key, and then inserting it back in the cluster).
In terms of security, Sealed Secrets allows you to restrict other users to decrypt your sealed secrets inside the cluster. There are three scopes that you can use (kubeseal CLI --scope flag):
strict(default): the secret must be sealed withexactlythe samenameandnamespace. Theseattributesbecomepartof theencrypted dataand thuschanging nameand/ornamespacewould lead to "decryption error".namespace-wide: you can freelyrenamethe sealed secret within a givennamespace.cluster-wide: thesecretcan beunsealedin anynamespaceand can be given anyname.
Next, you can apply some of the best practices highlighted below:
- Make sure to change both
secretsperiodically (like passwords, tokens, etc), and theprivate keyused forencryption. This way, if theencryption keyis everleaked, sensitive data doesn't get exposed. And even if it is, the secrets are not valid anymore. You can read more on the topic by referring to the Secret Rotation chapter, from the official documentation. - You can leverage the power of
RBACfor yourKubernetescluster torestrictaccess tonamespaces. So, if you store all your Kubernetes secrets in aspecific namespace, then you canrestrictaccess tounwanted usersandapplicationsfor thatspecific namespace. This is important, because plainKubernetes Secretsarebase64encoded and can bedecodedvery easy by anyone.Sealed Secretsprovides anencryptionlayer on top ofencoding, but in yourDOKScluster sealed secrets are transformed back togenericKubernetes secrets. - To avoid
private key leaks, please make sure that thenamespacewhere you deployed theSealed Secretscontroller is protected as well, via correspondingRBACrules.
In this tutorial, you learned how to use generic Kubernetes secrets in a secure way. You also learned that the encryption key is stored and secrets are decrypted in the cluster (the client doesn’t have access to the encryption key).
Then, you discovered how to use kubeseal CLI, to generate SealedSecret manifests that hold sensitive content encrypted. After applying the sealed secrets manifest file to your DOKS cluster, the Sealed Secrets Controller will recognize it as a new sealed secret resource, and decrypt it to generic Kubernetes Secret resource.
Lightweight, meaning implementation and management costs are low.Transparentintegration withKubernetes Secrets.Decryptionhappensserver side(DOKS cluster).- Works very well in a
GitOpssetup (encryptedfiles can be stored usingpublic Gitrepositories).
- For
each DOKS clustera separateprivateandpublic keypair needs to becreatedandmaintained. Private keysmust bebackedup (e.g. usingVelero) fordisasterrecovery.Updatingandre-sealingsecrets, as well asaddingormergingnew key/values is not quite straightforward.
Even though there are some cons to using Sealed Secrets, the ease of management and transparent integration with Kubernetes and GitOps flows makes it a good candidate in practice.
- Upgrade steps and notes.
- Sealed Secrets FAQ, for frequently asked questions about
Sealed Secrets.
Next, you will learn how to automatically scale your application workloads based on external load (or traffic). You will learn how to leverage metrics-server as well as Prometheus via prometheus-adapter to do the job, and let the Kubernetes horizontal (or vertical) Pod autoscaling system take smart decisions.
