Browse Source

feat: add support for decryption scheme from properties in senhasegura Devops Secrets Management (DSM) provider (#3895)

* Initial Commit

Signed-off-by: Felipe Oliveira dos Santos <felipeoliveira.s.br@gmail.com>

* Building an RSA-Based Sensitive Data Decryption Feature with Advanced Templating v2

Signed-off-by: Felipe Oliveira dos Santos <felipeoliveira.s.br@gmail.com>

* test: building an rsa-based sensitive data decryption feature with advanced templating v2

Signed-off-by: Felipe Oliveira dos Santos <felipeoliveira.s.br@gmail.com>

* docs: building an rsa-based sensitive data decryption feature with advanced templating v2

Signed-off-by: Felipe Oliveira dos Santos <felipeoliveira.s.br@gmail.com>

* reviewable: building an rsa-based sensitive data decryption feature with advanced templating v2

Signed-off-by: Felipe Oliveira dos Santos <felipeoliveira.s.br@gmail.com>

* docs: building an rsa-based sensitive data decryption feature with advanced templating v2

Signed-off-by: Felipe Oliveira dos Santos <felipeoliveira.s.br@gmail.com>

* docs: building an rsa-based sensitive data decryption feature with advanced templating v2

Signed-off-by: Felipe Oliveira dos Santos <felipeoliveira.s.br@gmail.com>

* chore(license): building an rsa-based sensitive data decryption feature with advanced templating v2

Signed-off-by: Felipe Oliveira dos Santos <felipeoliveira.s.br@gmail.com>

* test: building an rsa-based sensitive data decryption feature with advanced templating v2

Signed-off-by: Felipe Oliveira dos Santos <felipeoliveira.s.br@gmail.com>

* test: remove bin data test on building an rsa-based sensitive data decryption feature

Signed-off-by: Felipe Oliveira dos Santos <felipeoliveira.s.br@gmail.com>

* test: add encrypted data test on building an rsa-based sensitive data decryption feature

Signed-off-by: Felipe Oliveira dos Santos <felipeoliveira.s.br@gmail.com>

* test: add encrypted data test on building an rsa-based sensitive data decryption feature

Signed-off-by: Felipe Oliveira dos Santos <felipeoliveira.s.br@gmail.com>

---------

Signed-off-by: Felipe Oliveira dos Santos <felipeoliveira.s.br@gmail.com>
Felipe Oliveira 6 months ago
parent
commit
de3036042c

+ 32 - 0
docs/guides/templating.md

@@ -136,6 +136,36 @@ In case you have a secret that contains a (partial) certificate chain you can ex
 {% include 'filtercertchain-template-v2-external-secret.yaml' %}
 ```
 
+### 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.
+
+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).
+- Ciphertext must match the key pair and the chosen algorithm/hash.
+
+Full example:
+
+```yaml
+{% include 'rsadecrypt-template-v2-external-secret.yaml' %}
+```
+
+Useful variations (included as comments in the example):
+- Base64 decode in the template with `b64dec` or via `decodingStrategy: Base64` on `spec.data`.
+- Use a private key available in the same ExternalSecret (for example: `( .private_key | rsaDecrypt ... )`).
+
+Error notes
+- Referencing a missing key in the template will fail rendering.
+- If key/algorithm/hash do not match the ciphertext, decryption will fail and reconciliation will retry.
+
 ## Templating with PushSecret
 
 `PushSecret` templating is much like `ExternalSecrets` templating. In-fact under the hood, it's using the same data structure.
@@ -174,6 +204,8 @@ 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.                                                                                                                                                             |
 

+ 34 - 0
docs/snippets/rsadecrypt-template-v2-external-secret.yaml

@@ -0,0 +1,34 @@
+{% raw %}
+apiVersion: external-secrets.io/v1
+kind: ExternalSecret
+metadata:
+  name: rsa-decrypt-template-v2
+spec:
+  # ...
+  target:
+    template:
+      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 }}'
+  data:
+  - secretKey: password_encrypted_binary
+    remoteRef:
+      key: /credentials/password_encrypted_binary
+  # If ciphertext is Base64 encoded, either decode in-template (b64dec) or use decodingStrategy: Base64
+  # Example (decode here -> template receives binary):
+  # - secretKey: password_encrypted_base64
+  #   remoteRef:
+  #     key: /credentials/password_encrypted_base64
+  #     decodingStrategy: Base64
+  # ...
+{% endraw %}

+ 86 - 0
pkg/template/v2/decrypt.go

@@ -0,0 +1,86 @@
+/*
+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 (
+	"crypto"
+	"crypto/rsa"
+	"crypto/sha256"
+	"crypto/sha512"
+	"encoding/pem"
+	"errors"
+	"fmt"
+	"hash"
+)
+
+var (
+	errParsePK    = errors.New("could not parse private key")
+	errRSADecrypt = errors.New("error decrypting data with RSA")
+)
+
+const (
+	errSchemeNotSupported = "decryption scheme %v is not supported"
+	errParseRSAPK         = "could not parse RSA private key"
+	errDecodePEM          = "failed to decode PEM block"
+	errWrap               = "%w: %v"
+)
+
+func rsaDecrypt(scheme, hash, in, privateKey string) (string, error) {
+	switch scheme {
+	case "None":
+		return in, nil
+	case "RSA-OAEP":
+
+		pemBlock, _ := pem.Decode([]byte(privateKey))
+		if pemBlock == nil {
+			return "", fmt.Errorf(errDecodePEM)
+		}
+
+		parsedPrivateKey, err := parsePrivateKey(pemBlock.Bytes)
+		if err != nil {
+			return "", fmt.Errorf(errWrap, errParsePK, err)
+		}
+
+		rsaPrivateKey, isValid := parsedPrivateKey.(*rsa.PrivateKey)
+		if !isValid {
+			return "", fmt.Errorf(errParseRSAPK)
+		}
+
+		out, err := rsa.DecryptOAEP(getHash(hash), nil, rsaPrivateKey, []byte(in), nil)
+		if err != nil {
+			return "", fmt.Errorf(errWrap, errRSADecrypt, err)
+		}
+		return string(out), nil
+	default:
+		return "", fmt.Errorf(errSchemeNotSupported, scheme)
+	}
+}
+
+func getHash(hash string) hash.Hash {
+	switch hash {
+	case "None":
+		return sha256.New()
+	case "SHA1":
+		return crypto.SHA1.New()
+	case "SHA256":
+		return sha256.New()
+	case "SHA512":
+		return sha512.New()
+	default:
+		return sha256.New()
+	}
+}

+ 86 - 0
pkg/template/v2/decrypt_test.go

@@ -0,0 +1,86 @@
+/*
+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 (
+	"crypto/rand"
+	"crypto/rsa"
+	"crypto/x509"
+	"encoding/pem"
+	"fmt"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+func generateRSAPrivateKeyPEM(t testing.TB) (string, *rsa.PrivateKey) {
+	t.Helper()
+	// Generate a new RSA private key
+	priv, err := rsa.GenerateKey(rand.Reader, 2048)
+	require.NoError(t, err, "failed to generate RSA key")
+	privBytes := x509.MarshalPKCS1PrivateKey(priv)
+	privPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: privBytes})
+	return string(privPEM), priv
+}
+
+func TestRsaDecrypt_NoneScheme(t *testing.T) {
+	input := "plaintext"
+	privateKey := "irrelevant"
+	out, err := rsaDecrypt("None", "SHA256", input, privateKey)
+	assert.NoError(t, err)
+	assert.Equal(t, input, out)
+}
+
+func TestRsaDecrypt_InvalidPEM(t *testing.T) {
+	_, err := rsaDecrypt("RSA-OAEP", "SHA256", "data", "not-a-valid-pem")
+	assert.Error(t, err)
+	assert.Equal(t, errDecodePEM, err.Error())
+}
+
+func TestRsaDecrypt_InvalidPrivateKey(t *testing.T) {
+	// Use a valid PEM block but not a private key
+	pemBlock := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: []byte("invalid")})
+	_, err := rsaDecrypt("RSA-OAEP", "SHA256", "data", string(pemBlock))
+	assert.Error(t, err)
+	assert.ErrorIs(t, err, errParsePK)
+}
+
+func TestRsaDecrypt_UnsupportedScheme(t *testing.T) {
+	privateKey, _ := generateRSAPrivateKeyPEM(t)
+	_, err := rsaDecrypt("Unsupported", "SHA256", "data", privateKey)
+	assert.Error(t, err)
+	assert.Equal(t, fmt.Errorf(errSchemeNotSupported, "Unsupported"), err)
+}
+
+func TestRsaDecrypt_RSAOAEP_Success(t *testing.T) {
+	privateKeyPEM, priv := generateRSAPrivateKeyPEM(t)
+	plaintext := []byte("secret-data")
+	ciphertext, err := rsa.EncryptOAEP(getHash("SHA256"), rand.Reader, &priv.PublicKey, plaintext, nil)
+	assert.NoError(t, err)
+	out, err := rsaDecrypt("RSA-OAEP", "SHA256", string(ciphertext), privateKeyPEM)
+	assert.NoError(t, err)
+	assert.Equal(t, string(plaintext), out)
+}
+
+func TestRsaDecrypt_RSAOAEP_DecryptionError(t *testing.T) {
+	privateKeyPEM, _ := generateRSAPrivateKeyPEM(t)
+	// Pass random data as ciphertext
+	_, err := rsaDecrypt("RSA-OAEP", "SHA256", "not-encrypted-data", privateKeyPEM)
+	assert.Error(t, err)
+	assert.ErrorIs(t, err, errRSADecrypt)
+}

+ 54 - 0
pkg/template/v2/kubernetes.go

@@ -0,0 +1,54 @@
+/*
+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
+}

+ 3 - 0
pkg/template/v2/template.go

@@ -51,6 +51,9 @@ var tplFuncs = tpl.FuncMap{
 
 	"toYaml":   toYAML,
 	"fromYaml": fromYAML,
+
+	"getSecretKey": getSecretKey,
+	"rsaDecrypt":   rsaDecrypt,
 }
 
 var leftDelim, rightDelim string

+ 120 - 0
pkg/template/v2/template_test.go

@@ -17,6 +17,10 @@ limitations under the License.
 package template
 
 import (
+	"crypto/rand"
+	"crypto/rsa"
+	"crypto/x509"
+	"encoding/pem"
 	"os"
 	"strings"
 	"testing"
@@ -138,8 +142,85 @@ t8H2KyX5T92nGul1chFeMT5hlr2hRANCAAR8OODc2riM9/wg5nTbvto9VqX/yJfJ
 KfMtQkBmCFTNk3fOtz3sgTiv0OHbokplsICEc4tUT5RWU0frwAjJT4Pk
 -----END PRIVATE KEY-----
 `
+	rsaDecryptPKRSAPKCS8 = `-----BEGIN PRIVATE KEY-----
+MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQChzs1R+jA3Goqi
+ropPzF4Ehpbi6VklbeZWP+RoU3rJshONJO6w9tPhbp0YIXrPSM9P5a9xaaNxDR9e
+u84O05+vq3C4P9I0jb5GSjiuznrmsprFWGaGdd4Vr7Fir1oqfeVc+znQFUCvkq7B
+EWobOonl6ktQ2OHBK0bEzXB7qY7P4ETI6owDUL+pkAwHzdnwk4sXMDKBLT3WZVRn
+xPppIJ4VAEph1fJOCIIskxVBh8cAT4QRxsdu8oB7cAuYJLBBKiS/GGIA7vh9sQrb
++0BAgLqvy+kiQeQhYgF/Y/uVwkgAphFzSjSjoSFQ50nNE2VJA5J6o7QZ413DlIWr
+VJKNZJDbAgMBAAECggEAemklU5te1pExyJka8fu+NNZNWCUI2BQoaZ+0gGiHQAeE
+WwdRvHc/HBC+r/7EFgUTMXKmI7qzd1diIB0caoMXD6M3h2xg7nk9NZf5AeYbfGQq
+SpnyFk8dUHK2U94s7HCKEKnOtukdIrZplo5CI49Ju7JggC1TvPuscj6pliRUclYY
+Pc6pTVaG+bludDR8YkQ1mGi04wMQHpnisegRpMSjt9uZc1jKM7SSaHggu4/vuewo
+CFdra50/MjidIOXd5T5iVYY28J+gR8oCCKySTogNJ3JpNMNAW4FHfime5B+uS0IA
+YI/N8yjT/BPgRt4lQpR+6zi3fpguWNIM71xye1/R4QKBgQDNZNGFOntbFM2NTPYu
+4APRVu4a0gM+JEA/ozuxTigkgaj6dNeJvaNTxcKG0MmGxQLEcvgCDaWIoM6Qrj1K
+YVwdiv1MddRDdSHQjGjNM6n7jNfYVQ2gP+1wDwTMN+eyAW8KoZByaQimmjyBj/Ps
+C8VWPxyrw/UeHqzTazyEIZ2fHQKBgQDJrM2/xx7F63C6GaW+5gMeColxkdYb29Aw
+R3xb2rn5lVPLW0BORQQuepZuaI+ZLTb4Op7V4yAC/S9femFXpgkZa56aacwy1jxb
+R9WO0CWP3QCUCcIBF/4lbJDZ6gQLr51oahXhhjbgGMrlguC/j4R9n3i58EaeukeE
++Hsu/hMWVwKBgETsWALFJS/jQzbvZI1GTwGoki4d20i3EXhJZnaRK5dUi0fAfbOT
+F4O9ERH8biPzaIJTsjW+LpYyoB6c2aRkF20yft1xjNE2NSquc1yowZnQIX5OzEvC
+KAM6hvmgqPdq08BVhwtdg7GkgDlZ/Rhwur++XfilwVNiJ8yqZ5xPS31hAoGBALnu
+hB5MMPXd86bPoHyYSMV4h3DaOGCkzpLERUXWKOGOp5tzfJzsikdjo68U3VcmVWiT
+ev7MkCXRUMyg4n/RRtBV5PqNkcJIu4qYdq5c/lRdN3xEZsVlXl0Yc49EbghsFx49
+uACdIZiHov/oItbZNRgwXzhl6mXKbceM4tzXR7evAoGBALug2beVoVAl2nAB2RkQ
+Jy3viDKO+C6Z82gsS5x9Wif9cJTppIarZC+t7w33f4WHJiYT1VDxse08dohC5Nn7
+7WWKdtLMSyUaXE46s37Kl5tkTkROj3wBzSIzwLYAwsthcpQVubwDAMsig8EUAdr/
+0IwaauEPX9lBYMZDMYuSAR5n
+-----END PRIVATE KEY-----
+`
+	rsaDecryptDataPKCS8Base64 = `hAZJktRFdzSkGxxiiSE46T271veCgwvC0GrY+AwDYA/KeuFZFdPgZsJ74awu1WR6x4BrbMLTXNpQw4UqChdbaM7VoKUCkPTcCU1jsveqYNisM2MNF98QjNjvp+9jXHfAsClLA5AvJxe3GjfWIi18E4PieFpATn/BTrmoklx4rSkWmfifZol7Wcny0D2fhrj/JOdxEIqowUB/tNwYzNd+lXgm55wea+G3YnD3Fr4ARaCCaQMUcdW9Kgx7mmZGZE3xDAhs8WMfpe9xVZ17Ca7Sw2r1JKS0o0fYiZNHUmCXVsP9O+//+0sfEtETiVUF0jItrwlK4GL8+bVcXQ9N2TW7+g==`
+	rsaDecryptPKRSAPKCS1      = `-----BEGIN RSA PRIVATE KEY-----
+MIICXAIBAAKBgQC8ronsBTX6GD5YhoE/v76+ZkWX0gODzAD+aCYIyTs4PiWruxlV
+SOtjwq2gRgUexE5Hsz8cxhFz5Db8qFXBsA+GgXjByQuVbBw04SCKHgc0zhbcWonV
+3Rk03pjVB1HfuxcDRja8JZontfMAJyPNJovPu3rIi8npSC+T5g7Fq9UCbQIDAQAB
+AoGAAo2+MiKT63GejmYro6g9taf+syJVh9gf/1F7ikzm70jwC5X5rszQ2sXMwcmQ
+0izH/nJvnT0VCWOCVwMUPg3a9+odoMNFyg2u3XCLBNr3vlgG3xeCTdjzaMnY61ct
+xU4JgpIuiAlwCqhNfKuHxeesM/cvh9eC11ELXh27gLsNCEECQQDng5klIGPLHfiN
+3wam6wxLnqHPuhXAyrOAKA1qBlZGKI6n6iBYpfN+Y70gt10f3SBlFfkSyF7uZsUy
+maofmjARAkEA0KM6Rj+p2CRMFMh4NpON4RKaQIYNGMTpe/akYBOx6wcZy0saON9l
+eHS3nq77TDT+mA3uDbu+6VD8j8eEcXUInQJAYJkfQEd4fBrAR+nj66etVKwW1gbN
+5shtBy8vEasdOl7XzyY4YuSzaWwSUOFRYOcyChuV9olWWuDUrR1Cx7bdEQJBALzY
+gg7D4UA62oKVUfpUZL+szuJIc+JPmecSwIYWTZymuLpCKGICEx6Mxwdi6yN3dFq9
+gRP9NDiLjY+20DLB9CECQB5IqCvT396rjJn3g6sRXHX5qApJwInofLByafcjGd34
+ejJKh20FmJegJhkImmNTokNbQZbYiLAP07Ykx9A8jLg=
+-----END RSA PRIVATE KEY-----
+
+`
+	rsaDecryptDataPKCS1Base64 = `Xd9Jij8+hTqM7ii1nnKbKZy7pHhn3BJwxrENwIlvf0iRysVKn7gmAaD6UV4EpNwYOHvLbo6yLWBme6msVAhIV9KOp22jDe9j837C48rcUiF93Jb7+plabbwTQt4iqi1EKxEfVvKi4tLsLBRhu0v583oQAfCf5aLwF3Vb5bPgGeY=`
+	rsaDecryptPubKeyRSAPKCS1  = `-----BEGIN PUBLIC KEY-----
+MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC8ronsBTX6GD5YhoE/v76+ZkWX
+0gODzAD+aCYIyTs4PiWruxlVSOtjwq2gRgUexE5Hsz8cxhFz5Db8qFXBsA+GgXjB
+yQuVbBw04SCKHgc0zhbcWonV3Rk03pjVB1HfuxcDRja8JZontfMAJyPNJovPu3rI
+i8npSC+T5g7Fq9UCbQIDAQAB
+-----END PUBLIC KEY-----
+
+`
 )
 
