Skip to content

Commit

Permalink
Dedicated Vault centralized config
Browse files Browse the repository at this point in the history
  • Loading branch information
MouhsinElmajdouby committed Dec 23, 2024
1 parent 190a9c0 commit d088b34
Show file tree
Hide file tree
Showing 12 changed files with 569 additions and 0 deletions.
39 changes: 39 additions & 0 deletions ojdbc-provider-hashicorp/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<name>Oracle JDBC Hashicorp Providers</name>

<artifactId>ojdbc-provider-hashicorp</artifactId>
<packaging>jar</packaging>

<parent>
<groupId>com.oracle.database.jdbc</groupId>
<artifactId>ojdbc-extensions</artifactId>
<version>${extensions-version}</version>
</parent>

<dependencies>
<dependency>
<groupId>com.oracle.database.jdbc</groupId>
<artifactId>ojdbc-provider-common</artifactId>
</dependency>

<dependency>
<groupId>com.oracle.database.jdbc</groupId>
<artifactId>ojdbc-provider-common</artifactId>
<classifier>tests</classifier>
<type>test-jar</type>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
</dependency>
</dependencies>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package oracle.jdbc.provider.hashicorp;

import oracle.jdbc.provider.factory.Resource;
import oracle.jdbc.provider.factory.ResourceFactory;
import oracle.jdbc.provider.hashicorp.authentication.HashiCredentials;
import oracle.jdbc.provider.hashicorp.authentication.HashicorpCredentialsFactory;
import oracle.jdbc.provider.parameter.ParameterSet;

