Add support for custom kv2 metadata.

Original pull request: gh-808
Closes gh-789
This commit is contained in:
Jeroen Willemsen
2023-08-14 08:45:36 +02:00
committed by Mark Paluch
parent 5cc5254b38
commit c5a087acef
6 changed files with 129 additions and 13 deletions

View File

@@ -98,6 +98,7 @@ class VaultKeyValueMetadataTemplate implements VaultKeyValueMetadataOperations {
.oldestVersion(Integer.parseInt(String.valueOf(metadataResponse.get("oldest_version"))))
.updatedTime(toInstant((String) metadataResponse.get("updated_time")))
.versions(buildVersions((Map) metadataResponse.get("versions")))
.customMetadata((Map) metadataResponse.get("custom_metadata"))
.build();
}
@@ -115,13 +116,18 @@ class VaultKeyValueMetadataTemplate implements VaultKeyValueMetadataOperations {
Instant deletionTime = toInstant((String) versionData.get("deletion_time"));
boolean destroyed = (Boolean) versionData.get("destroyed");
Versioned.Version kvVersion = Versioned.Version.from(Integer.parseInt(version));
return Versioned.Metadata.builder()
Versioned.Metadata.MetadataBuilder builder = Versioned.Metadata.builder()
.createdAt(createdTime)
.deletedAt(deletionTime)
.destroyed(destroyed)
.version(kvVersion)
.build();
.version(kvVersion);
if (versionData.get("custom_metadata") != null) {
Map<String, String> customMetadata = (Map<String, String>) versionData.get("custom_metadata");
builder.customMetadata(customMetadata);
}
return builder.build();
}
@Nullable

View File

@@ -178,6 +178,11 @@ public class VaultVersionedKeyValueTemplate extends VaultKeyValue2Accessor imple
Integer version = (Integer) responseMetadata.get("version");
builder.version(Version.from(version));
if (responseMetadata.get("custom_metadata") != null) {
Map<String, String> customMetadata = (Map<String, String>) responseMetadata.get("custom_metadata");
builder.customMetadata(customMetadata);
}
return builder.build();
}

View File