+func rsaEncryptOAEP(t testing.TB, publicKeyPEM []byte, hash, plaintext string) []byte {
+	t.Helper()
+	block, _ := pem.Decode(publicKeyPEM)
+	if block == nil || block.Type != "PUBLIC KEY" {
+		t.Fatalf("failed to decode PEM block containing public key")
+	}
+
+	pub, err := x509.ParsePKIXPublicKey(block.Bytes)
+	if err != nil {
+		t.Fatalf("failed to parse DER encoded public key: %v", err)
+	}
+
+	rsaPub, ok := pub.(*rsa.PublicKey)
+	if !ok {
+		t.Fatalf("not RSA public key")
+	}
+	ciphertext, err := rsa.EncryptOAEP(getHash(hash), rand.Reader, rsaPub, []byte(plaintext), nil)
+	require.NoError(t, err)
+	return ciphertext
+}
+
 func TestExecute(t *testing.T) {
 	tbl := []struct {
 		name                string
@@ -507,6 +588,45 @@ func TestExecute(t *testing.T) {
 				"foo": "1234",
 			},
 		},
+		{
+			name: "rsa decrypt rsa-oaep sha1 pkcs8 data base64",
+			tpl: map[string][]byte{
+				"data_decrypted": []byte(`{{ .private_key | rsaDecrypt "RSA-OAEP" "SHA1" (.data_crypted_base64 | b64dec) }}`),
+			},
+			data: map[string][]byte{
+				"private_key":         []byte(rsaDecryptPKRSAPKCS8),
+				"data_crypted_base64": []byte(rsaDecryptDataPKCS8Base64),
+			},
+			expectedData: map[string][]byte{
+				"data_decrypted": []byte("a1b2c3d4"),
+			},
+		},
+		{
+			name: "rsa decrypt rsa-oaep sha256 pkcs1 data base64",
+			tpl: map[string][]byte{
+				"data_decrypted": []byte(`{{ .private_key | rsaDecrypt "RSA-OAEP" "SHA256" (.data_crypted_base64 | b64dec) }}`),
+			},
+			data: map[string][]byte{
+				"private_key":         []byte(rsaDecryptPKRSAPKCS1),
+				"data_crypted_base64": []byte(rsaDecryptDataPKCS1Base64),
+			},
+			expectedData: map[string][]byte{
+				"data_decrypted": []byte("hellopkcs1sha256"),
+			},
+		},
+		{
+			name: "rsa decrypt rsa-oaep sha256 pkcs1 data bin",
+			tpl: map[string][]byte{
+				"data_decrypted": []byte(`{{ .private_key | rsaDecrypt "RSA-OAEP" "SHA256" .data_crypted_bin }}`),
+			},
+			data: map[string][]byte{
+				"private_key":      []byte(rsaDecryptPKRSAPKCS1),
+				"data_crypted_bin": rsaEncryptOAEP(t, []byte(rsaDecryptPubKeyRSAPKCS1), "SHA256", "hellopkcs1sha256"),
+			},
+			expectedData: map[string][]byte{
+				"data_decrypted": []byte("hellopkcs1sha256"),
+			},
+		},
 	}
 
 	for i := range tbl {