diff --git a/.github/workflows/run-tests.yaml b/.github/workflows/run-tests.yaml index 63c9e3c3..576022c5 100644 --- a/.github/workflows/run-tests.yaml +++ b/.github/workflows/run-tests.yaml @@ -13,6 +13,10 @@ jobs: AWS_REGION: ${{ secrets.TEST_AWS_REGION }} AWS_ACCESS_KEY_ID: ${{ secrets.TEST_AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.TEST_AWS_SECRET_ACCESS_KEY }} + VAULT_ADDR: ${{ secrets.TEST_VAULT_ADDR }} + HCP_ORG_ID: ${{ secrets.TEST_HCP_ORG_ID }} + HCP_PROJECT_ID: ${{ secrets.TEST_HCP_PROJECT_ID }} + HCP_APP_NAME: ${{ secrets.TEST_HCP_APP_NAME }} steps: - name: Checkout the repository uses: actions/checkout@v4 @@ -127,6 +131,27 @@ jobs: AWS_SECRETS_MANAGER_URL=${{ secrets.TEST_AWS_SECRETS_MANAGER_URL }}\n " >> ojdbc-provider-aws/test.properties + + # Generate ojdbc-provider-hashicorp/test.properties + echo -e "VAULT_ADDR=${{ secrets.TEST_VAULT_ADDR }}\n + DEDICATED_VAULT_SECRET_PATH=${{ secrets.TEST_DEDICATED_VAULT_SECRET_PATH }}\n + DEDICATED_VAULT_SECRET_PATH_WITH_MULTIPLE_KEYS=${{ secrets.TEST_DEDICATED_VAULT_SECRET_PATH_WITH_MULTIPLE_KEYS }}\n + VAULT_USERNAME=${{ secrets.TEST_VAULT_USERNAME }}\n + VAULT_PASSWORD=${{ secrets.TEST_VAULT_PASSWORD }}\n + VAULT_NAMESPACE=${{ secrets.TEST_VAULT_NAMESPACE }}\n + ROLE_ID=${{ secrets.TEST_ROLE_ID }}\n + SECRET_ID=${{ secrets.TEST_SECRET_ID }}\n + GITHUB_TOKEN=${{ secrets.TEST_GITHUB_TOKEN }}\n + KEY=${{ secrets.TEST_KEY }}\n + HCP_ORG_ID=${{ secrets.TEST_HCP_ORG_ID }}\n + HCP_PROJECT_ID=${{ secrets.TEST_HCP_PROJECT_ID }}\n + HCP_APP_NAME=${{ secrets.TEST_HCP_APP_NAME }}\n + HCP_CLIENT_ID=${{ secrets.TEST_HCP_CLIENT_ID }}\n + HCP_CLIENT_SECRET=${{ secrets.TEST_HCP_CLIENT_SECRET }}\n + SECRET_NAME=${{ secrets.TEST_SECRET_NAME }}\n + SECRET_NAME_WITH_MULTIPLE_KEYS=${{ secrets.TEST_SECRET_NAME_WITH_MULTIPLE_KEYS }}\n + " >> ojdbc-provider-hashicorp/test.properties + - name: Run tests with Maven run: mvn -B test --file pom.xml @@ -143,3 +168,4 @@ jobs: rm ojdbc-provider-jackson-oson/test.properties rm ojdbc-provider-aws/test.properties + rm ojdbc-provider-hashicorp/test.properties diff --git a/README.md b/README.md index 073517fa..7e582c7c 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,8 @@ Each module of this project contains a set of providers. <dd>Providers for integration with Oracle Cloud Infrastructure (OCI).</dd> <dt><a href="ojdbc-provider-azure/README.md">Oracle JDBC Azure Providers</a></dt> <dd>Providers for integration with Microsoft Azure</dd> +<dt><a href="ojdbc-provider-hashicorp/README.md">Oracle JDBC HashiCorp Providers</a></dt> +<dd>Providers for integration with Hashicorp</dd> </dl> <u>The next one contains a provider for Open Telemetry:</u> <dl> diff --git a/ojdbc-provider-common/src/main/java/oracle/jdbc/provider/parameter/ParameterSet.java b/ojdbc-provider-common/src/main/java/oracle/jdbc/provider/parameter/ParameterSet.java index becefcec..a6837e65 100644 --- a/ojdbc-provider-common/src/main/java/oracle/jdbc/provider/parameter/ParameterSet.java +++ b/ojdbc-provider-common/src/main/java/oracle/jdbc/provider/parameter/ParameterSet.java @@ -40,6 +40,10 @@ import oracle.jdbc.provider.factory.ResourceFactory; +import java.util.Arrays; +import java.util.Map; +import java.util.stream.Collectors; + import static java.lang.String.format; import static java.util.Collections.emptyMap; @@ -137,11 +141,10 @@ default <T> T getRequired(Parameter<T> parameter) if (value != null) return value; - String name = getName(parameter); throw new IllegalStateException(format( - "No value defined for parameter \"%s\"", - name != null ? name : parameter.toString())); + "No value defined for parameter \"%s\"", + name != null ? name : parameter.toString())); } /** @@ -159,4 +162,17 @@ default <T> T getRequired(Parameter<T> parameter) */ ParameterSetBuilder copyBuilder(); + /** + * Filters the parameters from the {@link ParameterSet} based on the provided + * relevant keys. + * + * This method extracts only the parameters whose names are included + * in the specified array. + * + * @param relevantKeys An array of parameter names to include in the filtered + * result. + * @return A map containing only the filtered parameters. + */ + Map<String, Object> filterParameters(String[] relevantKeys); + } \ No newline at end of file diff --git a/ojdbc-provider-common/src/main/java/oracle/jdbc/provider/parameter/ParameterSetImpl.java b/ojdbc-provider-common/src/main/java/oracle/jdbc/provider/parameter/ParameterSetImpl.java index 3353e8a3..93d84cc1 100644 --- a/ojdbc-provider-common/src/main/java/oracle/jdbc/provider/parameter/ParameterSetImpl.java +++ b/ojdbc-provider-common/src/main/java/oracle/jdbc/provider/parameter/ParameterSetImpl.java @@ -38,9 +38,7 @@ package oracle.jdbc.provider.parameter; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; +import java.util.*; import java.util.stream.Collectors; final class ParameterSetImpl implements ParameterSet { @@ -102,7 +100,7 @@ public ParameterSetBuilder copyBuilder() { ParameterSetBuilder builder = ParameterSet.builder(); for (Map.Entry<Parameter<?>, Object> parameterValue - : parameterValues.entrySet()) { + : parameterValues.entrySet()) { @SuppressWarnings("unchecked") Parameter<Object> parameter = (Parameter<Object>) parameterValue.getKey(); Object value = parameterValue.getValue(); @@ -124,6 +122,28 @@ public boolean equals(Object other) { && ((ParameterSetImpl)other).parameterValues.equals(parameterValues); } + /** + * Retrieves the relevant parameter key-value pairs. + */ + private Map<String, Object> getParameterKeyValuePairs() { + return parameterNames.entrySet().stream() + .filter(entry -> parameterValues.get(entry.getKey()) != null) + .collect(Collectors.toMap( + Map.Entry::getValue, + entry -> parameterValues.get(entry.getKey()) + )); + } + + @Override + public Map<String, Object> filterParameters(String[] relevantKeys) { + Map<String, Object> allParameters = getParameterKeyValuePairs(); + List<String> relevantKeysList = Arrays.asList(relevantKeys); + + return allParameters.entrySet().stream() + .filter(entry -> relevantKeysList.contains(entry.getKey())) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + } + /** * Returns a key=value style text representation of this set, with parameter * names as keys, and the result of calling {@code toString()} on parameter diff --git a/ojdbc-provider-common/src/main/java/oracle/jdbc/provider/parameter/ParameterSetParserImpl.java b/ojdbc-provider-common/src/main/java/oracle/jdbc/provider/parameter/ParameterSetParserImpl.java index 7fef7843..cc3d0f28 100644 --- a/ojdbc-provider-common/src/main/java/oracle/jdbc/provider/parameter/ParameterSetParserImpl.java +++ b/ojdbc-provider-common/src/main/java/oracle/jdbc/provider/parameter/ParameterSetParserImpl.java @@ -113,11 +113,11 @@ public Builder addParameter(String name, Parameter<String> parameter) { @Override public Builder addParameter( - String name, Parameter<String> parameter, String defaultValue) { + String name, Parameter<String> parameter, String defaultValue) { addParameterParser( - name, - (builder) -> builder.add(name, parameter, defaultValue), - (value, builder) -> builder.add(name, parameter, value)); + name, + (builder) -> builder.add(name, parameter, defaultValue), + (value, builder) -> builder.add(name, parameter, value)); return this; } diff --git a/ojdbc-provider-hashicorp/README.md b/ojdbc-provider-hashicorp/README.md new file mode 100644 index 00000000..4922d48b --- /dev/null +++ b/ojdbc-provider-hashicorp/README.md @@ -0,0 +1,543 @@ +# Oracle JDBC Providers for HashiCorp Vault + +This module contains providers for integration between Oracle JDBC +and HashiCorp Vault (HCP). + +## Centralized Config Providers +<dl> +<dt><a href="#hcp-vault-dedicated-config-provider">HashiCorp Vault Dedicated Config Provider</a></dt> +<dd>Provides connection properties managed by the Vault Secrets service</dd> +<dt><a href="#hcp-vault-secrets-config-provider">HashiCorp Vault Secret Config Provider</a></dt> +<dd>Provides connection properties managed by the Dedicated Vault service</dd> +<dt><a href="#caching-configuration">Caching configuration</a></dt> +<dd>Caching mechanism adopted by Centralized Config Providers</dd> +</dl> + +Visit any of the links above to find information and usage examples for a particular provider. + +## Installation + +All providers in this module are distributed as a single jar on the Maven Central Repository. +The jar is compiled for JDK 8, and is forward compatible with later JDK versions. +The coordinates for the latest release are: + +```xml +<dependency> + <groupId>com.oracle.database.jdbc</groupId> + <artifactId>ojdbc-provider-hashicorp</artifactId> + <version>1.0.3</version> +</dependency> +``` + +## Authentication + +Providers use the HashiCorp Vault API to retrieve secrets and configurations. +The HashiCorp Vault Providers support two types of Vaults: **HCP Vault Dedicated** and **HCP Vault Secrets**. +Each type supports multiple authentication methods, each requiring specific parameters. + +The provider searches for these parameters in the following locations in a predefined sequence: + +1. Explicitly provided in the URL +2. System properties +3. Environment variables + +### HCP Vault Dedicated + +Authentication for the **HCP Vault Dedicated** offers various methods to suit different use cases, +including token-based, userpass, AppRole, and GitHub authentication. +Below is an overview of these methods: + +#### Token-based Authentication +Token-based authentication is the simplest way to authenticate with HashiCorp Vault. +It uses a static token provided by the Vault administrator, offering direct and straightforward access to the Vault's API. + +The provider searches for the following parameters: + +<table> +<thead> +<tr> +<th>Parameter Name</th> +<th>Description</th> +<th>Required</th> +</tr> +</thead> +<tbody> +<tr> +<td><code>VAULT_ADDR</code></td> +<td>The URL of the HashiCorp Vault instance (e.g., <code>https://vault-dedicated.example.com:8200</code>)</td> +<td>Yes</td> +</tr> +<tr> +<td><code>VAULT_TOKEN</code></td> +<td>Token provided by the Vault administrator</td> +<td>Yes</td> +</tr> +</tbody> +</table> + +#### Userpass Authentication +Userpass authentication is a method that uses a combination of a username and a password to authenticate against the Vault. +It relies on the userpass authentication backend and can optionally include additional configuration parameters +such as namespaces or custom authentication paths. + +The provider searches for the following parameters: + +<table> +<thead> +<tr> +<th>Parameter Name</th> +<th>Description</th> +<th>Required</th> +</tr> +</thead> +<tbody> +<tr> +<td><code>VAULT_ADDR</code></td> +<td>The URL of the HashiCorp Vault instance (e.g., <code>https://vault-dedicated.example.com:8200</code>)</td> +<td>Yes</td> +</tr> +<tr> +<td><code>USERPASS_AUTH_PATH</code></td> +<td>The authentication path in the Vault (default: <code>userpass</code>)</td> +<td>No</td> +</tr> +<tr> +<td><code>VAULT_NAMESPACE</code></td> +<td>The namespace in the Vault (default: <code>admin</code>)</td> +<td>No</td> +</tr> +<tr> +<td><code>VAULT_USERNAME</code></td> +<td>The username for the Userpass method</td> +<td>Yes</td> +</tr> +<tr> +<td><code>VAULT_PASSWORD</code></td> +<td>The password for the Userpass method</td> +<td>Yes</td> +</tr> +</tbody> +</table> + +Once authenticated, the `client_token` generated from the `Userpass` method is cached and reused until it expires. +This minimizes API calls to the Vault and enhances performance. + +For more information, visit the official documentation: [Userpass Authentication](https://developer.hashicorp.com/vault/api-docs/auth/userpass). + +#### AppRole Authentication +AppRole authentication is a method that relies on a `role_id` and a `secret_id` for secure authentication. It is based on the AppRole authentication backend in HashiCorp Vault, +which allows entities to authenticate and obtain a `client_token` by providing these identifiers. + +The provider searches for the following parameters: + +<table> +<thead> +<tr> +<th>Parameter Name</th> +<th>Description</th> +<th>Required</th> +</tr> +</thead> +<tbody> +<tr> +<td><code>VAULT_ADDR</code></td> +<td>The URL of the HashiCorp Vault instance (e.g., <code>https://vault-dedicated.example.com:8200</code>)</td> +<td>Yes</td> +</tr> +<tr> +<td><code>APPROLE_AUTH_PATH</code></td> +<td>The authentication path in the Vault for AppRole (default: <code>approle</code>)</td> +<td>No</td> +</tr> +<tr> +<td><code>VAULT_NAMESPACE</code></td> +<td>The namespace in the Vault (default: <code>admin</code>)</td> +<td>No</td> +</tr> +<tr> +<td><code>ROLE_ID</code></td> +<td>The role ID for the AppRole method</td> +<td>Yes</td> +</tr> +<tr> +<td><code>SECRET_ID</code></td> +<td>The secret ID for the AppRole method</td> +<td>Yes</td> +</tr> +</tbody> +</table> + +Once authenticated, the <code>client_token</code> generated from the <strong>AppRole</strong> method is cached and reused until it expires. This minimizes API calls to the Vault and enhances performance. + +For more information, visit the official documentation: <a href="https://developer.hashicorp.com/vault/api-docs/auth/approle">AppRole Authentication</a>. + +#### GitHub Authentication +GitHub authentication is a method that uses a personal access token to authenticate against the Vault. +It relies on the github authentication backend and supports configuring a custom authentication path. + +The provider searches for the following parameters: + +<table> +<thead> +<tr> +<th>Parameter Name</th> +<th>Description</th> +<th>Required</th> +</tr> +</thead> +<tbody> +<tr> +<td><code>VAULT_ADDR</code></td> +<td>The URL of the HashiCorp Vault instance (e.g., <code>https://vault-dedicated.example.com:8200</code>)</td> +<td>Yes</td> +</tr> +<tr> +<td><code>GITHUB_TOKEN</code></td> +<td>The GitHub personal access token for authentication</td> +<td>Yes</td> +</tr> +<tr> +<td><code>GITHUB_AUTH_PATH</code></td> +<td>The authentication path for GitHub in the Vault (default: <code>github</code>)</td> +<td>No</td> +</tr> +<tr> +<td><code>VAULT_NAMESPACE</code></td> +<td>The namespace in the Vault (default: <code>admin</code>)</td> +<td>No</td> +</tr> +</tbody> +</table> + +The `GITHUB` method allows authentication using a GitHub personal access token and supports +configuration of a custom path for GitHub authentication. + +For more information, visit the official documentation: <a href="https://developer.hashicorp.com/vault/api-docs/auth/github">Github Authentication</a>. + +#### AUTO_DETECT Authentication + +The **AUTO_DETECT** authentication method dynamically selects the most suitable authentication mechanism based on the provided parameters. This eliminates the need for users to manually specify an authentication method, ensuring a seamless and efficient authentication process. + +#### Selection Order: +1. **VAULT_TOKEN** → If a pre-existing Vault token is available, it is used. +2. **USERPASS** → If `VAULT_USERNAME` and `VAULT_PASSWORD` are provided, Userpass authentication is used. +3. **APPROLE** → If `ROLE_ID` and `SECRET_ID` are available, AppRole authentication is used as a fallback. +4. **GITHUB** → If no other method is available, GitHub authentication is attempted using `GITHUB_TOKEN`. + +The provider automatically detects the available parameters and chooses the best authentication method accordingly. + +**Note:** If no authentication method is explicitly specified, **AUTO_DETECT is used by default.** + + +### HCP Vault Secrets + +Authentication for **HCP Vault Secrets** supports multiple methods: + +Below is an overview of the supported authentication methods: + +1. **OAuth 2.0 Client Credentials Flow** + - Uses `HCP_CLIENT_ID` and `HCP_CLIENT_SECRET` to obtain a Bearer token for authentication. + - The token is then used to retrieve secrets from HCP Vault Secrets API. + +2. **Credentials File Authentication** + - Uses a JSON file (`creds-cache.json`) containing authentication credentials (`access_token`, `refresh_token`, and `access_token_expiry`). + - If the access token is expired, it is automatically refreshed using the stored refresh token. + - If the access token is expired, it is **automatically refreshed** using the stored refresh token. + +The generated token is cached and reused until it expires, minimizing API calls to HCP Vault Secrets. + +Secrets are retrieved from the following API endpoint: +`https://api.cloud.hashicorp.com/secrets/2023-11-28/organizations/$HCP_ORG_ID/projects/$HCP_PROJECT_ID/apps/$APP_NAME/secrets` + +For more information, visit the official HashiCorp Vault documentation: [HCP Vault Secrets](https://developer.hashicorp.com/hcp/tutorials/get-started-hcp-vault-secrets/hcp-vault-secrets-retrieve-secret). + +#### OAuth 2.0 Client Credentials Flow + +This method uses OAuth 2.0 **client credentials** to obtain a **Bearer token**, which is required for authentication. +The provider searches for the following parameters: + +<table> +<thead> +<tr> +<th>Parameter Name</th> +<th>Description</th> +<th>Required</th> +</tr> +</thead> +<tbody> +<tr> +<td><code>HCP_CLIENT_ID</code></td> +<td>The client ID for OAuth 2.0 authentication</td> +<td>Yes</td> +</tr> +<tr> +<td><code>HCP_CLIENT_SECRET</code></td> +<td>The client secret for OAuth 2.0 authentication</td> +<td>Yes</td> +</tr> +</tbody> +</table> + +In addition to the above parameters, the <a href="#Common-Parameters-for-HCP-Vault-Secrets-authentication-methods">Common parameters </a> +are also required. + +#### CLI CREDENTIALS FILE +This method **retrieves authentication details** from a **JSON file (`creds-cache.json`)** that contains access tokens. + +- If **HCP CLI is installed**, a **creds-cache.json** file is **automatically created** in: <code>~/.config/hcp/creds-cache.json</code> +- This file contains **access_token, refresh_token, and access_token_expiry**. +- If **the token is expired**, it is **automatically refreshed** using the **refresh_token**. +- The credentials file should be a JSON file containing the following structure: + +```json +{ + "login": { + "access_token": "YOUR_ACCESS_TOKEN", + "refresh_token": "YOUR_REFRESH_TOKEN", + "access_token_expiry": "2025-01-01T12:34:56.789Z" + } +} +``` +- access_token: The current access token for API authentication. +- refresh_token: The refresh token used to obtain a new access token when expired. +- access_token_expiry: The expiration timestamp of the access_token. + +When using this method, the provider will: + * Read the file and validate the access_token. + * Refresh the token if it's expired, using the refresh_token. + * Update the file with the new token details. + +The provider searches for the following parameters: + +<table> +<thead> +<tr> +<th>Parameter Name</th> +<th>Description</th> +<th>Required</th> +</tr> +</thead> +<tbody> +<tr> +<td><code>HCP_CREDENTIALS_FILE</code></td> +<td>The path of the credentials file ( by default <code>~/.config/hcp/creds-cache.json</code></td> +<td>No</td> +</tr> +</tbody> +</table> + +In addition to the above parameters, the <a href="#Common-Parameters-for-HCP-Vault-Secrets-authentication-methods">Common parameters </a> +are also required. + +#### AUTO_DETECT Authentication + +The **AUTO_DETECT** authentication method dynamically selects the most suitable authentication mechanism based on the provided parameters. +This eliminates the need for users to manually specify an authentication method, ensuring a seamless and efficient authentication process. + +#### Selection Order: +1. **CLI_CREDENTIALS_FILE** → If `HCP_CREDENTIALS_FILE` is provided or the default credentials file (`~/.config/hcp/creds-cache.json`) exists, it is used. +2. **CLIENT_CREDENTIALS** → If `HCP_CLIENT_ID` and `HCP_CLIENT_SECRET` are available, Client Credentials authentication is used as a fallback. + +The provider automatically detects the available parameters and chooses the best authentication method accordingly. + +**Note:** If no authentication method is explicitly specified, **AUTO_DETECT is used by default.** + +#### Common Parameters for HCP Vault Secrets authentication methods + +<table> +<thead> +<tr> +<th>Parameter Name</th> +<th>Description</th> +<th>Required</th> +</tr> +</thead> +<tbody> +<tr> +<td><code>HCP_ORG_ID</code></td> +<td>The organization ID associated with the Vault</td> +<td>Yes</td> +</tr> +<tr> +<td><code>HCP_PROJECT_ID</code></td> +<td>The project ID associated with the Vault</td> +<td>Yes</td> +</tr> +<tr> +<td><code>HCP_APP_NAME</code></td> +<td>The application name in HCP Vault Secrets</td> +<td>Yes</td> +</tr> +</tbody> +</table> + + +## Config Providers + +### HCP Vault Dedicated Config Provider + +The Oracle DataSource uses a new prefix `jdbc:oracle:thin:@config-hcpdedicatedvault://` to be able to identify that the configuration parameters should be loaded using HCP Vault Dedicated. Users need to indicate the secret path with the following syntax: + +<pre> +jdbc:oracle:thin:@config-hcpvaultdedicated://{secret-path}[?option1=value1&option2=value2...] +</pre> + +The `secret-path` refers to the exact location of the secret within the HCP Vault Dedicated. + +The query parameters (`option1=value1`, `option2=value2`, etc.) are optional key-value pairs that can be used to: + +- Specify authentication parameters (e.g., `VAULT_ADDR`, `VAULT_TOKEN`, `VAULT_USERNAME`) +- Pass additional context to the provider (e.g., `KEY` for specifying which set of credentials to use) + + All parameters that can be specified as environment variables or system properties can also be provided directly in the URL. + For example: +``` +jdbc:oracle:thin:@config-hcpvaultdedicated:///v1/namespace/secret/data/secret_name?KEY=sales_app1&authentication=approle +``` + +### HCP Vault Secrets Config Provider + +The Oracle DataSource uses a new prefix `jdbc:oracle:thin:@config-hcpvaultsecret://` to identify that the configuration parameters should be loaded using HCP Vault Secrets. Users need to indicate the secret name (`SECRET_NAME`) with the following syntax: + +<pre> +jdbc:oracle:thin:@config-hcpvaultsecret://{secret-name}[?option1=value1&option2=value2...] +</pre> + +The `secret-name` refers to the name of the secret to retrieve from HCP Vault Secrets + +The query parameters (`option1=value1`, `option2=value2`, etc.) are optional key-value pairs that can be used to: + +- Specify authentication parameters (e.g., `HCP_CLIENT_ID`, `HCP_ORG_ID`) +- Pass additional context information required by the provider + +All parameters that can be specified as environment variables or system properties can also be provided directly in the URL. + +For example: +``` +jdbc:oracle:thin:@config-hcpvaultsecret://secret-name?HCP_APP_NAME=app-name&key=sales_app1 +``` + +### JSON Payload format + +There are 3 fixed values that are looked at the root level: + +- `connect_descriptor` (required) +- `user` (optional) +- `password` (optional) + +The rest are dependent on the driver, in our case `/jdbc`. The key-value pairs that are under the `/jdbc` prefix will be applied to a `DataSource`. These keys correspond to the properties defined in the [OracleConnection](https://docs.oracle.com/en/database/oracle/oracle-database/23/jajdb/oracle/jdbc/OracleConnection.html) interface. + +For example, let's suppose a URL like: + +<pre> +jdbc:oracle:thin:@config-hcpvaultdedicated:///v1/namespace/secret/data/secret_name?KEY=sales_app1 +</pre> + +And the JSON Payload for the secret **test_config** stored in the HCP Vault Dedicated would look like the following: + +```json +{ + "connect_descriptor": "(description=(retry_count=20)(retry_delay=3)(address=(protocol=tcps)(port=1521)(host=adb.us-phoenix-1.oraclecloud.com))(connect_data=(service_name=xsxsxs_dbtest_medium.adb.oraclecloud.com))(security=(ssl_server_dn_match=yes)))", + "user": "scott", + "password": { + "type": "hcpvaultdedicated", + "value": "/v1/namespace/secret/data/password", + "field_name": "db-password" + }, + "jdbc": { + "oracle.jdbc.ReadTimeout": 1000, + "defaultRowPrefetch": 20, + "autoCommit": "false" + } +} +``` + +The sample code below executes as expected with the previous configuration. + +```java + OracleDataSource ds = new OracleDataSource(); + ds.setURL("jdbc:oracle:thin:@config-hcpvaultdedicated:///v1/namespace/secret/data/test_config"); + Connection cn = ds.getConnection(); + Statement st = cn.createStatement(); + ResultSet rs = st.executeQuery("select sysdate from dual"); + if (rs.next()) + System.out.println("select sysdate from dual: " + rs.getString(1)); +``` + +For **HCP Vault Secrets** +For example, let's suppose a URL like: + +<pre> jdbc:oracle:thin:@config-hcpvaultsecret://secret-name </pre> +And the JSON Payload for a secret stored within the application app_name in the HCP Vault Secrets would look like the following: + +```json +{ + "connect_descriptor": "(description=(retry_count=20)(retry_delay=3)(address=(protocol=tcps)(port=1521)(host=adb.us-phoenix-1.oraclecloud.com))(connect_data=(service_name=xsxsxs_dbtest_medium.adb.oraclecloud.com))(security=(ssl_server_dn_match=yes)))", + "user": "scott", + "password": { + "type": "hcpvaultsecret", + "value": "secret-name" + }, + "jdbc": { + "oracle.jdbc.ReadTimeout": 1000, + "defaultRowPrefetch": 20, + "autoCommit": "false" + } +} +``` + +The sample code below executes as expected with the previous configuration. + +```java + OracleDataSource ds = new OracleDataSource(); + ds.setURL("jdbc:oracle:thin:@config-hcpvaultsecret://secret-name"); + Connection cn = ds.getConnection(); + Statement st = cn.createStatement(); + ResultSet rs = st.executeQuery("select sysdate from dual"); + if (rs.next()) + System.out.println("select sysdate from dual: " + rs.getString(1)); +``` + +### Password JSON Object + +For the JSON type of provider (HCP Vault Dedicated, HCP Vault Secrets, HTTP/HTTPS, File), the password is an object itself with the following spec: + +- type + - Mandatory + - Possible values + - ocivault + - azurevault + - base64 + - hcpvaultdedicated + - hcpvaultsecret +- value + - Mandatory + - Possible values + - OCID of the secret (if ocivault) + - Azure Key Vault URI (if azurevault) + - Base64 Encoded password (if base64) + - Secret path (if hcpvaultdedicated) + - Secret name (if hcpvaultsecret) + - Text +- field_name (HCP Vault Dedicated only) + - Optional + - Description: Specifies the key within the secret JSON object to retrieve the password value. + For example, if the secret contains `{ "db-password": "mypassword" }`, + setting `field_name: "db-password"` will extract `"mypassword"`. + - **Logic behind the `field_name` attribute:** + - If `field_name` is **specified**, its corresponding value is extracted. + - If the **secret contains only one key-value pair**, that value is **automatically used**. + - If `field_name` is **missing** and **multiple keys exist**, an **error is thrown**. +- authentication + - Optional + - Possible Values + - method + - optional parameters (depends on the cloud provider). + +## Caching configuration + +Config providers in this module store the configuration in caches to minimize +the number of RPC requests to remote location. See +[Caching configuration](../ojdbc-provider-azure/README.md#caching-configuration) for more +details of the caching mechanism. + diff --git a/ojdbc-provider-hashicorp/example-test.properties b/ojdbc-provider-hashicorp/example-test.properties new file mode 100644 index 00000000..31b862fe --- /dev/null +++ b/ojdbc-provider-hashicorp/example-test.properties @@ -0,0 +1,151 @@ +################################################################################ +# Copyright (c) 2025 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. +################################################################################ + +# This file provides examples of properties that configure tests in this +# module. +# +# QUICK GUIDE TO RUNNING TESTS: +# 1. Create a copy of this file named "test.properties": +# cp example-test.properties test.properties +# 2. In test.properties, replace example values with real values (the file is +# .gitignore'd, so sensitive info won't be checked in to the repo) +# 3. Comment out any lines for which a value can not be provided (tests are +# skipped if no value is configured). +# 4. mvn clean verify +# +# CONFIGURING TEST PROPERTIES +# Test properties are read from a properties file by the TestProperties class. +# The TestProperties class can be found in: +# ojdbc-provider-common/src/testFixtures/java/oracle/jdbc/provider/TestProperties.java +# The default behavior of TestProperties is to read a file named +# "test.properties" in the current directory. A non-default location may be +# specified as a JVM system property: +# mvn clean verify -Doracle.jdbc.provider.TestProperties=/path/to/my-test.properties +# +# MAINTAINING THIS FILE +# Project maintainers should add an example to this file anytime they write a +# test which requires a new property. Not doing so will inflict pain and +# suffering upon our fellow programmers, and will also lead to increased +# maintenance costs. +# +# IGNORING UNCONFIGURED PROPERTIES +# No test should cause a build failure due to an unconfigured property. +# Using JUnit terminology: A test should "abort" rather than "fail" when a +# property is not configured. This means that the test does not pass, but it +# does not cause the build to fail either. +# Methods of the TestProperties class will automatically abort a test if a +# property is not configured. The org.junit.jupiter.api.Assumptions class may +# also be used directly to abort a test. +# There is NO environment in which ALL tests can be run. Some tests may +# require authentication as a managed identity in an Azure VM, while other +# tests require authentication as an instance principal in an OCI compute +# instance; These environments are mutually exclusive. This is one reason why +# tests can not fail the build if a required property is not set. +# A more practical reason is that developers may not need to run all tests if +# their changes are isolated to single module. For instance, a developer +# working on an OCI provider should not need to set up an Azure tenancy to test +# their changes. + +################################################################################ +# HASHICORP DEDICATED VAULT CONFIGURATION +################################################################################ + +# URL of the Dedicated Vault instance +VAULT_ADDR=https://your-dedicated-vault-instance-url:8200 + +# Vault token for authentication +VAULT_TOKEN=hvs.CAESINZGyourDedicatedVaultTokenHereRGikKImh2cy5ExampleToken + +# Path to the secret in the Dedicated Vault +DEDICATED_VAULT_SECRET_PATH=/v1/admin/secret/data/your-secret-path + +# Optional key to extract a specific value from the JSON secret +KEY=your-secret-key + +# Username for Userpass authentication +VAULT_USERNAME=your-username + +# Password for Userpass authentication +VAUL_TPASSWORD=your-password + +# Optional path to the Userpass authentication mount point in Vault (default: userpass) +USERPASS_AUTH_PATH=userpass + +# Optional namespace for Vault API requests +VAULT_NAMESPACE=your-namespace + +# Role ID for AppRole authentication +ROLE_ID=your-role-id + +# Secret ID for AppRole authentication +SECRET_ID=your-secret-id + +# Optional path to the AppRole authentication mount point in Vault (default: approle) +APPROLE_AUTH_PATH=approle + +# GitHub personal access token for authentication +GITHUB_TOKEN=your-github-personal-access-token + +# Optional path for GitHub authentication in Vault (default: github) +GITHUB_AUTH_PATH=github + +################################################################################ +# HCP VAULT SECRETS CONFIGURATION +################################################################################ + +# The name of the application where the secret is stored +HCP_APP_NAME=your-app-name + +# Client ID for OAuth2 client_credentials flow +HCP_CLIENT_ID=your-client-id + +# Client Secret for OAuth2 client_credentials flow +HCP_CLIENT_SECRET=your-client-secret + +# Organization ID in HCP Vault +HCP_ORG_ID=your-organization-id + +# Project ID in HCP Vault +HCP_PROJECT_ID=your-project-id + +# Name of the secret to be fetched from the application +SECRET_NAME=your-secret-name + +# Path to the credentials file containing authentication details +# (By default: ~/.config/hcp/creds-cache.json if using HCP CLI) +HCP_CREDENTIALS_FILE=/path/to/your/creds-cache.json diff --git a/ojdbc-provider-hashicorp/pom.xml b/ojdbc-provider-hashicorp/pom.xml new file mode 100644 index 00000000..f101e262 --- /dev/null +++ b/ojdbc-provider-hashicorp/pom.xml @@ -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>1.0.3</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> \ No newline at end of file diff --git a/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultdedicated/DedicatedVaultResourceFactory.java b/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultdedicated/DedicatedVaultResourceFactory.java new file mode 100644 index 00000000..530f817d --- /dev/null +++ b/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultdedicated/DedicatedVaultResourceFactory.java @@ -0,0 +1,71 @@ +/* + ** Copyright (c) 2025 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.hcpvaultdedicated; + +import oracle.jdbc.provider.factory.Resource; +import oracle.jdbc.provider.factory.ResourceFactory; +import oracle.jdbc.provider.hashicorp.hcpvaultdedicated.authentication.DedicatedVaultToken; +import oracle.jdbc.provider.hashicorp.hcpvaultdedicated.authentication.DedicatedVaultTokenFactory; +import oracle.jdbc.provider.parameter.ParameterSet; + +/** + * Common super class for ResourceFactory implementations that request + * a resource from Vault using HashiCredentials. + */ +public abstract class DedicatedVaultResourceFactory<T> implements ResourceFactory<T> { + + @Override + public final Resource<T> request(ParameterSet parameterSet) { + // Retrieve the Vault credentials from the credentials factory + DedicatedVaultToken credentials = DedicatedVaultTokenFactory + .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( + DedicatedVaultToken credentials, ParameterSet parameterSet); +} diff --git a/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultdedicated/authentication/AbstractDedicatedVaultAuthentication.java b/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultdedicated/authentication/AbstractDedicatedVaultAuthentication.java new file mode 100644 index 00000000..1d841d3e --- /dev/null +++ b/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultdedicated/authentication/AbstractDedicatedVaultAuthentication.java @@ -0,0 +1,154 @@ +/* + ** Copyright (c) 2025 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.hcpvaultdedicated.authentication; + +import oracle.jdbc.driver.oauth.OpaqueAccessToken; +import oracle.jdbc.provider.hashicorp.util.HttpUtil; +import oracle.jdbc.provider.hashicorp.util.JsonUtil; +import oracle.jdbc.provider.parameter.ParameterSet; +import oracle.sql.json.OracleJsonObject; + +import java.time.OffsetDateTime; +import java.util.Map; + +/** + * Base class for Dedicated Vault authentication. + * <p> + * Provides helper methods and shared constants for building endpoints, + * creating JSON payloads, + * and performing HTTP authentication requests to Vault. + * </p> + */ +public abstract class AbstractDedicatedVaultAuthentication { + + // Shared constants + protected static final String AUTH_PATH_TEMPLATE = "/v1/auth/%s"; + protected static final String USERPASS_LOGIN_TEMPLATE = AUTH_PATH_TEMPLATE + "/login/%s"; + protected static final String APPROLE_LOGIN_TEMPLATE = AUTH_PATH_TEMPLATE + "/login"; + protected static final String GITHUB_LOGIN_TEMPLATE = AUTH_PATH_TEMPLATE + "/login"; + + protected static final String USERPASS_PAYLOAD_TEMPLATE = "{\"password\": \"%s\"}"; + protected static final String APPROLE_PAYLOAD_TEMPLATE = "{\"role_id\":\"%s\", \"secret_id\":\"%s\"}"; + protected static final String GITHUB_PAYLOAD_TEMPLATE = "{\"token\": \"%s\"}"; + + protected static final String JSON_CONTENT_TYPE = "application/json"; + private static final String AUTH_FIELD = "auth"; + private static final String CLIENT_TOKEN_FIELD = "client_token"; + private static final String LEASE_DURATION_FIELD = "lease_duration"; + + /** + * Generates a token for the authentication method. + * + * @param parameterSet the set of parameters for the request. + * @return the generated token. + */ + public abstract CachedToken generateToken(ParameterSet parameterSet); + + /** + * Generates a cache key for the authentication method based on the provided parameters. + * + * @param parameterSet the set of parameters for the request. + * @return a ParameterSet to be used as a cache key. + */ + public abstract Map<String, Object> generateCacheKey(ParameterSet parameterSet); + + /** + * Builds an authentication endpoint URL. + * + * @param vaultAddr The Vault server address + * @param template The URL template to use + * @param args Arguments to fill into the template + * @return The complete endpoint URL + */ + protected String buildAuthEndpoint(String vaultAddr, String template, Object... args) { + return vaultAddr + String.format(template, args); + } + + /** + * Creates a JSON payload string. + * + * @param format The format string with JSON structure + * @param args The values to insert into the format string + * @return A JSON payload string + */ + protected String createJsonPayload(String format, Object... args) { + return String.format(format, args); + } + + /** + * Helper method that consolidates the common authentication steps. + * <p> + * This method performs the following: + * <ol> + * <li>Creates an HTTP connection to the specified authentication endpoint.</li> + * <li>Sends the provided JSON payload.</li> + * <li>Parses the JSON response to extract the client token and lease duration.</li> + * <li>Constructs and returns a new {@link CachedToken} based on the response.</li> + * </ol> + * + * @param authEndpoint the full URL of the Vault authentication endpoint. + * @param payload the JSON payload to send in the request. + * @param namespace the Vault namespace to include in the request headers. + * @param authToken an optional token to include in the "Authorization" header (used for GitHub authentication). + * @param failureMessage a descriptive error message used if authentication fails. + * @return a new {@link CachedToken} containing the client token and its expiration details. + * @throws IllegalStateException if the authentication request fails or if the response is malformed. + */ + protected CachedToken performAuthentication(String authEndpoint, + String payload, + String namespace, + String authToken, + String failureMessage) { + try { + String jsonResponse = HttpUtil.sendPostRequest(authEndpoint, payload, JSON_CONTENT_TYPE, authToken, namespace); + OracleJsonObject response = JsonUtil.convertJsonToOracleJsonObject(jsonResponse); + OracleJsonObject authObj = response.getObject(AUTH_FIELD); + String clientToken = JsonUtil.extractField(authObj, CLIENT_TOKEN_FIELD); + long leaseDurationInSeconds = authObj.getLong(LEASE_DURATION_FIELD); + + OffsetDateTime expiration = OffsetDateTime.now().plusSeconds(leaseDurationInSeconds); + OpaqueAccessToken opaqueToken = OpaqueAccessToken.create(clientToken.toCharArray(), expiration); + + return new CachedToken(opaqueToken); + } catch (Exception e) { + throw new IllegalStateException(failureMessage, e); + } + } +} + diff --git a/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultdedicated/authentication/AppRoleAuthentication.java b/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultdedicated/authentication/AppRoleAuthentication.java new file mode 100644 index 00000000..a0153d57 --- /dev/null +++ b/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultdedicated/authentication/AppRoleAuthentication.java @@ -0,0 +1,79 @@ +/* + ** Copyright (c) 2025 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.hcpvaultdedicated.authentication; + +import oracle.jdbc.provider.parameter.ParameterSet; +import java.util.Map; + +import static oracle.jdbc.provider.hashicorp.hcpvaultdedicated.authentication.DedicatedVaultParameters.*; + +/** + * Handles authentication using the AppRole method for HashiCorp Vault. + */ +public class AppRoleAuthentication extends AbstractDedicatedVaultAuthentication { + + /** + * Singleton instance of {@link AppRoleAuthentication}. + */ + public static final AppRoleAuthentication INSTANCE = new AppRoleAuthentication(); + + private AppRoleAuthentication() { + // Private constructor to prevent external instantiation + } + + @Override + public CachedToken generateToken(ParameterSet parameterSet) { + String vaultAddr = parameterSet.getRequired(VAULT_ADDR); + String namespace = parameterSet.getOptional(NAMESPACE); + String roleId = parameterSet.getRequired(ROLE_ID); + String secretId = parameterSet.getRequired(SECRET_ID); + String authPath = parameterSet.getOptional(APPROLE_AUTH_PATH); + + String authEndpoint = buildAuthEndpoint(vaultAddr, APPROLE_LOGIN_TEMPLATE, authPath); + String payload = createJsonPayload(APPROLE_PAYLOAD_TEMPLATE, roleId, secretId); + return performAuthentication(authEndpoint, payload, namespace, null, "Failed to authenticate with AppRole"); + } + + @Override + public Map<String, Object> generateCacheKey(ParameterSet parameterSet) { + return parameterSet.filterParameters(new String[]{ + PARAM_VAULT_ADDR, PARAM_VAULT_NAMESPACE, PARAM_APPROLE_AUTH_PATH, PARAM_VAULT_ROLE_ID, PARAM_VAULT_SECRET_ID + }); + } +} diff --git a/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultdedicated/authentication/AutoDetectAuthentication.java b/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultdedicated/authentication/AutoDetectAuthentication.java new file mode 100644 index 00000000..5fe60c9b --- /dev/null +++ b/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultdedicated/authentication/AutoDetectAuthentication.java @@ -0,0 +1,110 @@ +/* + ** Copyright (c) 2025 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.hcpvaultdedicated.authentication; + +import oracle.jdbc.provider.parameter.ParameterSet; + +import java.util.Collections; +import java.util.Map; + +/** + * Automatically selects the best authentication method based on available parameters. + * <p> + * The selection priority is: + * <ol> + * <li>VAULT_TOKEN</li> + * <li>USERPASS</li> + * <li>APPROLE</li> + * <li>GITHUB</li> + * </ol> + * The method attempts each authentication type in order until successful. + */ +public class AutoDetectAuthentication extends AbstractDedicatedVaultAuthentication{ + + /** + * Singleton instance of {@link AutoDetectAuthentication}. + */ + public static final AutoDetectAuthentication INSTANCE = new AutoDetectAuthentication(); + + /** + * Ordered list of authentication methods by priority. + */ + private static final AbstractDedicatedVaultAuthentication[] AUTHENTICATION_METHODS = { + VaultTokenAuthentication.INSTANCE, + UserpassAuthentication.INSTANCE, + AppRoleAuthentication.INSTANCE, + GitHubAuthentication.INSTANCE + }; + + private AutoDetectAuthentication() { + // Private constructor to prevent external instantiation + } + + @Override + public CachedToken generateToken(ParameterSet parameterSet) { + IllegalStateException previousFailure = null; + + for (AbstractDedicatedVaultAuthentication authentication : AUTHENTICATION_METHODS) { + try { + return authentication.generateToken(parameterSet); + } catch (RuntimeException e) { + IllegalStateException failure = new IllegalStateException( + "Failed to authenticate using " + authentication.getClass().getSimpleName(), e); + if (previousFailure == null) { + previousFailure = failure; + } else { + previousFailure.addSuppressed(failure); + } + } + } + + throw previousFailure; + } + + @Override + public Map<String, Object> generateCacheKey(ParameterSet parameterSet) { + for (AbstractDedicatedVaultAuthentication authentication : AUTHENTICATION_METHODS) { + Map<String, Object> cacheKey = authentication.generateCacheKey(parameterSet); + if (!cacheKey.isEmpty()) { + return cacheKey; + } + } + return Collections.emptyMap(); + } +} diff --git a/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultdedicated/authentication/CachedToken.java b/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultdedicated/authentication/CachedToken.java new file mode 100644 index 00000000..db39e55b --- /dev/null +++ b/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultdedicated/authentication/CachedToken.java @@ -0,0 +1,95 @@ +/* + ** Copyright (c) 2025 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.hcpvaultdedicated.authentication; + +import oracle.jdbc.driver.oauth.OpaqueAccessToken; + +import java.time.OffsetDateTime; + +/** + * Represents a cached Vault authentication token with its expiration time. + * <p> + * The cached token contains the {@link OpaqueAccessToken} and its expiration time. + * It is used to avoid redundant authentication requests by checking token validity + * before re-authenticating. + * </p> + */ +public class CachedToken { + private static final long TOKEN_TTL_BUFFER = 60_000; // 1-minute buffer in milliseconds + + private final OpaqueAccessToken token; + + /** + * Constructs a new {@code CachedToken} instance. + * + * @param token The {@link OpaqueAccessToken} to cache. Must not be null. + * @throws IllegalArgumentException if {@code token} is null. + */ + public CachedToken(OpaqueAccessToken token) { + if (token == null) { + throw new IllegalArgumentException("Token must not be null."); + } + this.token = token; + } + + /** + * Retrieves the cached access token. + * + * @return the cached {@link OpaqueAccessToken}. + */ + public OpaqueAccessToken getToken() { + return token; + } + + /** + * Checks if the cached token is still valid. + * + * @return {@code true} if the token is still valid; {@code false} otherwise. + */ + public boolean isValid() { + OffsetDateTime expiration = this.token.expiration(); + if (expiration == null) { + return false; + } + long currentTimeMillis = System.currentTimeMillis(); + long expirationTimeMillis = token.expiration().toInstant().toEpochMilli(); + + return currentTimeMillis < (expirationTimeMillis - TOKEN_TTL_BUFFER); + } +} diff --git a/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultdedicated/authentication/DedicatedVaultAuthenticationMethod.java b/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultdedicated/authentication/DedicatedVaultAuthenticationMethod.java new file mode 100644 index 00000000..11b933fe --- /dev/null +++ b/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultdedicated/authentication/DedicatedVaultAuthenticationMethod.java @@ -0,0 +1,106 @@ +/* + ** Copyright (c) 2025 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.hcpvaultdedicated.authentication; + +/** + * Enumeration of authentication methods supported by Dedicated HashiCorp Vault. + */ +public enum DedicatedVaultAuthenticationMethod { + + /** + * Authentication using a Vault token. + * <p> + * The Vault token is a secure string used to authenticate API requests + * to the HashiCorp Vault. It can be provided through configuration parameters, + * environment variables, or system properties. + * </p> + */ + VAULT_TOKEN, + + /** + * Authentication using the Userpass method. + * <p> + * The Userpass method allows authentication using a username and password. + * It is suitable for scenarios where user credentials are managed directly + * by Vault. For more information, see the HashiCorp Vault documentation: + * <a href="https://developer.hashicorp.com/vault/api-docs/auth/userpass"> + * Userpass Authentication API</a>. + * </p> + */ + USERPASS, + + /** + * Authentication using the AppRole method. + * <p> + * The AppRole method allows authentication using a Role ID and Secret ID. + * This method is designed for machine-to-machine authentication or + * service-based applications. For more information, see the HashiCorp Vault + * documentation: + * <a href="https://developer.hashicorp.com/vault/api-docs/auth/approle"> + * AppRole Authentication API</a>. + * </p> + */ + APPROLE, + + /** + * Authentication using the GitHub method. + * <p> + * The GitHub method allows authentication using a GitHub personal access token. + * This is particularly useful for applications or developers using GitHub + * as an identity provider for Vault. For more information, see: + * <a href="https://developer.hashicorp.com/vault/docs/auth/github"> + * GitHub Authentication API</a>. + * </p> + */ + GITHUB, + + /** + * Automatically selects the best authentication method based on available parameters. + * + * <p>Priority order:</p> + * <ol> + * <li>Uses the Vault token if available.</li> + * <li>Falls back to Userpass authentication.</li> + * <li>Then attempts AppRole authentication.</li> + * <li>Finally, tries GitHub authentication.</li> + * </ol> + */ + AUTO_DETECT; + +} diff --git a/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultdedicated/authentication/DedicatedVaultParameters.java b/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultdedicated/authentication/DedicatedVaultParameters.java new file mode 100644 index 00000000..8610e0c6 --- /dev/null +++ b/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultdedicated/authentication/DedicatedVaultParameters.java @@ -0,0 +1,293 @@ +/* + ** Copyright (c) 2025 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.hcpvaultdedicated.authentication; + +import oracle.jdbc.provider.hashicorp.util.Parameterutil; +import oracle.jdbc.provider.parameter.Parameter; +import oracle.jdbc.provider.parameter.ParameterSet; +import oracle.jdbc.provider.parameter.ParameterSetParser; + +import java.util.HashMap; +import java.util.Map; + +import static oracle.jdbc.provider.hashicorp.hcpvaultdedicated.authentication.DedicatedVaultAuthenticationMethod.AUTO_DETECT; +import static oracle.jdbc.provider.parameter.Parameter.CommonAttribute.*; + +/** + * Contains parameter definitions for interacting with Dedicated HashiCorp Vault. + * <p> + * This class provides a centralized definition of parameters used across different components + * when working with HashiCorp Vault Dedicated. It defines the parameter names, environment variable keys, + * and default values for various configuration settings such as the Vault address, token, namespace, and + * authentication credentials for multiple authentication methods (Vault token, Userpass, AppRole, and GitHub). + * In addition, it offers utility methods to retrieve these parameter values from a {@link ParameterSet} + * with appropriate fallbacks to system properties, environment variables, or default values. + * </p> + */ + +public class DedicatedVaultParameters { + + // Common constants for System Properties and environment variable and + // parameter names + public static final String PARAM_VAULT_ADDR = "VAULT_ADDR"; + public static final String PARAM_VAULT_TOKEN = "VAULT_TOKEN"; + public static final String PARAM_VAULT_NAMESPACE = "VAULT_NAMESPACE"; + public static final String PARAM_VAULT_USERNAME = "VAULT_USERNAME"; + public static final String PARAM_VAULT_PASSWORD = "VAULT_PASSWORD"; + public static final String PARAM_GITHUB_TOKEN = "GITHUB_TOKEN"; + public static final String PARAM_USERPASS_AUTH_PATH = "USERPASS_AUTH_PATH"; + public static final String PARAM_APPROLE_AUTH_PATH = "APPROLE_AUTH_PATH"; + public static final String PARAM_GITHUB_AUTH_PATH = "GITHUB_AUTH_PATH"; + public static final String PARAM_VAULT_ROLE_ID = "ROLE_ID"; + public static final String PARAM_VAULT_SECRET_ID = "SECRET_ID"; + private static final String PARAM_AUTHENTICATION = "AUTHENTICATION"; + private static final String PARAM_FIELD_NAME = "FIELD_NAME"; + + + // Default values + static final String DEFAULT_NAMESPACE = "admin"; + static final String DEFAULT_USERPASS_PATH = "userpass"; + static final String DEFAULT_APPROLE_PATH = "approle"; + static final String DEFAULT_GITHUB_PATH = "github"; + + /** + * <p> + * Parameter for configuring the authentication method. + * Must be provided in the {@link ParameterSet}. + * </p> + */ + public static final Parameter<DedicatedVaultAuthenticationMethod> AUTHENTICATION_METHOD = + Parameter.create(REQUIRED); + + /** The path of the secret in Vault. Required. */ + public static final Parameter<String> SECRET_PATH = Parameter.create(REQUIRED); + + /** + * The name of the key if the secret is a JSON with multiple fields. + * This is optional. + */ + public static final Parameter<String> KEY = Parameter.create(); + + /** + * The Vault address. If not specified, fallback to system property or environment var. + */ + public static final Parameter<String> VAULT_ADDR = Parameter.create(REQUIRED); + + /** + * The Vault token. If not specified, fallback to system property or environment var. + */ + public static final Parameter<String> VAULT_TOKEN = Parameter.create(REQUIRED); + + /** + * The field name for extracting a specific value from the JSON. + */ + public static final Parameter<String> FIELD_NAME = Parameter.create(); + + /** + * The username for Userpass authentication. Required for Userpass method. + */ + public static final Parameter<String> USERNAME = Parameter.create(REQUIRED); + + /** + * The password for Userpass authentication. Required for Userpass method. + */ + public static final Parameter<String> PASSWORD = Parameter.create(REQUIRED); + + /** + * The path for Userpass authentication. Optional. + */ + public static final Parameter<String> USERPASS_AUTH_PATH = Parameter.create(); + + /** + * The namespace for the Vault API request. Optional. + */ + public static final Parameter<String> NAMESPACE = Parameter.create(); + + /** + * The Role ID for AppRole authentication. Required for AppRole method. + * <p> + * The Role ID identifies the role to use for authentication. It must be + * configured in Vault as part of the AppRole authentication setup. + * </p> + */ + public static final Parameter<String> ROLE_ID = Parameter.create(REQUIRED); + + /** + * The Secret ID for AppRole authentication. Required for AppRole method. + * <p> + * The Secret ID is a credential tied to a specific role and used in + * conjunction with the Role ID for AppRole authentication. + * </p> + */ + public static final Parameter<String> SECRET_ID = Parameter.create(REQUIRED); + + /** + * The path for AppRole authentication. Optional. + * <p> + * This parameter specifies the path where the AppRole authentication + * method is enabled. The default path is "approle". If the method is + * enabled at a different path, that value should be provided here. + * </p> + */ + public static final Parameter<String> APPROLE_AUTH_PATH = Parameter.create(); + + /** + * The GitHub personal access token. Required for GitHub authentication method. + * <p> + * This token is used to authenticate with the HashiCorp Vault via the + * GitHub authentication method. The token should be a valid GitHub + * personal access token with the necessary permissions configured in + * the Vault policy. + * </p> + */ + public static final Parameter<String> GITHUB_TOKEN = Parameter.create(REQUIRED); + + /** + * The path for GitHub authentication. Optional. + * <p> + * This parameter specifies the path where the GitHub authentication method + * is enabled. The default path is "github". If the GitHub authentication + * method is enabled at a custom path, provide this parameter with the + * appropriate value. + * </p> + */ + public static final Parameter<String> GITHUB_AUTH_PATH = Parameter.create(); + + /** + * Builds a resolved ParameterSet from the given options map. + * <p> + * This method makes a defensive copy of the provided map, ensures that a default + * authentication method is set, fills in missing keys (using fallback values from + * system properties or environment variables) based on the authentication method, + * and then parses the updated map into a ParameterSet. + * </p> + * + * @param inputOpts The input options map. + * @return The resolved ParameterSet. + */ + public static ParameterSet buildResolvedParameterSet(Map<String, String> inputOpts) { + Map<String, String> opts = new HashMap<>(inputOpts); + + String authStr = opts.entrySet().stream() + .filter(entry -> entry.getKey().equalsIgnoreCase(PARAM_AUTHENTICATION)) + .map(Map.Entry::getValue) + .findFirst() + .orElse(DedicatedVaultAuthenticationMethod.AUTO_DETECT.name()); + + DedicatedVaultAuthenticationMethod authMethod = + DedicatedVaultAuthenticationMethod.valueOf(authStr.toUpperCase()); + + opts.computeIfAbsent(PARAM_VAULT_ADDR, Parameterutil::getFallback); + opts.computeIfAbsent(PARAM_VAULT_NAMESPACE, Parameterutil::getFallback); + + switch (authMethod) { + case VAULT_TOKEN: + opts.computeIfAbsent(PARAM_VAULT_TOKEN, Parameterutil::getFallback); + break; + case GITHUB: + opts.computeIfAbsent(PARAM_GITHUB_TOKEN, Parameterutil::getFallback); + opts.computeIfAbsent(PARAM_GITHUB_AUTH_PATH, Parameterutil::getFallback); + break; + case APPROLE: + opts.computeIfAbsent(PARAM_VAULT_ROLE_ID, Parameterutil::getFallback); + opts.computeIfAbsent(PARAM_VAULT_SECRET_ID, Parameterutil::getFallback); + opts.computeIfAbsent(PARAM_APPROLE_AUTH_PATH, Parameterutil::getFallback); + break; + case USERPASS: + opts.computeIfAbsent(PARAM_VAULT_USERNAME, Parameterutil::getFallback); + opts.computeIfAbsent(PARAM_VAULT_PASSWORD, Parameterutil::getFallback); + opts.computeIfAbsent(PARAM_USERPASS_AUTH_PATH, Parameterutil::getFallback); + break; + case AUTO_DETECT: + opts.computeIfAbsent(PARAM_VAULT_TOKEN, Parameterutil::getFallback); + opts.computeIfAbsent(PARAM_VAULT_USERNAME, Parameterutil::getFallback); + opts.computeIfAbsent(PARAM_VAULT_PASSWORD, Parameterutil::getFallback); + opts.computeIfAbsent(PARAM_USERPASS_AUTH_PATH, Parameterutil::getFallback); + opts.computeIfAbsent(PARAM_VAULT_ROLE_ID, Parameterutil::getFallback); + opts.computeIfAbsent(PARAM_VAULT_SECRET_ID, Parameterutil::getFallback); + opts.computeIfAbsent(PARAM_APPROLE_AUTH_PATH, Parameterutil::getFallback); + opts.computeIfAbsent(PARAM_GITHUB_TOKEN, Parameterutil::getFallback); + opts.computeIfAbsent(PARAM_GITHUB_AUTH_PATH, Parameterutil::getFallback); + break; + default: + break; + } + + return PARAMETER_SET_PARSER.parseNamedValues(opts); + } + + /** + * Parses the authentication method from a string value. + * @param value the string value representing the authentication method. Must + * not be null. + * @return the parsed {@link DedicatedVaultAuthenticationMethod}. + * @throws IllegalArgumentException if the value is unrecognized. + */ + private static DedicatedVaultAuthenticationMethod parseAuthentication(String value) { + try { + return DedicatedVaultAuthenticationMethod.valueOf(value.toUpperCase()); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException( + "Unrecognized Hashicorp authentication value: " + value, e); + } + } + + public static final ParameterSetParser PARAMETER_SET_PARSER = + ParameterSetParser.builder() + .addParameter("value", SECRET_PATH) + .addParameter("key", KEY) + .addParameter(PARAM_AUTHENTICATION, AUTHENTICATION_METHOD, AUTO_DETECT, + DedicatedVaultParameters::parseAuthentication) + .addParameter(PARAM_VAULT_ADDR, VAULT_ADDR) + .addParameter(PARAM_VAULT_TOKEN, VAULT_TOKEN) + .addParameter(PARAM_VAULT_USERNAME, USERNAME) + .addParameter(PARAM_VAULT_PASSWORD, PASSWORD) + .addParameter(PARAM_USERPASS_AUTH_PATH, USERPASS_AUTH_PATH, DEFAULT_USERPASS_PATH) + .addParameter(PARAM_VAULT_NAMESPACE, NAMESPACE, DEFAULT_NAMESPACE) + .addParameter(PARAM_VAULT_ROLE_ID, ROLE_ID) + .addParameter(PARAM_VAULT_SECRET_ID, SECRET_ID) + .addParameter(PARAM_APPROLE_AUTH_PATH, APPROLE_AUTH_PATH, DEFAULT_APPROLE_PATH) + .addParameter(PARAM_GITHUB_TOKEN, GITHUB_TOKEN) + .addParameter(PARAM_GITHUB_AUTH_PATH, GITHUB_AUTH_PATH, DEFAULT_GITHUB_PATH) + .addParameter(PARAM_FIELD_NAME, FIELD_NAME) + .addParameter("type", Parameter.create()) + .build(); + + +} diff --git a/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultdedicated/authentication/DedicatedVaultToken.java b/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultdedicated/authentication/DedicatedVaultToken.java new file mode 100644 index 00000000..bd649bfb --- /dev/null +++ b/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultdedicated/authentication/DedicatedVaultToken.java @@ -0,0 +1,77 @@ +/* + ** Copyright (c) 2025 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.hcpvaultdedicated.authentication; + +/** + * <p> + * Holds the credentials required for authenticating with Dedicated + * HashiCorp Vault. + * </p><p> + * This class encapsulates credentials used for making secure + * requests to the Vault API. + * </p> + */ +public final class DedicatedVaultToken { + + private final String vaultToken; + + /** + * Constructs a new {@code DedicatedVaultCredentials} object with + * the provided Vault token. + * + * @param vaultToken the token used to authenticate API requests to + * the Vault. Must not be null or empty. + * @throws IllegalArgumentException if {@code vaultToken} is null or empty. + */ + public DedicatedVaultToken(String vaultToken) { + if (vaultToken == null || vaultToken.isEmpty()) { + throw new IllegalArgumentException("Vault token must not be null or empty."); + } + this.vaultToken = vaultToken; + } + /** + * Returns the Vault token used for authentication. + * + * @return the Vault token as a {@link String}. + */ + public String getVaultToken() { + return vaultToken; + } + +} diff --git a/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultdedicated/authentication/DedicatedVaultTokenFactory.java b/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultdedicated/authentication/DedicatedVaultTokenFactory.java new file mode 100644 index 00000000..0c2a4c1a --- /dev/null +++ b/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultdedicated/authentication/DedicatedVaultTokenFactory.java @@ -0,0 +1,142 @@ +/* + ** Copyright (c) 2025 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.hcpvaultdedicated.authentication; + +import oracle.jdbc.provider.factory.Resource; +import oracle.jdbc.provider.factory.ResourceFactory; +import oracle.jdbc.provider.parameter.ParameterSet; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import static oracle.jdbc.provider.hashicorp.hcpvaultdedicated.authentication.DedicatedVaultParameters.AUTHENTICATION_METHOD; + +/** + * <p> + * Factory for creating {@link DedicatedVaultToken} objects for authenticating + * with Dedicated HashiCorp Vault. + * </p><p> + * This factory determines the appropriate authentication method based on the provided + * {@link ParameterSet} and creates credentials accordingly. + * </p> + */ +public final class DedicatedVaultTokenFactory + implements ResourceFactory<DedicatedVaultToken> { + + // Map to cache tokens based on generated cache keys + private static final ConcurrentHashMap<Map<String, Object>, CachedToken> tokenCache = new ConcurrentHashMap<>(); + + private static final DedicatedVaultTokenFactory INSTANCE = + new DedicatedVaultTokenFactory(); + + private DedicatedVaultTokenFactory() { + } + + /** + * Returns a singleton instance of {@code DedicatedVaultCredentialsFactory}. + * + * @return a singleton instance. Not null. + */ + public static DedicatedVaultTokenFactory getInstance() { + return INSTANCE; + } + + @Override + public Resource<DedicatedVaultToken> request(ParameterSet parameterSet) { + DedicatedVaultToken credentials = getCredential(parameterSet); + return Resource.createPermanentResource(credentials, true); + } + + /** + * Determines the appropriate credentials based on the provided parameters. + * + * @param parameterSet the set of parameters configuring the request. Must + * not be null. + * @return the created {@code DedicatedVaultToken} instance. + */ + private static DedicatedVaultToken getCredential(ParameterSet parameterSet) { + DedicatedVaultAuthenticationMethod method = parameterSet.getRequired(AUTHENTICATION_METHOD); + AbstractDedicatedVaultAuthentication authentication = getAuthenticationInstance(method); + return createCachedToken(parameterSet, authentication); + } + + /** + * Creates or retrieves a cached {@link DedicatedVaultToken} for the specified + * authentication method. + * + * @param parameterSet the set of parameters for the request. + * @param authentication the authentication method to generate the token. + * @return a {@code DedicatedVaultToken} instance. + */ + private static DedicatedVaultToken createCachedToken( + ParameterSet parameterSet, AbstractDedicatedVaultAuthentication authentication) { + Map<String, Object> cacheKey = authentication.generateCacheKey(parameterSet); + CachedToken validCachedToken = tokenCache.compute(cacheKey, (k, cachedToken) -> { + if (cachedToken == null || !cachedToken.isValid()) { + return authentication.generateToken(parameterSet); + } + return cachedToken; + }); + return new DedicatedVaultToken(validCachedToken.getToken().token().get()); + } + + /** + * Returns an instance of the appropriate authentication class based on the method. + * + * @param method The selected authentication method. + * @return An instance of {@link AbstractDedicatedVaultAuthentication}. + */ + private static AbstractDedicatedVaultAuthentication getAuthenticationInstance(DedicatedVaultAuthenticationMethod method) { + switch (method) { + case VAULT_TOKEN: + return VaultTokenAuthentication.INSTANCE; + case USERPASS: + return UserpassAuthentication.INSTANCE; + case APPROLE: + return AppRoleAuthentication.INSTANCE; + case GITHUB: + return GitHubAuthentication.INSTANCE; + case AUTO_DETECT: + return AutoDetectAuthentication.INSTANCE; + default: + throw new IllegalArgumentException("Unsupported authentication method: " + method); + } + } + +} diff --git a/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultdedicated/authentication/GitHubAuthentication.java b/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultdedicated/authentication/GitHubAuthentication.java new file mode 100644 index 00000000..332aaf49 --- /dev/null +++ b/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultdedicated/authentication/GitHubAuthentication.java @@ -0,0 +1,78 @@ +/* + ** Copyright (c) 2025 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.hcpvaultdedicated.authentication; + +import oracle.jdbc.provider.parameter.ParameterSet; +import java.util.Map; + +import static oracle.jdbc.provider.hashicorp.hcpvaultdedicated.authentication.DedicatedVaultParameters.*; + +/** + * Handles authentication using the GitHub authentication method for HashiCorp Vault. + */ +public class GitHubAuthentication extends AbstractDedicatedVaultAuthentication { + + /** + * Singleton instance of {@link GitHubAuthentication}. + */ + public static final GitHubAuthentication INSTANCE = new GitHubAuthentication(); + + private GitHubAuthentication() { + // Private constructor to prevent external instantiation + } + + @Override + public CachedToken generateToken(ParameterSet parameterSet) { + String vaultAddr = parameterSet.getRequired(VAULT_ADDR); + String githubToken = parameterSet.getRequired(GITHUB_TOKEN); + String namespace = parameterSet.getOptional(NAMESPACE); + String githubAuthPath = parameterSet.getOptional(GITHUB_AUTH_PATH); + + String authEndpoint = buildAuthEndpoint(vaultAddr, GITHUB_LOGIN_TEMPLATE, githubAuthPath); + String payload = createJsonPayload(GITHUB_PAYLOAD_TEMPLATE, githubToken); + return performAuthentication(authEndpoint, payload, namespace, githubToken, "Failed to authenticate with GitHub"); + } + + @Override + public Map<String, Object> generateCacheKey(ParameterSet parameterSet) { + return parameterSet.filterParameters(new String[]{ + PARAM_VAULT_ADDR, PARAM_VAULT_NAMESPACE, PARAM_GITHUB_AUTH_PATH, PARAM_GITHUB_TOKEN + }); + } +} diff --git a/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultdedicated/authentication/UserpassAuthentication.java b/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultdedicated/authentication/UserpassAuthentication.java new file mode 100644 index 00000000..ff5b4583 --- /dev/null +++ b/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultdedicated/authentication/UserpassAuthentication.java @@ -0,0 +1,80 @@ +/* + ** Copyright (c) 2025 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.hcpvaultdedicated.authentication; + +import oracle.jdbc.provider.parameter.ParameterSet; +import java.util.Map; + +import static oracle.jdbc.provider.hashicorp.hcpvaultdedicated.authentication.DedicatedVaultParameters.*; + +/** + * Handles authentication using the Userpass method for HashiCorp Vault + * DEDICATED + */ +public class UserpassAuthentication extends AbstractDedicatedVaultAuthentication { + + /** + * Singleton instance of {@link UserpassAuthentication}. + */ + public static final UserpassAuthentication INSTANCE = new UserpassAuthentication(); + + private UserpassAuthentication() { + // Private constructor to prevent external instantiation + } + + @Override + public CachedToken generateToken(ParameterSet parameterSet) { + String vaultAddr = parameterSet.getRequired(VAULT_ADDR); + String authPath = parameterSet.getOptional(USERPASS_AUTH_PATH); + String namespace = parameterSet.getOptional(NAMESPACE); + String username = parameterSet.getRequired(USERNAME); + String password = parameterSet.getRequired(PASSWORD); + + String authEndpoint = buildAuthEndpoint(vaultAddr, USERPASS_LOGIN_TEMPLATE, authPath, username); + String payload = createJsonPayload(USERPASS_PAYLOAD_TEMPLATE, password); + return performAuthentication(authEndpoint, payload, namespace, null, "Failed to authenticate using Userpass"); + } + + @Override + public Map<String, Object> generateCacheKey(ParameterSet parameterSet) { + return parameterSet.filterParameters(new String[]{ + PARAM_VAULT_ADDR, PARAM_VAULT_NAMESPACE, PARAM_USERPASS_AUTH_PATH, PARAM_VAULT_USERNAME, PARAM_VAULT_PASSWORD + }); + } +} diff --git a/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultdedicated/authentication/VaultTokenAuthentication.java b/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultdedicated/authentication/VaultTokenAuthentication.java new file mode 100644 index 00000000..9da22cd7 --- /dev/null +++ b/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultdedicated/authentication/VaultTokenAuthentication.java @@ -0,0 +1,72 @@ +/* + ** Copyright (c) 2025 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.hcpvaultdedicated.authentication; + +import oracle.jdbc.driver.oauth.OpaqueAccessToken; +import oracle.jdbc.provider.parameter.ParameterSet; + +import java.util.Map; + +import static oracle.jdbc.provider.hashicorp.hcpvaultdedicated.authentication.DedicatedVaultParameters.*; + +/** + * Handles authentication using a Vault token for HashiCorp Vault. + */ +public class VaultTokenAuthentication extends AbstractDedicatedVaultAuthentication { + + /** + * Singleton instance of {@link VaultTokenAuthentication}. + */ + public static final VaultTokenAuthentication INSTANCE = new VaultTokenAuthentication(); + + private VaultTokenAuthentication() { + // Private constructor to prevent external instantiation + } + + @Override + public CachedToken generateToken(ParameterSet parameterSet) { + String vaultToken = parameterSet.getRequired(VAULT_TOKEN); + return new CachedToken(OpaqueAccessToken.create(vaultToken.toCharArray(), null)); + } + + @Override + public Map<String, Object> generateCacheKey(ParameterSet parameterSet) { + return parameterSet.filterParameters(new String[]{PARAM_VAULT_ADDR, PARAM_VAULT_TOKEN}); + } +} diff --git a/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultdedicated/configuration/DedicatedVaultJsonSecretProvider.java b/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultdedicated/configuration/DedicatedVaultJsonSecretProvider.java new file mode 100644 index 00000000..e719e021 --- /dev/null +++ b/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultdedicated/configuration/DedicatedVaultJsonSecretProvider.java @@ -0,0 +1,118 @@ +/* + ** Copyright (c) 2025 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.hcpvaultdedicated.configuration; + +import oracle.jdbc.provider.hashicorp.util.JsonUtil; +import oracle.jdbc.provider.hashicorp.hcpvaultdedicated.secrets.DedicatedVaultSecretsManagerFactory; +import oracle.jdbc.provider.parameter.ParameterSet; +import oracle.jdbc.spi.OracleConfigurationSecretProvider; +import oracle.sql.json.OracleJsonObject; + +import java.util.Base64; +import java.util.Map; + +import static oracle.jdbc.provider.hashicorp.hcpvaultdedicated.authentication.DedicatedVaultParameters.FIELD_NAME; +import static oracle.jdbc.provider.hashicorp.hcpvaultdedicated.authentication.DedicatedVaultParameters.PARAMETER_SET_PARSER; + +/** + * <p> + * Implementation of {@link OracleConfigurationSecretProvider} for + * Dedicated HashiCorp Vault. This provider retrieves secrets stored in the HCP + * Vault Dedicated and optionally extracts a specific field if specified. + * </p> + * <p> + * The {@code jsonObject} must adhere to the following structure: + * </p> + * + * <pre>{@code + * "password": { + * "type": "hcpdedicatedvault", + * "value": "<secret-path>", + * "FIELD_NAME": "<field-name>" + * } + * }</pre> + * + * <h2>Behavior for Extracting the Secret</h2> + * <ul> + * <li> If {@code field_name} is provided, the corresponding value is + * extracted.</li> + * <li>If the secret contains <b>only one key-value pair</b>, that value + * is automatically selected.</li> + * <li>If multiple keys exist and {@code field_name} is <b>not provided</b>, + * an error is thrown.</li> + * </ul> + * <p> + * The secret path specified in the JSON is used to query the Vault and fetch + * the desired secret. + * </p> + */ +public class DedicatedVaultJsonSecretProvider implements OracleConfigurationSecretProvider { + + @Override + public char[] getSecret(Map<String, String> map) { + ParameterSet parameterSet = PARAMETER_SET_PARSER.parseNamedValues(map); + String secretString = DedicatedVaultSecretsManagerFactory + .getInstance() + .request(parameterSet) + .getContent(); + + OracleJsonObject secretJsonObj = JsonUtil.convertJsonToOracleJsonObject(secretString); + + String fieldName = parameterSet.getOptional(FIELD_NAME); + String extractedPassword; + if (fieldName != null && secretJsonObj.containsKey(fieldName)) { + extractedPassword = secretJsonObj.getString(fieldName); + } else if (secretJsonObj.size() == 1) { + extractedPassword = secretJsonObj.values().iterator().next().toString(); + } else { + throw new IllegalStateException( + "FIELD_NAME is required when multiple keys exist in the secret." + ); + } + + return Base64.getEncoder() + .encodeToString(extractedPassword.getBytes()) + .toCharArray(); + } + + @Override + public String getSecretType() { + return "hcpvaultdedicated"; + } +} diff --git a/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultdedicated/configuration/DedicatedVaultSecretsManagerConfigurationProvider.java b/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultdedicated/configuration/DedicatedVaultSecretsManagerConfigurationProvider.java new file mode 100644 index 00000000..eb080394 --- /dev/null +++ b/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultdedicated/configuration/DedicatedVaultSecretsManagerConfigurationProvider.java @@ -0,0 +1,88 @@ +/* + ** Copyright (c) 2025 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.hcpvaultdedicated.configuration; + +import oracle.jdbc.driver.configuration.OracleConfigurationParsableProvider; +import oracle.jdbc.provider.hashicorp.hcpvaultdedicated.secrets.DedicatedVaultSecretsManagerFactory; +import oracle.jdbc.provider.parameter.ParameterSet; +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; + +import static oracle.jdbc.provider.hashicorp.hcpvaultdedicated.authentication.DedicatedVaultParameters.*; + +public class DedicatedVaultSecretsManagerConfigurationProvider extends OracleConfigurationParsableProvider { + + @Override + public InputStream getInputStream(String secretPath) { + final String valueFieldName = "value"; + + Map<String, String> optionsWithSecret = new HashMap<>(options); + optionsWithSecret.put(valueFieldName, secretPath); + + ParameterSet finalParameters = buildResolvedParameterSet(optionsWithSecret); + + String secretString = DedicatedVaultSecretsManagerFactory + .getInstance() + .request(finalParameters) + .getContent(); + + return new ByteArrayInputStream(secretString.getBytes(StandardCharsets.UTF_8)); + } + + @Override + public String getType() { + return "hcpvaultdedicated"; + } + + @Override + public String getParserType(String location) { + return "json"; + } + + @Override + public OracleConfigurationCache getCache() { + return CACHE; + } + +} diff --git a/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultdedicated/secrets/DedicatedVaultSecretsManagerFactory.java b/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultdedicated/secrets/DedicatedVaultSecretsManagerFactory.java new file mode 100644 index 00000000..0ca52af6 --- /dev/null +++ b/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultdedicated/secrets/DedicatedVaultSecretsManagerFactory.java @@ -0,0 +1,114 @@ +/* + ** Copyright (c) 2025 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.hcpvaultdedicated.secrets; + +import oracle.jdbc.provider.cache.CachedResourceFactory; +import oracle.jdbc.provider.factory.Resource; +import oracle.jdbc.provider.factory.ResourceFactory; +import oracle.jdbc.provider.hashicorp.util.HttpUtil; +import oracle.jdbc.provider.hashicorp.util.JsonUtil; +import oracle.jdbc.provider.hashicorp.hcpvaultdedicated.DedicatedVaultResourceFactory; +import oracle.jdbc.provider.hashicorp.hcpvaultdedicated.authentication.DedicatedVaultToken; +import oracle.jdbc.provider.parameter.ParameterSet; +import oracle.sql.json.OracleJsonObject; + +import static oracle.jdbc.provider.hashicorp.hcpvaultdedicated.authentication.DedicatedVaultParameters.*; + +public final class DedicatedVaultSecretsManagerFactory extends DedicatedVaultResourceFactory<String> { + /** + * The single instance of this factory, cached for performance. + */ + private static final ResourceFactory<String> INSTANCE = + CachedResourceFactory.create(new DedicatedVaultSecretsManagerFactory()); + + private DedicatedVaultSecretsManagerFactory() {} + + public static ResourceFactory<String> getInstance() { + return INSTANCE; + } + + @Override + public Resource<String> request(DedicatedVaultToken credentials, ParameterSet parameterSet) { + String secretPath = parameterSet.getRequired(SECRET_PATH); + String vaultAddr = parameterSet.getRequired(VAULT_ADDR); + + if (credentials.getVaultToken() != null) { + String secretString = fetchSecretFromVaultWithToken(vaultAddr + secretPath, credentials.getVaultToken()); + return Resource.createPermanentResource(parseSecretJson(secretString, secretPath), true); + } + + throw new IllegalStateException("Invalid credentials: Vault token is missing."); + } + + /** + * Fetches a secret from the Vault using the given Vault URL and + * authentication token. + * + * @param vaultUrl The complete Vault URL including the secret path. + * @param token The Vault token for authentication. + * @return The raw secret as a JSON string. + * @throws IllegalArgumentException If there is an error during the Vault request. + */ + private static String fetchSecretFromVaultWithToken(String vaultUrl, String token) { + try { + return HttpUtil.sendGetRequest(vaultUrl, token, null); + } catch (Exception e) { + throw new IllegalArgumentException("Failed to read secret from Vault at " + vaultUrl, e); + } + } + + /** + * Parses the JSON secret to extract the nested data node. + * + * @param secretJson The raw secret JSON string. + * @param secretPath The secret path (for error messages). + * @return The extracted JSON data as a string. + */ + private static String parseSecretJson(String secretJson, String secretPath) { + try { + OracleJsonObject rootObject = JsonUtil.convertJsonToOracleJsonObject(secretJson); + OracleJsonObject dataNode = rootObject.getObject("data"); + OracleJsonObject nestedData = dataNode.getObject("data"); + return nestedData.toString(); + } catch (Exception e) { + throw new IllegalArgumentException( + "Failed to parse JSON for secret at path: " + secretPath, e); + } + } +} diff --git a/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultsecret/HcpVaultResourceFactory.java b/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultsecret/HcpVaultResourceFactory.java new file mode 100644 index 00000000..5e88d5c1 --- /dev/null +++ b/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultsecret/HcpVaultResourceFactory.java @@ -0,0 +1,66 @@ +/* + ** Copyright (c) 2025 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.hcpvaultsecret; + +import oracle.jdbc.provider.factory.Resource; +import oracle.jdbc.provider.factory.ResourceFactory; +import oracle.jdbc.provider.hashicorp.hcpvaultsecret.authentication.HcpVaultSecretToken; +import oracle.jdbc.provider.hashicorp.hcpvaultsecret.authentication.HcpVaultTokenFactory; +import oracle.jdbc.provider.parameter.ParameterSet; + +public abstract class HcpVaultResourceFactory<T> implements ResourceFactory<T> { + + @Override + public final Resource<T> request(ParameterSet parameterSet) { + HcpVaultSecretToken credentials = HcpVaultTokenFactory + .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( + HcpVaultSecretToken credentials, ParameterSet parameterSet); +} diff --git a/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultsecret/authentication/AbstractHcpVaultAuthentication.java b/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultsecret/authentication/AbstractHcpVaultAuthentication.java new file mode 100644 index 00000000..dc7ad40c --- /dev/null +++ b/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultsecret/authentication/AbstractHcpVaultAuthentication.java @@ -0,0 +1,69 @@ +/* + ** Copyright (c) 2025 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.hcpvaultsecret.authentication; + +import oracle.jdbc.provider.parameter.ParameterSet; + +import java.util.Map; + +/** + * Base class for HCP Vault Secrets authentication strategies. + * <p> + * Subclasses must implement methods to generate an access token and a cache key. + * </p> + */ +public abstract class AbstractHcpVaultAuthentication { + + /** + * Generates an HCP Vault Secrets token based on the provided parameters. + * + * @param parameterSet the parameters for the authentication request. + * @return the generated {@link HcpVaultSecretToken}. + */ + public abstract HcpVaultSecretToken generateToken(ParameterSet parameterSet); + + /** + * Generates a cache key for the authentication request. + * + * @param parameterSet the parameters for the authentication request. + * @return a {@link ParameterSet} to be used as a cache key. + */ + public abstract Map<String, Object> generateCacheKey(ParameterSet parameterSet); + +} diff --git a/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultsecret/authentication/AutoDetectAuthentication.java b/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultsecret/authentication/AutoDetectAuthentication.java new file mode 100644 index 00000000..3ed5725f --- /dev/null +++ b/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultsecret/authentication/AutoDetectAuthentication.java @@ -0,0 +1,106 @@ +/* + ** Copyright (c) 2025 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.hcpvaultsecret.authentication; + +import oracle.jdbc.provider.parameter.ParameterSet; + +import java.util.Collections; +import java.util.Map; + +/** + * Automatically selects the best authentication method based on available parameters. + * <p> + * The priority order is: + * <ol> + * <li>CLI_CREDENTIALS_FILE</li> + * <li>CLIENT_CREDENTIALS</li> + * </ol> + */ +public class AutoDetectAuthentication extends AbstractHcpVaultAuthentication { + + /** + * Singleton instance of {@link AutoDetectAuthentication}. + */ + public static final AutoDetectAuthentication INSTANCE = new AutoDetectAuthentication(); + + /** + * Ordered list of authentication methods by priority. + */ + private static final AbstractHcpVaultAuthentication[] AUTHENTICATION_METHODS = { + CliCredentialsFileAuthentication.INSTANCE, + ClientCredentialsAuthentication.INSTANCE + }; + + private AutoDetectAuthentication() { + // Private constructor to enforce singleton + } + + @Override + public HcpVaultSecretToken generateToken(ParameterSet parameterSet) { + IllegalStateException previousFailure = null; + + for (AbstractHcpVaultAuthentication authentication : AUTHENTICATION_METHODS) { + try { + return authentication.generateToken(parameterSet); + } catch (RuntimeException e) { + IllegalStateException failure = new IllegalStateException( + "Failed to authenticate using " + authentication.getClass().getSimpleName(), e); + if (previousFailure == null) { + previousFailure = failure; + } else { + previousFailure.addSuppressed(failure); + } + } + } + + throw previousFailure; + } + + @Override + public Map<String, Object> generateCacheKey(ParameterSet parameterSet) { + for (AbstractHcpVaultAuthentication authentication : AUTHENTICATION_METHODS) { + Map<String, Object> cacheKey = authentication.generateCacheKey(parameterSet); + if (!cacheKey.isEmpty()) { + return cacheKey; + } + } + return Collections.emptyMap(); + } + +} diff --git a/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultsecret/authentication/CliCredentialsFileAuthentication.java b/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultsecret/authentication/CliCredentialsFileAuthentication.java new file mode 100644 index 00000000..0716c93a --- /dev/null +++ b/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultsecret/authentication/CliCredentialsFileAuthentication.java @@ -0,0 +1,77 @@ +/* + ** Copyright (c) 2025 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.hcpvaultsecret.authentication; + +import oracle.jdbc.provider.parameter.ParameterSet; +import java.util.Map; + +import static oracle.jdbc.provider.hashicorp.hcpvaultsecret.authentication.HcpVaultSecretParameters.*; + +/** + * Handles authentication using a credentials file generated by the HCP CLI. + */ +public class CliCredentialsFileAuthentication extends AbstractHcpVaultAuthentication { + + /** + * Singleton instance of {@link CliCredentialsFileAuthentication}. + */ + public static final CliCredentialsFileAuthentication INSTANCE = new CliCredentialsFileAuthentication(); + + private CliCredentialsFileAuthentication() { + // Private constructor to enforce singleton + } + + @Override + public HcpVaultSecretToken generateToken(ParameterSet parameterSet) { + try { + String credentialsFile = parameterSet.getRequired(HCP_CREDENTIALS_FILE); + HcpVaultCredentialsFileAuthenticator fileAuthenticator = + new HcpVaultCredentialsFileAuthenticator(credentialsFile); + String token = fileAuthenticator.getValidAccessToken(); + return new HcpVaultSecretToken(token); + } catch (Exception e) { + throw new IllegalStateException("Failed to authenticate using HCP CLI credentials file", e); + } + } + + @Override + public Map<String, Object> generateCacheKey(ParameterSet parameterSet) { + return parameterSet.filterParameters(new String[]{PARAM_HCP_CREDENTIALS_FILE}); + } +} diff --git a/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultsecret/authentication/ClientCredentialsAuthentication.java b/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultsecret/authentication/ClientCredentialsAuthentication.java new file mode 100644 index 00000000..7bea53e2 --- /dev/null +++ b/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultsecret/authentication/ClientCredentialsAuthentication.java @@ -0,0 +1,74 @@ +/* + ** Copyright (c) 2025 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.hcpvaultsecret.authentication; + +import oracle.jdbc.provider.parameter.ParameterSet; +import java.util.Map; + +import static oracle.jdbc.provider.hashicorp.hcpvaultsecret.authentication.HcpVaultSecretParameters.*; + +/** + * Handles authentication using the OAuth2 client_credentials flow for HCP Vault Secrets. + */ +public class ClientCredentialsAuthentication extends AbstractHcpVaultAuthentication { + + /** + * Singleton instance of {@link ClientCredentialsAuthentication}. + */ + public static final ClientCredentialsAuthentication INSTANCE = new ClientCredentialsAuthentication(); + + private ClientCredentialsAuthentication() { + // Private constructor to enforce singleton + } + + @Override + public HcpVaultSecretToken generateToken(ParameterSet parameterSet) { + String clientId = parameterSet.getRequired(HCP_CLIENT_ID); + String clientSecret = parameterSet.getRequired(HCP_CLIENT_SECRET); + String rawToken = HcpVaultOAuthClient.fetchHcpAccessToken(clientId, clientSecret); + return new HcpVaultSecretToken(rawToken); + } + + @Override + public Map<String, Object> generateCacheKey(ParameterSet parameterSet) { + return parameterSet.filterParameters(new String[]{ + PARAM_HCP_CLIENT_ID, PARAM_HCP_CLIENT_SECRET + }); + } +} diff --git a/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultsecret/authentication/HcpVaultAuthenticationMethod.java b/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultsecret/authentication/HcpVaultAuthenticationMethod.java new file mode 100644 index 00000000..cc52ef50 --- /dev/null +++ b/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultsecret/authentication/HcpVaultAuthenticationMethod.java @@ -0,0 +1,106 @@ +/* + ** Copyright (c) 2025 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.hcpvaultsecret.authentication; + +/** + * Enumeration of authentication methods supported by HCP Vault Secrets. + * <p> + * This represents the different ways to authenticate with the HCP Vault Secrets API. + * </p> + */ +public enum HcpVaultAuthenticationMethod { + + /** + * Authentication using client credentials via the OAuth2 client_credentials flow. + * <p> + * This method requires the following: + * </p> + * <ul> + * <li>A <b>Client ID</b> provided by the HCP Vault console or associated + * with an HCP Service Principal. + * </li> + * <li>A <b>Client Secret</b> corresponding to the Client ID, ensuring + * secure access. + * </li> + * </ul> + * <p> + * By using these credentials, the method retrieves a short-lived API token + * by calling the HCP OAuth2 endpoint. + * </p> + */ + CLIENT_CREDENTIALS, + + /** + * Authentication using the credentials file generated by the HCP CLI. + * <p> + * This method retrieves an access token from the standard CLI-generated + * credentials file located at + * <code>System.getProperty("user.home") + "/.config/hcp/creds-cache.json"</code>. + * If the token is expired, + * it will be automatically refreshed using the stored refresh token. + * </p> + * <p> + * The credentials file must follow the standard JSON structure containing: + * </p> + * <pre> + * { + * "login": { + * "access_token": "...", + * "refresh_token": "...", + * "access_token_expiry": "..." + * } + * } + * </pre> + * <p> + * The user can provide a custom path to the credentials file if needed. + * </p> + */ + CLI_CREDENTIALS_FILE, + + /** + * Automatically selects the best authentication method based on available parameters. + * + * <p>Priority order:</p> + * <ol> + * <li>Uses the credentials file if present and valid.</li> + * <li>Falls back to client credentials authentication.</li> + * </ol> + */ + AUTO_DETECT; +} \ No newline at end of file diff --git a/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultsecret/authentication/HcpVaultCredentialsFileAuthenticator.java b/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultsecret/authentication/HcpVaultCredentialsFileAuthenticator.java new file mode 100644 index 00000000..e551925a --- /dev/null +++ b/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultsecret/authentication/HcpVaultCredentialsFileAuthenticator.java @@ -0,0 +1,246 @@ +/* + ** Copyright (c) 2025 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.hcpvaultsecret.authentication; + +import oracle.jdbc.provider.hashicorp.util.HttpUtil; +import oracle.jdbc.provider.hashicorp.util.JsonUtil; +import oracle.sql.json.OracleJsonObject; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.time.Instant; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import java.util.Base64; +import java.util.concurrent.locks.ReentrantLock; + +/** + * Handles authentication using the HashiCorp CLI credentials cache. + * <p> + * This class reads the authentication details from the CLI-generated credentials file + * (`creds-cache.json`) and manages the token lifecycle, including: + * </p> + * <ul> + * <li>Validating the access token's expiration.</li> + * <li>Refreshing the token using the stored refresh token when expired.</li> + * <li>Updating the credentials file with the new token details.</li> + * </ul> + * <p> + * By default, the credentials file is expected at: + * <code>System.getProperty("user.home") + "/.config/hcp/creds-cache.json"</code>. + * However, users can provide a custom file path through configuration. + * </p> + */ +public final class HcpVaultCredentialsFileAuthenticator { + private static final String TOKEN_URL = "https://auth.idp.hashicorp.com/oauth2/token"; + private static final String GRANT_TYPE = "refresh_token"; + private static final String CONTENT_TYPE = "application/x-www-form-urlencoded"; + private static final String TOKEN_REFRESH_PAYLOAD_FORMAT = "grant_type=%s&refresh_token=%s&client_id=%s"; + private static final String CREDENTIALS_JSON_FORMAT = + "{ \"login\": { \"access_token\": \"%s\", \"refresh_token\": \"%s\", \"access_token_expiry\": \"%s\" } }"; + + // JSON field constants + public static final String ACCESS_TOKEN_FIELD = "access_token"; + private static final String REFRESH_TOKEN_FIELD = "refresh_token"; + private static final String ACCESS_TOKEN_EXPIRY_FIELD = "access_token_expiry"; + private static final String EXPIRES_IN_FIELD = "expires_in"; + private static final String CLIENT_ID_FIELD = "client_id"; + private static final String LOGIN_FIELD = "login"; + + private final ReentrantLock lock = new ReentrantLock(); + + private volatile String accessToken; + private volatile String refreshToken; + private volatile Instant tokenExpiry; + + private final Path credsFilePath; + + /** + * Creates an instance of {@link HcpVaultCredentialsFileAuthenticator} to handle authentication + * via the HCP CLI credentials cache file. + * + * @param credentialsFilePath The path to the credentials file. + */ + public HcpVaultCredentialsFileAuthenticator(String credentialsFilePath) { + this.credsFilePath = Paths.get(credentialsFilePath); + } + + /** + * Retrieves a valid access token, refreshing it if expired. + * + * @return A valid access token. + * @throws IOException if authentication fails. + */ + public String getValidAccessToken() throws Exception { + lock.lock(); + try { + if (accessToken == null || isTokenExpired()) { + loadCredentials(); + if (isTokenExpired()) { + refreshAccessToken(); + } + } + return accessToken; + } finally { + lock.unlock(); + } + } + + /** + * Loads credentials from the CLI cache file. + * + * @throws IOException if there is an error reading the file + */ + private void loadCredentials() throws IOException { + if (!Files.exists(credsFilePath)) { + throw new IOException("HCP Vault credentials file not found: " + credsFilePath); + } + + String content = new String(Files.readAllBytes(credsFilePath), StandardCharsets.UTF_8); + + OracleJsonObject rootObject = JsonUtil.convertJsonToOracleJsonObject(content); + if (rootObject == null) { + throw new IOException("Failed to parse credentials file: invalid JSON format"); + } + + OracleJsonObject loginObject; + try { + loginObject = rootObject.getObject(LOGIN_FIELD); + } catch (NullPointerException e) { + throw new IOException("Invalid credentials file format: missing 'login'" + + " object", e); + } + accessToken = JsonUtil.extractField(loginObject, ACCESS_TOKEN_FIELD); + refreshToken = JsonUtil.extractField(loginObject, REFRESH_TOKEN_FIELD); + + String expiryStr = JsonUtil.extractField(loginObject, ACCESS_TOKEN_EXPIRY_FIELD); + if (expiryStr != null && !expiryStr.isEmpty()) { + tokenExpiry = OffsetDateTime.parse(expiryStr, DateTimeFormatter.ISO_OFFSET_DATE_TIME).toInstant(); + } + } + + /** + * Checks if the current token is expired. + * + * @return true if the token is expired + */ + private boolean isTokenExpired() { + return tokenExpiry == null || Instant.now().isAfter(tokenExpiry); + } + + /** + * Refreshes the access token using the refresh token. + * + * @throws IOException if the refresh operation fails + */ + private void refreshAccessToken() throws Exception { + String clientId = extractClientIdFromToken(accessToken); + if (clientId == null || refreshToken == null) { + throw new IllegalStateException("Missing required parameters for token refresh."); + } + + String payload = String.format(TOKEN_REFRESH_PAYLOAD_FORMAT, GRANT_TYPE, refreshToken, clientId); + String jsonResponse = HttpUtil.sendPostRequest(TOKEN_URL, payload, CONTENT_TYPE, null + , null); + + OracleJsonObject response = JsonUtil.convertJsonToOracleJsonObject(jsonResponse); + updateTokensFromResponse(response); + updateCredsFile(); + } + + /** + * Updates tokens and expiry from the refresh response. + * + * @param response The JSON response from the refresh request + */ + private void updateTokensFromResponse(OracleJsonObject response) { + accessToken = JsonUtil.extractField(response, ACCESS_TOKEN_FIELD); + + try { + long expiresInSeconds = response.getLong(EXPIRES_IN_FIELD); + tokenExpiry = Instant.now().plusSeconds(expiresInSeconds); + } catch (NullPointerException e) { + throw new IllegalStateException("Missing '" + EXPIRES_IN_FIELD + "' field in token response", e); + } + + // Update refresh token if provided + String newRefreshToken = JsonUtil.extractField(response, REFRESH_TOKEN_FIELD); + if (newRefreshToken != null && !newRefreshToken.isEmpty()) { + refreshToken = newRefreshToken; + } + } + + /** + * Updates the credentials cache file with new token information. + * + * @throws IOException if file writing fails + */ + private void updateCredsFile() throws IOException { + String updatedContent = String.format(CREDENTIALS_JSON_FORMAT, accessToken, + refreshToken, OffsetDateTime.ofInstant(tokenExpiry, ZoneOffset.UTC).format(DateTimeFormatter.ISO_OFFSET_DATE_TIME) + ); + + Files.write(credsFilePath, updatedContent.getBytes(StandardCharsets.UTF_8)); + } + + /** + * Extracts the client ID from a JWT token. + * + * @param token The JWT token + * @return The extracted client ID + * @throws IllegalArgumentException if the token is invalid or client_id extraction fails. + */ + private static String extractClientIdFromToken(String token) { + try { + String[] parts = token.split("\\."); + if (parts.length != 3) { + throw new IllegalArgumentException("Invalid JWT token format."); + } + String payloadJson = new String(Base64.getUrlDecoder().decode(parts[1]), StandardCharsets.UTF_8); + OracleJsonObject payload = JsonUtil.convertJsonToOracleJsonObject(payloadJson); + return JsonUtil.extractField(payload, CLIENT_ID_FIELD); + } catch (Exception e) { + throw new IllegalArgumentException("Failed to extract client_id from JWT token.", e); + } + } +} diff --git a/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultsecret/authentication/HcpVaultOAuthClient.java b/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultsecret/authentication/HcpVaultOAuthClient.java new file mode 100644 index 00000000..7b1773c3 --- /dev/null +++ b/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultsecret/authentication/HcpVaultOAuthClient.java @@ -0,0 +1,93 @@ +/* + ** Copyright (c) 2025 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.hcpvaultsecret.authentication; + +import oracle.jdbc.provider.hashicorp.util.HttpUtil; +import oracle.jdbc.provider.hashicorp.util.JsonUtil; +import oracle.sql.json.OracleJsonObject; + +import static oracle.jdbc.provider.hashicorp.hcpvaultsecret.authentication.HcpVaultCredentialsFileAuthenticator.ACCESS_TOKEN_FIELD; + +/** + * A client for performing OAuth2 operations with HCP Vault Secrets. + * <p> + * This class implements the client_credentials flow to obtain an API token + * required for interacting with HCP Vault Secrets. + * </p> + */ +public final class HcpVaultOAuthClient { + + private static final String OAUTH_TOKEN_URL = "https://auth.idp.hashicorp.com/oauth/token"; + private static final String CONTENT_TYPE = "application/x-www-form-urlencoded"; + private static final String GRANT_TYPE = "client_credentials"; + private static final String AUDIENCE = "https://api.hashicorp.cloud"; + private static final String CLIENT_CREDENTIALS_PAYLOAD_FORMAT = + "grant_type=%s&client_id=%s&client_secret=%s&audience=%s"; + + private HcpVaultOAuthClient() {} + + /** + * Fetches an access token from HCP Vault Secrets using the client_credentials flow. + * + * @param clientId the OAuth2 client ID. Must not be null or empty. + * @param clientSecret the OAuth2 client secret. Must not be null or empty. + * @return the access token as a {@code String}. Never null or empty. + * @throws IllegalStateException if the token cannot be obtained. + */ + public static String fetchHcpAccessToken(String clientId, String clientSecret) { + try { + String payload = String.format( + CLIENT_CREDENTIALS_PAYLOAD_FORMAT, GRANT_TYPE, clientId, clientSecret, AUDIENCE); + + String jsonResponse = HttpUtil.sendPostRequest( + OAUTH_TOKEN_URL, + payload, + CONTENT_TYPE, + null, + null + ); + OracleJsonObject response = JsonUtil.convertJsonToOracleJsonObject(jsonResponse); + return JsonUtil.extractField(response, ACCESS_TOKEN_FIELD); + + } catch (Exception e) { + throw new IllegalStateException("Failed to fetch HCP access token", e); + } + } + +} diff --git a/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultsecret/authentication/HcpVaultSecretParameters.java b/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultsecret/authentication/HcpVaultSecretParameters.java new file mode 100644 index 00000000..80da8158 --- /dev/null +++ b/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultsecret/authentication/HcpVaultSecretParameters.java @@ -0,0 +1,205 @@ +/* + ** Copyright (c) 2025 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.hcpvaultsecret.authentication; + +import oracle.jdbc.provider.hashicorp.util.Parameterutil; +import oracle.jdbc.provider.parameter.Parameter; +import oracle.jdbc.provider.parameter.ParameterSet; +import oracle.jdbc.provider.parameter.ParameterSetParser; + +import java.util.HashMap; +import java.util.Map; + +import static oracle.jdbc.provider.hashicorp.hcpvaultsecret.authentication.HcpVaultAuthenticationMethod.AUTO_DETECT; +import static oracle.jdbc.provider.parameter.Parameter.CommonAttribute.*; + +/** + * Contains parameter definitions for interacting with HCP Vault Secrets. + * <p> + * This class centralizes configuration parameters used for authenticating + * with and retrieving secrets from HCP Vault Secrets. + * </p> + */ +public class HcpVaultSecretParameters { + + /** + * Constants representing the configuration parameter names for HCP Vault Secrets. + * <p> + * These constants serve as both parameter names within the {@link ParameterSet} + * and as keys for environment variables or system properties. + * </p> + */ + public static final String PARAM_HCP_ORG_ID = "HCP_ORG_ID"; + public static final String PARAM_HCP_PROJECT_ID = "HCP_PROJECT_ID"; + public static final String PARAM_HCP_APP_NAME = "HCP_APP_NAME"; + public static final String PARAM_HCP_CLIENT_ID = "HCP_CLIENT_ID"; + public static final String PARAM_HCP_CLIENT_SECRET = "HCP_CLIENT_SECRET"; + public static final String PARAM_HCP_CREDENTIALS_FILE = + "HCP_CREDENTIALS_FILE"; + private static final String DEFAULT_CREDENTIALS_FILE_PATH = + System.getProperty("user.home") + "/.config/hcp/creds-cache.json"; + private static final String PARAM_AUTHENTICATION = "AUTHENTICATION"; + + /** + * Parameter indicating the authentication method to use for HCP Vault Secrets. + */ + public static final Parameter<HcpVaultAuthenticationMethod> AUTHENTICATION_METHOD = Parameter.create(REQUIRED); + + /** + * Parameter for the OAuth2 client ID. Required. + */ + public static final Parameter<String> HCP_CLIENT_ID = Parameter.create(REQUIRED); + + /** + * Parameter for the OAuth2 client secret. Required. + */ + public static final Parameter<String> HCP_CLIENT_SECRET = Parameter.create(REQUIRED); + + /** + * Parameter for the credentials file path. + * By default, the credentials file is expected at: + * <code>System.getProperty("user.home") + "/.config/hcp/creds-cache.json"</code>. + */ + public static final Parameter<String> HCP_CREDENTIALS_FILE = Parameter.create(REQUIRED); + + /** + * Parameter for the organization ID. Required. + */ + public static final Parameter<String> HCP_ORG_ID = Parameter.create(REQUIRED); + + /** + * Parameter for the project ID. Required. + */ + public static final Parameter<String> HCP_PROJECT_ID = Parameter.create(REQUIRED); + + /** + * Parameter for the application name. Required. + */ + public static final Parameter<String> HCP_APP_NAME = Parameter.create(REQUIRED); + + /** + * Parameter for the secret name. Required. + */ + public static final Parameter<String> SECRET_NAME = Parameter.create(REQUIRED); + + /** + * Parameter for the optional key in the secret JSON. + */ + public static final Parameter<String> KEY = Parameter.create(); + + /** + * Builds a ParameterSet from the given options map. + * <p> + * This method makes a defensive copy of the provided map, ensures that a default + * authentication method is set, and then fills in missing keys using fallback values + * (from system properties or environment variables) based on the authentication method. + * Finally, it parses the updated map into a ParameterSet. + * </p> + * + * @param inputOpts The input options map. + * @return The ParameterSet. + */ + public static ParameterSet buildResolvedParameterSet(Map<String, String> inputOpts) { + Map<String, String> opts = new HashMap<>(inputOpts); + + String authStr = opts.entrySet().stream() + .filter(entry -> entry.getKey().equalsIgnoreCase(PARAM_AUTHENTICATION)) + .map(Map.Entry::getValue) + .findFirst() + .orElse(HcpVaultAuthenticationMethod.AUTO_DETECT.name()); + + HcpVaultAuthenticationMethod authMethod = + HcpVaultAuthenticationMethod.valueOf(authStr.toUpperCase()); + + opts.computeIfAbsent(PARAM_HCP_ORG_ID, Parameterutil::getFallback); + opts.computeIfAbsent(PARAM_HCP_PROJECT_ID, Parameterutil::getFallback); + opts.computeIfAbsent(PARAM_HCP_APP_NAME, Parameterutil::getFallback); + + switch (authMethod) { + case CLIENT_CREDENTIALS: + opts.computeIfAbsent(PARAM_HCP_CLIENT_ID, Parameterutil::getFallback); + opts.computeIfAbsent(PARAM_HCP_CLIENT_SECRET, Parameterutil::getFallback); + break; + case CLI_CREDENTIALS_FILE: + opts.computeIfAbsent(PARAM_HCP_CREDENTIALS_FILE, Parameterutil::getFallback); + break; + case AUTO_DETECT: + opts.computeIfAbsent(PARAM_HCP_CLIENT_ID, Parameterutil::getFallback); + opts.computeIfAbsent(PARAM_HCP_CLIENT_SECRET, Parameterutil::getFallback); + opts.computeIfAbsent(PARAM_HCP_CREDENTIALS_FILE, Parameterutil::getFallback); + break; + default: + break; + } + return PARAMETER_SET_PARSER.parseNamedValues(opts); + } + + /** + * Parses the authentication method from a string value. + * + * @param value the string value representing the authentication method. Must + * not be null. + * @return the parsed {@link HcpVaultAuthenticationMethod}. + * @throws IllegalArgumentException if the value is unrecognized. + */ + private static HcpVaultAuthenticationMethod parseAuthMethod(String value) { + try { + return HcpVaultAuthenticationMethod.valueOf(value.toUpperCase()); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException( + "Unrecognized HCP auth method: " + value, e); + } + } + + public static final ParameterSetParser PARAMETER_SET_PARSER = + ParameterSetParser.builder() + .addParameter("value", SECRET_NAME) + .addParameter(PARAM_AUTHENTICATION, AUTHENTICATION_METHOD, AUTO_DETECT, + HcpVaultSecretParameters::parseAuthMethod) + .addParameter(PARAM_HCP_ORG_ID, HCP_ORG_ID) + .addParameter(PARAM_HCP_PROJECT_ID, HCP_PROJECT_ID) + .addParameter(PARAM_HCP_APP_NAME, HCP_APP_NAME) + .addParameter(PARAM_HCP_CLIENT_ID, HCP_CLIENT_ID) + .addParameter(PARAM_HCP_CLIENT_SECRET, HCP_CLIENT_SECRET) + .addParameter(PARAM_HCP_CREDENTIALS_FILE, HCP_CREDENTIALS_FILE, DEFAULT_CREDENTIALS_FILE_PATH) + .addParameter("KEY", KEY) + .addParameter("type", Parameter.create()) + .build(); + +} diff --git a/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultsecret/authentication/HcpVaultSecretToken.java b/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultsecret/authentication/HcpVaultSecretToken.java new file mode 100644 index 00000000..14a2d677 --- /dev/null +++ b/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultsecret/authentication/HcpVaultSecretToken.java @@ -0,0 +1,73 @@ +/* + ** Copyright (c) 2025 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.hcpvaultsecret.authentication; + +/** + * Represents the credentials required to authenticate with HCP Vault Secrets. + * <p> + * This class holds the API token obtained from the client_credentials OAuth2 flow. + * </p> + */ +public final class HcpVaultSecretToken { + private final String hcpApiToken; + + /** + * Constructs a new {@code HcpVaultSecretToken} object with + * the provided API token. + * + * @param hcpApiToken the token used to authenticate API requests to + * the HCP Vault Secret. Must not be null or empty. + * @throws IllegalArgumentException if {@code hcpApiToken} is null or empty. + */ + public HcpVaultSecretToken(String hcpApiToken) { + if (hcpApiToken == null || hcpApiToken.isEmpty()) { + throw new IllegalArgumentException("HCP API token must not be null or empty."); + } + this.hcpApiToken = hcpApiToken; + } + + /** + * Returns the HCP API token used for authentication. + * + * @return the HCP API token as a {@link String}. + */ + public String getHcpApiToken() { + return hcpApiToken; + } +} diff --git a/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultsecret/authentication/HcpVaultTokenFactory.java b/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultsecret/authentication/HcpVaultTokenFactory.java new file mode 100644 index 00000000..05bfdfb1 --- /dev/null +++ b/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultsecret/authentication/HcpVaultTokenFactory.java @@ -0,0 +1,135 @@ +/* + ** Copyright (c) 2025 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.hcpvaultsecret.authentication; + +import oracle.jdbc.AccessToken; +import oracle.jdbc.driver.oauth.JsonWebToken; +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 java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Supplier; + +import static oracle.jdbc.provider.hashicorp.hcpvaultsecret.authentication.HcpVaultSecretParameters.AUTHENTICATION_METHOD; +import static oracle.jdbc.provider.parameter.Parameter.CommonAttribute.REQUIRED; +/** + * A factory for creating {@link HcpVaultSecretToken} objects for HCP Vault Secrets. + * <p> + * Implements the client_credentials flow as well as file-based authentication. + * The auto-detect mode attempts file-based authentication first, then falls back + * to client credentials. + * </p> + */ +public final class HcpVaultTokenFactory implements ResourceFactory<HcpVaultSecretToken> { + + private static final HcpVaultTokenFactory INSTANCE = new HcpVaultTokenFactory(); + + private static final ConcurrentHashMap<Map<String, Object>, Supplier<? extends AccessToken>> tokenCache = + new ConcurrentHashMap<>(); + + private HcpVaultTokenFactory() {} + + public static HcpVaultTokenFactory getInstance() { + return INSTANCE; + } + + @Override + public Resource<HcpVaultSecretToken> request(ParameterSet parameterSet) { + HcpVaultSecretToken credentials = getCredential(parameterSet); + return Resource.createPermanentResource(credentials, true); + } + + /** + * Determines the authentication method and retrieves credentials accordingly. + * + * @param parameterSet The parameter set containing authentication details. + * @return The HCP Vault secret token. + */ + private HcpVaultSecretToken getCredential(ParameterSet parameterSet) { + HcpVaultAuthenticationMethod method = parameterSet.getRequired(AUTHENTICATION_METHOD); + AbstractHcpVaultAuthentication authentication = getAuthentication(method); + return createCachedToken(parameterSet, authentication); + } + + /** + * Creates or retrieves a cached {@link HcpVaultSecretToken} for the specified + * authentication method. + * + * @param parameterSet the set of parameters for the request. + * @param authentication the authentication method being used. + * @return a {@code HcpVaultSecretToken} instance. + */ + private HcpVaultSecretToken createCachedToken( + ParameterSet parameterSet, AbstractHcpVaultAuthentication authentication) { + + Map<String, Object> cacheKey = authentication.generateCacheKey(parameterSet); + + Supplier<? extends AccessToken> tokenSupplier = tokenCache.computeIfAbsent(cacheKey, k -> AccessToken.createJsonWebTokenCache(() -> { + HcpVaultSecretToken token = authentication.generateToken(parameterSet); + return AccessToken.createJsonWebToken(token.getHcpApiToken().toCharArray()); + })); + + AccessToken cachedToken = tokenSupplier.get(); + JsonWebToken jwt = (JsonWebToken) cachedToken; + return new HcpVaultSecretToken(jwt.token().get()); + } + + /** + * Returns the appropriate authentication strategy for the specified method. + * + * @param method the authentication method + * @return the corresponding {@link AbstractHcpVaultAuthentication} instance + */ + private AbstractHcpVaultAuthentication getAuthentication(HcpVaultAuthenticationMethod method) { + switch (method) { + case CLIENT_CREDENTIALS: + return ClientCredentialsAuthentication.INSTANCE; + case CLI_CREDENTIALS_FILE: + return CliCredentialsFileAuthentication.INSTANCE; + case AUTO_DETECT: + return AutoDetectAuthentication.INSTANCE; + default: + throw new IllegalArgumentException("Unsupported authentication method: " + method); + } + } + +} diff --git a/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultsecret/configuration/HcpVaultJsonVaultProvider.java b/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultsecret/configuration/HcpVaultJsonVaultProvider.java new file mode 100644 index 00000000..af21af4c --- /dev/null +++ b/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultsecret/configuration/HcpVaultJsonVaultProvider.java @@ -0,0 +1,95 @@ +/* + ** Copyright (c) 2025 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.hcpvaultsecret.configuration; + +import oracle.jdbc.provider.hashicorp.hcpvaultsecret.secrets.HcpVaultSecretsManagerFactory; +import oracle.jdbc.provider.parameter.ParameterSet; +import oracle.jdbc.spi.OracleConfigurationSecretProvider; + +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.Map; + +import static oracle.jdbc.provider.hashicorp.hcpvaultsecret.authentication.HcpVaultSecretParameters.PARAMETER_SET_PARSER; +/** + * <p> + * Implementation of {@link OracleConfigurationSecretProvider} for + * HCP Vault Secret. This provider retrieves secrets stored in the HCP Vault + * Secret using + * the specified {@code SECRET_NAME}. + * </p> + * <p> + * The {@code jsonObject} must adhere to the following structure: + * </p> + * + * <pre>{@code + * "password": { + * "type": "hcpvaultsecret", + * "value": "<secret-name>", + * } + * }</pre> + * + * <p> + * The {@code SECRET_NAME} the specific secret to retrieve from hcp vault. + * The secret's value is retrieved and returned as a Base64-encoded + * character array. + * </p> + */ +public class HcpVaultJsonVaultProvider implements OracleConfigurationSecretProvider { + + + @Override + public char[] getSecret(Map<String, String> map) { + ParameterSet parameterSet = PARAMETER_SET_PARSER.parseNamedValues(map); + + String secretString = HcpVaultSecretsManagerFactory + .getInstance() + .request(parameterSet) + .getContent(); + + String base64Encoded = Base64.getEncoder() + .encodeToString(secretString.getBytes(StandardCharsets.UTF_8)); + return base64Encoded.toCharArray(); + } + + @Override + public String getSecretType() { + return "hcpvaultsecret"; + } +} diff --git a/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultsecret/configuration/HcpVaultSecretsManagerConfigurationProvider.java b/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultsecret/configuration/HcpVaultSecretsManagerConfigurationProvider.java new file mode 100644 index 00000000..164c8487 --- /dev/null +++ b/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultsecret/configuration/HcpVaultSecretsManagerConfigurationProvider.java @@ -0,0 +1,88 @@ +/* + ** Copyright (c) 2025 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.hcpvaultsecret.configuration; + +import oracle.jdbc.driver.configuration.OracleConfigurationParsableProvider; +import oracle.jdbc.provider.hashicorp.hcpvaultsecret.secrets.HcpVaultSecretsManagerFactory; +import oracle.jdbc.provider.parameter.ParameterSet; +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; + +import static oracle.jdbc.provider.hashicorp.hcpvaultsecret.authentication.HcpVaultSecretParameters.*; + + +public class HcpVaultSecretsManagerConfigurationProvider extends OracleConfigurationParsableProvider { + + @Override + public InputStream getInputStream(String secretName) { + final String valueField = "value"; + Map<String, String> optionsWithAppName = new HashMap<>(options); + optionsWithAppName.put(valueField, secretName); + + ParameterSet finalParams = buildResolvedParameterSet(optionsWithAppName); + + String secretsJson = HcpVaultSecretsManagerFactory + .getInstance() + .request(finalParams) + .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 "hcpvaultsecret"; + } + + @Override + public OracleConfigurationCache getCache() { + return CACHE; + } + + @Override + public String getParserType(String location) { + return "json"; + } +} diff --git a/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultsecret/secrets/HcpVaultApiClient.java b/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultsecret/secrets/HcpVaultApiClient.java new file mode 100644 index 00000000..330c5526 --- /dev/null +++ b/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultsecret/secrets/HcpVaultApiClient.java @@ -0,0 +1,101 @@ +/* + ** Copyright (c) 2025 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.hcpvaultsecret.secrets; + +import oracle.jdbc.provider.hashicorp.util.HttpUtil; +import oracle.sql.json.OracleJsonException; +import oracle.sql.json.OracleJsonObject; + +import static oracle.jdbc.provider.hashicorp.util.JsonUtil.convertJsonToOracleJsonObject; + +/** + * <p> + * Utility class for interacting with the HCP Vault Secrets API. Provides + * methods to fetch secrets and extract specific fields from JSON responses. + * </p> + * <p> + * This class is responsible for making HTTP requests to the HCP Vault API + * and parsing the JSON responses using the Oracle JSON library. + * </p> + */ +public final class HcpVaultApiClient { + + private static final String SECRET_FIELD = "secret"; + private static final String STATIC_VERSION_FIELD = "static_version"; + private static final String VALUE_FIELD = "value"; + + private HcpVaultApiClient() { + } + + /** + * Fetches the secret value from the HCP Vault Secrets API. + * <p> + * The API response contains metadata along with the secret. The expected format is: + * <pre> + * { + * "secret": { + * "static_version": { + * "value": "OUR_SECRET" + * } + * } + * } + * </pre> + * This method extracts and returns the `value` field. + * + * @param urlStr The HCP Vault API endpoint. + * @param token The Bearer token for authentication. + * @return The extracted secret value. Never null. + * @throws IllegalStateException If the request fails or JSON is invalid. + */ + public static String fetchSecret(String urlStr, String token) { + try { + String jsonResponse = HttpUtil.sendGetRequest(urlStr, token, null); + OracleJsonObject jsonObject = convertJsonToOracleJsonObject(jsonResponse); + + return jsonObject.getObject(SECRET_FIELD) + .getObject(STATIC_VERSION_FIELD) + .getString(VALUE_FIELD); + + } catch (OracleJsonException e) { + throw new IllegalStateException("Invalid JSON structure or missing fields in response", e); + } catch (Exception e) { + throw new IllegalStateException("Failed to fetch HCP secrets from URL: " + urlStr, e); + } + } +} diff --git a/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultsecret/secrets/HcpVaultSecretsManagerFactory.java b/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultsecret/secrets/HcpVaultSecretsManagerFactory.java new file mode 100644 index 00000000..3348a328 --- /dev/null +++ b/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultsecret/secrets/HcpVaultSecretsManagerFactory.java @@ -0,0 +1,99 @@ +/* + ** Copyright (c) 2025 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.hcpvaultsecret.secrets; + +import oracle.jdbc.provider.cache.CachedResourceFactory; +import oracle.jdbc.provider.factory.Resource; +import oracle.jdbc.provider.factory.ResourceFactory; +import oracle.jdbc.provider.hashicorp.hcpvaultsecret.HcpVaultResourceFactory; +import oracle.jdbc.provider.hashicorp.hcpvaultsecret.authentication.HcpVaultSecretToken; +import oracle.jdbc.provider.parameter.ParameterSet; + +import static oracle.jdbc.provider.hashicorp.hcpvaultsecret.authentication.HcpVaultSecretParameters.*; + +/** + * <p> + * Factory for retrieving secrets from (HCP) Vault Secrets. + * Responsible for making API calls to the HCP Vault and parsing responses. + * </p> + * <p> + * The secrets API URL structure follows the format: + * </p> + * <pre> + * {@code + * https://api.cloud.hashicorp.com/secrets/2023-11-28/organizations/{ORG_ID}/projects/{PROJECT_ID}/apps/{APP_NAME}/secrets/{SECRET_NAME}:open + * } + * </pre> + * <p> + * For more details, refer to the official HCP Vault Secrets API documentation: + * <a href="https://developer.hashicorp.com/hcp/tutorials/get-started-hcp-vault-secrets/hcp-vault-secrets-retrieve-secret"> + * Retrieve a Secret from HCP Vault Secrets + * </a> + * </p> + */ +public final class HcpVaultSecretsManagerFactory extends HcpVaultResourceFactory<String> { + + private static final String HCP_SECRETS_API_URL_FORMAT = + "https://api.cloud.hashicorp.com/secrets/2023-11-28/organizations/%s/projects/%s/apps/%s/secrets/%s:open"; + + + private static final ResourceFactory<String> INSTANCE = + CachedResourceFactory.create(new HcpVaultSecretsManagerFactory()); + + private HcpVaultSecretsManagerFactory() {} + + public static ResourceFactory<String> getInstance() { + return INSTANCE; + } + + @Override + public Resource<String> request(HcpVaultSecretToken credentials, ParameterSet parameterSet) { + String orgId = parameterSet.getRequired(HCP_ORG_ID); + String projectId = parameterSet.getRequired(HCP_PROJECT_ID); + String appName = parameterSet.getRequired(HCP_APP_NAME); + String secretName = parameterSet.getRequired(SECRET_NAME); + + String hcpUrl = String.format(HCP_SECRETS_API_URL_FORMAT, orgId, projectId, appName, secretName); + + String secretsJson = HcpVaultApiClient.fetchSecret(hcpUrl, credentials.getHcpApiToken()); + + return Resource.createPermanentResource(secretsJson, true); + } + +} diff --git a/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/util/HttpUtil.java b/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/util/HttpUtil.java new file mode 100644 index 00000000..b247aff9 --- /dev/null +++ b/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/util/HttpUtil.java @@ -0,0 +1,217 @@ +/* + ** Copyright (c) 2025 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.util; + +import java.io.*; +import java.net.HttpURLConnection; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.stream.Collectors; + +/** + * Utility class for handling HTTP requests and responses. + */ +public class HttpUtil { + // HTTP methods + private static final String HTTP_METHOD_GET = "GET"; + private static final String HTTP_METHOD_POST = "POST"; + + // HTTP headers + private static final String HEADER_ACCEPT = "Accept"; + private static final String HEADER_AUTHORIZATION = "Authorization"; + private static final String HEADER_CONTENT_TYPE = "Content-Type"; + private static final String HEADER_VAULT_NAMESPACE = "X-Vault-Namespace"; + private static final String ACCEPT_JSON = "application/json"; + + /** + * Sends a GET request to the specified URL and retrieves the response. + * + * @param urlStr The URL to send the request to. Must not be null. + * @param authToken The optional Bearer token for authorization. Can be null or empty. + * @param namespace The optional Vault namespace. Can be null or empty. + * @return The response as a UTF-8 encoded string. Never null. + * @throws Exception if the request fails or the response cannot be read. + */ + public static String sendGetRequest(String urlStr, String authToken, String namespace) + throws Exception { + if (urlStr == null) { + throw new IllegalArgumentException("URL must not be null"); + } + + HttpURLConnection conn = createConnection(urlStr, HTTP_METHOD_GET, authToken, namespace); + try { + return sendGetRequestAndGetResponse(conn); + } finally { + conn.disconnect(); + } + } + + /** + * Sends a POST request with a payload to the specified URL and retrieves the response. + * + * @param urlStr The URL to send the request to. Must not be null. + * @param payload The payload to send in UTF-8 encoding. Must not be null. + * @param contentType The content type for the request payload (e.g., "application/json"). + * @param authToken The optional Bearer token for authorization. Can be null or empty. + * @param namespace The optional Vault namespace. Can be null or empty. + * @return The response as a UTF-8 encoded string. Never null. + * @throws Exception if the request fails or the response cannot be read. + */ + public static String sendPostRequest(String urlStr, String payload, String contentType, + String authToken, String namespace) throws Exception { + if (urlStr == null) { + throw new IllegalArgumentException("URL must not be null"); + } + if (payload == null) { + throw new IllegalArgumentException("Payload must not be null"); + } + + HttpURLConnection conn = createConnection(urlStr, HTTP_METHOD_POST, authToken, + namespace); + try { + return sendPayloadAndGetResponse(conn, payload, contentType); + } finally { + conn.disconnect(); + } + } + + /** + * Creates an HTTP connection to the specified URL with the given settings. + * + * @param urlStr The URL to connect to. Must not be null. + * @param method The HTTP method (e.g., "GET", "POST"). Must not be null. + * @param authToken The optional Bearer token for authorization. Can be null or empty. + * @param namespace The optional Vault namespace. Can be null or empty. + * @return A configured {@link HttpURLConnection}. Never null. + * @throws IOException if an I/O error occurs while creating the connection + * @throws IllegalArgumentException if urlStr or method is null + * such as network failures, invalid configurations or authentication issues + */ + public static HttpURLConnection createConnection(String urlStr, String method, String authToken, String namespace) + throws IOException { + if (method == null) { + throw new IllegalArgumentException("HTTP method must not be null"); + } + URL url = new URL(urlStr); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + conn.setRequestMethod(method); + conn.setRequestProperty(HEADER_ACCEPT, ACCEPT_JSON); + if (authToken != null && !authToken.isEmpty()) { + conn.setRequestProperty(HEADER_AUTHORIZATION, "Bearer " + authToken); + } + if (namespace != null && !namespace.isEmpty()) { + conn.setRequestProperty(HEADER_VAULT_NAMESPACE, namespace); + } + conn.setDoOutput(true); + return conn; + } + + /** + * Reads the full response from an InputStream as a String. + * + * @param in the input stream to read. + * @return the response string. + */ + private static String readResponse(InputStream in) { + try (BufferedReader reader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8))) { + return reader.lines().collect(Collectors.joining("\n")); + } catch (Exception e) { + throw new IllegalStateException("Failed to read HTTP response", e); + } + } + + /** + * Sends a payload via the provided {@link HttpURLConnection} and retrieves + * the response as a string. + * + * @param conn The configured HTTP connection. Must not be null. + * @param payload The payload to send in UTF-8 encoding. Must not be null. + * @param contentType The content type for the request payload (e.g., "application/json"). + * @return The response as a UTF-8 encoded string. Never null. + * @throws Exception if the request fails or the response cannot be read. + */ + public static String sendPayloadAndGetResponse(HttpURLConnection conn, String payload, String contentType) throws Exception { + if (contentType != null && !contentType.isEmpty()) { + conn.setRequestProperty(HEADER_CONTENT_TYPE, contentType); + } + try (OutputStream os = conn.getOutputStream()) { + os.write(payload.getBytes(StandardCharsets.UTF_8)); + } + handleErrorResponse(conn); + return readResponse(conn.getInputStream()); + } + + /** + * Sends a GET request via the provided {@link HttpURLConnection} and + * retrieves the response as a string. + * + * @param conn The configured HTTP connection. Must not be null. + * @return The response as a string. Never null. + * @throws Exception if the request fails or the response cannot be read. + */ + public static String sendGetRequestAndGetResponse(HttpURLConnection conn) throws Exception { + handleErrorResponse(conn); + return readResponse(conn.getInputStream()); + } + + /** + * Handles HTTP error responses. + * + * @param conn The HTTP connection. Must not be null. + * @throws IllegalStateException if the response indicates an error. + */ + private static void handleErrorResponse(HttpURLConnection conn) { + try { + int responseCode = conn.getResponseCode(); + if (responseCode != HttpURLConnection.HTTP_OK) { + String errorResponse = ""; + try { + errorResponse = readResponse(conn.getErrorStream()); + } catch (Exception ignore) { } + String errorMessage = String.format("HTTP request failed with status code %d. " + + "Please verify any provided parameters. Error Response:" + + " %s ", responseCode, errorResponse); + throw new IllegalStateException(errorMessage); + } + } catch (Exception e) { + throw new IllegalStateException("Failed to process HTTP response", e); + } + } + +} diff --git a/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/util/JsonUtil.java b/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/util/JsonUtil.java new file mode 100644 index 00000000..8c12dd5d --- /dev/null +++ b/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/util/JsonUtil.java @@ -0,0 +1,83 @@ +/* + ** Copyright (c) 2025 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.util; + +import oracle.sql.json.OracleJsonFactory; +import oracle.sql.json.OracleJsonObject; + +import java.io.ByteArrayInputStream; +import java.nio.charset.StandardCharsets; + +/** + * Utility class for JSON parsing and extraction using Oracle JSON library. + */ +public class JsonUtil { + + /** + * Converts a JSON string into an {@link OracleJsonObject}. + * + * @param jsonResponse the JSON response string to convert. Must not be null. + * @return the corresponding {@link OracleJsonObject}. Never null. + * @throws IllegalStateException if conversion fails due to invalid JSON. + */ + public static OracleJsonObject convertJsonToOracleJsonObject(String jsonResponse) { + try { + return new OracleJsonFactory() + .createJsonTextValue(new ByteArrayInputStream(jsonResponse.getBytes(StandardCharsets.UTF_8))) + .asJsonObject(); + } catch (Exception e) { + throw new IllegalStateException("Failed to convert JSON string to OracleJsonObject", e); + } + } + + /** + * Extracts a specific field from an {@link OracleJsonObject}. + * + * @param jsonObject The JSON object containing the field. Must not be null. + * @param fieldName The name of the field to extract. Must not be null. + * @return The value of the field as a string. Never null. + * @throws IllegalStateException if the field does not exist or is not a string. + */ + public static String extractField(OracleJsonObject jsonObject, String fieldName) { + if (jsonObject.containsKey(fieldName)) { + return jsonObject.getString(fieldName); + } + throw new IllegalStateException("Missing field '" + fieldName + "' in the response JSON."); + } +} \ No newline at end of file diff --git a/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/util/Parameterutil.java b/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/util/Parameterutil.java new file mode 100644 index 00000000..f0d55af6 --- /dev/null +++ b/ojdbc-provider-hashicorp/src/main/java/oracle/jdbc/provider/hashicorp/util/Parameterutil.java @@ -0,0 +1,60 @@ +/* + ** Copyright (c) 2025 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.util; + +/** + * Utility class for parameter resolution. + */ +public final class Parameterutil { + + private Parameterutil() { + // Prevent instantiation. + } + + /** + * Returns the fallback value for the given key by checking system properties first, + * then environment variables. + * + * @param key the key to look up + * @return the fallback value, or null if not found + */ + public static String getFallback(String key) { + return System.getProperty(key, System.getenv(key)); + } +} diff --git a/ojdbc-provider-hashicorp/src/main/resources/META-INF/services/oracle.jdbc.spi.OracleConfigurationProvider b/ojdbc-provider-hashicorp/src/main/resources/META-INF/services/oracle.jdbc.spi.OracleConfigurationProvider new file mode 100644 index 00000000..489b2cdd --- /dev/null +++ b/ojdbc-provider-hashicorp/src/main/resources/META-INF/services/oracle.jdbc.spi.OracleConfigurationProvider @@ -0,0 +1,2 @@ +oracle.jdbc.provider.hashicorp.hcpvaultdedicated.configuration.DedicatedVaultSecretsManagerConfigurationProvider +oracle.jdbc.provider.hashicorp.hcpvaultsecret.configuration.HcpVaultSecretsManagerConfigurationProvider \ No newline at end of file diff --git a/ojdbc-provider-hashicorp/src/main/resources/META-INF/services/oracle.jdbc.spi.OracleConfigurationSecretProvider b/ojdbc-provider-hashicorp/src/main/resources/META-INF/services/oracle.jdbc.spi.OracleConfigurationSecretProvider new file mode 100644 index 00000000..d29219b9 --- /dev/null +++ b/ojdbc-provider-hashicorp/src/main/resources/META-INF/services/oracle.jdbc.spi.OracleConfigurationSecretProvider @@ -0,0 +1,2 @@ +oracle.jdbc.provider.hashicorp.hcpvaultdedicated.configuration.DedicatedVaultJsonSecretProvider +oracle.jdbc.provider.hashicorp.hcpvaultsecret.configuration.HcpVaultJsonVaultProvider \ No newline at end of file diff --git a/ojdbc-provider-hashicorp/src/test/java/oracle/jdbc/provider/hashicorp/hcpvaultdedicated/configuration/DedicatedVaultConfigurationProviderTest.java b/ojdbc-provider-hashicorp/src/test/java/oracle/jdbc/provider/hashicorp/hcpvaultdedicated/configuration/DedicatedVaultConfigurationProviderTest.java new file mode 100644 index 00000000..b321543c --- /dev/null +++ b/ojdbc-provider-hashicorp/src/test/java/oracle/jdbc/provider/hashicorp/hcpvaultdedicated/configuration/DedicatedVaultConfigurationProviderTest.java @@ -0,0 +1,254 @@ +/* + ** Copyright (c) 2025 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.hcpvaultdedicated.configuration; + +import oracle.jdbc.provider.TestProperties; +import oracle.jdbc.spi.OracleConfigurationProvider; +import org.junit.jupiter.api.Test; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Test class for the Dedicated Vault Configuration Provider. + */ +public class DedicatedVaultConfigurationProviderTest { + + static { + OracleConfigurationProvider.allowedProviders.add("hcpvaultdedicated"); + } + + + private static final OracleConfigurationProvider PROVIDER = + OracleConfigurationProvider.find("hcpvaultdedicated"); + + /** + * Verifies if Dedicated Vault Configuration Provider works with TOKEN-based + * authentication. + * Without the key option. + **/ + @Test + public void testTokenAuthentication() throws SQLException { + String location = + composeUrl(TestProperties.getOrAbort(DedicatedVaultTestProperty.DEDICATED_VAULT_SECRET_PATH), + "VAULT_ADDR="+TestProperties.getOrAbort(DedicatedVaultTestProperty.VAULT_ADDR), + "VAULT_TOKEN="+TestProperties.getOrAbort(DedicatedVaultTestProperty.VAULT_TOKEN), + "authentication=vault_token"); + + Properties properties = PROVIDER.getConnectionProperties(location); + + assertTrue(properties.containsKey("URL"), "Contains property URL"); + assertTrue(properties.containsKey("user"), "Contains property user"); + assertTrue(properties.containsKey("password"), "Contains property password"); + } + + + /** + * Verifies if Dedicated Vault Configuration Provider works with TOKEN-based + * authentication. + * With the key option. + */ + @Test + public void testTokenAuthenticationWithKeyOption() throws SQLException { + String location = + composeUrl(TestProperties.getOrAbort(DedicatedVaultTestProperty.DEDICATED_VAULT_SECRET_PATH_WITH_MULTIPLE_KEYS), + "key="+TestProperties.getOrAbort(DedicatedVaultTestProperty.KEY), + "VAULT_ADDR="+TestProperties.getOrAbort(DedicatedVaultTestProperty.VAULT_ADDR), + "VAULT_TOKEN="+TestProperties.getOrAbort(DedicatedVaultTestProperty.VAULT_TOKEN), + "authentication=vault_token"); + + + Properties properties = PROVIDER.getConnectionProperties(location); + + assertTrue(properties.containsKey("URL"), "Contains property URL"); + assertTrue(properties.containsKey("user"), "Contains property user"); + assertTrue(properties.containsKey("password"), "Contains property password"); + } + + /** + * Verifies if Dedicated Vault Configuration Provider works with TOKEN-based + * authentication. + * Without the key option. + */ + @Test + public void testUserPassAuthenticationWithoutKeyOption() throws SQLException { + String location = + composeUrl(TestProperties.getOrAbort(DedicatedVaultTestProperty.DEDICATED_VAULT_SECRET_PATH), + "VAULT_ADDR="+TestProperties.getOrAbort(DedicatedVaultTestProperty.VAULT_ADDR), + "VAULT_USERNAME="+TestProperties.getOrAbort(DedicatedVaultTestProperty.VAULT_USERNAME), + "VAULT_PASSWORD="+TestProperties.getOrAbort(DedicatedVaultTestProperty.VAULT_PASSWORD), + "VAULT_NAMESPACE="+TestProperties.getOrAbort(DedicatedVaultTestProperty.VAULT_NAMESPACE), + "authentication=userpass"); + + + Properties properties = PROVIDER.getConnectionProperties(location); + + assertTrue(properties.containsKey("URL"), "Contains property URL"); + assertTrue(properties.containsKey("user"), "Contains property user"); + assertTrue(properties.containsKey("password"), "Contains property password"); + } + + /** + * Verifies if Dedicated Vault Configuration Provider works with TOKEN-based + * authentication. + * With the key option. + */ + @Test + public void testUserPassAuthenticationWithKeyOption() throws SQLException { + String location = + composeUrl(TestProperties.getOrAbort(DedicatedVaultTestProperty.DEDICATED_VAULT_SECRET_PATH_WITH_MULTIPLE_KEYS), + "key="+TestProperties.getOrAbort(DedicatedVaultTestProperty.KEY), + "VAULT_ADDR="+TestProperties.getOrAbort(DedicatedVaultTestProperty.VAULT_ADDR), + "VAULT_USERNAME="+TestProperties.getOrAbort(DedicatedVaultTestProperty.VAULT_USERNAME), + "VAULT_PASSWORD="+TestProperties.getOrAbort(DedicatedVaultTestProperty.VAULT_PASSWORD), + "VAULT_NAMESPACE="+TestProperties.getOrAbort(DedicatedVaultTestProperty.VAULT_NAMESPACE), + "authentication=userpass"); + + Properties properties = PROVIDER.getConnectionProperties(location); + + assertTrue(properties.containsKey("URL"), "Contains property URL"); + assertTrue(properties.containsKey("user"), "Contains property user"); + assertTrue(properties.containsKey("password"), "Contains property password"); + } + + /** + * Verifies if Dedicated Vault Configuration Provider works with AppRole + * authentication and a key option for secrets with multiple keys. + */ + @Test + public void testAppRoleAuthenticationWithKeyOption() throws SQLException { + String location = composeUrl( + TestProperties.getOrAbort(DedicatedVaultTestProperty.DEDICATED_VAULT_SECRET_PATH_WITH_MULTIPLE_KEYS), + "key=" + TestProperties.getOrAbort(DedicatedVaultTestProperty.KEY), + "VAULT_ADDR=" + TestProperties.getOrAbort(DedicatedVaultTestProperty.VAULT_ADDR), + "ROLE_ID=" + TestProperties.getOrAbort(DedicatedVaultTestProperty.ROLE_ID), + "VAULT_NAMESPACE="+TestProperties.getOrAbort(DedicatedVaultTestProperty.VAULT_NAMESPACE), + "SECRET_ID=" + TestProperties.getOrAbort(DedicatedVaultTestProperty.SECRET_ID), + "authentication=approle"); + + Properties properties = PROVIDER.getConnectionProperties(location); + + assertTrue(properties.containsKey("URL"), "Contains property URL"); + assertTrue(properties.containsKey("user"), "Contains property user"); + assertTrue(properties.containsKey("password"), "Contains property password"); + } + + /** + * Verifies if Dedicated Vault Configuration Provider works with GitHub + * authentication and a key option for secrets with multiple keys. + */ + @Test + public void testGithubAuthenticationWithKeyOption() throws SQLException { + String location = composeUrl( + TestProperties.getOrAbort(DedicatedVaultTestProperty.DEDICATED_VAULT_SECRET_PATH_WITH_MULTIPLE_KEYS), + "key=" + TestProperties.getOrAbort(DedicatedVaultTestProperty.KEY), + "VAULT_ADDR=" + TestProperties.getOrAbort(DedicatedVaultTestProperty.VAULT_ADDR), + "GITHUB_TOKEN=" + TestProperties.getOrAbort(DedicatedVaultTestProperty.GITHUB_TOKEN), + "VAULT_NAMESPACE="+TestProperties.getOrAbort(DedicatedVaultTestProperty.VAULT_NAMESPACE), + "authentication=github"); + + Properties properties = PROVIDER.getConnectionProperties(location); + + assertTrue(properties.containsKey("URL"), "Contains property URL"); + assertTrue(properties.containsKey("user"), "Contains property user"); + assertTrue(properties.containsKey("password"), "Contains property password"); + } + + /** + * Verifies if Dedicated Vault Configuration Provider works with the + * AUTO-DETECT authentication and a key option for secrets with multiple keys. + */ + @Test + public void testAutoDetectAuthenticationWithKeyOption() throws SQLException { + String baseUrl = TestProperties.getOrAbort(DedicatedVaultTestProperty.DEDICATED_VAULT_SECRET_PATH_WITH_MULTIPLE_KEYS); + String key = "key=" + TestProperties.getOrAbort(DedicatedVaultTestProperty.KEY); + String vaultAddr = "VAULT_ADDR=" + TestProperties.getOrAbort(DedicatedVaultTestProperty.VAULT_ADDR); + + List<String> params = new ArrayList<>(); + params.add(vaultAddr); + params.add(key); + + // Add available authentication methods dynamically + String vaultToken = TestProperties.getOptional(DedicatedVaultTestProperty.VAULT_TOKEN); + if (vaultToken != null) { + params.add("VAULT_TOKEN=" + vaultToken); + } + + String vaultUsername = TestProperties.getOptional(DedicatedVaultTestProperty.VAULT_USERNAME); + String vaultPassword = + TestProperties.getOrAbort(DedicatedVaultTestProperty.VAULT_PASSWORD); + if (vaultUsername != null && vaultPassword != null) { + params.add("VAULT_USERNAME=" + vaultUsername); + params.add("VAULT_PASSWORD=" + vaultPassword); + } + + String roleId = TestProperties.getOptional(DedicatedVaultTestProperty.ROLE_ID); + String secretId = TestProperties.getOptional(DedicatedVaultTestProperty.SECRET_ID); + if (roleId != null && secretId != null) { + params.add("ROLE_ID=" + roleId); + params.add("SECRET_ID=" + secretId); + } + + String githubToken = TestProperties.getOptional(DedicatedVaultTestProperty.GITHUB_TOKEN); + if (githubToken != null) { + params.add("GITHUB_TOKEN=" + githubToken); + } + + params.add("authentication=auto_detect"); + + String location = composeUrl(baseUrl, params.toArray(new String[0])); + + Properties properties = PROVIDER.getConnectionProperties(location); + + assertTrue(properties.containsKey("URL"), "Contains property URL"); + assertTrue(properties.containsKey("user"), "Contains property user"); + assertTrue(properties.containsKey("password"), "Contains property password"); + } + + /** + * Composes a full URL from a base URL and query options. + */ + private static String composeUrl(String baseUrl, String... options) { + return String.format("%s?%s", baseUrl, String.join("&", options)); + } +} diff --git a/ojdbc-provider-hashicorp/src/test/java/oracle/jdbc/provider/hashicorp/hcpvaultdedicated/configuration/DedicatedVaultTestProperty.java b/ojdbc-provider-hashicorp/src/test/java/oracle/jdbc/provider/hashicorp/hcpvaultdedicated/configuration/DedicatedVaultTestProperty.java new file mode 100644 index 00000000..97212117 --- /dev/null +++ b/ojdbc-provider-hashicorp/src/test/java/oracle/jdbc/provider/hashicorp/hcpvaultdedicated/configuration/DedicatedVaultTestProperty.java @@ -0,0 +1,66 @@ +/* + ** Copyright (c) 2025 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.hcpvaultdedicated.configuration; + +/** + * Enumeration of test properties for Dedicated Vault. + */ +public enum DedicatedVaultTestProperty { + DEDICATED_VAULT_SECRET_PATH, + + DEDICATED_VAULT_SECRET_PATH_WITH_MULTIPLE_KEYS, + + KEY, + + VAULT_TOKEN, + + VAULT_ADDR, + + VAULT_USERNAME, + + VAULT_PASSWORD, + + VAULT_NAMESPACE, + + ROLE_ID, + + SECRET_ID, + + GITHUB_TOKEN +} diff --git a/ojdbc-provider-hashicorp/src/test/java/oracle/jdbc/provider/hashicorp/hcpvaultsecret/configuration/HcpVaultConfigurationProviderTest.java b/ojdbc-provider-hashicorp/src/test/java/oracle/jdbc/provider/hashicorp/hcpvaultsecret/configuration/HcpVaultConfigurationProviderTest.java new file mode 100644 index 00000000..dcee9b56 --- /dev/null +++ b/ojdbc-provider-hashicorp/src/test/java/oracle/jdbc/provider/hashicorp/hcpvaultsecret/configuration/HcpVaultConfigurationProviderTest.java @@ -0,0 +1,193 @@ +/* + ** Copyright (c) 2025 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.hcpvaultsecret.configuration; + +import oracle.jdbc.provider.TestProperties; +import oracle.jdbc.spi.OracleConfigurationProvider; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Test class for the HCP Vault Configuration Provider. + */ +public class HcpVaultConfigurationProviderTest { + + static { + OracleConfigurationProvider.allowedProviders.add("hcpvaultsecret"); + } + + private static final OracleConfigurationProvider PROVIDER = + OracleConfigurationProvider.find("hcpvaultsecret"); + + @BeforeAll + public static void setUp() { + System.setProperty("HCP_ORG_ID", + TestProperties.getOrAbort(HcpVaultTestProperty.HCP_ORG_ID)); + System.setProperty("HCP_PROJECT_ID", + TestProperties.getOrAbort(HcpVaultTestProperty.HCP_PROJECT_ID)); + System.setProperty("HCP_APP_NAME", + TestProperties.getOrAbort(HcpVaultTestProperty.HCP_APP_NAME)); + + } + + /** + * Verifies if HCP Vault Configuration Provider works with + * CLIENT_CREDENTIALS authentication. + * Without Key Option + */ + @Test + public void testClientCredentialsAuthentication() throws SQLException { + // Load parameters from TestProperties + String baseUrl = TestProperties.getOrAbort(HcpVaultTestProperty.SECRET_NAME); + String clientId = "HCP_CLIENT_ID=" + TestProperties.getOrAbort(HcpVaultTestProperty.HCP_CLIENT_ID); + String clientSecret = "HCP_CLIENT_SECRET=" + TestProperties.getOrAbort(HcpVaultTestProperty.HCP_CLIENT_SECRET); + // Compose the connection URL + String location = composeUrl(baseUrl, clientId, clientSecret); + + // Fetch properties using the provider + Properties properties = PROVIDER.getConnectionProperties(location); + + // Assert required properties + assertTrue(properties.containsKey("URL"), "Contains property URL"); + assertTrue(properties.containsKey("user"), "Contains property user"); + assertTrue(properties.containsKey("password"), "Contains property password"); + } + + /** + * Verifies if HCP Vault Configuration Provider works with + * CLIENT_CREDENTIALS authentication. + * With Key Option + */ + @Test + public void testClientCredentialsAuthenticationWithKeyOption() throws SQLException { + // Load parameters from TestProperties + String baseUrl = TestProperties.getOrAbort(HcpVaultTestProperty.SECRET_NAME_WITH_MULTIPLE_KEYS); + String clientId = "HCP_CLIENT_ID=" + TestProperties.getOrAbort(HcpVaultTestProperty.HCP_CLIENT_ID); + String clientSecret = "HCP_CLIENT_SECRET=" + TestProperties.getOrAbort(HcpVaultTestProperty.HCP_CLIENT_SECRET); + String authMethod = "authentication=CLIENT_CREDENTIALS"; + String key = "key=" + TestProperties.getOrAbort(HcpVaultTestProperty.KEY); + // Compose the connection URL + String location = composeUrl(baseUrl, clientId, clientSecret, key, authMethod); + + // Fetch properties using the provider + Properties properties = PROVIDER.getConnectionProperties(location); + + // Assert required properties + assertTrue(properties.containsKey("URL"), "Contains property URL"); + assertTrue(properties.containsKey("user"), "Contains property user"); + assertTrue(properties.containsKey("password"), "Contains property password"); + } + + + /** + * Verifies if HCP Vault Configuration Provider works with + * CLI_CREDENTIALS_FILE authentication. + * With Key Option + */ + @Test + @Disabled + public void testCLICredentialsFileAuthenticationWithKeyOption() throws SQLException { + // Load parameters from TestProperties + String baseUrl = TestProperties.getOrAbort(HcpVaultTestProperty.SECRET_NAME_WITH_MULTIPLE_KEYS); + String key = "key=" + TestProperties.getOrAbort(HcpVaultTestProperty.KEY); + String authMethod = "authentication=CLI_CREDENTIALS_FILE"; + // Compose the connection URL + String location = composeUrl(baseUrl, key, authMethod); + + // Fetch properties using the provider + Properties properties = PROVIDER.getConnectionProperties(location); + + // Assert required properties + assertTrue(properties.containsKey("URL"), "Contains property URL"); + assertTrue(properties.containsKey("user"), "Contains property user"); + assertTrue(properties.containsKey("password"), "Contains property password"); + } + + /** + * Verifies if the HCP Vault Configuration Provider works with + * AUTO_DETECT authentication (with the key option). + */ + @Test + public void testAutoDetectAuthenticationWithKeyOption() throws SQLException { + List<String> params = new ArrayList<>(); + String baseUrl = TestProperties.getOrAbort(HcpVaultTestProperty.SECRET_NAME_WITH_MULTIPLE_KEYS); + String key = "key=" + TestProperties.getOrAbort(HcpVaultTestProperty.KEY); + params.add(key); + // Construct optional authentication parameters + String clientId = + TestProperties.getOptional(HcpVaultTestProperty.HCP_CLIENT_ID); + String clientSecret = + TestProperties.getOptional(HcpVaultTestProperty.HCP_CLIENT_SECRET); + if(clientId!=null && clientSecret!=null) { + params.add("HCP_CLIENT_ID=" + clientId); + params.add("HCP_CLIENT_SECRET=" + clientSecret); + } + String credentialsFile = + TestProperties.getOptional(HcpVaultTestProperty.HCP_CREDENTIALS_FILE); + if(credentialsFile!=null) { + params.add("HCP_CREDENTIALS_FILE=" + credentialsFile); + } + + // Compose the connection URL + String location = composeUrl(baseUrl, params.toArray(new String[0])); + + // Fetch properties using the provider + Properties properties = PROVIDER.getConnectionProperties(location); + + // Assert required properties + assertTrue(properties.containsKey("URL"), "Contains property URL"); + assertTrue(properties.containsKey("user"), "Contains property user"); + assertTrue(properties.containsKey("password"), "Contains property password"); + } + + /** + * Composes a full URL from a base URL and query options. + */ + private static String composeUrl(String baseUrl, String... options) { + return String.format("%s?%s", baseUrl, String.join("&", options)); + } +} diff --git a/ojdbc-provider-hashicorp/src/test/java/oracle/jdbc/provider/hashicorp/hcpvaultsecret/configuration/HcpVaultTestProperty.java b/ojdbc-provider-hashicorp/src/test/java/oracle/jdbc/provider/hashicorp/hcpvaultsecret/configuration/HcpVaultTestProperty.java new file mode 100644 index 00000000..c69eb5de --- /dev/null +++ b/ojdbc-provider-hashicorp/src/test/java/oracle/jdbc/provider/hashicorp/hcpvaultsecret/configuration/HcpVaultTestProperty.java @@ -0,0 +1,62 @@ +/* + ** Copyright (c) 2025 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.hcpvaultsecret.configuration; + +/** + * Enumeration of test properties for HCP Vault. + */ +public enum HcpVaultTestProperty { + HCP_APP_NAME, + + HCP_ORG_ID, + + HCP_PROJECT_ID, + + HCP_CLIENT_ID, + + HCP_CLIENT_SECRET, + + SECRET_NAME, + + SECRET_NAME_WITH_MULTIPLE_KEYS, + + KEY, + + HCP_CREDENTIALS_FILE +} diff --git a/ojdbc-provider-oci/README.md b/ojdbc-provider-oci/README.md index 9ed54084..bc90350c 100644 --- a/ojdbc-provider-oci/README.md +++ b/ojdbc-provider-oci/README.md @@ -161,6 +161,8 @@ For the JSON type of provider (OCI Object Storage, HTTPS, File) the password is - `azurevault` - `base64` - `awssecretsmanager` + - `hcpvaultdedicated` + - `hcpvaultsecret` - `value` - Mandatory - Possible values: @@ -168,6 +170,8 @@ For the JSON type of provider (OCI Object Storage, HTTPS, File) the password is - Azure Key Vault URI (if azurevault) - Base64 Encoded password (if base64) - AWS resource name of the secret (if awssecretsmanager) + - Secret path (if hcpvaultdedicated) + - Secret name (if hcpvaultsecret) - `authentication` - Optional. It will apply defaults in the same way as described in [Configuring Authentication](#configuring-authentication) - Possible Values: diff --git a/ojdbc-provider-samples/pom.xml b/ojdbc-provider-samples/pom.xml index fe4ec2bc..d8bd27db 100644 --- a/ojdbc-provider-samples/pom.xml +++ b/ojdbc-provider-samples/pom.xml @@ -40,6 +40,11 @@ <artifactId>ojdbc-provider-aws</artifactId> <version>${project.parent.version}</version> </dependency> + <dependency> + <groupId>com.oracle.database.jdbc</groupId> + <artifactId>ojdbc-provider-hashicorp</artifactId> + <version>${project.parent.version}</version> + </dependency> <dependency> <groupId>com.oracle.database.security</groupId> <artifactId>oraclepki</artifactId> diff --git a/ojdbc-provider-samples/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultdedicated/configuration/SimpleVaultDedicatedJsonExample.java b/ojdbc-provider-samples/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultdedicated/configuration/SimpleVaultDedicatedJsonExample.java new file mode 100644 index 00000000..9d74fb62 --- /dev/null +++ b/ojdbc-provider-samples/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultdedicated/configuration/SimpleVaultDedicatedJsonExample.java @@ -0,0 +1,105 @@ +/* + ** Copyright (c) 2025 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.hcpvaultdedicated.configuration; + +import oracle.jdbc.datasource.impl.OracleDataSource; +import oracle.jdbc.provider.Configuration; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; + +/** + * A standalone example demonstrating how to connect to an Oracle database using + * connection properties retrieved from a dedicated HashiCorp Vault. + */ +public class SimpleVaultDedicatedJsonExample { + + /** + * The configuration parameter for the Vault secret path. + * <p> + * This parameter should be set as a JVM system property, environment variable, + * or an entry in the configuration.properties file under the key + * "DEDICATED_VAULT_SECRET_PATH". + * </p> + */ + private static final String VAULT_DEDICATED_SECRET_PATH = Configuration.getRequired( + "DEDICATED_VAULT_SECRET_PATH"); + + /** + * <p> + * Connects to a database using connection properties retrieved from the + * configured Vault secret path. + * </p> + * + * <p> + * Ensure that the dedicated provider is properly configured and the secret + * path is accessible for authentication and configuration retrieval. + * </p> + * + * @param args the command line arguments + * @throws SQLException if an error occurs during the database calls + */ + public static void main(String[] args) throws SQLException { + + // Construct a JDBC URL for the dedicated type provider + String url = "jdbc:oracle:thin:@config-hcpvaultdedicated://" + VAULT_DEDICATED_SECRET_PATH; + + // Sample default URL if not provided in arguments + if (args.length > 0) { + url = args[0]; + } + + // Configure the data source + OracleDataSource ds = new OracleDataSource(); + ds.setURL(url); + + // Standard JDBC code + try (Connection cn = ds.getConnection()) { + System.out.println("Connected to: " + cn.getMetaData().getURL()); + + Statement st = cn.createStatement(); + ResultSet rs = st.executeQuery("SELECT 'Hello, Dedicated Vault' FROM sys.dual"); + if (rs.next()) { + System.out.println(rs.getString(1)); + } + } + } +} diff --git a/ojdbc-provider-samples/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultsecret/configuration/SimpleVaultSecretsJsonExample.java b/ojdbc-provider-samples/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultsecret/configuration/SimpleVaultSecretsJsonExample.java new file mode 100644 index 00000000..4779bdfc --- /dev/null +++ b/ojdbc-provider-samples/src/main/java/oracle/jdbc/provider/hashicorp/hcpvaultsecret/configuration/SimpleVaultSecretsJsonExample.java @@ -0,0 +1,101 @@ +/* + ** Copyright (c) 2025 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.hcpvaultsecret.configuration; + +import oracle.jdbc.datasource.impl.OracleDataSource; +import oracle.jdbc.provider.Configuration; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; + +public class SimpleVaultSecretsJsonExample { + + /** + * The configuration parameter for the Vault application name. + * <p> + * This parameter should be set as a JVM system property, environment variable, + * or an entry in the configuration.properties file under the key + * "VAULT_APP_NAME". + * </p> + */ + private static final String VAULT_SECRET_NAME = Configuration.getRequired( + "VAULT_SECRET_NAME"); + + /** + * <p> + * Connects to a database using connection properties retrieved from the + * configured Vault application in HCP Vault Secrets. + * </p> + * + * <p> + * Ensure that the HCP Vault Secrets provider is properly configured, and the + * specified application name is valid and accessible. + * </p> + * + * @param args the command line arguments + * @throws SQLException if an error occurs during the database calls + */ + public static void main(String[] args) throws SQLException { + // Construct a JDBC URL for the dedicated type provider + String url = "jdbc:oracle:thin:@config-hcpvaultsecret://" + VAULT_SECRET_NAME; + + // Sample default URL if not provided in arguments + if (args.length > 0) { + url = args[0]; + } + + // Configure the data source + OracleDataSource ds = new OracleDataSource(); + ds.setURL(url); + + // Standard JDBC code + try (Connection cn = ds.getConnection()) { + System.out.println("Connected to: " + cn.getMetaData().getURL()); + + Statement st = cn.createStatement(); + ResultSet rs = st.executeQuery("SELECT 'Hello, Vault Secrets' FROM sys" + + ".dual"); + if (rs.next()) { + System.out.println(rs.getString(1)); + } + } + } +} diff --git a/pom.xml b/pom.xml index 28583708..d14264a2 100644 --- a/pom.xml +++ b/pom.xml @@ -63,6 +63,7 @@ <module>ojdbc-provider-opentelemetry</module> <module>ojdbc-provider-aws</module> <module>ojdbc-provider-jackson-oson</module> + <module>ojdbc-provider-hashicorp</module> </modules> <dependencyManagement>