External Secrets Operator insecurely retrieves secrets through the getSecretKey templating function
Description
External Secrets Operator reads information from a third-party service and automatically injects the values as Kubernetes Secrets. Starting in version 0.20.2 and prior to version 1.2.0, the getSecretKey template function, while introduced for senhasegura Devops Secrets Management (DSM) provider, has the ability to fetch secrets cross-namespaces with the roleBinding of the external-secrets controller, bypassing our security mechanisms. This function was completely removed in version 1.2.0, as everything done with that templating function can be done in a different way while respecting External Secrets Operator's safeguards As a workaround, use a policy engine such as Kubernetes, Kyverno, Kubewarden, or OPA to prevent the usage of getSecretKey in any ExternalSecret resource.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
github.com/external-secrets/external-secretsGo | >= 0.20.2, < 1.2.0 | 1.2.0 |
Affected products
1- Range: helm-chart-0.20.2, helm-chart-0.20.3, helm-chart-0.20.4, …
Patches
117d3e22b8d3ffix!: Get rid of getSecretKey (#5738)
4 files changed · +14 −72
docs/guides/templating.md+3 −6 modified@@ -138,18 +138,16 @@ In case you have a secret that contains a (partial) certificate chain you can ex ### RSA Decryption Data From Provider -When a provider returns RSA-encrypted values, you can decrypt them directly in the template using the `getSecretKey` and `rsaDecrypt` functions (engine v2). - -- `getSecretKey` reads a specific key from a Kubernetes Secret. Use it to fetch the RSA private key (PEM in plain text, without passphrase) used for decryption. (**Note:** It is recommended to fetch the key from a different Secret to ensure stronger security in the process). -- `rsaDecrypt` performs decryption with the private key passed through the pipeline: `<privateKeyPEM | rsaDecrypt "<SCHEME>" "<HASH>" <ciphertext> >`. `SCHEME` and `HASH` are strings (for example, `"RSA-OAEP"` and `"SHA1"`). The third argument must be the ciphertext in binary form. +When a provider returns RSA-encrypted values, you can decrypt them directly in the template using the `rsaDecrypt` functions (engine v2). +`rsaDecrypt` performs decryption with the private key passed through the pipeline: `<privateKeyPEM | rsaDecrypt "<SCHEME>" "<HASH>" <ciphertext> >`. `SCHEME` and `HASH` are strings (for example, `"RSA-OAEP"` and `"SHA1"`). The third argument must be the ciphertext in binary form. Base64 handling: providers often return ciphertext as Base64. You can either: - decode in the template with `b64dec` (for example: `(.password_encrypted_base64 | b64dec)`), or - set `decodingStrategy: Base64` on the corresponding `spec.data.remoteRef` so the template receives binary data. Prerequisites - `spec.target.template.engineVersion: v2`. -- A valid RSA private key in PEM format without passphrase (from another Secret via `getSecretKey`, or from the same ExternalSecret). +- A valid RSA private key in PEM format without passphrase (from another reference in the same ExternalSecret). - Ciphertext must match the key pair and the chosen algorithm/hash. Full example: @@ -204,7 +202,6 @@ In addition to that you can use over 200+ [sprig functions](http://masterminds.g | filterCertChain | Filters PEM block(s) with a specific certificate type (`leaf`, `intermediate` or `root`) from a certificate chain of PEM blocks (PEM blocks with type `CERTIFICATE`). | | jwkPublicKeyPem | Takes an json-serialized JWK and returns an PEM block of type `PUBLIC KEY` that contains the public key. [See here](https://golang.org/pkg/crypto/x509/#MarshalPKIXPublicKey) for details. | | jwkPrivateKeyPem | Takes an json-serialized JWK as `string` and returns an PEM block of type `PRIVATE KEY` that contains the private key in PKCS #8 format. [See here](https://golang.org/pkg/crypto/x509/#MarshalPKCS8PrivateKey) for details. | -| getSecretKey | Reads a specific key from a Kubernetes `Secret` and returns it as a string. Typical usage: ``getSecretKey "secret-name" "namespace" "key"``. | | rsaDecrypt | Decrypts RSA ciphertext using a PEM private key. Usage: ``<rsaDecrypt "SCHEME" "HASH" ciphertext privateKeyPEM>`` or ``<privateKeyPEM \| rsaDecrypt "SCHEME" "HASH" ciphertext>``. **SCHEME**: supported values are `"None"` and `"RSA-OAEP"`. **HASH**: supported values are `"SHA1"` and `"SHA256"`. **Ciphertext** must be binary — use `b64dec` or `decodingStrategy: Base64` to convert Base64 payloads. | | toYaml | Takes an interface, marshals it to yaml. It returns a string, even on marshal error (empty string). | | fromYaml | Function converts a YAML document into a map[string]any. |
docs/snippets/rsadecrypt-template-v2-external-secret.yaml+10 −10 modified@@ -10,17 +10,17 @@ spec: engineVersion: v2 data: # Decrypt a binary ciphertext using a private key stored in a Kubernetes Secret. - # getSecretKey("secret-name", "namespace", "key") reads the PEM private key. # rsaDecrypt("SCHEME", "HASH", ciphertext, privateKeyPEM) decrypts the ciphertext (binary). - password: '{{ getSecretKey "my_secret_with_pk" "namespace_pk" "key_pk" | rsaDecrypt "RSA-OAEP" "SHA1" .password_encrypted_binary }}' - - # Alternatives: - # - If provider returns Base64, decode in-template with b64dec: - # password: '{{ getSecretKey "my_secret_with_pk" "namespace_pk" "key_pk" | rsaDecrypt "RSA-OAEP" "SHA1" (.password_encrypted_base64 | b64dec) }}' - # - Or set decodingStrategy: Base64 on the spec.data.remoteRef so template receives binary. - # - Or use a private key pulled into this ExternalSecret (then use {{ .private_key }}): - # password: '{{ .private_key | rsaDecrypt "RSA-OAEP" "SHA1" .password_encrypted_binary }}' + password: '{{ rsaDecrypt "RSA-OAEP" "SHA1" .password_encrypted_binary .privatekey }}' data: + - secretKey: privatekey + remoteRef: + key: a-secretname-in-cluster + property: privatekey + sourceRef: + storeRef: + kind: SecretStore # or ClusterSecretStore + name: kubernetes # name of the k8s provider - secretKey: password_encrypted_binary remoteRef: key: /credentials/password_encrypted_binary @@ -31,4 +31,4 @@ spec: # key: /credentials/password_encrypted_base64 # decodingStrategy: Base64 # ... -{% endraw %} \ No newline at end of file +{% endraw %}
runtime/template/v2/kubernetes.go+0 −54 removed@@ -1,54 +0,0 @@ -/* -Copyright © 2025 ESO Maintainer Team - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - https://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package template - -import ( - "context" - "fmt" - - "k8s.io/client-go/kubernetes" - ctrlcfg "sigs.k8s.io/controller-runtime/pkg/client/config" - - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -const ( - errCreatingConfig = "error creating cluster configuration: %v" - errCreatingClientset = "error creating Kubernetes clientset: %v" - errFetchingSecret = "error fetching secret: %v" - errKeyNotFound = "key %s not found in secret %s" -) - -func getSecretKey(secretName, namespace, keyName string) (string, error) { - restCfg, err := ctrlcfg.GetConfig() - if err != nil { - return "", fmt.Errorf(errCreatingConfig, err) - } - clientset, err := kubernetes.NewForConfig(restCfg) - if err != nil { - return "", fmt.Errorf(errCreatingClientset, err) - } - secret, err := clientset.CoreV1().Secrets(namespace).Get(context.Background(), secretName, metav1.GetOptions{}) - if err != nil { - return "", fmt.Errorf(errFetchingSecret, err) - } - val, ok := secret.Data[keyName] - if !ok { - return "", fmt.Errorf(errKeyNotFound, keyName, secretName) - } - return string(val), nil -}
runtime/template/v2/template.go+1 −2 modified@@ -55,8 +55,7 @@ var tplFuncs = tpl.FuncMap{ "toYaml": toYAML, "fromYaml": fromYAML, - "getSecretKey": getSecretKey, - "rsaDecrypt": rsaDecrypt, + "rsaDecrypt": rsaDecrypt, } var leftDelim, rightDelim string
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
7- github.com/advisories/GHSA-77v3-r3jw-j2v2ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2026-22822ghsaADVISORY
- github.com/external-secrets/external-secrets/commit/17d3e22b8d3fbe339faf8515a95ec06ec92b1febghsax_refsource_MISCWEB
- github.com/external-secrets/external-secrets/issues/5690ghsax_refsource_MISCWEB
- github.com/external-secrets/external-secrets/pull/3895ghsax_refsource_MISCWEB
- github.com/external-secrets/external-secrets/releases/tag/v1.2.0ghsax_refsource_MISCWEB
- github.com/external-secrets/external-secrets/security/advisories/GHSA-77v3-r3jw-j2v2ghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.