Skip to content

Commit f4fd3ee

Browse files
committed
Support versioned key/value secrets engine using Vault repositories.
We now support CRUD operations using key/value secrets engine version 2 and optimistic locking through Vault's cas mechanism. Closes gh-593
1 parent 67ea636 commit f4fd3ee

File tree

14 files changed

+1022
-83
lines changed

14 files changed

+1022
-83
lines changed

spring-vault-core/pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@
100100
<dependency>
101101
<groupId>org.springframework.data</groupId>
102102
<artifactId>spring-data-keyvalue</artifactId>
103+
<version>2.7.1-SNAPSHOT</version>
103104
<optional>true</optional>
104105
</dependency>
105106

spring-vault-core/src/main/java/org/springframework/vault/core/util/KeyValueDelegate.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ private MountInfo doGetMountInfo(String path) {
118118
return MountInfo.from((String) data.get("path"), (Map) data.get("options"));
119119
}
120120

121-
private MountInfo getMountInfo(String path) {
121+
public MountInfo getMountInfo(String path) {
122122

123123
MountInfo mountInfo = this.mountInfo.get(path);
124124

@@ -136,15 +136,15 @@ private MountInfo getMountInfo(String path) {
136136
return mountInfo;
137137
}
138138

139-
static class MountInfo {
139+
public static class MountInfo {
140140

141141
static final MountInfo UNAVAILABLE = new MountInfo("", Collections.emptyMap(), false);
142142

143-
final String path;
143+
private final String path;
144144

145-
final @Nullable Map<String, Object> options;
145+
private final @Nullable Map<String, Object> options;
146146

147-
final boolean available;
147+
private final boolean available;
148148

149149
private MountInfo(String path, @Nullable Map<String, Object> options, boolean available) {
150150
this.path = path;

spring-vault-core/src/main/java/org/springframework/vault/repository/convert/MappingVaultConverter.java

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -443,8 +443,7 @@ private void writeProperties(VaultPersistentEntity<?> entity, PersistentProperty
443443
writePropertyInternal(value, sink, prop);
444444
}
445445
else {
446-
447-
sink.put(prop, getPotentiallyConvertedSimpleWrite(value));
446+
sink.put(prop, getPotentiallyConvertedSimpleWrite(value, prop.getTypeInformation().getType()));
448447
}
449448
}
450449
}
@@ -529,7 +528,7 @@ private List<Object> writeCollectionInternal(Collection<?> source, @Nullable Typ
529528
Class<?> elementType = element == null ? null : element.getClass();
530529

531530
if (elementType == null || this.conversions.isSimpleType(elementType)) {
532-
sink.add(getPotentiallyConvertedSimpleWrite(element));
531+
sink.add(getPotentiallyConvertedSimpleWrite(element, elementType == null ? Object.class : elementType));
533532
}
534533
else if (element instanceof Collection || elementType.isArray()) {
535534
sink.add(writeCollectionInternal(asCollection(element), componentType, new ArrayList<>()));
@@ -627,10 +626,11 @@ protected void addCustomTypeKeyIfNecessary(@Nullable TypeInformation<?> type, Ob
627626
* arbitrary simple Vault type. Returns the converted value if so. If not, we perform
628627
* special enum handling or simply return the value as is.
629628
* @param value the value to write.
629+
* @param targetType
630630
* @return the converted value. Can be {@literal null}.
631631
*/
632632
@Nullable
633-
private Object getPotentiallyConvertedSimpleWrite(@Nullable Object value) {
633+
private Object getPotentiallyConvertedSimpleWrite(@Nullable Object value, Class<?> targetType) {
634634

635635
if (value == null) {
636636
return null;
@@ -650,7 +650,15 @@ private Object getPotentiallyConvertedSimpleWrite(@Nullable Object value) {
650650
return asCollection(value);
651651
}
652652

653-
return Enum.class.isAssignableFrom(value.getClass()) ? ((Enum<?>) value).name() : value;
653+
if (Enum.class.isAssignableFrom(value.getClass())) {
654+
return ((Enum<?>) value).name();
655+
}
656+
657+
if (!ClassUtils.isAssignableValue(targetType, value)) {
658+
return this.conversionService.convert(value, targetType);
659+
}
660+
661+
return value;
654662
}
655663

656664
/**

spring-vault-core/src/main/java/org/springframework/vault/repository/convert/SecretDocument.java

Lines changed: 71 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,10 @@
1717

1818
import java.util.LinkedHashMap;
1919
import java.util.Map;
20-
import java.util.Objects;
2120

2221
import org.springframework.lang.Nullable;
2322
import org.springframework.util.Assert;
23+
import org.springframework.util.ObjectUtils;
2424
import org.springframework.vault.support.VaultResponse;
2525

2626
/**
@@ -42,6 +42,8 @@ public class SecretDocument {
4242

4343
private final Map<String, Object> body;
4444

45+
private @Nullable Integer version;
46+
4547
/**
4648
* Create a new, empty {@link SecretDocument}.
4749
*/
@@ -70,6 +72,22 @@ public SecretDocument(@Nullable String id, Map<String, Object> body) {
7072
this.body = body;
7173
}
7274

75+
/**
76+
* Create a new {@link SecretDocument} given an {@code id} and {@link Map body map}.
77+
* @param id may be {@literal null}.
78+
* @param version for versioned secrets, may be {@literal null} if not available.
79+
* @param body must not be {@literal null}.
80+
* @since 2.4
81+
*/
82+
public SecretDocument(@Nullable String id, @Nullable Integer version, Map<String, Object> body) {
83+
84+
Assert.notNull(body, "Body must not be null");
85+
86+
this.id = id;
87+
this.version = version;
88+
this.body = body;
89+
}
90+
7391
public SecretDocument(String id) {
7492
this(id, new LinkedHashMap<>());
7593
}
@@ -87,21 +105,55 @@ public static SecretDocument from(@Nullable String id, VaultResponse vaultRespon
87105
}
88106

89107
/**
90-
* @return the Id or {@literal null} if the Id is not set.
108+
* @return the identifier or {@literal null} if the identifier is not set.
91109
*/
92110
@Nullable
93111
public String getId() {
94112
return this.id;
95113
}
96114

97115
/**
98-
* Set the Id.
116+
* Return the required Id or throw {@link IllegalStateException} if the Id is not set.
117+
* @return the required Id.
118+
* @throws IllegalStateException if the Id is not set.
119+
* @since 2.4
120+
*/
121+
public String getRequiredId() {
122+
123+
String id = getId();
124+
125+
if (id == null) {
126+
throw new IllegalStateException("Id is not set");
127+
}
128+
129+
return id;
130+
}
131+
132+
/**
133+
* Set the identifier value.
99134
* @param id may be {@literal null}.
100135
*/
101136
public void setId(@Nullable String id) {
102137
this.id = id;
103138
}
104139

140+
/**
141+
* @return the version number, may be {@code null} if absent.
142+
* @since 2.4
143+
*/
144+
@Nullable
145+
public Integer getVersion() {
146+
return version;
147+
}
148+
149+
/**
150+
* @param version
151+
* @since 2.4
152+
*/
153+
public void setVersion(@Nullable Integer version) {
154+
this.version = version;
155+
}
156+
105157
/**
106158
* @return the body of this {@link SecretDocument}
107159
*/
@@ -130,17 +182,28 @@ public void put(String key, Object value) {
130182

131183
@Override
132184
public boolean equals(Object o) {
133-
if (this == o)
185+
if (this == o) {
134186
return true;
135-
if (!(o instanceof SecretDocument))
187+
}
188+
if (!(o instanceof SecretDocument)) {
136189
return false;
190+
}
137191
SecretDocument that = (SecretDocument) o;
138-
return Objects.equals(this.id, that.id) && Objects.equals(this.body, that.body);
192+
if (!ObjectUtils.nullSafeEquals(this.id, that.id)) {
193+
return false;
194+
}
195+
if (!ObjectUtils.nullSafeEquals(this.body, that.body)) {
196+
return false;
197+
}
198+
return ObjectUtils.nullSafeEquals(this.version, that.version);
139199
}
140200

141201
@Override
142202
public int hashCode() {
143-
return Objects.hash(this.id, this.body);
203+
int result = ObjectUtils.nullSafeHashCode(this.id);
204+
result = 31 * result + ObjectUtils.nullSafeHashCode(this.body);
205+
result = 31 * result + ObjectUtils.nullSafeHashCode(this.version);
206+
return result;
144207
}
145208

146209
@Override
@@ -149,6 +212,7 @@ public String toString() {
149212
sb.append(getClass().getSimpleName());
150213
sb.append(" [id='").append(this.id).append('\'');
151214
sb.append(", body=").append(this.body);
215+
sb.append(", version=").append(this.version);
152216
sb.append(']');
153217
return sb.toString();
154218
}

spring-vault-core/src/main/java/org/springframework/vault/repository/convert/SecretDocumentAccessor.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,11 @@ void put(VaultPersistentProperty prop, @Nullable Object value) {
8787
return;
8888
}
8989

90+
if (prop.isVersionProperty()) {
91+
this.document.setVersion(value == null ? null : ((Number) value).intValue());
92+
return;
93+
}
94+
9095
if (!fieldName.contains(".")) {
9196
this.body.put(fieldName, value);
9297
return;
@@ -125,6 +130,10 @@ Object get(VaultPersistentProperty property) {
125130
return this.document.getId();
126131
}
127132

133+
if (property.isVersionProperty()) {
134+
return this.document.getVersion();
135+
}
136+
128137
if (!fieldName.contains(".")) {
129138
return this.body.get(fieldName);
130139
}
@@ -159,6 +168,10 @@ boolean hasValue(VaultPersistentProperty property) {
159168
return StringUtils.hasText(this.document.getId());
160169
}
161170

171+
if (property.isVersionProperty()) {
172+
return this.document.getVersion() != null;
173+
}
174+
162175
String fieldName = property.getName();
163176

164177
if (!fieldName.contains(".")) {

0 commit comments

Comments
 (0)