Kaynağa Gözat

feat: enhance VaultDynamicSecret GET method to support parameters from the spec (#6267)

Co-authored-by: Gergely Brautigam <182850+Skarlso@users.noreply.github.com>
Co-authored-by: Gergely Bräutigam <gergely.brautigam@sap.com>
Alex Samorukov 1 ay önce
ebeveyn
işleme
95cfe9cfb2

+ 1 - 1
apis/generators/v1alpha1/types_vault.go

@@ -33,7 +33,7 @@ type VaultDynamicSecretSpec struct {
 	// Vault API method to use (GET/POST/other)
 	Method string `json:"method,omitempty"`
 
-	// Parameters to pass to Vault write (for non-GET methods)
+	// Parameters to pass to Vault for write and Get calls. GET calls only support string value types.
 	Parameters *apiextensions.JSON `json:"parameters,omitempty"`
 
 	// Result type defines which data is returned from the generator.

+ 2 - 2
config/crds/bases/generators.external-secrets.io_clustergenerators.yaml

@@ -1178,8 +1178,8 @@ spec:
                         description: Vault API method to use (GET/POST/other)
                         type: string
                       parameters:
-                        description: Parameters to pass to Vault write (for non-GET
-                          methods)
+                        description: Parameters to pass to Vault for write and Get
+                          calls. GET calls only support string value types.
                         x-kubernetes-preserve-unknown-fields: true
                       path:
                         description: Vault path to obtain the dynamic secret from

+ 2 - 1
config/crds/bases/generators.external-secrets.io_vaultdynamicsecrets.yaml

@@ -58,7 +58,8 @@ spec:
                 description: Vault API method to use (GET/POST/other)
                 type: string
               parameters:
-                description: Parameters to pass to Vault write (for non-GET methods)
+                description: Parameters to pass to Vault for write and Get calls.
+                  GET calls only support string value types.
                 x-kubernetes-preserve-unknown-fields: true
               path:
                 description: Vault path to obtain the dynamic secret from

+ 2 - 2
deploy/crds/bundle.yaml

@@ -25895,7 +25895,7 @@ spec:
                           description: Vault API method to use (GET/POST/other)
                           type: string
                         parameters:
-                          description: Parameters to pass to Vault write (for non-GET methods)
+                          description: Parameters to pass to Vault for write and Get calls. GET calls only support string value types.
                           x-kubernetes-preserve-unknown-fields: true
                         path:
                           description: Vault path to obtain the dynamic secret from
@@ -28585,7 +28585,7 @@ spec:
                   description: Vault API method to use (GET/POST/other)
                   type: string
                 parameters:
-                  description: Parameters to pass to Vault write (for non-GET methods)
+                  description: Parameters to pass to Vault for write and Get calls. GET calls only support string value types.
                   x-kubernetes-preserve-unknown-fields: true
                 path:
                   description: Vault path to obtain the dynamic secret from

+ 2 - 2
docs/api/spec.md

@@ -28548,7 +28548,7 @@ k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.JSON
 </em>
 </td>
 <td>
-<p>Parameters to pass to Vault write (for non-GET methods)</p>
+<p>Parameters to pass to Vault for write and Get calls. GET calls only support string value types.</p>
 </td>
 </tr>
 <tr>
@@ -28701,7 +28701,7 @@ k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.JSON
 </em>
 </td>
 <td>
-<p>Parameters to pass to Vault write (for non-GET methods)</p>
+<p>Parameters to pass to Vault for write and Get calls. GET calls only support string value types.</p>
 </td>
 </tr>
 <tr>

+ 15 - 1
generators/v1/vault/vault.go

@@ -116,7 +116,21 @@ func (g *Generator) fetchVaultSecret(ctx context.Context, res *genv1alpha1.Vault
 	)
 
 	if res.Spec.Method == "" || res.Spec.Method == "GET" {
-		result, err = cl.Logical().ReadWithDataWithContext(ctx, res.Spec.Path, nil)
+		var raw map[string]any
+		if res.Spec.Parameters != nil {
+			if err := json.Unmarshal(res.Spec.Parameters.Raw, &raw); err != nil {
+				return nil, fmt.Errorf("failed to parse parameters for GET call: %w", err)
+			}
+		}
+		params := make(map[string][]string, len(raw))
+		for k, v := range raw {
+			s, ok := v.(string)
+			if !ok {
+				return nil, fmt.Errorf("unsupported type for GET parameter %q: %T", k, v)
+			}
+			params[k] = []string{s}
+		}
+		result, err = cl.Logical().ReadWithDataWithContext(ctx, res.Spec.Path, params)
 	} else if res.Spec.Method == "LIST" {
 		result, err = cl.Logical().ListWithContext(ctx, res.Spec.Path)
 	} else if res.Spec.Method == "DELETE" {

+ 54 - 0
generators/v1/vault/vault_test.go

@@ -412,3 +412,57 @@ spec:
 		})
 	}
 }
+
+func TestVaultDynamicSecretGetParameters(t *testing.T) {
+	sa := &corev1.ServiceAccount{
+		ObjectMeta: metav1.ObjectMeta{Name: "testing", Namespace: "testing"},
+		Secrets:    []corev1.ObjectReference{{Name: "test"}},
+	}
+	spec := func(params string) *apiextensions.JSON {
+		return &apiextensions.JSON{Raw: []byte(`apiVersion: generators.external-secrets.io/v1alpha1
+kind: VaultDynamicSecret
+spec:
+  provider:
+    auth:
+      kubernetes:
+        role: test
+        serviceAccountRef:
+          name: "testing"
+  method: GET
+  parameters:
+    ` + params + `
+  path: "github/token/example"`)}
+	}
+
+	t.Run("ForwardsStringParams", func(t *testing.T) {
+		var got map[string][]string
+		clientFn := fake.ModifiableClientWithLoginMock(func(cl *fake.VaultClient) {
+			cl.MockLogical.ReadWithDataWithContextFn = func(_ context.Context, _ string, data map[string][]string) (*vaultapi.Secret, error) {
+				got = data
+				return &vaultapi.Secret{Data: map[string]any{"key": "value"}}, nil
+			}
+		})
+		c := &provider.Provider{NewVaultClient: clientFn}
+		_, _, err := (&Generator{}).generate(context.Background(),
+			c, spec(`scope: "applied-permissions/user"`),
+			clientfake.NewClientBuilder().WithObjects(sa).Build(),
+			utilfake.NewCreateTokenMock().WithToken("ok"), "testing")
+		if err != nil {
+			t.Fatalf("unexpected error: %v", err)
+		}
+		if diff := cmp.Diff(map[string][]string{"scope": {"applied-permissions/user"}}, got); diff != "" {
+			t.Errorf("forwarded params mismatch:\n%s", diff)
+		}
+	})
+
+	t.Run("RejectsNonStringParams", func(t *testing.T) {
+		c := &provider.Provider{NewVaultClient: fake.ClientWithLoginMock}
+		_, _, err := (&Generator{}).generate(context.Background(),
+			c, spec(`ttl: 60`),
+			clientfake.NewClientBuilder().WithObjects(sa).Build(),
+			utilfake.NewCreateTokenMock().WithToken("ok"), "testing")
+		if err == nil || err.Error() != `unsupported type for GET parameter "ttl": float64` {
+			t.Errorf("want unsupported-type error, got: %v", err)
+		}
+	})
+}