Provide Key-Value backend API.

We now provide a VaultKeyValueOperations API to provide a uniform API for versioned and non-versioned Key-Value access.

Closes gh-246.
This commit is contained in:
Mark Paluch
2018-04-20 17:34:54 +02:00
parent 421a99bd08
commit 814f42afc2
13 changed files with 762 additions and 91 deletions

View File

@@ -0,0 +1,245 @@
/*
* Copyright 2018 the original author or authors.
*
* 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
*
* http://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 org.springframework.vault.core;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.function.BiFunction;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.vault.VaultException;
import org.springframework.vault.client.VaultResponses;
import org.springframework.vault.support.VaultResponse;
import org.springframework.vault.support.VaultResponseSupport;
import org.springframework.web.client.HttpStatusCodeException;
import org.springframework.web.client.RestOperations;
import org.springframework.web.client.RestTemplate;
/**
* Base class for {@link VaultVersionedKeyValueTemplate} and other Vault KV-accessing
* helpers, defining common
* <p/>
* Not intended to be used directly. See {@link VaultVersionedKeyValueTemplate}.
*
* @author Mark Paluch
* @since 2.1
*/
public abstract class VaultKeyValueAccessor implements VaultKeyValueOperationsSupport {
private final VaultOperations vaultOperations;
private final String path;
private final ObjectMapper mapper;
/**
* Create a new {@link VaultKeyValueAccessor} given {@link VaultOperations} and the
* mount {@code path}.
*
* @param vaultOperations must not be {@literal null}.
* @param path must not be empty or {@literal null}.
*/
public VaultKeyValueAccessor(VaultOperations vaultOperations, String path) {
Assert.notNull(vaultOperations, "VaultOperations must not be null");
Assert.hasText(path, "Path must not be empty");
this.vaultOperations = vaultOperations;
this.path = path;
this.mapper = extractObjectMapper(vaultOperations);
}
@Nullable
@Override
@SuppressWarnings("unchecked")
public List<String> list(String path) {
String pathToUse = path.equals("/") ? "" : path.endsWith("/") ? path
: (path + "/");
VaultListResponse read = doRead((restOperations, httpEntity) -> {
return restOperations.exchange(String.format("%s?list=true",
createBackendPath("metadata", pathToUse)), HttpMethod.GET,
httpEntity, VaultListResponse.class);
});
if (read == null) {
return Collections.emptyList();
}
return (List<String>) read.getRequiredData().get("keys");
}
@Override
public void delete(String path) {
Assert.hasText(path, "Path must not be empty");
doWithSession(((restOperations, httpHeaders) -> {
restOperations.exchange(createDataPath(path), HttpMethod.DELETE,
new HttpEntity<>(httpHeaders), Void.class);
return null;
}));
}
/**
* Read a secret at {@code path} and deserialize the nested {@literal data} element to
* the given {@link Class type}.
*
* @param path must not be {@literal null}.
* @param deserializeAs must not be {@literal null}.
* @param mappingFunction Mapping function to convert from the intermediate to the
* target data type. Must not be {@literal null}.
* @param <I> intermediate data type for {@literal data} deserialization.
* @param <T> return type. Value is created by the {@code mappingFunction}.
* @return mapped value.
*/
@Nullable
<I, T> T doRead(String path, Class<I> deserializeAs,
BiFunction<VaultResponseSupport<?>, I, T> mappingFunction) {
ParameterizedTypeReference<VaultResponseSupport<JsonNode>> ref = VaultResponses
.getTypeReference(JsonNode.class);
VaultResponseSupport<JsonNode> response = doRead(createDataPath(path), ref);
if (response != null) {
JsonNode jsonNode = response.getRequiredData().at("/data");
try {
I data = mapper.reader().readValue(jsonNode.traverse(), deserializeAs);
return mappingFunction.apply(response, data);
}
catch (IOException e) {
throw new VaultException("Cannot deserialize response", e);
}
}
return null;
}
@Nullable
<T> T doRead(String path, ParameterizedTypeReference<T> typeReference) {
return doRead((restOperations, httpEntity) -> {
return restOperations.exchange(path, HttpMethod.GET, httpEntity,
typeReference);
});
}
@Nullable
private <T> T doRead(
BiFunction<RestOperations, HttpEntity<?>, ResponseEntity<T>> callback) {
return doWithSession((restOperations, headers) -> {
try {
return callback.apply(restOperations, new HttpEntity<>(headers))
.getBody();
}
catch (HttpStatusCodeException e) {
if (e.getStatusCode() == HttpStatus.NOT_FOUND) {
return null;
}
throw VaultResponses.buildException(e, path);
}
});
}
@Nullable
VaultResponse doWrite(String path, Object body) {
Assert.hasText(path, "Path must not be empty");
try {
return doWithSession((restOperations, httpHeaders) -> {
return restOperations.exchange(path, HttpMethod.POST,
new HttpEntity<>(body, httpHeaders), VaultResponse.class)
.getBody();
});
}
catch (HttpStatusCodeException e) {
throw VaultResponses.buildException(e, path);
}
}
@Nullable
<T> T doWithSession(BiFunction<RestOperations, HttpHeaders, T> callback) {
return vaultOperations.doWithSession(restOperations -> {
HttpHeaders headers = new HttpHeaders();
headers.set("X-Vault-Kv-Client", "v2");
return callback.apply(restOperations, headers);
});
}
String createDataPath(String path) {
return createBackendPath("data", path);
}
String createBackendPath(String segment, String path) {
return String.format("%s/%s/%s", this.path, segment, path);
}
private static ObjectMapper extractObjectMapper(VaultOperations vaultOperations) {
Optional<ObjectMapper> mapper = vaultOperations
.doWithSession(operations -> {
if (operations instanceof RestTemplate) {
RestTemplate template = (RestTemplate) operations;
Optional<AbstractJackson2HttpMessageConverter> jackson2Converter = template
.getMessageConverters()
.stream()
//
.filter(AbstractJackson2HttpMessageConverter.class::isInstance) //
.map(AbstractJackson2HttpMessageConverter.class::cast) //
.findFirst();
return jackson2Converter
.map(AbstractJackson2HttpMessageConverter::getObjectMapper);
}
return Optional.empty();
});
return mapper.orElseGet(ObjectMapper::new);
}
}

