30. 06. 2026 Alessandro Valentini Uncategorized

K8s Secrets: why we migrated from SealedSecrets to ExternalSecrets

For a long time, Sealed Secrets was our go-to solution for managing confidential data like database credentials, tokens and private keys within Kubernetes. However, as our operations grew, the operational overhead of this approach led us to migrate to External Secrets. This transition has significantly streamlined our workflow, enhanced our security posture during credential rotations, and unified our secret management strategy under AWS Systems Manager Parameter Store.

Why We Moved Away from Sealed Secrets

Sealed Secrets works by using asymmetric cryptography. You take a plain-text secret, encrypt it using a public key tied to a specific Kubernetes cluster, and commit the resulting encrypted file into Git. Inside the cluster, a controller decrypts it back into a standard Kubernetes Secret.

This model is effective when you manage a single cluster and allows a completely a complitely gitops and self-contained secret management. However, its architectural limits become obvious when scaling across different environments like Development, Staging, and Production. Because the encryption is tied to a specific cluster’s private key, you cannot use the same Sealed Secret across multiple environments. If you need to deploy an identical application configuration to five different clusters, you must encrypt that secret five separate times, managing five distinct files in your repository. Adding new environments quickly becomes an administrative bottleneck.

Furthermore, credential rotation is an operational burden. If a token changes, a developer must retrieve the new password, run the encryption command-line tool locally to generate a new Sealed Secret, and push it to Git. In a modern automated environment, this manual re-sealing process introduces delays and increases the risk of human error.

How External Secrets Works

The External Secrets Operator shifts the paradigm from pushing encrypted files into Git to pulling secrets dynamically from an external authority. Instead of storing encrypted values in your repository, you store them securely in a dedicated enterprise secret manager: in our case, AWS Systems Manager Parameter Store.

In Kubernetes, we define a blueprint called an ExternalSecret. This object does not contain any sensitive data. Instead, it acts as a pointer, telling the cluster exactly where to look inside AWS to find the data. A controller running inside the cluster periodically checks these pointers. When it detects a change or a new deployment, it securely reaches out to AWS, fetches the plain text, and creates a standard Kubernetes Secret entirely in memory.

This approach completely decouples secret values from your configuration repository. If you need to rotate a password, you change it once in AWS. The Kubernetescluster automatically detects the update and refreshes the secret without any manual re-encryption or code deployment.

To deploy External Secret you can use the official helmchart and then setup a ClusterSecretStore like the following to tell External Secrets where to look:

---
apiVersion: external-secrets.io/v1
kind: ClusterSecretStore
metadata:
  name: my-parameter-store
spec:
  provider:
    aws:
      service: ParameterStore
      region: eu-central-1
      role: arn:aws:iam::123456789012:role/ssm-read-only
      auth:
        secretRef:
          accessKeyIDSecretRef:
            name: aws-ssm-secret
            key: access-key
            namespace: external-secrets
          secretAccessKeySecretRef:
            name: aws-ssm-secret
            key: secret-access-key
            namespace: external-secrets

Connecting Kubernetes to AWS Parameter Store

To bridge OpenShift and AWS safely, you must establish a secure, authenticated channel. This setup relies on standard AWS Identity and Access Management principles to ensure the cluster has read-only access to only the parameters it requires.

First, you need an AWS IAM Policy that explicitly allows access to the Parameter Store API. This policy grants permissions to describe, get, and decrypt parameters matching specific paths.

Second, you configure an IAM User or leverage an Kubernetees ServiceAccount mapped to an AWS IAM Role via IAM Roles for Service Accounts. This identity uses the generated policy to authenticate. The credentials or role token are then supplied to a ClusterSecretStore object within OpenShift, which defines the global connection details to your AWS region.

When utilizing AWS Parameter Store, it is critical to understand its technical tiers. Standard parameters have a strict size limit of 4 KB per secret. While this is more than sufficient for standard passwords, database connection strings, and API keys, it becomes a hard blocker for larger payloads. For instance, private SSH keys, TLS certificates, or large configuration files easily exceed this limit. To handle these long secrets, you must configure the parameters in AWS as Advanced parameters, which expand the capacity up to 8 KB per secret.

Structural Comparison: Sealed Secrets vs. External Secrets

To illustrate the difference in configuration clarity and repository safety, consider how the same set of three application configuration parameters looks under both paradigms.

Under the old Sealed Secrets model, the sensitive data is entirely obscured by ciphertext within the repository:

apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
  name: database-credentials
  namespace: my-ns
spec:
  encryptedData:
    database-host: AgBwA1F...[Truncated Ciphertext]...7k9p==
    database-user: AgBwA1F...[Truncated Ciphertext]...m2n1==
    database-password: AgBwA1F...[Truncated Ciphertext]...x8z9==

With the External Secrets model, the configuration file contains no ciphertext at all. It simply acts as a clean, human-readable reference map to your central AWS repository:

apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: database-credentials
  namespace: my-ns
spec:
  refreshInterval: 1h
  secretStoreRef:
    name: my-parameter-store
    kind: ClusterSecretStore
  target:
    name: database-credentials
  data:
    - secretKey: database-host
      remoteRef:
        key: /production/my-app/db_host
    - secretKey: database-user
      remoteRef:
        key: /production/my-app/db_user
    - secretKey: database-password
      remoteRef:
        key: /production/my-app/db_password

By transitioning to External Secrets and AWS Parameter Store, we have removed the friction of environment replication and simplified credential management, allowing our engineering teams to focus on platform velocity rather than cryptographic maintenance.

Alessandro Valentini

Alessandro Valentini

DevOps Engineer at Würth IT Italy
Platform Engineer at Würth IT Italy

Author

Alessandro Valentini

Platform Engineer at Würth IT Italy

Leave a Reply

Your email address will not be published. Required fields are marked *

Archive