Skip to content

Commit 7e488bb

Browse files
HCP vault secrets centralized config
1 parent 60afbb5 commit 7e488bb

12 files changed

+535
-2
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package oracle.jdbc.provider.hashicorp.hcpvault;
2+
3+
import oracle.jdbc.provider.factory.Resource;
4+
import oracle.jdbc.provider.factory.ResourceFactory;
5+
import oracle.jdbc.provider.hashicorp.hcpvault.authentication.HcpVaultCredentials;
6+
import oracle.jdbc.provider.hashicorp.hcpvault.authentication.HcpVaultCredentialsFactory;
7+
import oracle.jdbc.provider.parameter.ParameterSet;
8+
9+
public abstract class HcpVaultResourceFactory<T> implements ResourceFactory<T> {
10+
11+
@Override
12+
public final Resource<T> request(ParameterSet parameterSet) {
13+
// Retrieve the HCP credentials (token) from the credentials factory
14+
HcpVaultCredentials credentials = HcpVaultCredentialsFactory
15+
.getInstance()
16+
.request(parameterSet)
17+
.getContent();
18+
19+
try {
20+
return request(credentials, parameterSet);
21+
} catch (Exception e) {
22+
throw new IllegalStateException(
23+
"Request failed with parameters: " + parameterSet, e);
24+
}
25+
}
26+
27+
public abstract Resource<T> request(
28+
HcpVaultCredentials credentials, ParameterSet parameterSet);
29+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package oracle.jdbc.provider.hashicorp.hcpvault.authentication;
2+
3+
4+
public enum HcpVaultAuthenticationMethod {
5+
CLIENT_CREDENTIALS
6+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package oracle.jdbc.provider.hashicorp.hcpvault.authentication;
2+
3+
/**
4+
* Holds the HCP API token obtained from the client_credentials flow.
5+
*/
6+
public final class HcpVaultCredentials {
7+
private final String hcpApiToken;
8+
9+
public HcpVaultCredentials(String hcpApiToken) {
10+
this.hcpApiToken = hcpApiToken;
11+
}
12+
13+
public String getHcpApiToken() {
14+
return hcpApiToken;
15+
}
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package oracle.jdbc.provider.hashicorp.hcpvault.authentication;
2+
3+
import oracle.jdbc.provider.factory.Resource;
4+
import oracle.jdbc.provider.factory.ResourceFactory;
5+
import oracle.jdbc.provider.parameter.Parameter;
6+
import oracle.jdbc.provider.parameter.ParameterSet;
7+
8+
import static oracle.jdbc.provider.parameter.Parameter.CommonAttribute.REQUIRED;
9+
import static oracle.jdbc.provider.util.ParameterUtil.getRequiredOrFallback;
10+
11+
/**
12+
* A factory for creating {@link HcpVaultCredentials} objects for HCP.
13+
*/
14+
public final class HcpVaultCredentialsFactory implements ResourceFactory<HcpVaultCredentials> {
15+
16+
// The param that indicates which HCP auth method to use (only one for now).
17+
public static final Parameter<HcpVaultAuthenticationMethod> AUTHENTICATION_METHOD = Parameter.create(REQUIRED);
18+
19+
// The OAuth2 client_id and client_secret
20+
public static final Parameter<String> CLIENT_ID = Parameter.create(REQUIRED);
21+
public static final Parameter<String> CLIENT_SECRET = Parameter.create(REQUIRED);
22+
23+
private static final HcpVaultCredentialsFactory INSTANCE = new HcpVaultCredentialsFactory();
24+
25+
private HcpVaultCredentialsFactory() {}
26+
27+
public static HcpVaultCredentialsFactory getInstance() {
28+
return INSTANCE;
29+
}
30+
31+
@Override
32+
public Resource<HcpVaultCredentials> request(ParameterSet parameterSet) {
33+
HcpVaultCredentials credentials = getCredential(parameterSet);
34+
return Resource.createPermanentResource(credentials, true);
35+
}
36+
37+
private HcpVaultCredentials getCredential(ParameterSet parameterSet) {
38+
HcpVaultAuthenticationMethod method = parameterSet.getRequired(AUTHENTICATION_METHOD);
39+
40+
switch (method) {
41+
case CLIENT_CREDENTIALS:
42+
return createClientCredentials(parameterSet);
43+
default:
44+
throw new IllegalArgumentException("Unrecognized HCP auth method: " + method);
45+
}
46+
}
47+
48+
private HcpVaultCredentials createClientCredentials(ParameterSet parameterSet) {
49+
String clientId = getRequiredOrFallback(parameterSet, CLIENT_ID, "CLIENT_ID");
50+
String clientSecret = getRequiredOrFallback(parameterSet, CLIENT_SECRET, "CLIENT_SECRET");
51+
52+
// Call OAuth endpoint to fetch the token
53+
String apiToken = HcpVaultOAuthClient.fetchHcpAccessToken(clientId, clientSecret);
54+
if (apiToken == null || apiToken.isEmpty()) {
55+
throw new IllegalStateException("Failed to obtain HCP token using client_credentials flow");
56+
}
57+
58+
return new HcpVaultCredentials(apiToken);
59+
}
60+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package oracle.jdbc.provider.hashicorp.hcpvault.authentication;
2+
3+
import oracle.sql.json.OracleJsonFactory;
4+
import oracle.sql.json.OracleJsonObject;
5+
6+
import java.io.*;
7+
import java.net.HttpURLConnection;
8+
import java.net.URL;
9+
import java.nio.charset.StandardCharsets;
10+
11+
public final class HcpVaultOAuthClient {
12+
13+
private HcpVaultOAuthClient() {}
14+
15+
public static String fetchHcpAccessToken(String clientId, String clientSecret) {
16+
HttpURLConnection conn = null;
17+
try {
18+
URL url = new URL("https://auth.idp.hashicorp.com/oauth/token");
19+
conn = (HttpURLConnection) url.openConnection();
20+
conn.setRequestMethod("POST");
21+
conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
22+
conn.setDoOutput(true);
23+
24+
String body = "grant_type=client_credentials"
25+
+ "&client_id=" + clientId
26+
+ "&client_secret=" + clientSecret
27+
+ "&audience=https://api.hashicorp.cloud";
28+
29+
try (OutputStream os = conn.getOutputStream()) {
30+
os.write(body.getBytes(StandardCharsets.UTF_8));
31+
}
32+
33+
if (conn.getResponseCode() == 200) {
34+
try (InputStream in = conn.getInputStream()) {
35+
OracleJsonObject response = new OracleJsonFactory()
36+
.createJsonTextValue(new ByteArrayInputStream(readAll(in).getBytes(StandardCharsets.UTF_8)))
37+
.asJsonObject();
38+
39+
return response.getString("access_token");
40+
}
41+
} else {
42+
throw new IllegalStateException("Failed to obtain HCP token. HTTP=" + conn.getResponseCode());
43+
}
44+
} catch (IOException e) {
45+
throw new IllegalStateException("Failed to fetch HCP access token", e);
46+
} finally {
47+
if (conn != null) {
48+
conn.disconnect();
49+
}
50+
}
51+
}
52+
53+
private static String readAll(InputStream in) throws IOException {
54+
ByteArrayOutputStream baos = new ByteArrayOutputStream();
55+
byte[] buffer = new byte[1024];
56+
int len;
57+
while ((len = in.read(buffer)) != -1) {
58+
baos.write(buffer, 0, len);
59+
}
60+
return new String(baos.toByteArray(), StandardCharsets.UTF_8);
61+
}
62+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package oracle.jdbc.provider.hashicorp.hcpvault.configuration;
2+
3+
import oracle.jdbc.provider.hashicorp.hcpvault.authentication.HcpVaultAuthenticationMethod;
4+
import oracle.jdbc.provider.hashicorp.hcpvault.authentication.HcpVaultCredentialsFactory;
5+
import oracle.jdbc.provider.parameter.ParameterSetParser;
6+
7+
/**
8+
* Defines how we parse HCP parameters in the JDBC URL or property sets.
9+
*/
10+
public final class HcpVaultConfigurationParameters {
11+
12+
private HcpVaultConfigurationParameters() {}
13+
14+
public static ParameterSetParser.Builder configureBuilder(ParameterSetParser.Builder builder) {
15+
return builder
16+
.addParameter(
17+
"AUTHENTICATION_METHOD",
18+
HcpVaultCredentialsFactory.AUTHENTICATION_METHOD,
19+
HcpVaultAuthenticationMethod.CLIENT_CREDENTIALS,
20+
HcpVaultConfigurationParameters::parseAuthMethod
21+
)
22+
.addParameter(
23+
"CLIENT_ID",
24+
HcpVaultCredentialsFactory.CLIENT_ID
25+
)
26+
.addParameter(
27+
"CLIENT_SECRET",
28+
HcpVaultCredentialsFactory.CLIENT_SECRET
29+
);
30+
}
31+
32+
private static HcpVaultAuthenticationMethod parseAuthMethod(String value) {
33+
if ("CLIENT_CREDENTIALS".equalsIgnoreCase(value)) {
34+
return HcpVaultAuthenticationMethod.CLIENT_CREDENTIALS;
35+
}
36+
throw new IllegalArgumentException("Unrecognized HCP auth method: " + value);
37+
}
38+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/*
2+
** Copyright (c) 2023 Oracle and/or its affiliates.
3+
**
4+
** The Universal Permissive License (UPL), Version 1.0
5+
**
6+
** Subject to the condition set forth below, permission is hereby granted to any
7+
** person obtaining a copy of this software, associated documentation and/or data
8+
** (collectively the "Software"), free of charge and under any and all copyright
9+
** rights in the Software, and any and all patent rights owned or freely
10+
** licensable by each licensor hereunder covering either (i) the unmodified
11+
** Software as contributed to or provided by such licensor, or (ii) the Larger
12+
** Works (as defined below), to deal in both
13+
**
14+
** (a) the Software, and
15+
** (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
16+
** one is included with the Software (each a "Larger Work" to which the Software
17+
** is contributed by such licensors),
18+
**
19+
** without restriction, including without limitation the rights to copy, create
20+
** derivative works of, display, perform, and distribute the Software and make,
21+
** use, sell, offer for sale, import, export, have made, and have sold the
22+
** Software and the Larger Work(s), and to sublicense the foregoing rights on
23+
** either these or other terms.
24+
**
25+
** This license is subject to the following condition:
26+
** The above copyright notice and either this complete permission notice or at
27+
** a minimum a reference to the UPL must be included in all copies or
28+
** substantial portions of the Software.
29+
**
30+
** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
31+
** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
32+
** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
33+
** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
34+
** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
35+
** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
36+
** SOFTWARE.
37+
*/
38+
39+
package oracle.jdbc.provider.hashicorp.hcpvault.configuration;
40+
41+
import oracle.jdbc.provider.configuration.JsonSecretUtil;
42+
import oracle.jdbc.provider.hashicorp.hcpvault.secrets.HcpVaultSecretsManagerFactory;
43+
import oracle.jdbc.provider.parameter.ParameterSet;
44+
import oracle.jdbc.spi.OracleConfigurationJsonSecretProvider;
45+
import oracle.sql.json.OracleJsonObject;
46+
47+
import java.nio.charset.StandardCharsets;
48+
import java.util.Base64;
49+
50+
import static oracle.jdbc.provider.hashicorp.hcpvault.configuration.HcpVaultSecretsManagerConfigurationProvider.PARAMETER_SET_PARSER;
51+
52+
public class HcpVaultJsonVaultProvider implements OracleConfigurationJsonSecretProvider {
53+
54+
@Override
55+
public char[] getSecret(OracleJsonObject jsonObject) {
56+
57+
// 1) Parse the top-level fields from the snippet.
58+
// e.g. "type" : "hcpvault", "value" : "my-hcp-app"
59+
ParameterSet parameterSet =
60+
PARAMETER_SET_PARSER.parseNamedValues(
61+
JsonSecretUtil.toNamedValues(jsonObject)
62+
);
63+
64+
// 2) Call HcpSecretsManagerFactory to fetch the secret "value".
65+
String secretString = HcpVaultSecretsManagerFactory
66+
.getInstance()
67+
.request(parameterSet)
68+
.getContent();
69+
70+
// 3) Base64-encode the secret content to produce the final char[].
71+
String base64Encoded = Base64.getEncoder()
72+
.encodeToString(secretString.getBytes(StandardCharsets.UTF_8));
73+
return base64Encoded.toCharArray();
74+
}
75+
76+
@Override
77+
public String getSecretType() {
78+
// Must match the "type" field used in the JSON snippet (e.g. "hcpvault").
79+
return "hcpvault";
80+
}
81+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package oracle.jdbc.provider.hashicorp.hcpvault.configuration;
2+
3+
import oracle.jdbc.driver.OracleConfigurationJsonProvider;
4+
import oracle.jdbc.provider.hashicorp.hcpvault.secrets.HcpVaultSecretsManagerFactory;
5+
import oracle.jdbc.provider.parameter.ParameterSet;
6+
import oracle.jdbc.provider.parameter.ParameterSetParser;
7+
import oracle.jdbc.util.OracleConfigurationCache;
8+
9+
import java.io.ByteArrayInputStream;
10+
import java.io.InputStream;
11+
import java.nio.charset.StandardCharsets;
12+
import java.util.HashMap;
13+
import java.util.Map;
14+
15+
16+
public class HcpVaultSecretsManagerConfigurationProvider extends OracleConfigurationJsonProvider {
17+
18+
static final ParameterSetParser PARAMETER_SET_PARSER =
19+
HcpVaultConfigurationParameters.configureBuilder(
20+
ParameterSetParser.builder()
21+
.addParameter("value", HcpVaultSecretsManagerFactory.APP_NAME)
22+
.addParameter("ORG_ID", HcpVaultSecretsManagerFactory.ORG_ID)
23+
.addParameter("PROJECT_ID", HcpVaultSecretsManagerFactory.PROJECT_ID)
24+
.addParameter("SECRET_NAME", HcpVaultSecretsManagerFactory.SECRET_NAME)
25+
.addParameter("KEY", HcpVaultSecretsManagerFactory.KEY)
26+
).build();
27+
28+
@Override
29+
public InputStream getJson(String appName) {
30+
// 'appName' is the part after config-hcpvault://
31+
final String valueField = "value";
32+
33+
Map<String, String> optionsWithAppName = new HashMap<>(options);
34+
optionsWithAppName.put(valueField, appName);
35+
36+
ParameterSet parameterSet = PARAMETER_SET_PARSER.parseNamedValues(optionsWithAppName);
37+
38+
// Call the factory
39+
String secretsJson = HcpVaultSecretsManagerFactory
40+
.getInstance()
41+
.request(parameterSet)
42+
.getContent();
43+
44+
return new ByteArrayInputStream(secretsJson.getBytes(StandardCharsets.UTF_8));
45+
}
46+
47+
@Override
48+
public String getType() {
49+
// The provider name that appears in the JDBC URL after "config-"
50+
return "hcpvault";
51+
}
52+
53+
54+
@Override
55+
public OracleConfigurationCache getCache() {
56+
return CACHE;
57+
}
58+
59+
}

0 commit comments

Comments
 (0)