Skip to content

Commit

Permalink
HCP vault secrets centralized config
Browse files Browse the repository at this point in the history
  • Loading branch information
MouhsinElmajdouby committed Jan 2, 2025
1 parent 60afbb5 commit 7e488bb
Show file tree
Hide file tree
Showing 12 changed files with 535 additions and 2 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package oracle.jdbc.provider.hashicorp.hcpvault;

import oracle.jdbc.provider.factory.Resource;
import oracle.jdbc.provider.factory.ResourceFactory;
import oracle.jdbc.provider.hashicorp.hcpvault.authentication.HcpVaultCredentials;
import oracle.jdbc.provider.hashicorp.hcpvault.authentication.HcpVaultCredentialsFactory;
import oracle.jdbc.provider.parameter.ParameterSet;

public abstract class HcpVaultResourceFactory<T> implements ResourceFactory<T> {

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

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

public abstract Resource<T> request(
HcpVaultCredentials credentials, ParameterSet parameterSet);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package oracle.jdbc.provider.hashicorp.hcpvault.authentication;


public enum HcpVaultAuthenticationMethod {
CLIENT_CREDENTIALS
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package oracle.jdbc.provider.hashicorp.hcpvault.authentication;

/**
* Holds the HCP API token obtained from the client_credentials flow.
*/
public final class HcpVaultCredentials {
private final String hcpApiToken;

public HcpVaultCredentials(String hcpApiToken) {
this.hcpApiToken = hcpApiToken;
}

public String getHcpApiToken() {
return hcpApiToken;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package oracle.jdbc.provider.hashicorp.hcpvault.authentication;

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

import static oracle.jdbc.provider.parameter.Parameter.CommonAttribute.REQUIRED;
import static oracle.jdbc.provider.util.ParameterUtil.getRequiredOrFallback;

/**
* A factory for creating {@link HcpVaultCredentials} objects for HCP.
*/
public final class HcpVaultCredentialsFactory implements ResourceFactory<HcpVaultCredentials> {

// The param that indicates which HCP auth method to use (only one for now).
public static final Parameter<HcpVaultAuthenticationMethod> AUTHENTICATION_METHOD = Parameter.create(REQUIRED);

// The OAuth2 client_id and client_secret
public static final Parameter<String> CLIENT_ID = Parameter.create(REQUIRED);
public static final Parameter<String> CLIENT_SECRET = Parameter.create(REQUIRED);

private static final HcpVaultCredentialsFactory INSTANCE = new HcpVaultCredentialsFactory();

private HcpVaultCredentialsFactory() {}

public static HcpVaultCredentialsFactory getInstance() {
return INSTANCE;
}

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

private HcpVaultCredentials getCredential(ParameterSet parameterSet) {
HcpVaultAuthenticationMethod method = parameterSet.getRequired(AUTHENTICATION_METHOD);

switch (method) {
case CLIENT_CREDENTIALS:
return createClientCredentials(parameterSet);
default:
throw new IllegalArgumentException("Unrecognized HCP auth method: " + method);
}
}

private HcpVaultCredentials createClientCredentials(ParameterSet parameterSet) {
String clientId = getRequiredOrFallback(parameterSet, CLIENT_ID, "CLIENT_ID");
String clientSecret = getRequiredOrFallback(parameterSet, CLIENT_SECRET, "CLIENT_SECRET");

// Call OAuth endpoint to fetch the token
String apiToken = HcpVaultOAuthClient.fetchHcpAccessToken(clientId, clientSecret);
if (apiToken == null || apiToken.isEmpty()) {
throw new IllegalStateException("Failed to obtain HCP token using client_credentials flow");
}

return new HcpVaultCredentials(apiToken);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package oracle.jdbc.provider.hashicorp.hcpvault.authentication;

import oracle.sql.json.OracleJsonFactory;
import oracle.sql.json.OracleJsonObject;

import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;

public final class HcpVaultOAuthClient {

private HcpVaultOAuthClient() {}

public static String fetchHcpAccessToken(String clientId, String clientSecret) {
HttpURLConnection conn = null;
try {
URL url = new URL("https://auth.idp.hashicorp.com/oauth/token");
conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
conn.setDoOutput(true);

String body = "grant_type=client_credentials"
+ "&client_id=" + clientId
+ "&client_secret=" + clientSecret
+ "&audience=https://api.hashicorp.cloud";

try (OutputStream os = conn.getOutputStream()) {
os.write(body.getBytes(StandardCharsets.UTF_8));
}

if (conn.getResponseCode() == 200) {
try (InputStream in = conn.getInputStream()) {
OracleJsonObject response = new OracleJsonFactory()
.createJsonTextValue(new ByteArrayInputStream(readAll(in).getBytes(StandardCharsets.UTF_8)))
.asJsonObject();

return response.getString("access_token");
}
} else {
throw new IllegalStateException("Failed to obtain HCP token. HTTP=" + conn.getResponseCode());
}
} catch (IOException e) {
throw new IllegalStateException("Failed to fetch HCP access token", e);
} finally {
if (conn != null) {
conn.disconnect();
}
}
}

private static String readAll(InputStream in) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len;
while ((len = in.read(buffer)) != -1) {
baos.write(buffer, 0, len);
}
return new String(baos.toByteArray(), StandardCharsets.UTF_8);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package oracle.jdbc.provider.hashicorp.hcpvault.configuration;

import oracle.jdbc.provider.hashicorp.hcpvault.authentication.HcpVaultAuthenticationMethod;
import oracle.jdbc.provider.hashicorp.hcpvault.authentication.HcpVaultCredentialsFactory;
import oracle.jdbc.provider.parameter.ParameterSetParser;

/**
* Defines how we parse HCP parameters in the JDBC URL or property sets.
*/
public final class HcpVaultConfigurationParameters {

private HcpVaultConfigurationParameters() {}

public static ParameterSetParser.Builder configureBuilder(ParameterSetParser.Builder builder) {
return builder
.addParameter(
"AUTHENTICATION_METHOD",
HcpVaultCredentialsFactory.AUTHENTICATION_METHOD,
HcpVaultAuthenticationMethod.CLIENT_CREDENTIALS,
HcpVaultConfigurationParameters::parseAuthMethod
)
.addParameter(
"CLIENT_ID",
HcpVaultCredentialsFactory.CLIENT_ID
)
.addParameter(
"CLIENT_SECRET",
HcpVaultCredentialsFactory.CLIENT_SECRET
);
}

private static HcpVaultAuthenticationMethod parseAuthMethod(String value) {
if ("CLIENT_CREDENTIALS".equalsIgnoreCase(value)) {
return HcpVaultAuthenticationMethod.CLIENT_CREDENTIALS;
}
throw new IllegalArgumentException("Unrecognized HCP auth method: " + value);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
** Copyright (c) 2023 Oracle and/or its affiliates.
**
** The Universal Permissive License (UPL), Version 1.0
**
** Subject to the condition set forth below, permission is hereby granted to any
** person obtaining a copy of this software, associated documentation and/or data
** (collectively the "Software"), free of charge and under any and all copyright
** rights in the Software, and any and all patent rights owned or freely
** licensable by each licensor hereunder covering either (i) the unmodified
** Software as contributed to or provided by such licensor, or (ii) the Larger
** Works (as defined below), to deal in both
**
** (a) the Software, and
** (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
** one is included with the Software (each a "Larger Work" to which the Software
** is contributed by such licensors),
**
** without restriction, including without limitation the rights to copy, create
** derivative works of, display, perform, and distribute the Software and make,
** use, sell, offer for sale, import, export, have made, and have sold the
** Software and the Larger Work(s), and to sublicense the foregoing rights on
** either these or other terms.
**
** This license is subject to the following condition:
** The above copyright notice and either this complete permission notice or at
** a minimum a reference to the UPL must be included in all copies or
** substantial portions of the Software.
**
** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
** SOFTWARE.
*/

package oracle.jdbc.provider.hashicorp.hcpvault.configuration;

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

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

import static oracle.jdbc.provider.hashicorp.hcpvault.configuration.HcpVaultSecretsManagerConfigurationProvider.PARAMETER_SET_PARSER;

public class HcpVaultJsonVaultProvider implements OracleConfigurationJsonSecretProvider {

@Override
public char[] getSecret(OracleJsonObject jsonObject) {

// 1) Parse the top-level fields from the snippet.
// e.g. "type" : "hcpvault", "value" : "my-hcp-app"
ParameterSet parameterSet =
PARAMETER_SET_PARSER.parseNamedValues(
JsonSecretUtil.toNamedValues(jsonObject)
);

// 2) Call HcpSecretsManagerFactory to fetch the secret "value".
String secretString = HcpVaultSecretsManagerFactory
.getInstance()
.request(parameterSet)
.getContent();

// 3) Base64-encode the secret content to produce the final char[].
String base64Encoded = Base64.getEncoder()
.encodeToString(secretString.getBytes(StandardCharsets.UTF_8));
return base64Encoded.toCharArray();
}

@Override
public String getSecretType() {
// Must match the "type" field used in the JSON snippet (e.g. "hcpvault").
return "hcpvault";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package oracle.jdbc.provider.hashicorp.hcpvault.configuration;

import oracle.jdbc.driver.OracleConfigurationJsonProvider;
import oracle.jdbc.provider.hashicorp.hcpvault.secrets.HcpVaultSecretsManagerFactory;
import oracle.jdbc.provider.parameter.ParameterSet;
import oracle.jdbc.provider.parameter.ParameterSetParser;
import oracle.jdbc.util.OracleConfigurationCache;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;


public class HcpVaultSecretsManagerConfigurationProvider extends OracleConfigurationJsonProvider {

static final ParameterSetParser PARAMETER_SET_PARSER =
HcpVaultConfigurationParameters.configureBuilder(
ParameterSetParser.builder()
.addParameter("value", HcpVaultSecretsManagerFactory.APP_NAME)
.addParameter("ORG_ID", HcpVaultSecretsManagerFactory.ORG_ID)
.addParameter("PROJECT_ID", HcpVaultSecretsManagerFactory.PROJECT_ID)
.addParameter("SECRET_NAME", HcpVaultSecretsManagerFactory.SECRET_NAME)
.addParameter("KEY", HcpVaultSecretsManagerFactory.KEY)
).build();

@Override
public InputStream getJson(String appName) {
// 'appName' is the part after config-hcpvault://
final String valueField = "value";

Map<String, String> optionsWithAppName = new HashMap<>(options);
optionsWithAppName.put(valueField, appName);

ParameterSet parameterSet = PARAMETER_SET_PARSER.parseNamedValues(optionsWithAppName);

// Call the factory
String secretsJson = HcpVaultSecretsManagerFactory
.getInstance()
.request(parameterSet)
.getContent();

return new ByteArrayInputStream(secretsJson.getBytes(StandardCharsets.UTF_8));
}

@Override
public String getType() {
// The provider name that appears in the JDBC URL after "config-"
return "hcpvault";
}


@Override
public OracleConfigurationCache getCache() {
return CACHE;
}

}
Loading

0 comments on commit 7e488bb

Please sign in to comment.