View File

@@ -0,0 +1,59 @@
/*
* Copyright 2018 the original author or authors.
*
* 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
*
* http://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 org.springframework.vault.core;
import org.springframework.lang.Nullable;
import org.springframework.vault.support.VaultResponse;
import org.springframework.vault.support.VaultResponseSupport;
/**
* Interface that specifies a basic set of Vault operations using Vault's Key/Value secret
* backend. Paths used in this operations interface are relative and outgoing requests
* prepend paths with the according operation-specific prefix.
*
* @author Mark Paluch
* @since 2.1
*/
public interface VaultKeyValueOperations extends VaultKeyValueOperationsSupport {
/**
* Read the secret at {@code path}.
*
* @param path must not be {@literal null}.
* @return the data. May be {@literal null} if the path does not exist.
*/
@Nullable
VaultResponse get(String path);
/**
* Read the secret at {@code path}.
*
* @param path must not be {@literal null}.
* @param responseType must not be {@literal null}.
* @return the data. May be {@literal null} if the path does not exist.
*/
@Nullable
<T> VaultResponseSupport<T> get(String path, Class<T> responseType);
/**
* Write the secret at {@code path}.
*
* @param path must not be {@literal null}.
* @param body must not be {@literal null}.
* @return the resulting {@link VaultResponse}.
*/
void put(String path, Object body);
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright 2018 the original author or authors.
*
* 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
*
* http://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 org.springframework.vault.core;
import java.util.List;
import org.springframework.lang.Nullable;
/**
* Interface that specifies a basic set of Vault operations using Vault's Key/Value secret
* backend. Paths used in this operations interface are relative and outgoing requests
* prepend paths with the according operation-specific prefix.
* <p/>
*
* @author Mark Paluch
* @since 2.1
*/
public interface VaultKeyValueOperationsSupport {
/**
* Enumerate keys from a Vault path.
*
* @param path must not be {@literal null}.
* @return the data. May be {@literal null} if the path does not exist.
*/
@Nullable
List<String> list(String path);
/**
* Read the secret at {@code path}.
*
* @param path must not be {@literal null}.
* @return the data. May be {@literal null} if the path does not exist.
*/
@Nullable
Object get(String path);
/**
* Delete the secret at {@code path}.
*
* @param path must not be {@literal null}.
*/
void delete(String path);
}

View File

@@ -0,0 +1,93 @@
/*
* Copyright 2018 the original author or authors.
*
* 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
*
* http://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 org.springframework.vault.core;
import java.util.Collections;
import java.util.Map;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.vault.support.VaultResponse;
import org.springframework.vault.support.VaultResponseSupport;
/**
* Default implementation of {@link VaultKeyValueOperations}.
*
* @author Mark Paluch
* @since 2.1
*/
public class VaultKeyValueTemplate extends VaultKeyValueAccessor implements
VaultKeyValueOperations {
/**
* Create a new {@link VaultKeyValueTemplate} given {@link VaultOperations} and the
* mount {@code path}.
*
* @param vaultOperations must not be {@literal null}.
* @param path must not be empty or {@literal null}.
*/
public VaultKeyValueTemplate(VaultOperations vaultOperations, String path) {
super(vaultOperations, path);
}
@Nullable
@Override
public VaultResponse get(String path) {
Assert.hasText(path, "Path must not be empty");
return doRead(path, Map.class, (response, data) -> {
VaultResponse vaultResponse = new VaultResponse();
vaultResponse.setRenewable(response.isRenewable());
vaultResponse.setAuth(response.getAuth());
vaultResponse.setLeaseDuration(response.getLeaseDuration());
vaultResponse.setLeaseId(response.getLeaseId());
vaultResponse.setMetadata(response.getMetadata());
vaultResponse.setRequestId(response.getRequestId());
vaultResponse.setWarnings(response.getWarnings());
vaultResponse.setWrapInfo(response.getWrapInfo());
vaultResponse.setData(data);
return vaultResponse;
});
}
@Nullable
@Override
@SuppressWarnings("unchecked")
public <T> VaultResponseSupport<T> get(String path, Class<T> responseType) {
Assert.hasText(path, "Path must not be empty");
Assert.notNull(responseType, "Response type must not be null");
return doRead(path, responseType, (response, data) -> {
VaultResponseSupport result = response;
result.setData(data);
return result;
});
}
@Override
public void put(String path, Object body) {
Assert.hasText(path, "Path must not be empty");
doWrite(createDataPath(path), Collections.singletonMap("data", body));
}
}

View File

@@ -0,0 +1,28 @@
/*
* Copyright 2018 the original author or authors.
*
* 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
*
* http://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 org.springframework.vault.core;
import java.util.Map;
import org.springframework.vault.support.VaultResponseSupport;
/**
* Type declaration for a list response.
*
* @author Mark Paluch
*/
class VaultListResponse extends VaultResponseSupport<Map<String, Object>> {
}

View File

@@ -43,12 +43,23 @@ import org.springframework.web.client.RestClientException;
*/
public interface VaultOperations {
/**
* Return {@link VaultKeyValueOperations}.
*
* @param path the mount path
* @return the operations interface to interact with the Vault Key/Value (version 2)
* backend.
* @since 2.1
*/
VaultKeyValueOperations opsForKeyValue(String path);
/**
* Return {@link VaultVersionedKeyValueOperations}.
*
* @param path the mount path
* @return the operations interface to interact with the versioned Vault Key/Value
* (version 2) backend.
* @since 2.1
*/
VaultVersionedKeyValueOperations opsForVersionedKeyValue(String path);

View File

@@ -17,7 +17,6 @@ package org.springframework.vault.core;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
@@ -206,6 +205,11 @@ public class VaultTemplate implements InitializingBean, VaultOperations, Disposa
}
}
@Override
public VaultKeyValueOperations opsForKeyValue(String path) {
return new VaultKeyValueTemplate(this, path);
}
@Override
public VaultVersionedKeyValueOperations opsForVersionedKeyValue(String path) {
return new VaultVersionedKeyValueTemplate(this, path);
@@ -241,7 +245,6 @@ public class VaultTemplate implements InitializingBean, VaultOperations, Disposa
return new VaultTransitTemplate(this, path);
}
@Override
public VaultResponse read(String path) {
@@ -367,8 +370,4 @@ public class VaultTemplate implements InitializingBean, VaultOperations, Disposa
}
});
}
private static class VaultListResponse extends
VaultResponseSupport<Map<String, Object>> {
}
}