@@ -16,6 +16,7 @@
package org.springframework.vault.support;
import java.time.Duration;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonProperty;
@@ -41,11 +42,16 @@ public class VaultMetadataRequest {
@JsonProperty("delete_version_after")
private final String deleteVersionAfter;
private VaultMetadataRequest(int maxVersions, boolean casRequired, @Nullable Duration deleteVersionAfter) {
@JsonProperty("custom_metadata")
private final Map<String, String> customMetadata;
private VaultMetadataRequest(int maxVersions, boolean casRequired, @Nullable Duration deleteVersionAfter,
@Nullable Map<String, String> customMetadata) {
this.maxVersions = maxVersions;
this.casRequired = casRequired;
this.deleteVersionAfter = DurationParser
.formatDuration(deleteVersionAfter != null ? deleteVersionAfter : Duration.ZERO);
this.customMetadata = customMetadata;
}
public static VaultMetadataRequestBuilder builder() {
@@ -75,6 +81,11 @@ public class VaultMetadataRequest {
return this.deleteVersionAfter;
}
@Nullable
public Map<String, String> getCustomMetadata() {
return this.customMetadata;
}
public static class VaultMetadataRequestBuilder {
private int maxVersions;
@@ -84,6 +95,9 @@ public class VaultMetadataRequest {
@Nullable
private Duration deleteVersionAfter;
@Nullable
private Map<String, String> customMetadata;
/**
* Set the number of versions to keep per key.
* @param maxVersions
@@ -115,11 +129,22 @@ public class VaultMetadataRequest {
return this;
}
/**
* Sets the custom Metadata for the metadatarequest
* @param customMetadata
* @return {@link VaultMetadataRequest}
*/
public VaultMetadataRequestBuilder customMetadata(Map<String, String> customMetadata) {
this.customMetadata = customMetadata;
return this;
}
/**
* @return a new {@link VaultMetadataRequest}
*/
public VaultMetadataRequest build() {
return new VaultMetadataRequest(this.maxVersions, this.casRequired, this.deleteVersionAfter);
return new VaultMetadataRequest(this.maxVersions, this.casRequired, this.deleteVersionAfter,
this.customMetadata);
}
}

View File

@@ -19,6 +19,7 @@ import java.time.Duration;
import java.time.Instant;
import java.time.Period;
import java.util.List;
import java.util.Map;
import org.springframework.lang.Nullable;
@@ -46,9 +47,11 @@ public class VaultMetadataResponse {
private final List<Versioned.Metadata> versions;
private final Map<String, String> customMetadata;
private VaultMetadataResponse(boolean casRequired, Instant createdTime, int currentVersion,
Duration deleteVersionAfter, int maxVersions, int oldestVersion, Instant updatedTime,
List<Versioned.Metadata> versions) {
List<Versioned.Metadata> versions, Map<String, String> customMetadata) {
this.casRequired = casRequired;
this.createdTime = createdTime;
this.currentVersion = currentVersion;
@@ -57,6 +60,7 @@ public class VaultMetadataResponse {
this.oldestVersion = oldestVersion;
this.updatedTime = updatedTime;
this.versions = versions;
this.customMetadata = customMetadata;
}
public static VaultMetadataResponseBuilder builder() {
@@ -93,6 +97,14 @@ public class VaultMetadataResponse {
return this.deleteVersionAfter;
}
/**
* @return KV of customMetadata. Entries can be any arbitrary key-value pairs
*/
@Nullable
public Map<String, String> getCustomMetadata() {
return this.customMetadata;
}
/**
* @return max secret versions accepted by this key
*/
@@ -145,6 +157,8 @@ public class VaultMetadataResponse {
private List<Versioned.Metadata> versions;
private Map<String, String> customMetadata;
public VaultMetadataResponseBuilder casRequired(boolean casRequired) {
this.casRequired = casRequired;
return this;
@@ -185,9 +199,15 @@ public class VaultMetadataResponse {
return this;
}
public VaultMetadataResponseBuilder customMetadata(Map<String, String> customMetadata) {
this.customMetadata = customMetadata;
return this;
}
public VaultMetadataResponse build() {
return new VaultMetadataResponse(this.casRequired, this.createdTime, this.currentVersion,
this.deleteVersionAfter, this.maxVersions, this.oldestVersion, this.updatedTime, this.versions);
this.deleteVersionAfter, this.maxVersions, this.oldestVersion, this.updatedTime, this.versions,
this.customMetadata);
}
}

View File

@@ -16,6 +16,7 @@
package org.springframework.vault.support;
import java.time.Instant;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
@@ -32,7 +33,7 @@ import org.springframework.util.Assert;
* <li>Versioned secrets with {@link Metadata} attached
* {@link Versioned#create(Object, Metadata)}</li>
* </ul>
*
* <p>
* Versioned secrets follow a lifecycle that spans from creation to destruction:
*
* <ol>
@@ -44,9 +45,9 @@ import org.springframework.util.Assert;
* </ol>
*
* @author Mark Paluch
* @since 2.1
* @see Version
* @see Metadata
* @since 2.1
*/
public class Versioned<T> {
@@ -223,11 +224,15 @@ public class Versioned<T> {
private final Version version;
private Metadata(Instant createdAt, @Nullable Instant deletedAt, boolean destroyed, Version version) {
private final @Nullable Map<String, String> customMetadata;
private Metadata(Instant createdAt, @Nullable Instant deletedAt, boolean destroyed, Version version,
@Nullable Map<String, String> customMetadata) {
this.createdAt = createdAt;
this.deletedAt = deletedAt;
this.destroyed = destroyed;
this.version = version;
this.customMetadata = customMetadata;
}
/**
@@ -275,11 +280,29 @@ public class Versioned<T> {
return this.destroyed;
}
/**
* @return Metadata .
*/
@Nullable
public Map<String, String> getCustomMetadata() {
return customMetadata;
}
@Override
public String toString() {
String customMetadataString = "";
if (customMetadata != null && customMetadata.keySet().size() > 0) {
StringBuilder metadataPrintBuilder = new StringBuilder(", customMetadata=Map[");
for (String key : customMetadata.keySet()) {
metadataPrintBuilder.append(key).append(":").append(customMetadata.get(key)).append(" ");
}
metadataPrintBuilder.append("]");
customMetadataString = metadataPrintBuilder.toString();
}
return getClass().getSimpleName() + " [createdAt=" + this.createdAt + ", deletedAt=" + this.deletedAt
+ ", destroyed=" + this.destroyed + ", version=" + this.version + ']';
+ ", destroyed=" + this.destroyed + ", version=" + this.version + customMetadataString + ']';
}
/**
@@ -295,6 +318,8 @@ public class Versioned<T> {
private @Nullable Version version;
private @Nullable Map<String, String> customMetadata;
private MetadataBuilder() {
}
@@ -354,6 +379,15 @@ public class Versioned<T> {
return this;
}
public MetadataBuilder customMetadata(Map<String, String> customMetadata) {
Assert.notNull(customMetadata, "customMetadata should not be null");
Assert.notEmpty(customMetadata.keySet(), "customMetadata should have at least one key");
Assert.notEmpty(customMetadata.values(), "customMetadata should have at least one value");
this.customMetadata = customMetadata;
return this;
}
/**
* Build the {@link Versioned.Metadata} object. Requires
* {@link #createdAt(Instant)} and {@link #version(Versioned.Version)} to be
@@ -365,7 +399,7 @@ public class Versioned<T> {
Assert.notNull(this.createdAt, "CreatedAt must not be null");
Assert.notNull(this.version, "Version must not be null");
return new Metadata(this.createdAt, this.deletedAt, this.destroyed, this.version);
return new Metadata(this.createdAt, this.deletedAt, this.destroyed, this.version, this.customMetadata);
}
}

View File

@@ -17,6 +17,7 @@ package org.springframework.vault.core;
import java.time.Instant;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
@@ -29,6 +30,7 @@ import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.vault.VaultException;
import org.springframework.vault.domain.Person;
import org.springframework.vault.support.VaultMetadataRequest;
import org.springframework.vault.support.Versioned;
import org.springframework.vault.support.Versioned.Metadata;
import org.springframework.vault.support.Versioned.Version;
@@ -103,6 +105,30 @@ class VaultVersionedKeyValueTemplateIntegrationTests extends IntegrationTestSupp
.hasMessageContaining("check-and-set parameter did not match the current version");
}
@Test
void shouldWriteSecretWithCustomMetadata() {
Person person = new Person();
person.setFirstname("Walter");
person.setLastname("White");
String key = UUID.randomUUID().toString();
Map<String, String> customMetadata = new HashMap<>();
customMetadata.put("foo", "bar");
customMetadata.put("uid", "werwer");
this.versionedOperations.put(key, Versioned.create(person));
VaultMetadataRequest request = VaultMetadataRequest.builder().customMetadata(customMetadata).build();
this.versionedOperations.opsForKeyValueMetadata().put(key, request);
Versioned<Person> versioned = this.versionedOperations.get(key, Person.class);
assertThat(versioned.getMetadata().getCustomMetadata().get("foo")).isEqualTo("bar");
}
@Test
void shouldReadAndWriteVersionedSecret() {