Skip to content

Commit 012b4bd

Browse files
committed
Propagate failure to detect MountInfo.
We now propagate failures to detect the MountInfo to simplify problem analysis when using repositories, the Key-Value API and Property Sources. Closes gh-929
1 parent 118a170 commit 012b4bd

File tree

5 files changed

+47
-13
lines changed

5 files changed

+47
-13
lines changed

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

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,16 @@
2222

2323
import org.apache.commons.logging.Log;
2424
import org.apache.commons.logging.LogFactory;
25-
2625
import org.jspecify.annotations.Nullable;
26+
27+
import org.springframework.http.HttpStatus;
2728
import org.springframework.util.ConcurrentReferenceHashMap;
2829
import org.springframework.util.StringUtils;
2930
import org.springframework.vault.VaultException;
3031
import org.springframework.vault.core.VaultKeyValueOperationsSupport.KeyValueBackend;
3132
import org.springframework.vault.core.VaultOperations;
3233
import org.springframework.vault.support.VaultResponse;
34+
import org.springframework.web.client.RestClientResponseException;
3335

3436
/**
3537
* Key-Value utility to retrieve secrets from a versioned key-value backend. For internal
@@ -132,19 +134,22 @@ public MountInfo getMountInfo(String path) {
132134
try {
133135
mountInfo = doGetMountInfo(path);
134136
}
135-
catch (VaultException e) {
136-
if (logger.isDebugEnabled()) {
137-
logger.debug("Unable to determine mount information for [%s]. Returning unavailable MountInfo: %s"
138-
.formatted(path, e.getMessage()), e);
139-
}
140-
return MountInfo.unavailable();
141-
}
142137
catch (RuntimeException e) {
143-
if (logger.isDebugEnabled()) {
144-
logger.debug("Unable to determine mount information for [%s]. Caching unavailable MountInfo: %s"
145-
.formatted(path, e.getMessage()), e);
138+
139+
if (e.getCause() instanceof RestClientResponseException rcx
140+
&& rcx.getStatusCode().value() == HttpStatus.FORBIDDEN.value()) {
141+
142+
if (logger.isDebugEnabled()) {
143+
logger
144+
.debug("Unable to determine mount information for [%s]. Returning unavailable MountInfo: %s"
145+
.formatted(path, e.getMessage()), e);
146+
}
147+
148+
return MountInfo.unavailable();
146149
}
147-
mountInfo = MountInfo.unavailable();
150+
151+
throw new VaultException(
152+
"Cannot determine MountInfo for path '%s' using 'sys/internal/ui/mounts'".formatted(path), e);
148153
}
149154

150155
this.mountInfo.put(path, mountInfo);

spring-vault-core/src/test/java/org/springframework/vault/core/env/VaultPropertySourceUnitTests.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,13 @@
1919
import java.util.LinkedHashMap;
2020
import java.util.Map;
2121

22+
import org.junit.jupiter.api.BeforeEach;
2223
import org.junit.jupiter.api.Test;
2324
import org.junit.jupiter.api.extension.ExtendWith;
2425
import org.mockito.Mock;
2526
import org.mockito.junit.jupiter.MockitoExtension;
27+
import org.mockito.junit.jupiter.MockitoSettings;
28+
import org.mockito.quality.Strictness;
2629

2730
import org.springframework.vault.VaultException;
2831
import org.springframework.vault.core.VaultTemplate;
@@ -38,16 +41,26 @@
3841
* @author Mark Paluch
3942
*/
4043
@ExtendWith(MockitoExtension.class)
44+
@MockitoSettings(strictness = Strictness.LENIENT)
4145
class VaultPropertySourceUnitTests {
4246

4347
@Mock
4448
VaultTemplate vaultTemplate;
4549

50+
@BeforeEach
51+
void setUp() {
52+
53+
VaultResponse mountInfo = new VaultResponse();
54+
Map<String, Object> data = Map.of("path", "data", "options", Map.of("version", "1"));
55+
mountInfo.setData(data);
56+
57+
when(vaultTemplate.read("sys/internal/ui/mounts/my-secret")).thenReturn(mountInfo);
58+
}
59+
4660
@Test
4761
void shouldRejectEmptyPath() {
4862
assertThatIllegalArgumentException()
4963
.isThrownBy(() -> new VaultPropertySource("hello", this.vaultTemplate, "", PropertyTransformers.noop()));
50-
5164
}
5265

5366
@Test

spring-vault-core/src/test/java/org/springframework/vault/core/lease/SecretLeaseContainerUnitTests.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@
3232
import org.mockito.Captor;
3333
import org.mockito.Mock;
3434
import org.mockito.junit.jupiter.MockitoExtension;
35+
import org.mockito.junit.jupiter.MockitoSettings;
36+
import org.mockito.quality.Strictness;
3537

3638
import org.springframework.http.HttpStatus;
3739
import org.springframework.scheduling.TaskScheduler;
@@ -51,6 +53,7 @@
5153
import org.springframework.vault.core.lease.event.SecretLeaseExpiredEvent;
5254
import org.springframework.vault.core.lease.event.SecretLeaseRotatedEvent;
5355
import org.springframework.vault.core.lease.event.SecretNotFoundEvent;
56+
import org.springframework.vault.core.util.KeyValueDelegate;
5457
import org.springframework.vault.support.LeaseStrategy;
5558
import org.springframework.vault.support.VaultResponse;
5659
import org.springframework.web.client.HttpClientErrorException;
@@ -67,6 +70,7 @@
6770
* @author Thomas Kåsene
6871
*/
6972
@ExtendWith(MockitoExtension.class)
73+
@MockitoSettings(strictness = Strictness.LENIENT)
7074
class SecretLeaseContainerUnitTests {
7175

7276
@Mock
@@ -97,6 +101,12 @@ void before() throws Exception {
97101
this.secretLeaseContainer.addLeaseListener(this.leaseListenerAdapter);
98102
this.secretLeaseContainer.addErrorListener(this.leaseListenerAdapter);
99103
this.secretLeaseContainer.afterPropertiesSet();
104+
105+
VaultResponse mountInfo = new VaultResponse();
106+
Map<String, Object> data = Map.of("path", "data", "options", Map.of("version", "1"));
107+
mountInfo.setData(data);
108+
109+
when(vaultOperations.read("sys/internal/ui/mounts/my-secret")).thenReturn(mountInfo);
100110
}
101111

102112
@Test

src/main/antora/modules/ROOT/pages/vault/propertysource.adoc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ NOTE: You can reference properties stored inside Vault in other property sources
1010
NOTE: Spring Boot/Spring Cloud users can benefit from https://github.com/spring-cloud/spring-cloud-vault-config[Spring Cloud Vault]'s
1111
configuration integration that initializes various property sources during application startup.
1212

13+
NOTE: Vault determines 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 Vault Property sources.
14+
1315
== Registering `VaultPropertySource`
1416

1517
Spring Vault provides a javadoc:org.springframework.vault.core.env.VaultPropertySource[] to be used with Vault to obtain

src/main/antora/modules/ROOT/pages/vault/vault-secret-engines.adoc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ include::example$KeyValueV1.java[tags=vaultOperations]
5353

5454
You can find more details about the https://www.vaultproject.io/api-docs/secret/kv/kv-v1[Vault Key-Value version 1 API] in the Vault reference documentation.
5555

56+
NOTE: Vault determines 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 Key-Value API.
57+
5658
[[vault.core.backends.kv2]]
5759
== Key-Value Version 2 ("versioned secrets")
5860

@@ -105,6 +107,8 @@ include::example$KeyValueV2.java[tags=vaultOperations]
105107

106108
You can find more details about the https://www.vaultproject.io/api-docs/secret/kv/kv-v2[Vault Key-Value version 2 API] in the Vault reference documentation.
107109

110+
NOTE: Vault determines 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 Key-Value API.
111+
108112
[[vault.core.backends.pki]]
109113
== PKI (Public Key Infrastructure)
110114

0 commit comments

Comments
 (0)