/**
* Common super class for ResourceFactory implementations that request
* a resource from Vault using HashiCredentials (Vault token).
*/
public abstract class HashiVaultResourceFactory<T> implements ResourceFactory<T> {

@Override
public final Resource<T> request(ParameterSet parameterSet) {
// Retrieve the Vault credentials (token) from the credentials factory
HashiCredentials credentials = HashicorpCredentialsFactory.getInstance()
.request(parameterSet)
.getContent();

try {
return request(credentials, parameterSet);
} catch (Exception e) {
throw new IllegalStateException(
"Request failed with parameters: " + parameterSet, e);
}
}

/**
* Subclasses implement to request the resource from Vault using
* the given credentials and parameters.
*/
public abstract Resource<T> request(
HashiCredentials credentials, ParameterSet parameterSet);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package oracle.jdbc.provider.hashicorp.authentication;

/**
* Simple credentials object for HashiCorp Vault that holds a token.
*/
public final class HashiCredentials {
private final String vaultToken;

public HashiCredentials(String vaultToken) {
this.vaultToken = vaultToken;
}

public String getVaultToken() {
return vaultToken;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package oracle.jdbc.provider.hashicorp.authentication;

/**
* A method of authentication using HashiCorp Vault.
*/
public enum HashicorpAuthenticationMethod {
/**
* Authentication using a Vault token (e.g., read from environment variable).
*/
TOKEN
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package oracle.jdbc.provider.hashicorp.authentication;

import oracle.jdbc.provider.factory.Resource;
import oracle.jdbc.provider.factory.ResourceFactory;
import oracle.jdbc.provider.hashicorp.secrets.HashiVaultSecretsManagerFactory;
import oracle.jdbc.provider.parameter.Parameter;
import oracle.jdbc.provider.parameter.ParameterSet;

import static oracle.jdbc.provider.parameter.Parameter.CommonAttribute.REQUIRED;

/**
* A factory for creating {@link HashiCredentials} objects for Vault.
*/
public final class HashicorpCredentialsFactory implements ResourceFactory<HashiCredentials> {

// Example parameter referencing the authentication method
public static final Parameter<HashicorpAuthenticationMethod> AUTHENTICATION_METHOD =
Parameter.create(REQUIRED);

private static final HashicorpCredentialsFactory INSTANCE =
new HashicorpCredentialsFactory();

private HashicorpCredentialsFactory() { }

public static HashicorpCredentialsFactory getInstance() {
return INSTANCE;
}

@Override
public Resource<HashiCredentials> request(ParameterSet parameterSet) {
HashiCredentials credentials = getCredential(parameterSet);
return Resource.createPermanentResource(credentials, true);
}

private static HashiCredentials getCredential(ParameterSet parameterSet) {
System.out.println("parameterSet = " + parameterSet);
// Check which authentication method is requested
HashicorpAuthenticationMethod method =
parameterSet.getRequired(AUTHENTICATION_METHOD);

switch (method) {
case TOKEN:
return tokenCredentials(parameterSet);
default:
throw new IllegalArgumentException(
"Unrecognized authentication method: " + method);
}
}

/**
* Example: read Vault token from an environment variable "VAULT_TOKEN".
*/
private static HashiCredentials tokenCredentials(ParameterSet parameterSet) {
// (1) Try parameter
String paramToken = parameterSet.getOptional(HashiVaultSecretsManagerFactory.VAULT_TOKEN);
if (paramToken != null && !paramToken.isEmpty()) {
return new HashiCredentials(paramToken);
}

// (2) Fallback to system property or env
String vaultToken = System.getProperty("VAULT_TOKEN", System.getenv("VAULT_TOKEN"));
if (vaultToken == null || vaultToken.isEmpty()) {
throw new IllegalStateException("Vault token not provided in tokenCredentials()");
}

return new HashiCredentials(vaultToken);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package oracle.jdbc.provider.hashicorp.configuration;

import oracle.jdbc.provider.configuration.JsonSecretUtil;
import oracle.jdbc.provider.hashicorp.secrets.HashiVaultSecretsManagerFactory;
import oracle.jdbc.provider.parameter.ParameterSet;
import oracle.jdbc.spi.OracleConfigurationJsonSecretProvider;
import oracle.sql.json.OracleJsonObject;

import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;
import java.util.Base64;

import static oracle.jdbc.provider.hashicorp.configuration.HashiVaultSecretsManagerConfigurationProvider.PARAMETER_SET_PARSER;
import static oracle.jdbc.provider.hashicorp.secrets.HashiVaultSecretsManagerFactory.FIELD_NAME;

/**
* Mirrors the AWS pattern for retrieving a single secret
* field from HashiCorp Vault, base64-encoding it.
*
* Example JSON input might look like:
* {
* "password": {
* "type": "hashicorpvault",
* "value": "/v1/secret/data/test-config2"
* }
* }
*
* The provider will retrieve the secret from Vault, then
* base64-encode it and return as a char[].
*/
public class HashiJsonVaultProvider implements OracleConfigurationJsonSecretProvider {

@Override
public char[] getSecret(OracleJsonObject jsonObject) {
// 1) Convert the JSON object to named key-value pairs
ParameterSet parameterSet =
PARAMETER_SET_PARSER.parseNamedValues(
JsonSecretUtil.toNamedValues(jsonObject)
);

// 2) Call the Vault factory to fetch the raw secret string
String secretString = HashiVaultSecretsManagerFactory
.getInstance()
.request(parameterSet)
.getContent();

ByteArrayInputStream inputStream = new ByteArrayInputStream(secretString.getBytes(StandardCharsets.UTF_8));


// 3) Parse that JSON to find "myPassword"
// Using the Oracle JSON library, for example:
OracleJsonObject secretJsonObj =
new oracle.sql.json.OracleJsonFactory()
.createJsonTextValue(inputStream)
.asJsonObject();

System.out.println(secretJsonObj);

// 4) Retrieve the field we want
//String myPasswordValue = secretJsonObj.getString("myPassword");
String myPasswordValue = parameterSet.getOptional(FIELD_NAME);
System.out.println(myPasswordValue);

// 5) Base64-encode just that field
return Base64.getEncoder()
.encodeToString(myPasswordValue.getBytes())
.toCharArray();
}

@Override
public String getSecretType() {
// Must match the "type" field in your JSON.
// E.g. "hashicorpvault" or "hashicorsecret"—your choice.
return "hashicorpvault";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package oracle.jdbc.provider.hashicorp.configuration;

import oracle.jdbc.driver.OracleConfigurationJsonProvider;
import oracle.jdbc.provider.hashicorp.secrets.HashiVaultSecretsManagerFactory;
import oracle.jdbc.provider.parameter.ParameterSet;
import oracle.jdbc.provider.parameter.ParameterSetParser;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;

/**
* A provider for JSON payload from Vault, analogous to AWS Secrets Manager version.
*/
public class HashiVaultSecretsManagerConfigurationProvider extends OracleConfigurationJsonProvider {

// Reuse a ParameterSetParser approach
// If you need more parameters, add them below.
static final ParameterSetParser PARAMETER_SET_PARSER =
HashicorpConfigurationParameters.configureBuilder(
ParameterSetParser.builder()
.addParameter("value", HashiVaultSecretsManagerFactory.SECRET_PATH)
.addParameter("key_name", HashiVaultSecretsManagerFactory.KEY_NAME)
.addParameter(
"VAULT_ADDR",
HashiVaultSecretsManagerFactory.VAULT_ADDRESS
)
.addParameter(
"VAULT_TOKEN",
HashiVaultSecretsManagerFactory.VAULT_TOKEN
)
.addParameter("FILED_NAME",
HashiVaultSecretsManagerFactory.FIELD_NAME)
).build();

@Override
public InputStream getJson(String secretPath) {
// 'secretPath' is the location or name of the Vault secret
final String valueFieldName = "value";

// Build a map of user-provided options
Map<String, String> optionsWithSecret = new HashMap<>(options);
optionsWithSecret.put(valueFieldName, secretPath);

// Parse into a ParameterSet
ParameterSet parameters = PARAMETER_SET_PARSER.parseNamedValues(optionsWithSecret);

// Fetch the secret from Vault
String secretString = HashiVaultSecretsManagerFactory
.getInstance()
.request(parameters)
.getContent();

// Return the JSON as an InputStream
return new ByteArrayInputStream(secretString.getBytes());
}

@Override
public String getType() {
// We'll reference this in our JDBC URL, e.g. "jdbc:oracle:thin:@config-hashicorpvault://..."
return "hashicorpvault";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package oracle.jdbc.provider.hashicorp.configuration;

import oracle.jdbc.provider.hashicorp.authentication.HashicorpAuthenticationMethod;
import oracle.jdbc.provider.hashicorp.authentication.HashicorpCredentialsFactory;
import oracle.jdbc.provider.hashicorp.secrets.HashiVaultSecretsManagerFactory;
import oracle.jdbc.provider.parameter.ParameterSetParser;

/**
* Defines how we parse common Vault parameters (similar to AWS approach).
*/
public final class HashicorpConfigurationParameters {

private HashicorpConfigurationParameters() {}

public static ParameterSetParser.Builder configureBuilder(ParameterSetParser.Builder builder) {
return builder.addParameter(
// The parameter name is "AUTHENTICATION"
"AUTHENTICATION",
// Tied to HashicorpCredentialsFactory.AUTHENTICATION_METHOD
HashicorpCredentialsFactory.AUTHENTICATION_METHOD,
// Default value if none is specified:
HashicorpAuthenticationMethod.TOKEN,
HashicorpConfigurationParameters::parseAuthentication)
;
}

private static HashicorpAuthenticationMethod parseAuthentication(String value) {
// Map user-provided string to enum
if ("TOKEN".equalsIgnoreCase(value) || "VAULT_TOKEN".equalsIgnoreCase(value)) {
return HashicorpAuthenticationMethod.TOKEN;
}
throw new IllegalArgumentException(
"Unrecognized Hashicorp authentication value: " + value);
}
}
Loading

0 comments on commit d088b34

Please sign in to comment.