View File

@@ -15,7 +15,6 @@
*/
package org.springframework.vault.core;
import java.util.List;
import java.util.Map;
import org.springframework.lang.Nullable;
@@ -36,16 +35,7 @@ import org.springframework.vault.support.Versioned.Version;
* @author Mark Paluch
* @since 2.1
*/
public interface VaultVersionedKeyValueOperations {
/**
* Enumerate keys from a Vault path.
*
* @param path must not be {@literal null}.
* @return the data. May be {@literal null} if the path does not exist.
*/
@Nullable
List<String> list(String path);
public interface VaultVersionedKeyValueOperations extends VaultKeyValueOperationsSupport {
/**
* Read the most recent secret at {@code path}.
@@ -54,8 +44,8 @@ public interface VaultVersionedKeyValueOperations {
* @return the data. May be {@literal null} if the path does not exist.
*/
@Nullable
default Versioned<Map<String, Object>> read(String path) {
return read(path, Version.unversioned());
default Versioned<Map<String, Object>> get(String path) {
return get(path, Version.unversioned());
}
/**
@@ -66,7 +56,7 @@ public interface VaultVersionedKeyValueOperations {
* @return the data. May be {@literal null} if the path does not exist.
*/
@Nullable
Versioned<Map<String, Object>> read(String path, Version version);
Versioned<Map<String, Object>> get(String path, Version version);
/**
* Write the {@link Versioned versioned secret} at {@code path}. {@code body} may be
@@ -77,14 +67,7 @@ public interface VaultVersionedKeyValueOperations {
* @param body must not be {@literal null}.
* @return the resulting {@link Metadata}.
*/
Metadata write(String path, Object body);
/**
* Delete latest version of the secret at {@code path}.
*
* @param path must not be {@literal null}.
*/
void delete(String path);
Metadata put(String path, Object body);
/**
* Delete one or more {@link Version versions} of the secret at {@code path}.

View File

@@ -25,6 +25,8 @@ import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@@ -43,11 +45,8 @@ import org.springframework.web.client.HttpStatusCodeException;
* @author Mark Paluch
* @since 2.1
*/
public class VaultVersionedKeyValueTemplate implements VaultVersionedKeyValueOperations {
private final VaultOperations vaultOperations;
private final String path;
public class VaultVersionedKeyValueTemplate extends VaultKeyValueAccessor implements
VaultVersionedKeyValueOperations {
/**
* Create a new {@link VaultVersionedKeyValueTemplate} given {@link VaultOperations}
@@ -58,34 +57,26 @@ public class VaultVersionedKeyValueTemplate implements VaultVersionedKeyValueOpe
*/
public VaultVersionedKeyValueTemplate(VaultOperations vaultOperations, String path) {
Assert.notNull(vaultOperations, "VaultOperations must not be null");
Assert.hasText(path, "Path must not be empty");
this.vaultOperations = vaultOperations;
this.path = path;
}
@Nullable
@Override
public List<String> list(String path) {
return vaultOperations.list(createBackendPath("metadata", path));
super(vaultOperations, path);
}
@Override
@Nullable
public Versioned<Map<String, Object>> read(String path, Version version) {
public Versioned<Map<String, Object>> get(String path, Version version) {
Assert.hasText(path, "Path must not be empty");
Assert.notNull(version, "Version must not be null");
String secretPath = version.isVersioned() ? String.format(
"%s/data/%s?version=%d", this.path, path, version.getVersion())
"%s?version=%d",
createDataPath(path), version.getVersion())
: createDataPath(path);
VaultResponse response = vaultOperations.doWithSession(restOperations -> {
VaultResponse response = doWithSession((restOperations, httpHeaders) -> {
try {
return restOperations.getForObject(secretPath, VaultResponse.class);
return restOperations.exchange(secretPath, HttpMethod.GET,
new HttpEntity<>(httpHeaders), VaultResponse.class).getBody();
}
catch (HttpStatusCodeException e) {
@@ -113,7 +104,7 @@ public class VaultVersionedKeyValueTemplate implements VaultVersionedKeyValueOpe
}
@Override
public Metadata write(String path, Object body) {
public Metadata put(String path, Object body) {
Assert.hasText(path, "Path must not be empty");
@@ -133,7 +124,7 @@ public class VaultVersionedKeyValueTemplate implements VaultVersionedKeyValueOpe
data.put("data", body);
}
VaultResponse response = vaultOperations.write(createDataPath(path), data);
VaultResponse response = doWrite(createDataPath(path), data);
return getMetadata(response.getRequiredData());
}
@@ -171,14 +162,6 @@ public class VaultVersionedKeyValueTemplate implements VaultVersionedKeyValueOpe
return null;
}
@Override
public void delete(String path) {
Assert.hasText(path, "Path must not be empty");
vaultOperations.delete(createDataPath(path));
}
@Override
public void delete(String path, Version... versionsToDelete) {
@@ -192,7 +175,7 @@ public class VaultVersionedKeyValueTemplate implements VaultVersionedKeyValueOpe
List<Integer> versions = toVersionList(versionsToDelete);
vaultOperations.write(createBackendPath("delete", path),
doWrite(createBackendPath("delete", path),
Collections.singletonMap("versions", versions));
}
@@ -209,7 +192,7 @@ public class VaultVersionedKeyValueTemplate implements VaultVersionedKeyValueOpe
List<Integer> versions = toVersionList(versionsToDelete);
vaultOperations.write(createBackendPath("undelete", path),
doWrite(createBackendPath("undelete", path),
Collections.singletonMap("versions", versions));
}
@@ -221,15 +204,7 @@ public class VaultVersionedKeyValueTemplate implements VaultVersionedKeyValueOpe
List<Integer> versions = toVersionList(versionsToDelete);
vaultOperations.write(createBackendPath("destroy", path),
doWrite(createBackendPath("destroy", path),
Collections.singletonMap("versions", versions));
}
private String createDataPath(String path) {
return createBackendPath("data", path);
}
private String createBackendPath(String segment, String path) {
return String.format("%s/%s/%s", this.path, segment, path);
}
}

View File

@@ -0,0 +1,125 @@
/*
* Copyright 2018 the original author or authors.
*
* 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
*
* http://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 org.springframework.vault.core;
import java.util.Collections;
import java.util.Map;
import java.util.UUID;
import lombok.Data;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.vault.util.IntegrationTestSupport;
import org.springframework.vault.util.VaultRule;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assume.assumeTrue;
/**
* Integration tests for {@link VaultKeyValueTemplate}.
*
* @author Mark Paluch
*/
public abstract class AbstractVaultKeyValueTemplateIntegrationTests extends
IntegrationTestSupport {
private final String path;
@Autowired
VaultOperations vaultOperations;
VaultKeyValueOperations kvOperations;
AbstractVaultKeyValueTemplateIntegrationTests(String path) {
this.path = path;
}
@Before
public void before() {
assumeTrue(prepare().getVersion().isGreaterThanOrEqualTo(
VaultRule.VERSIONING_INTRODUCED_WITH));
kvOperations = vaultOperations.opsForKeyValue(path);
}
@Test
public void shouldCreateSecret() {
Map<String, String> secret = Collections.singletonMap("key", "value");
String key = UUID.randomUUID().toString();
kvOperations.put(key, secret);
assertThat(kvOperations.list("/")).contains(key);
}
@Test
public void shouldReadSecret() {
Map<String, String> secret = Collections.singletonMap("key", "value");
String key = UUID.randomUUID().toString();
kvOperations.put(key, secret);
assertThat(kvOperations.get(key).getRequiredData()).containsEntry("key", "value");
}
@Test
public void shouldReadAbsentSecret() {
assertThat(kvOperations.get("absent")).isNull();
assertThat(kvOperations.get("absent", Person.class)).isNull();
}
@Test
public void shouldReadComplexSecret() {
Person person = new Person();
person.setFirstname("Walter");
person.setLastname("Heisenberg");
kvOperations.put("my-secret", person);
assertThat(kvOperations.get("my-secret").getRequiredData()).containsEntry(
"firstname", "Walter");
assertThat(kvOperations.get("my-secret", Person.class).getRequiredData())
.isEqualTo(person);
}
@Test
public void shouldDeleteSecret() {
Map<String, String> secret = Collections.singletonMap("key", "value");
String key = UUID.randomUUID().toString();
kvOperations.put(key, secret);
kvOperations.delete(key);
assertThat(kvOperations.get(key)).isNull();
}
@Data
static class Person {
String firstname;
String lastname;
}
}

View File

@@ -0,0 +1,59 @@
/*
* Copyright 2018 the original author or authors.
*
* 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
*
* http://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 org.springframework.vault.core;
import java.util.HashMap;
import java.util.Map;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.vault.support.VaultResponse;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Integration tests for {@link VaultKeyValueTemplate} using the non-versioned key-value
* backend.
*
* @author Mark Paluch
*/
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = VaultIntegrationTestConfiguration.class)
public class VaultKeyValueTemplateIntegrationTests extends
AbstractVaultKeyValueTemplateIntegrationTests {
public VaultKeyValueTemplateIntegrationTests() {
super("secret");
}
@Test
public void shouldReadSecretWithTtl() {
Map<String, Object> secret = new HashMap<>();
secret.put("key", "value");
secret.put("ttl", "5");
kvOperations.put("my-secret", secret);
VaultResponse response = kvOperations.get("my-secret");
assertThat(response.getRequiredData()).containsEntry("key", "value");
assertThat(response.getLeaseDuration()).isEqualTo(5L);
}
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright 2018 the original author or authors.
*
* 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
*
* http://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 org.springframework.vault.core;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
/**
* Integration tests for {@link VaultKeyValueTemplate} using the versioned key-value
* backend.
*
* @author Mark Paluch
*/
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = VaultIntegrationTestConfiguration.class)
public class VaultKeyValueTemplateVersionedIntegrationTests extends
AbstractVaultKeyValueTemplateIntegrationTests {
public VaultKeyValueTemplateVersionedIntegrationTests() {
super("versioned");
}
}

View File

@@ -68,7 +68,7 @@ public class VaultVersionedKeyValueTemplateIntegrationTests extends
String key = UUID.randomUUID().toString();
Metadata metadata = versionedOperations.write(key, Versioned.create(secret));
Metadata metadata = versionedOperations.put(key, Versioned.create(secret));
assertThat(metadata.isDestroyed()).isFalse();
assertThat(metadata.getCreatedAt()).isBetween(Instant.now().minusSeconds(60),
@@ -83,11 +83,11 @@ public class VaultVersionedKeyValueTemplateIntegrationTests extends
String key = UUID.randomUUID().toString();
versionedOperations.write(key, Versioned.create(secret, Version.unversioned()));
versionedOperations.put(key, Versioned.create(secret, Version.unversioned()));
// this should fail
assertThatThrownBy(
() -> versionedOperations.write(key,
() -> versionedOperations.put(key,
Versioned.create(secret, Version.unversioned())))
.isExactlyInstanceOf(VaultException.class).hasMessageContaining(
"check-and-set parameter did not match the current version");
@@ -100,9 +100,9 @@ public class VaultVersionedKeyValueTemplateIntegrationTests extends
String key = UUID.randomUUID().toString();
versionedOperations.write(key, Versioned.create(secret));
versionedOperations.put(key, Versioned.create(secret));
Versioned<Map<String, Object>> loaded = versionedOperations.read(key);
Versioned<Map<String, Object>> loaded = versionedOperations.get(key);
assertThat(loaded.getData()).isEqualTo(secret);
assertThat(loaded.getMetadata()).isNotNull();
@@ -115,7 +115,7 @@ public class VaultVersionedKeyValueTemplateIntegrationTests extends
Map<String, String> secret = Collections.singletonMap("key", "value");
String key = UUID.randomUUID().toString();
versionedOperations.write(key, secret);
versionedOperations.put(key, secret);
assertThat(versionedOperations.list("")).contains(key);
}
@@ -125,12 +125,12 @@ public class VaultVersionedKeyValueTemplateIntegrationTests extends
String key = UUID.randomUUID().toString();
versionedOperations.write(key, Collections.singletonMap("key", "v1"));
versionedOperations.write(key, Collections.singletonMap("key", "v2"));
versionedOperations.put(key, Collections.singletonMap("key", "v1"));
versionedOperations.put(key, Collections.singletonMap("key", "v2"));
assertThat(versionedOperations.read(key, Version.from(1)).getData()).isEqualTo(
assertThat(versionedOperations.get(key, Version.from(1)).getData()).isEqualTo(
Collections.singletonMap("key", "v1"));
assertThat(versionedOperations.read(key, Version.from(2)).getData()).isEqualTo(
assertThat(versionedOperations.get(key, Version.from(2)).getData()).isEqualTo(
Collections.singletonMap("key", "v2"));
}
@@ -139,12 +139,12 @@ public class VaultVersionedKeyValueTemplateIntegrationTests extends
String key = UUID.randomUUID().toString();
versionedOperations.write(key, Collections.singletonMap("key", "v1"));
versionedOperations.write(key, Collections.singletonMap("key", "v2"));
versionedOperations.put(key, Collections.singletonMap("key", "v1"));
versionedOperations.put(key, Collections.singletonMap("key", "v2"));
versionedOperations.delete(key);
Versioned<Map<String, Object>> versioned = versionedOperations.read(key);
Versioned<Map<String, Object>> versioned = versionedOperations.get(key);
assertThat(versioned.getData()).isNull();
assertThat(versioned.getVersion()).isEqualTo(Version.from(2));
@@ -158,13 +158,13 @@ public class VaultVersionedKeyValueTemplateIntegrationTests extends
String key = UUID.randomUUID().toString();
versionedOperations.write(key, Collections.singletonMap("key", "v1"));
versionedOperations.write(key, Collections.singletonMap("key", "v2"));
versionedOperations.put(key, Collections.singletonMap("key", "v1"));
versionedOperations.put(key, Collections.singletonMap("key", "v2"));
versionedOperations.delete(key, Version.from(2));
versionedOperations.undelete(key, Version.from(2));
Versioned<Map<String, Object>> versioned = versionedOperations.read(key);
Versioned<Map<String, Object>> versioned = versionedOperations.get(key);
assertThat(versioned.getData()).isEqualTo(Collections.singletonMap("key", "v2"));
assertThat(versioned.getVersion()).isEqualTo(Version.from(2));
@@ -177,12 +177,12 @@ public class VaultVersionedKeyValueTemplateIntegrationTests extends
String key = UUID.randomUUID().toString();
versionedOperations.write(key, Collections.singletonMap("key", "v1"));
versionedOperations.write(key, Collections.singletonMap("key", "v2"));
versionedOperations.put(key, Collections.singletonMap("key", "v1"));
versionedOperations.put(key, Collections.singletonMap("key", "v2"));
versionedOperations.delete(key, Version.from(1));
Versioned<Map<String, Object>> versioned = versionedOperations.read(key,
Versioned<Map<String, Object>> versioned = versionedOperations.get(key,
Version.from(1));
assertThat(versioned.getData()).isNull();
@@ -197,12 +197,12 @@ public class VaultVersionedKeyValueTemplateIntegrationTests extends
String key = UUID.randomUUID().toString();
versionedOperations.write(key, Collections.singletonMap("key", "v1"));
versionedOperations.write(key, Collections.singletonMap("key", "v2"));
versionedOperations.put(key, Collections.singletonMap("key", "v1"));
versionedOperations.put(key, Collections.singletonMap("key", "v2"));
versionedOperations.destroy(key, Version.from(2));
Versioned<Map<String, Object>> versioned = versionedOperations.read(key);
Versioned<Map<String, Object>> versioned = versionedOperations.get(key);
assertThat(versioned.getData()).isNull();
assertThat(versioned.getVersion()).isEqualTo(Version.from(2));