Skip to content

Commit 9e74350

Browse files
Vault property source (#57)
* Vault Property Source --------- Signed-off-by: Anders Swanson <anders.swanson@oracle.com>
1 parent 688beed commit 9e74350

File tree

13 files changed

+379
-17
lines changed

13 files changed

+379
-17
lines changed

docs/src/main/asciidoc/vault.adoc

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
// Copyright (c) 2024, Oracle and/or its affiliates.
2+
// Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/
3+
4+
[#vault]
5+
== Vault
6+
7+
https://docs.oracle.com/en-us/iaas/Content/KeyManagement/home.htm[Vault] can be used as a Spring property source for Vault secrets, and as an application bean for creating, updating, listing, and deleting secrets from a Vault.
8+
9+
Maven coordinates, using <<getting-started.adoc#bill-of-materials, Spring Cloud OCI BOM>>:
10+
11+
[source,xml]
12+
----
13+
<dependency>
14+
<groupId>com.oracle.cloud.spring</groupId>
15+
<artifactId>spring-cloud-oci-starter-vault</artifactId>
16+
</dependency>
17+
----
18+
19+
Gradle coordinates:
20+
21+
[source,subs="normal"]
22+
----
23+
dependencies {
24+
implementation("com.oracle.cloud.spring:spring-cloud-oci-starter-vault")
25+
}
26+
----
27+
28+
=== Using VaultPropertySource
29+
30+
By configuring Vault as a property source, secrets can be dynamically loaded from OCI Vault into the Spring application context.
31+
For each vault specified as a property source, Spring will inject secrets as properties identified by their name.
32+
33+
[source,yaml]
34+
----
35+
spring:
36+
cloud:
37+
oci:
38+
config:
39+
type: file
40+
region:
41+
static: us-ashburn-1
42+
vault:
43+
enabled: true
44+
compartment: ${OCI_COMPARTMENT_ID}
45+
# Spring will automatically refresh properties from Vault,
46+
# if the property-refresh-interval is greater than 0ms.
47+
# Defaults to 10 minutes.
48+
property-refresh-interval: 10000ms
49+
property-sources:
50+
- vault-id: ${OCI_VAULT_ID}
51+
----
52+
53+
[source,java]
54+
----
55+
// The secret "secretName" will be loaded from the Vault,
56+
// and it's String value bound to the secretValue variable.
57+
@Value("${secretname}")
58+
String secretValue;
59+
----
60+
61+
=== Using Vault APIs in an application
62+
63+
The starter automatically configures and registers an `Vault` bean in the Spring application context.
64+
The `Vault` bean can be used to create, update, list, and delete secrets in an OCI Vault
65+
66+
[source,yaml]
67+
----
68+
spring:
69+
cloud:
70+
oci:
71+
config:
72+
type: file
73+
region:
74+
static: us-ashburn-1
75+
vault:
76+
compartment: ${OCI_COMPARTMENT_ID}
77+
vault-id: ${OCI_VAULT_ID}
78+
enabled: true
79+
----
80+
81+
[source,java]
82+
----
83+
@Autowired
84+
private Vault vault;
85+
86+
public String getSecretByName(String secretName) {
87+
GetSecretBundleByNameResponse bundle = vault.getSecret(secretName);
88+
return vault.decodeBundle(bundle);
89+
}
90+
91+
----
92+
93+
94+
=== Configuration
95+
96+
The Spring Boot Starter for Oracle Vault provides the following configuration options:
97+
98+
|===
99+
^| Name ^| Description ^| Required ^| Default value
100+
| `spring.cloud.oci.vault.enabled` | Enables the OCI Vault APIs. | No | `true`
101+
| `spring.cloud.oci.vault.compartment` | Compartment for Vault APIs and property sources. | Yes |
102+
| `spring.cloud.oci.vault.vault-id` | Vault OCID for Vault APIs. | Yes |
103+
| `spring.cloud.oci.vault.property-refresh-interval` | Duration on which properties will be refreshed. | No | 10m
104+
| `spring.cloud.oci.vault.property-sources` | List of Vaults to use as Spring property sources. | No |
105+
| `spring.cloud.oci.vault.property-sources[i].vault-id` | Vault OCID to use as a property source. | Yes |
106+
|===
107+
108+
=== Sample
109+
110+
A sample application provided https://github.com/oracle/spring-cloud-oci/tree/main/spring-cloud-oci-samples/spring-cloud-oci-vault-sample[here] contains the examples to demonstrates the usage of OCI Spring Cloud Vault module.

spring-cloud-oci-autoconfigure/src/main/java/com/oracle/cloud/spring/autoconfigure/core/RegionProviderAutoConfiguration.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ public RegionProvider regionProvider() {
3737
return createRegionProvider(properties);
3838
}
3939

40-
private static RegionProvider createRegionProvider(RegionProperties properties) {
40+
public static RegionProvider createRegionProvider(RegionProperties properties) {
4141
if (properties.getStatic() != null && properties.isStatic()) {
4242
return new StaticRegionProvider(properties.getStatic().trim());
4343
}

spring-cloud-oci-autoconfigure/src/main/java/com/oracle/cloud/spring/vault/VaultAutoConfiguration.java

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -53,12 +53,7 @@ public Vault vault(Vaults vaults, Secrets secrets) {
5353
public Vaults vaults(@Qualifier(regionProviderQualifier) RegionProvider regionProvider,
5454
@Qualifier(credentialsProviderQualifier)
5555
CredentialsProvider cp) {
56-
Vaults vaults = VaultsClient.builder()
57-
.build(cp.getAuthenticationDetailsProvider());
58-
if (regionProvider.getRegion() != null) {
59-
vaults.setRegion(regionProvider.getRegion());
60-
}
61-
return vaults;
56+
return createVaultClient(regionProvider, cp);
6257
}
6358

6459
@Bean
@@ -67,12 +62,25 @@ public Vaults vaults(@Qualifier(regionProviderQualifier) RegionProvider regionPr
6762
public Secrets secrets(@Qualifier(regionProviderQualifier) RegionProvider regionProvider,
6863
@Qualifier(credentialsProviderQualifier)
6964
CredentialsProvider cp) {
65+
return createSecretsClient(regionProvider, cp);
66+
}
67+
68+
public static Secrets createSecretsClient(RegionProvider regionProvider, CredentialsProvider cp) {
7069
Secrets secrets = SecretsClient.builder()
7170
.build(cp.getAuthenticationDetailsProvider());
7271
if (regionProvider.getRegion() != null) {
7372
secrets.setRegion(regionProvider.getRegion());
7473
}
7574
return secrets;
7675
}
76+
77+
public static Vaults createVaultClient(RegionProvider regionProvider, CredentialsProvider cp) {
78+
Vaults vaults = VaultsClient.builder()
79+
.build(cp.getAuthenticationDetailsProvider());
80+
if (regionProvider.getRegion() != null) {
81+
vaults.setRegion(regionProvider.getRegion());
82+
}
83+
return vaults;
84+
}
7785
}
7886

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
// Copyright (c) 2024, Oracle and/or its affiliates.
2+
// Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/
3+
package com.oracle.cloud.spring.vault;
4+
5+
import java.io.IOException;
6+
7+
import com.oracle.bmc.auth.RegionProvider;
8+
import com.oracle.bmc.secrets.Secrets;
9+
import com.oracle.bmc.vault.Vaults;
10+
import com.oracle.cloud.spring.autoconfigure.core.CredentialsProperties;
11+
import com.oracle.cloud.spring.autoconfigure.core.CredentialsProvider;
12+
import com.oracle.cloud.spring.autoconfigure.core.RegionProperties;
13+
import org.springframework.boot.SpringApplication;
14+
import org.springframework.boot.context.config.ConfigDataEnvironmentPostProcessor;
15+
import org.springframework.boot.context.properties.bind.Bindable;
16+
import org.springframework.boot.context.properties.bind.Binder;
17+
import org.springframework.boot.env.EnvironmentPostProcessor;
18+
import org.springframework.core.Ordered;
19+
import org.springframework.core.env.ConfigurableEnvironment;
20+
import org.springframework.core.env.MutablePropertySources;
21+
import org.springframework.util.ClassUtils;
22+
23+
import static com.oracle.cloud.spring.autoconfigure.core.RegionProviderAutoConfiguration.createRegionProvider;
24+
import static com.oracle.cloud.spring.vault.VaultAutoConfiguration.createSecretsClient;
25+
import static com.oracle.cloud.spring.vault.VaultAutoConfiguration.createVaultClient;
26+
import static org.springframework.core.env.StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME;
27+
28+
/**
29+
* Injects a VaultPropertySource for each OCI Vault property source specified in the application properties.
30+
* OCI Vault property sources will only be loaded if the com.oracle.cloud.spring.vault.Vault class is on the classpath.
31+
*/
32+
public class VaultEnvironmentPostProcessor implements EnvironmentPostProcessor, Ordered {
33+
@Override
34+
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
35+
if (areClassesLoaded()) {
36+
// Load Vault Properties
37+
Binder binder = Binder.get(environment);
38+
CredentialsProperties credentialsProperties = binder.bind(CredentialsProperties.PREFIX, Bindable.of(CredentialsProperties.class))
39+
.orElse(new CredentialsProperties());
40+
RegionProperties regionProperties = binder.bind(RegionProperties.PREFIX, Bindable.of(RegionProperties.class))
41+
.orElse(new RegionProperties());
42+
VaultProperties vaultProperties = binder.bind(VaultProperties.PREFIX, Bindable.of(VaultProperties.class))
43+
.orElse(new VaultProperties());
44+
45+
// Create vault/secrets clients
46+
RegionProvider regionProvider = createRegionProvider(regionProperties);
47+
CredentialsProvider credentialsProvider = getCredentialsProvider(credentialsProperties);
48+
Secrets secretsClient = createSecretsClient(regionProvider, credentialsProvider);
49+
Vaults vaultClient = createVaultClient(regionProvider, credentialsProvider);
50+
51+
// Inject VaultPropertySources into the system property sources
52+
MutablePropertySources propertySources = environment.getPropertySources();
53+
for (VaultPropertySourceProperties properties : vaultProperties.getPropertySources()) {
54+
Vault vault = new VaultImpl(vaultClient, secretsClient, properties.getVaultId(), vaultProperties.getCompartment());
55+
VaultPropertyLoader vaultPropertyLoader = new VaultPropertyLoader(vault, vaultProperties.getPropertyRefreshInterval());
56+
VaultPropertySource vaultPropertySource = new VaultPropertySource(properties.getVaultId(), vaultPropertyLoader);
57+
if (propertySources.contains(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME)) {
58+
propertySources.addAfter(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, vaultPropertySource);
59+
} else {
60+
propertySources.addFirst(vaultPropertySource);
61+
}
62+
}
63+
}
64+
}
65+
66+
@Override
67+
public int getOrder() {
68+
return ConfigDataEnvironmentPostProcessor.ORDER + 1;
69+
}
70+
71+
private boolean areClassesLoaded() {
72+
return ClassUtils.isPresent("com.oracle.cloud.spring.vault.Vault", VaultEnvironmentPostProcessor.class.getClassLoader());
73+
}
74+
75+
private CredentialsProvider getCredentialsProvider(CredentialsProperties credentialsProperties) {
76+
try {
77+
return new CredentialsProvider(credentialsProperties);
78+
} catch (IOException e) {
79+
throw new RuntimeException(e);
80+
}
81+
}
82+
}

spring-cloud-oci-autoconfigure/src/main/java/com/oracle/cloud/spring/vault/VaultProperties.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
// Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/
33
package com.oracle.cloud.spring.vault;
44

5+
import java.time.Duration;
6+
import java.util.List;
7+
58
import org.springframework.boot.context.properties.ConfigurationProperties;
69

710
@ConfigurationProperties(prefix = VaultProperties.PREFIX)
@@ -11,6 +14,10 @@ public class VaultProperties {
1114
private String compartment;
1215
private String vaultId;
1316

17+
private List<VaultPropertySourceProperties> propertySources;
18+
19+
private Duration propertyRefreshInterval;
20+
1421
public String getCompartment() {
1522
return compartment;
1623
}
@@ -26,4 +33,20 @@ public String getVaultId() {
2633
public void setVaultId(String vaultId) {
2734
this.vaultId = vaultId;
2835
}
36+
37+
public List<VaultPropertySourceProperties> getPropertySources() {
38+
return propertySources;
39+
}
40+
41+
public void setPropertySources(List<VaultPropertySourceProperties> propertySources) {
42+
this.propertySources = propertySources;
43+
}
44+
45+
public Duration getPropertyRefreshInterval() {
46+
return propertyRefreshInterval;
47+
}
48+
49+
public void setPropertyRefreshInterval(Duration propertyRefreshInterval) {
50+
this.propertyRefreshInterval = propertyRefreshInterval;
51+
}
2952
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// Copyright (c) 2024, Oracle and/or its affiliates.
2+
// Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/
3+
package com.oracle.cloud.spring.vault;
4+
5+
import java.time.Duration;
6+
import java.util.LinkedHashMap;
7+
import java.util.List;
8+
import java.util.Map;
9+
import java.util.Optional;
10+
import java.util.Timer;
11+
import java.util.TimerTask;
12+
13+
import com.oracle.bmc.secrets.responses.GetSecretBundleByNameResponse;
14+
import com.oracle.bmc.vault.model.SecretSummary;
15+
16+
public class VaultPropertyLoader implements AutoCloseable {
17+
private static Timer timer;
18+
19+
private final Vault vault;
20+
private final Map<String, Object> properties = new LinkedHashMap<>();
21+
22+
public VaultPropertyLoader(Vault vault, Duration refresh) {
23+
this.vault = vault;
24+
reload();
25+
long refreshMillis = Optional.ofNullable(refresh)
26+
.orElse(Duration.ofMinutes(10))
27+
.toMillis();
28+
if (refreshMillis > 0) {
29+
synchronized (VaultPropertyLoader.class) {
30+
if (timer == null) {
31+
timer = new Timer(true);
32+
;
33+
timer.scheduleAtFixedRate(new TimerTask() {
34+
@Override
35+
public void run() {
36+
reload();
37+
}
38+
}, refreshMillis, refreshMillis);
39+
}
40+
}
41+
}
42+
}
43+
44+
boolean containsProperty(String key) {
45+
return properties.containsKey(key);
46+
}
47+
48+
Object getProperty(String key) {
49+
return properties.get(key);
50+
}
51+
52+
String[] getPropertyNames() {
53+
return properties.keySet().toArray(String[]::new);
54+
}
55+
56+
private void reload() {
57+
List<SecretSummary> secrets = vault.listSecrets();
58+
for (SecretSummary secretSummary : secrets) {
59+
GetSecretBundleByNameResponse getSecretResponse = vault.getSecret(secretSummary.getSecretName());
60+
String secretValue = vault.decodeBundle(getSecretResponse);
61+
properties.put(secretSummary.getSecretName(), secretValue);
62+
}
63+
}
64+
65+
@Override
66+
public void close() throws Exception {
67+
synchronized (VaultPropertyLoader.class) {
68+
if (timer != null) {
69+
timer.cancel();
70+
timer = null;
71+
}
72+
}
73+
}
74+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Copyright (c) 2024, Oracle and/or its affiliates.
2+
// Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/
3+
package com.oracle.cloud.spring.vault;
4+
5+
import org.springframework.core.env.EnumerablePropertySource;
6+
7+
public class VaultPropertySource extends EnumerablePropertySource<VaultPropertyLoader> {
8+
public VaultPropertySource(String name, VaultPropertyLoader source) {
9+
super(name, source);
10+
}
11+
12+
@Override
13+
public String[] getPropertyNames() {
14+
return source.getPropertyNames();
15+
}
16+
17+
@Override
18+
public Object getProperty(String name) {
19+
return source.getProperty(name);
20+
}
21+
22+
@Override
23+
public boolean containsProperty(String name) {
24+
return source.containsProperty(name);
25+
}
26+
}

0 commit comments

Comments
 (0)