Browse Source

feat: expose more values for custom metadata for vault v2 (#6652)

Signed-off-by: Gergely Brautigam <182850+Skarlso@users.noreply.github.com>
Co-authored-by: Jean-Philippe Evrard <jean-philippe.evrard+rochepub@external.roche.com>
Gergely Bräutigam 1 week ago
parent
commit
0fb157559b

+ 11 - 0
docs/provider/hashicorp-vault.md

@@ -100,6 +100,17 @@ spec:
 
 Keep in mind that fetching the labels with `metadataPolicy: Fetch` only works with KV sercrets engine version v2.
 
+In addition to the labels defined under `custom_metadata`, `metadataPolicy: Fetch` also exposes the following system metadata of the secret: `created_time`, `current_version` and `delete_version_after`. All values are returned as strings. If a `custom_metadata` label uses the same name as one of these keys, the `custom_metadata` value takes precedence.
+
+```yaml
+  # metadataPolicy to fetch the current version of the secret
+  - secretKey: version
+    remoteRef:
+      metadataPolicy: Fetch
+      key: foo
+      property: current_version
+```
+
 #### Fetching Raw Values
 
 You can fetch all key/value pairs for a given path If you leave the `remoteRef.property` empty. This returns the json-encoded secret value for that path.

+ 15 - 2
providers/v1/vault/client_get.go

@@ -41,6 +41,8 @@ const (
 	errSecretKeyFmt                 = "cannot find secret data for key: %q"
 )
 
+var systemMetadataKeys = []string{"created_time", "current_version", "delete_version_after"}
+
 // GetSecret supports two types:
 //  1. get the full secret as json-encoded value
 //     by leaving the ref.Property empty.
@@ -200,16 +202,27 @@ func (c *client) readSecretMetadata(ctx context.Context, path string) (map[strin
 	if secret == nil {
 		return nil, errors.New(errNotFound)
 	}
+	if c.store.Version == esv1.VaultKVStoreV2 {
+		for _, key := range systemMetadataKeys {
+			if v, ok := secret.Data[key]; ok && v != nil {
+				metadata[key] = fmt.Sprintf("%v", v)
+			}
+		}
+	}
 	t, ok := secret.Data["custom_metadata"]
 	if !ok {
-		return nil, nil
+		return metadata, nil
 	}
 	d, ok := t.(map[string]any)
 	if !ok {
 		return metadata, nil
 	}
 	for k, v := range d {
-		metadata[k] = v.(string)
+		if s, ok := v.(string); ok {
+			metadata[k] = s
+		} else {
+			metadata[k] = fmt.Sprintf("%v", v)
+		}
 	}
 	return metadata, nil
 }

+ 66 - 0
providers/v1/vault/client_get_test.go

@@ -60,6 +60,23 @@ func TestGetSecret(t *testing.T) {
 		},
 		"json_number": json.Number("42"),
 	}
+	metadataWithSystemVal := map[string]any{
+		"custom_metadata": map[string]any{
+			"access_key":   "access_key",
+			"created_time": "custom-time",
+		},
+		"created_time":         "2026-04-27T12:32:50.049520998Z",
+		"current_version":      json.Number("2"),
+		"delete_version_after": "0s",
+		"max_versions":         json.Number("0"),
+		"cas_required":         false,
+	}
+	metadataNilCustomVal := map[string]any{
+		"custom_metadata":      nil,
+		"created_time":         "2026-04-27T12:32:50.049520998Z",
+		"current_version":      json.Number("2"),
+		"delete_version_after": "0s",
+	}
 
 	type args struct {
 		store    *esv1.VaultProvider
@@ -329,6 +346,55 @@ func TestGetSecret(t *testing.T) {
 				err: errors.New(errUnsupportedMetadataKvVersion),
 			},
 		},
+		"ReadSecretMetadataWithSystemMetadata": {
+			reason: "Should return system metadata alongside custom metadata, custom keys taking precedence",
+			args: args{
+				store: makeValidSecretStoreWithVersion(esv1.VaultKVStoreV2).Spec.Provider.Vault,
+				data: esv1.ExternalSecretDataRemoteRef{
+					MetadataPolicy: "Fetch",
+				},
+				vLogical: &fake.Logical{
+					ReadWithDataWithContextFn: fake.NewReadMetadataWithContextFn(metadataWithSystemVal, nil),
+				},
+			},
+			want: want{
+				err: nil,
+				val: []byte(`{"access_key":"access_key","created_time":"custom-time","current_version":"2","delete_version_after":"0s"}`),
+			},
+		},
+		"ReadSecretMetadataSystemProperty": {
+			reason: "Should return a single system metadata value via property",
+			args: args{
+				store: makeValidSecretStoreWithVersion(esv1.VaultKVStoreV2).Spec.Provider.Vault,
+				data: esv1.ExternalSecretDataRemoteRef{
+					MetadataPolicy: "Fetch",
+					Property:       "current_version",
+				},
+				vLogical: &fake.Logical{
+					ReadWithDataWithContextFn: fake.NewReadMetadataWithContextFn(metadataWithSystemVal, nil),
+				},
+			},
+			want: want{
+				err: nil,
+				val: []byte("2"),
+			},
+		},
+		"ReadSecretMetadataNilCustomMetadata": {
+			reason: "Should return system metadata when custom_metadata is null",
+			args: args{
+				store: makeValidSecretStoreWithVersion(esv1.VaultKVStoreV2).Spec.Provider.Vault,
+				data: esv1.ExternalSecretDataRemoteRef{
+					MetadataPolicy: "Fetch",
+				},
+				vLogical: &fake.Logical{
+					ReadWithDataWithContextFn: fake.NewReadMetadataWithContextFn(metadataNilCustomVal, nil),
+				},
+			},
+			want: want{
+				err: nil,
+				val: []byte(`{"created_time":"2026-04-27T12:32:50.049520998Z","current_version":"2","delete_version_after":"0s"}`),
+			},
+		},
 	}
 
 	for name, tc := range cases {