Skip to content

Commit

Permalink
OCI Vault
Browse files Browse the repository at this point in the history
Signed-off-by: Anders Swanson <[email protected]>
  • Loading branch information
anders-swanson committed Jun 20, 2024
1 parent 72c7c03 commit ec35da5
Show file tree
Hide file tree
Showing 12 changed files with 423 additions and 2 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
** Copyright (c) 2024, Oracle and/or its affiliates.
** Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/
*/
package com.oracle.cloud.spring.vault;


import com.oracle.bmc.auth.RegionProvider;
import com.oracle.bmc.secrets.Secrets;
import com.oracle.bmc.secrets.SecretsClient;
import com.oracle.bmc.vault.Vaults;
import com.oracle.bmc.vault.VaultsClient;
import com.oracle.cloud.spring.autoconfigure.core.CredentialsProvider;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.annotation.Bean;

import static com.oracle.cloud.spring.autoconfigure.core.CredentialsProviderAutoConfiguration.credentialsProviderQualifier;
import static com.oracle.cloud.spring.autoconfigure.core.RegionProviderAutoConfiguration.regionProviderQualifier;

/**
* Auto-configuration for initializing the OCI Vault component.
* Depends on {@link com.oracle.cloud.spring.autoconfigure.core.CredentialsProviderAutoConfiguration} and
* {@link com.oracle.cloud.spring.autoconfigure.core.RegionProviderAutoConfiguration}
* for loading the Authentication configuration
*
* @see Vault
*/
@AutoConfiguration
@ConditionalOnClass({Vault.class})
@EnableConfigurationProperties(VaultProperties.class)
@ConditionalOnProperty(name = "spring.cloud.oci.vault.enabled", havingValue = "true", matchIfMissing = true)
public class VaultAutoConfiguration {
private final VaultProperties properties;

public VaultAutoConfiguration(VaultProperties properties) {
this.properties = properties;
}

@Bean
@RefreshScope
@ConditionalOnProperty(name = "spring.cloud.oci.genai.embedding.enabled", havingValue = "true", matchIfMissing = true)
@ConditionalOnMissingBean(Vault.class)
public Vault embeddingModel(Vaults vaults, Secrets secrets) {
return new VaultImpl(vaults, secrets, properties.getVaultId(), properties.getCompartment());
}

@Bean
@RefreshScope
@ConditionalOnMissingBean
public Vaults vaults(@Qualifier(regionProviderQualifier) RegionProvider regionProvider,
@Qualifier(credentialsProviderQualifier)
CredentialsProvider cp) {
Vaults vaults = VaultsClient.builder()
.build(cp.getAuthenticationDetailsProvider());
if (regionProvider.getRegion() != null) {
vaults.setRegion(regionProvider.getRegion());
}
return vaults;
}

@Bean
@RefreshScope
@ConditionalOnMissingBean
public Secrets secrets(@Qualifier(regionProviderQualifier) RegionProvider regionProvider,
@Qualifier(credentialsProviderQualifier)
CredentialsProvider cp) {
Secrets secrets = SecretsClient.builder()
.build(cp.getAuthenticationDetailsProvider());
if (regionProvider.getRegion() != null) {
secrets.setRegion(regionProvider.getRegion());
}
return secrets;
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.oracle.cloud.spring.vault;

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(prefix = VaultProperties.PREFIX)
public class VaultProperties {
public static final String PREFIX = "spring.cloud.oci.vault";

private String compartment;
private String vaultId;

public String getCompartment() {
return compartment;
}

public void setCompartment(String compartment) {
this.compartment = compartment;
}

public String getVaultId() {
return vaultId;
}

public void setVaultId(String vaultId) {
this.vaultId = vaultId;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@
"description": "Auto-configure OCI Cloud streaming components.",
"defaultValue": true
},
{
"name": "spring.cloud.oci.vault.enabled",
"type": "java.lang.Boolean",
"description": "Auto-configure OCI Cloud vault components.",
"defaultValue": true
},
{
"name": "spring.cloud.oci.queue.enabled",
"type": "java.lang.Boolean",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ com.oracle.cloud.spring.function.FunctionAutoConfiguration
com.oracle.cloud.spring.streaming.StreamingAutoConfiguration
com.oracle.cloud.spring.queue.QueueAutoConfiguration
com.oracle.cloud.spring.genai.GenAIAutoConfiguration
com.oracle.cloud.spring.vault.VaultAutoConfiguration
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
package com.oracle.cloud.spring.sample.streaming.springcloudocivaultsample;
/*
** Copyright (c) 2024, Oracle and/or its affiliates.
** Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/
*/
package com.oracle.cloud.spring.sample.vault.springcloudocivaultsample;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
** Copyright (c) 2024, Oracle and/or its affiliates.
** Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/
*/
package com.oracle.cloud.spring.sample.vault.springcloudocivaultsample;

import com.oracle.bmc.secrets.responses.GetSecretBundleByNameResponse;
import com.oracle.cloud.spring.vault.Vault;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/demoapp/api/vault/")
@Tag(name = "streaming APIs")
public class VaultController {
private final Vault vault;

public VaultController(Vault vault) {
this.vault = vault;
}

@GetMapping("secret")
public ResponseEntity<?> getSecret(@RequestParam String secretName) {
GetSecretBundleByNameResponse secret = vault.getSecret(secretName);
return ResponseEntity.ok(vault.decodeBundle(secret));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Copyright (c) 2024, Oracle and/or its affiliates.
# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/

spring.cloud.oci.region.static=us-chicago-1
spring.cloud.oci.config.type=file

spring.cloud.oci.vault.compartment=${OCI_COMPARTMENT_ID}
spring.cloud.oci.vault.vault-id=${OCI_VAULT_ID}
spring.cloud.oci.vault.enabled=true
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
** Copyright (c) 2024, Oracle and/or its affiliates.
** Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/
*/
package com.oracle.cloud.spring.sample.vault.springcloudocivaultsample;

import java.util.Base64;
import java.util.UUID;

import com.oracle.bmc.secrets.responses.GetSecretBundleByNameResponse;
import com.oracle.bmc.vault.model.Base64SecretContentDetails;
import com.oracle.bmc.vault.model.UpdateSecretDetails;
import com.oracle.bmc.vault.responses.UpdateSecretResponse;
import com.oracle.cloud.spring.vault.Vault;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import static org.assertj.core.api.Assertions.assertThat;

/**
* Requires an existing vault, identified by the OCI_VAULT_ID environment variable.
*/
@SpringBootTest
@EnabledIfEnvironmentVariable(named = "OCI_COMPARTMENT_ID", matches = ".+")
@EnabledIfEnvironmentVariable(named = "OCI_VAULT_ID", matches = ".+")
public class VaultIT {
@Autowired
Vault vault;

private final String secretName = "mysecret";

@Test
@Disabled
void getSecret() {
GetSecretBundleByNameResponse secret = vault.getSecret(secretName);
String decoded = vault.decodeBundle(secret);
assertThat(decoded).isNotNull();
assertThat(decoded).hasSizeGreaterThan(1);
}

@Test
void updateSecret() {
String content = UUID.randomUUID().toString();
Base64SecretContentDetails contentDetails = Base64SecretContentDetails.builder()
.content(Base64.getEncoder().encodeToString(content.getBytes()))
.name(content)
.build();
UpdateSecretResponse response = vault.updateSecret(secretName, UpdateSecretDetails.builder()
.secretContent(contentDetails)
.build());
assertThat(response.getSecret()).isNotNull();
}
}
4 changes: 4 additions & 0 deletions spring-cloud-oci-vault/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ Licensed under the Universal Permissive License v 1.0 as shown at https://oss.or
<groupId>com.oracle.oci.sdk</groupId>
<artifactId>oci-java-sdk-vault</artifactId>
</dependency>
<dependency>
<groupId>com.oracle.oci.sdk</groupId>
<artifactId>oci-java-sdk-secrets</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,35 @@
/*
** Copyright (c) 2024, Oracle and/or its affiliates.
** Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/
*/
package com.oracle.cloud.spring.vault;

import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Date;

import com.oracle.bmc.secrets.model.Base64SecretBundleContentDetails;
import com.oracle.bmc.secrets.model.SecretBundleContentDetails;
import com.oracle.bmc.secrets.responses.GetSecretBundleByNameResponse;
import com.oracle.bmc.vault.model.CreateSecretDetails;
import com.oracle.bmc.vault.model.UpdateSecretDetails;
import com.oracle.bmc.vault.responses.CreateSecretResponse;
import com.oracle.bmc.vault.responses.ScheduleSecretDeletionResponse;
import com.oracle.bmc.vault.responses.UpdateSecretResponse;

public interface Vault {
GetSecretBundleByNameResponse getSecret(String secretName);
CreateSecretResponse createSecret(String secretName, CreateSecretDetails body);
ScheduleSecretDeletionResponse scheduleSecretDeletion(String secretName, Date timeOfDeletion);
UpdateSecretResponse updateSecret(String secretName, UpdateSecretDetails body);

default String decodeBundle(GetSecretBundleByNameResponse bundle) {
SecretBundleContentDetails content = bundle.getSecretBundle().getSecretBundleContent();
if (content instanceof Base64SecretBundleContentDetails) {
Base64SecretBundleContentDetails encoded = (Base64SecretBundleContentDetails) content;
return new String(Base64.getDecoder().decode(encoded.getContent()), StandardCharsets.UTF_8);
} else {
return content.toString();
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,91 @@
/*
** Copyright (c) 2024, Oracle and/or its affiliates.
** Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/
*/
package com.oracle.cloud.spring.vault;

public class VaultImpl {
import java.util.Date;

import com.oracle.bmc.secrets.Secrets;
import com.oracle.bmc.secrets.requests.GetSecretBundleByNameRequest;
import com.oracle.bmc.secrets.responses.GetSecretBundleByNameResponse;
import com.oracle.bmc.vault.Vaults;
import com.oracle.bmc.vault.model.CreateSecretDetails;
import com.oracle.bmc.vault.model.ScheduleSecretDeletionDetails;
import com.oracle.bmc.vault.model.UpdateSecretDetails;
import com.oracle.bmc.vault.requests.CreateSecretRequest;
import com.oracle.bmc.vault.requests.ScheduleSecretDeletionRequest;
import com.oracle.bmc.vault.requests.UpdateSecretRequest;
import com.oracle.bmc.vault.responses.CreateSecretResponse;
import com.oracle.bmc.vault.responses.ScheduleSecretDeletionResponse;
import com.oracle.bmc.vault.responses.UpdateSecretResponse;
import org.springframework.util.Assert;

public class VaultImpl implements Vault {
private final Vaults vaults;
private final Secrets secrets;
private final String vaultId;
private final String compartmentId;

public VaultImpl(Vaults vaults, Secrets secrets, String vaultId, String compartmentId) {
Assert.notNull(vaults, "vaults must not be null");
Assert.notNull(secrets, "secrets must not be null");
Assert.hasText(vaultId, "vaultId must not be empty");
Assert.hasText(compartmentId, "compartmentId must not be empty");
this.vaults = vaults;
this.secrets = secrets;
this.vaultId = vaultId;
this.compartmentId = compartmentId;
}

public GetSecretBundleByNameResponse getSecret(String secretName) {
Assert.hasText(secretName, "secretName must not be empty");
GetSecretBundleByNameRequest request = GetSecretBundleByNameRequest.builder()
.vaultId(vaultId)
.secretName(secretName)
.build();
return secrets.getSecretBundleByName(request);
}

public CreateSecretResponse createSecret(String secretName, CreateSecretDetails body) {
Assert.hasText(secretName, "secretName must not be empty");
Assert.notNull(body, "body must not be null");
CreateSecretRequest request = CreateSecretRequest.builder()
.body$(body.toBuilder()
.vaultId(vaultId)
.compartmentId(compartmentId)
.secretName(secretName)
.build())
.build();
return vaults.createSecret(request);
}

public ScheduleSecretDeletionResponse scheduleSecretDeletion(String secretName, Date timeOfDeletion) {
Assert.hasText(secretName, "secretName must not be empty");
Assert.notNull(timeOfDeletion, "timeOfDeletion must not be null");
String secretId = getSecret(secretName)
.getSecretBundle()
.getSecretId();
ScheduleSecretDeletionDetails body = ScheduleSecretDeletionDetails.builder()
.timeOfDeletion(timeOfDeletion)
.build();
ScheduleSecretDeletionRequest request = ScheduleSecretDeletionRequest.builder()
.secretId(secretId)
.body$(body)
.build();
return vaults.scheduleSecretDeletion(request);
}

public UpdateSecretResponse updateSecret(String secretName, UpdateSecretDetails body) {
Assert.hasText(secretName, "secretName must not be empty");
Assert.notNull(body, "body must not be null");
String secretId = getSecret(secretName)
.getSecretBundle()
.getSecretId();
UpdateSecretRequest request = UpdateSecretRequest.builder()
.secretId(secretId)
.body$(body)
.build();
return vaults.updateSecret(request);
}
}
Loading

0 comments on commit ec35da5

Please sign in to comment.