diff --git a/spring-vault-core/src/main/java/org/springframework/vault/core/VaultKeyValueAccessor.java b/spring-vault-core/src/main/java/org/springframework/vault/core/VaultKeyValueAccessor.java new file mode 100644 index 00000000..4a94fedf --- /dev/null +++ b/spring-vault-core/src/main/java/org/springframework/vault/core/VaultKeyValueAccessor.java @@ -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 + *

+ * 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 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) 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 intermediate data type for {@literal data} deserialization. + * @param return type. Value is created by the {@code mappingFunction}. + * @return mapped value. + */ + @Nullable + T doRead(String path, Class deserializeAs, + BiFunction, I, T> mappingFunction) { + + ParameterizedTypeReference> ref = VaultResponses + .getTypeReference(JsonNode.class); + + VaultResponseSupport 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 doRead(String path, ParameterizedTypeReference typeReference) { + + return doRead((restOperations, httpEntity) -> { + return restOperations.exchange(path, HttpMethod.GET, httpEntity, + typeReference); + }); + } + + @Nullable + private T doRead( + BiFunction, ResponseEntity> 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 doWithSession(BiFunction 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 mapper = vaultOperations + .doWithSession(operations -> { + + if (operations instanceof RestTemplate) { + + RestTemplate template = (RestTemplate) operations; + + Optional 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); + } +} diff --git a/spring-vault-core/src/main/java/org/springframework/vault/core/VaultKeyValueOperations.java b/spring-vault-core/src/main/java/org/springframework/vault/core/VaultKeyValueOperations.java new file mode 100644 index 00000000..0cca11e0 --- /dev/null +++ b/spring-vault-core/src/main/java/org/springframework/vault/core/VaultKeyValueOperations.java @@ -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 + VaultResponseSupport get(String path, Class 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); +} diff --git a/spring-vault-core/src/main/java/org/springframework/vault/core/VaultKeyValueOperationsSupport.java b/spring-vault-core/src/main/java/org/springframework/vault/core/VaultKeyValueOperationsSupport.java new file mode 100644 index 00000000..c436134f --- /dev/null +++ b/spring-vault-core/src/main/java/org/springframework/vault/core/VaultKeyValueOperationsSupport.java @@ -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. + *

+ * + * @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 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); +} diff --git a/spring-vault-core/src/main/java/org/springframework/vault/core/VaultKeyValueTemplate.java b/spring-vault-core/src/main/java/org/springframework/vault/core/VaultKeyValueTemplate.java new file mode 100644 index 00000000..caac0495 --- /dev/null +++ b/spring-vault-core/src/main/java/org/springframework/vault/core/VaultKeyValueTemplate.java @@ -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 VaultResponseSupport get(String path, Class 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)); + } +} diff --git a/spring-vault-core/src/main/java/org/springframework/vault/core/VaultListResponse.java b/spring-vault-core/src/main/java/org/springframework/vault/core/VaultListResponse.java new file mode 100644 index 00000000..bd3d8258 --- /dev/null +++ b/spring-vault-core/src/main/java/org/springframework/vault/core/VaultListResponse.java @@ -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> { +} diff --git a/spring-vault-core/src/main/java/org/springframework/vault/core/VaultOperations.java b/spring-vault-core/src/main/java/org/springframework/vault/core/VaultOperations.java index 3cd77a46..a520507a 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/core/VaultOperations.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/core/VaultOperations.java @@ -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); diff --git a/spring-vault-core/src/main/java/org/springframework/vault/core/VaultTemplate.java b/spring-vault-core/src/main/java/org/springframework/vault/core/VaultTemplate.java index 0f8f4e05..d06e89a6 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/core/VaultTemplate.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/core/VaultTemplate.java @@ -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> { - } } diff --git a/spring-vault-core/src/main/java/org/springframework/vault/core/VaultVersionedKeyValueOperations.java b/spring-vault-core/src/main/java/org/springframework/vault/core/VaultVersionedKeyValueOperations.java index 7665655b..ba286339 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/core/VaultVersionedKeyValueOperations.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/core/VaultVersionedKeyValueOperations.java @@ -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 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> read(String path) { - return read(path, Version.unversioned()); + default Versioned> 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> read(String path, Version version); + Versioned> 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}. diff --git a/spring-vault-core/src/main/java/org/springframework/vault/core/VaultVersionedKeyValueTemplate.java b/spring-vault-core/src/main/java/org/springframework/vault/core/VaultVersionedKeyValueTemplate.java index aa4997b9..b71e8ea2 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/core/VaultVersionedKeyValueTemplate.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/core/VaultVersionedKeyValueTemplate.java @@ -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 list(String path) { - return vaultOperations.list(createBackendPath("metadata", path)); + super(vaultOperations, path); } @Override @Nullable - public Versioned> read(String path, Version version) { + public Versioned> 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 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 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 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); - } } diff --git a/spring-vault-core/src/test/java/org/springframework/vault/core/AbstractVaultKeyValueTemplateIntegrationTests.java b/spring-vault-core/src/test/java/org/springframework/vault/core/AbstractVaultKeyValueTemplateIntegrationTests.java new file mode 100644 index 00000000..013e5ab3 --- /dev/null +++ b/spring-vault-core/src/test/java/org/springframework/vault/core/AbstractVaultKeyValueTemplateIntegrationTests.java @@ -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 secret = Collections.singletonMap("key", "value"); + + String key = UUID.randomUUID().toString(); + + kvOperations.put(key, secret); + + assertThat(kvOperations.list("/")).contains(key); + } + + @Test + public void shouldReadSecret() { + + Map 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 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; + } +} diff --git a/spring-vault-core/src/test/java/org/springframework/vault/core/VaultKeyValueTemplateIntegrationTests.java b/spring-vault-core/src/test/java/org/springframework/vault/core/VaultKeyValueTemplateIntegrationTests.java new file mode 100644 index 00000000..e02da0de --- /dev/null +++ b/spring-vault-core/src/test/java/org/springframework/vault/core/VaultKeyValueTemplateIntegrationTests.java @@ -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 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); + } +} diff --git a/spring-vault-core/src/test/java/org/springframework/vault/core/VaultKeyValueTemplateVersionedIntegrationTests.java b/spring-vault-core/src/test/java/org/springframework/vault/core/VaultKeyValueTemplateVersionedIntegrationTests.java new file mode 100644 index 00000000..6f917e52 --- /dev/null +++ b/spring-vault-core/src/test/java/org/springframework/vault/core/VaultKeyValueTemplateVersionedIntegrationTests.java @@ -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"); + } +} diff --git a/spring-vault-core/src/test/java/org/springframework/vault/core/VaultVersionedKeyValueTemplateIntegrationTests.java b/spring-vault-core/src/test/java/org/springframework/vault/core/VaultVersionedKeyValueTemplateIntegrationTests.java index 2494415c..7af07281 100644 --- a/spring-vault-core/src/test/java/org/springframework/vault/core/VaultVersionedKeyValueTemplateIntegrationTests.java +++ b/spring-vault-core/src/test/java/org/springframework/vault/core/VaultVersionedKeyValueTemplateIntegrationTests.java @@ -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> loaded = versionedOperations.read(key); + Versioned> loaded = versionedOperations.get(key); assertThat(loaded.getData()).isEqualTo(secret); assertThat(loaded.getMetadata()).isNotNull(); @@ -115,7 +115,7 @@ public class VaultVersionedKeyValueTemplateIntegrationTests extends Map 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> versioned = versionedOperations.read(key); + Versioned> 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> versioned = versionedOperations.read(key); + Versioned> 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> versioned = versionedOperations.read(key, + Versioned> 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> versioned = versionedOperations.read(key); + Versioned> versioned = versionedOperations.get(key); assertThat(versioned.getData()).isNull(); assertThat(versioned.getVersion()).isEqualTo(Version.from(2));