From 015ae2e667731466fec7c0c367c865e4b85b480e Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Fri, 25 Apr 2025 11:47:02 +0200 Subject: [PATCH] Refine error handling in MountInfo lookup. We now do not cache failures caused by VaultException but rather return an unavailable MountInfo. Also, VaultKeyValueAdapter now throws an exception if the mount info isn't available. Closes gh-889 --- .../vault/core/util/KeyValueDelegate.java | 4 + .../repository/core/VaultKeyValueAdapter.java | 4 + .../VaultKeyValueAdapterIntegrationTests.java | 76 +++++++++++++++++++ .../ROOT/pages/vault/vault-repositories.adoc | 2 + 4 files changed, 86 insertions(+) create mode 100644 spring-vault-core/src/test/java/org/springframework/vault/repository/core/VaultKeyValueAdapterIntegrationTests.java diff --git a/spring-vault-core/src/main/java/org/springframework/vault/core/util/KeyValueDelegate.java b/spring-vault-core/src/main/java/org/springframework/vault/core/util/KeyValueDelegate.java index 0cb053eb..a05552f7 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/core/util/KeyValueDelegate.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/core/util/KeyValueDelegate.java @@ -23,6 +23,7 @@ import java.util.function.Supplier; import org.springframework.lang.Nullable; import org.springframework.util.ConcurrentReferenceHashMap; import org.springframework.util.StringUtils; +import org.springframework.vault.VaultException; import org.springframework.vault.core.VaultKeyValueOperationsSupport.KeyValueBackend; import org.springframework.vault.core.VaultOperations; import org.springframework.vault.support.VaultResponse; @@ -127,6 +128,9 @@ public class KeyValueDelegate { mountInfo = doGetMountInfo(path); } + catch (VaultException e) { + return MountInfo.unavailable(); + } catch (RuntimeException e) { mountInfo = MountInfo.unavailable(); } diff --git a/spring-vault-core/src/main/java/org/springframework/vault/repository/core/VaultKeyValueAdapter.java b/spring-vault-core/src/main/java/org/springframework/vault/repository/core/VaultKeyValueAdapter.java index edbcae54..e9e6adde 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/repository/core/VaultKeyValueAdapter.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/repository/core/VaultKeyValueAdapter.java @@ -255,6 +255,10 @@ public class VaultKeyValueAdapter extends AbstractKeyValueAdapter { KeyValueDelegate.MountInfo mountInfo = keyValueDelegate.getMountInfo(keyspace); + if (!mountInfo.isAvailable()) { + throw new VaultException("Cannot determine MountInfo"); + } + return accessors.computeIfAbsent(keyspace, it -> { if (keyValueDelegate.isVersioned(it)) { diff --git a/spring-vault-core/src/test/java/org/springframework/vault/repository/core/VaultKeyValueAdapterIntegrationTests.java b/spring-vault-core/src/test/java/org/springframework/vault/repository/core/VaultKeyValueAdapterIntegrationTests.java new file mode 100644 index 00000000..49e89266 --- /dev/null +++ b/spring-vault-core/src/test/java/org/springframework/vault/repository/core/VaultKeyValueAdapterIntegrationTests.java @@ -0,0 +1,76 @@ +/* + * Copyright 2025 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 + * + * https://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.repository.core; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.vault.VaultException; +import org.springframework.vault.core.VaultIntegrationTestConfiguration; +import org.springframework.vault.core.VaultTemplate; +import org.springframework.vault.core.util.KeyValueDelegate; +import org.springframework.vault.domain.Person; +import org.springframework.vault.util.IntegrationTestSupport; + +import static org.assertj.core.api.Assertions.*; + +/** + * Integration tests for {@link VaultKeyValueAdapter}. + * + * @author Mark Paluch + */ +@ExtendWith(SpringExtension.class) +@ContextConfiguration(classes = VaultIntegrationTestConfiguration.class) +class VaultKeyValueAdapterIntegrationTests extends IntegrationTestSupport { + + @Autowired + VaultTemplate template; + + @Test + void shouldFailOnAbsentKeyspace() { + + VaultKeyValueAdapter adapter = new VaultKeyValueAdapter(template); + + assertThatExceptionOfType(VaultException.class).isThrownBy(() -> adapter.get("some-id", "absent", Person.class)) + .withMessageContaining("Cannot determine MountInfo"); + } + + @Test + void shouldReturnVersionedMountInfo() { + + KeyValueDelegate delegate = new KeyValueDelegate(template); + + KeyValueDelegate.MountInfo mountInfo = delegate.getMountInfo("versioned/nothing/here"); + + assertThat(mountInfo.isAvailable()).isTrue(); + assertThat(mountInfo.getOptions()).containsEntry("version", "2"); + } + + @Test + void shouldReturnUnversionedMountInfo() { + + KeyValueDelegate delegate = new KeyValueDelegate(template); + + KeyValueDelegate.MountInfo mountInfo = delegate.getMountInfo("secret/nothing/here"); + + assertThat(mountInfo.isAvailable()).isTrue(); + assertThat(mountInfo.getOptions()).doesNotContainEntry("version", "2"); + } + +} diff --git a/src/main/antora/modules/ROOT/pages/vault/vault-repositories.adoc b/src/main/antora/modules/ROOT/pages/vault/vault-repositories.adoc index 24353a2c..66c23c11 100644 --- a/src/main/antora/modules/ROOT/pages/vault/vault-repositories.adoc +++ b/src/main/antora/modules/ROOT/pages/vault/vault-repositories.adoc @@ -9,6 +9,8 @@ As of version 2.4, Spring Vault can use additionally key/value version 2 secrets NOTE: Deletes within versioned key/value secrets engine use the `DELETE` operation. Secrets are not destroyed through `CrudRepository.delete(…)`. +NOTE: Vault Repositories determine the mount path through Vault's `sys/internal/ui/mounts/…` endpoint. Make sure that your policy allows accessing that path, otherwise you won't be able to use the repository abstraction. + NOTE: Read more about Spring Data Repositories in the https://docs.spring.io/spring-data/commons/docs/current/reference/html/#repositories[Spring Data Commons reference documentation]. The reference documentation will give you an introduction to Spring Data repositories.