Securely Store Database Connection Details in an Oracle Cloud Infrastructure Vault

This guide describes how to use the Graal Development Kit for Micronaut (GDK) to create an application that stores MySQL database connection details as secrets in an Oracle Cloud Infrastructure vault.

Instead of storing a database URL, username, or password in plain text or via environment variables, a secret manager such as Oracle Cloud Infrastructure Vault provides a convenient way to securely store and retrieve sensitive data.

This guide describes how to use a Vault to store and retrieve details for a MySQL database connection, and demonstrates how to use Micronaut® integration with the Oracle Cloud Infrastructure Vault.

Note: If this is your first experience of using an Oracle Cloud Infrastructure Vault with Micronaut, please review the Securely Store Application Secrets in an Oracle Cloud Infrastructure Vault first. Unlike that guide, this guide describes how to use the Oracle Cloud Infrastructure Command Line Interface (CLI) to set up your tenancy.

Prerequisites #

  • JDK 17 or higher. See Setting up Your Desktop.
  • An Oracle Cloud Infrastructure account. See Setting up Your Cloud Accounts.
  • The Oracle Cloud Infrastructure CLI installed with local access configured.
  • An Oracle Cloud Infrastructure compartment with appropriate permission granted to your Oracle Cloud Infrastructure user account to manage secret-family, key-family, and to deploy and manage a database in the compartment.
  • A Virtual Cloud Network (VCN) in Oracle Cloud Infrastructure with a single regional public subnet, which will be used by both Compute and MySQL Database instances.
  • jq: a lightweight and flexible command-line JSON processor.
  • A Docker-API compatible container runtime such as Rancher Desktop or Docker installed to build a deployable GraalVM Native Executable.
  • The GDK CLI. See Setting up Your Desktop. (Optional.)

Follow the steps below to create the application from scratch. However, you can also download the completed example.

A note regarding your development environment

Consider using Visual Studio Code, which provides native support for developing applications with the Graal Development Kit extension.

Note: If you use IntelliJ IDEA, enable annotation processing.

Windows platform: The GDK guides are compatible with Gradle only. Maven support is coming soon.

1. Create the Application #

Create an application using the GDK Launcher.

  1. Open the GDK Launcher in advanced mode.

  2. Create a new project using the following selections.
    • Project Type: Application (Default)
    • Project Name: oci-secrets-demo
    • Base Package: com.example (Default)
    • Clouds: OCI
    • Build Tool: Gradle (Groovy) or Maven
    • Language: Java (Default)
    • Test Framework: JUnit (Default)
    • Java Version: 17 (Default)
    • Micronaut Version: (Default)
    • Cloud Services: Database and Secret Management
    • Features: GraalVM Native Image and MySQL
    • Sample Code: Yes (Default)
  3. Click Generate Project, then click Download Zip. The GDK Launcher creates an application with the default package com.example in a directory named oci-secrets-demo. The application ZIP file will be downloaded to your default downloads directory. Unzip it, open it in your code editor, and proceed to the next steps.

Alternatively, use the GDK CLI as follows:

gdk create-app com.example.oci-secrets-demo \
    --clouds=oci \
    --services=database,secretmanagement \
    --features=mysql,graalvm \
    --build=gradle \
    --jdk=17 \
    --lang=java
gdk create-app com.example.oci-secrets-demo \
    --clouds=oci \
    --services=database,secretmanagement \
    --features=mysql,graalvm \
    --build=maven \
    --jdk=17 \
    --lang=java

For more information, see Using the GDK CLI.

2. Create an Oracle MySQL Database Instance #

Create a MySQL database using the Create and Connect a Micronaut Application to an Oracle Cloud Infrastructure MySQL Database guide. Follow the steps in 2 (Create an Oracle MySQL Database Instance).

3. Create a Compute Instance and Configure the Database #

There is no direct way to externally connect to a MySQL database in Oracle Cloud Infrastructure, so you need to create a Compute instance (virtual machine) in Oracle Cloud Infrastructure.

Create a Compute instance using the Create and Connect a Micronaut Application to an Oracle Cloud Infrastructure MySQL Database guide. Follow the steps in section 3 (Create a Compute Instance).

Note: Use the same subnet as the one containing the MySQL database (otherwise the application will not be able to access the database).

Configure your database by following the steps in section 4 (Configure a MySQL Database).

4. Create an Oracle Cloud Infrastructure Vault, Master Encryption Key, and Secrets #

Before you can create a secret, you require a Vault (to hold the secret) and a master encryption key (to secure the secret).

Note: In this guide, you create all the Oracle Cloud Infrastructure resources in the same compartment. However, the best practice is to have a one Vault in a compartment (managed by an administrator)—application owners manage their application secrets in their individual compartments.

4.1. Create a Vault #

Create a Vault using the Oracle Cloud Infrastructure CLI.

The create command requires three parameters: compartment OCID, display name, and Vault type. (See Key Management for more information.)

Use the same compartment as the one you used for your database, above. You should have saved the compartment OCID as the value of the environment variable $C.

Choose a display name (1-100 characters) and a Vault type, either DEFAULT or VIRTUAL_PRIVATE.

Note: Choose DEFAULT to avoid the cost of a virtual private Vault.

Run the create command, substituting the display name and Vault type, as follows:

oci kms management vault create -c $C \
    --display-name gdk_guide_vault \
    --vault-type DEFAULT \
    | jq -r '.data.id'

The jq utility extracts the OCID of the Vault from the id property in the response; this is the unique identifier for your Vault which you will need later. Store the OCID as the value of the VAULT_ID environment variable:

export VAULT_ID=ocid1.vault.oc1.iad.b5re3....abuwcljryp5dmxbu3si7...

4.2. Create a Master Encryption Key #

To create a Master Encryption Key, you need the Vault’s management endpoint URL. It is not in the response from the Vault create command, so run the get command, as follows:

oci kms management vault get --vault-id $VAULT_ID

The response to the command should look like:

{
  "data": {
    "compartment-id": "ocid1.tenancy.oc1..aaaaaaaaixksuiqo3rx6...",
    "crypto-endpoint": "https://b5re3...-crypto.kms.us-ashburn-1.oraclecloud.com",
    ...
    "display-name": "gdk_guide_vault",
    "id": "ocid1.vault.oc1.iad.b5re3....abuwcljryp5dmxbu3si7...",
    "is-primary": true,
    "lifecycle-state": "ACTIVE",
    "management-endpoint": "https://b5re3...-management.kms.us-ashburn-1.oraclecloud.com",
    ...
  }
}

Wait a few minutes for the Vault to be provisioned. Re-run the command until the value of lifecycle-state is ACTIVE. The value of the management-endpoint property provides the URL.

Store the URL as the value of the VAULT_ENDPOINT environment variable:

export VAULT_ENDPOINT=https://b5re3...-management.kms.us-ashburn-1.oraclecloud.com

Choose a name for the key, such as “gdk-guide-encryption-key”, and a “Protection Mode”, either SOFTWARE or HSM.

Note: Choose SOFTWARE to avoid the cost of using a hardware security module (HSM).

Use the AES key shape algorithm and a key length of 32 (256 bits).

Run the create command, substituting the display name, protection mode, key shape, and management endpoint, as shown below. (Use the same compartment OCID as before.)

oci kms management key create -c $C \
    --display-name gdk-guide-encryption-key \
    --protection-mode SOFTWARE \
    --key-shape '{"algorithm":"AES","length":"32"}' \
    --endpoint $VAULT_ENDPOINT \
    | jq -r '.data.id'

If you use Windows, escape the inner double quotes in the value for key-shape: '{\"algorithm\":\"AES\",\"length\":\"32\"}

The jq utility extracts the id value from the response:

ocid1.key.oc1.iad.b5re3.....abuwcljrcuie7t6gctc6...

Store the OCID as the value of the VAULT_KEY_ID environment variable:

export VAULT_KEY_ID=ocid1.key.oc1.iad.b5re3.....abuwcljrcuie7t6gctc6...

4.3. Create Secrets #

In this guide you store values for the database username, password, and URL in a Vault by creating secrets for the properties named “datasources.default.username”, “datasources.default.password”, and “datasources.default.url”. You create the secrets in the same compartment as your other Oracle Cloud Infrastructure resources.

4.3.1. Create a Secret named “datasources.default.username”

The first secret is for the database username, named “datasources.default.username”.

You must Base64-encode the contents of a secret. You can encode the contents programmatically, for example via the Java statement Base64.getEncoder().encodeToString("the value".getBytes()), or use an online tool such as https://www.base64encode.org/.

Run the create-base64 command substituting the secret name and Base64-encoded secret. (If you use “guide_user” as the database username, the Base64-encoded version is “Z3VpZGVfdXNlcg==”.)

oci vault secret create-base64 -c $C \
    --key-id $VAULT_KEY_ID \
    --vault-id $VAULT_ID \
    --secret-name datasources.default.username \
    --secret-content-content Z3VpZGVfdXNlcg==

The response should look like this:

{
  "data": {
    "compartment-id": "ocid1.compartment.oc1..aaaaaaaarkh3s2wcxbbm...",
    ...
    "id": "ocid1.vaultsecret.oc1.iad.amaaaaaafzr7royabqgz...",
    "key-id": "ocid1.key.oc1.iad.b5re3.....abuwcljrcuie7t6gctc6...",
    "lifecycle-state": "CREATING",
    "secret-name": "datasources.default.username",
    ...
    "vault-id": "ocid1.vault.oc1.iad.b5re3....abuwcljryp5dmxbu3si7..."
  }
}

4.3.2. Create Secret named: “datasources.default.password”

Create a second secret with the name “datasources.default.password”. Set the contents of the section as the Base64-encoded version of the database user password you chose earlier.

4.3.3. Create Secret named: “datasources.default.url”

Create a third secret with the name “datasources.default.url”. The URL is “jdbc:mysql://<MySQL IP address>:3306/<MySQL Database Name>” substituting the private IP address and name of your MySQL database. Set the contents of the secret as the Base64-encoded version of the URL.

5. Use a Vault with Micronaut #

Micronaut Oracle Cloud provides integration between Micronaut applications and Oracle Cloud Infrastructure services, including using Vault as a distributed configuration source. The GDK Launcher added the appropriate dependencies to your build file when you selected the Database and Secret Management services in the GDK Launcher.

5.1. Modify the Micronaut Configuration #

  1. The Micronaut bootstrap configuration file contains the properties to authenticate your application against Oracle Cloud Infrastructure, as well as the compartment OCIDs of the Vault and secrets.

    Modify the file named oci/src/main/resources/bootstrap.properties so that its contents matches the following:

     micronaut.application.name=oci
     micronaut.config-client.enabled=true
     # <1>
     oci.config.instance-principal.enabled=true
     oci.vault.config.enabled=true
     # <2>
     oci.vault.vaults[0].compartment-ocid=${C}
     # <3>
     oci.vault.vaults[0].ocid=${VAULT_ID}
    

    1 Use Instance Principal authentication to enable the Micronaut application to access secrets in the Vault.

    2 Set the value of the oci.vault.vaults[0].compartment-ocid property with the OCID of the compartment where you created the secrets.

    3 Set the value of the oci.vault.vaults[0].ocid property with the OCID you saved when creating the Vault.

  2. The Micronaut application configuration file contains the properties for your database connection. Modify the file named oci/src/main/resources/application.properties so that its contents matches the following:

     datasources.default.db-type=mysql
     datasources.default.dialect=MYSQL
     datasources.default.driver-class-name=com.mysql.cj.jdbc.Driver
     flyway.datasources.default.enabled=true
     oci.config.profile=DEFAULT
    

6. Set Up Oracle Cloud Authentication #

In step 5.1 above, you modified the Micronaut bootstrap configuration file to use Instance Principal authentication so that the application can retrieve secrets. To use this from your compute instance, create a dynamic group and add a policy statement granting permission.

6.1. Create a Dynamic Group #

Choose a group name, such as “gdk-guide-dg”, and a matching rule (the logic that will be used to determine group membership). For this guide, use a broad rule that includes all compute instances in your compartment: ALL {instance.compartment.id = 'ocid1.compartment.oc1..aaaaaaaarkh3s2wcxbbm…'} replacing ocid1.compartment.oc1..aaaaaaaarkh3s2wcxbbm…` with the compartment OCID.

Run the create command:

oci iam dynamic-group create \
   --name mn-guide-dg \
   --description mn-guide-dg \
   --matching-rule "ALL {instance.compartment.id = 'ocid1.compartment.oc1..aaaaaaaarkh3s2wcxbbm...'}" \
   | jq -r '.data."compartment-id"'

The jq utility extracts the compartment-id value from the response. Store it (the tenancy OCID) as the value of the T environment variable:

export T=ocid1.tenancy.oc1..aaaaaaaaud4g4e5ovjaw...

For more information, see Dynamic Groups.

6.2. Add Dynamic Group Policy Statements #

Create a policy to grant read access to Vault secrets in your compartment. Create the policy in the root compartment (the tenancy), use the tenancy OCID saved from the dynamic group creation response.

Run the create command:

oci iam policy create -c $T \
    --name gdk-guide-policy \
    --description gdk-guide-policy \
    --statements '["allow dynamic-group gdk-guide-dg to read secret-family in tenancy"]' \
    | jq -r '.data.id'

The jq utility extracts the policy OCID from the id property in the response. Store it as the value of the POLICY_ID environment variable:

export POLICY_ID=ocid1.policy.oc1..aaaaaaaau7uhwxr3ynlr...

7. Start the Application #

  1. Create an executable JAR file including all application’s dependencies:

    ./gradlew :oci:shadowJar
    ./mvnw install -pl lib -am
    ./mvnw package -pl oci -DskipTests
  2. From a local console, copy the JAR file from your computer to the compute instance using scp:

    scp -i /path/to/ssh-key-*.key path/to/oci-secrets-demo/oci/build/libs/oci-1.0-SNAPSHOT-all.jar opc@[COMPUTE_INSTANCE_PUBLIC_IP]:~/application.jar
           
    scp -i /path/to/ssh-key-*.key path/to/oci-secrets-demo/oci/target/oci-1.0-SNAPSHOT.jar opc@[COMPUTE_INSTANCE_PUBLIC_IP]:~/application.jar
           
  3. Once copied, connect to the compute instance (if it has disconnected by now):
     ssh -i /path/to/ssh-key-*.key opc@[COMPUTE_INSTANCE_PUBLIC_IP]
    
  4. Start the application:
     java -jar application.jar
    
  5. From a local console, run this command to test that you can create and store a new Genre in the database:
     curl -X "POST" "http://[COMPUTE_INSTANCE_PUBLIC_IP]:8080/genres" \
           -H 'Content-Type: application/json; charset=utf-8' \
           -d $'{ "name": "music" }'
    

    Then list the genres:

     curl [COMPUTE_INSTANCE_PUBLIC_IP]:8080/genres/list
    

    The response should look like this:

     {"id":1,"name":"music"}
    

8. Generate a Native Executable Using GraalVM #

The GDK supports compiling Java applications ahead-of-time into native executables using GraalVM Native Image. You can use the Gradle plugin for GraalVM Native Image building/Maven plugin for GraalVM Native Image building. Packaged as a native executable, it significantly reduces application startup time and memory footprint.

Prerequisites: Make sure you have installed a GraalVM JDK. The easiest way to get started is with SDKMAN!. For other installation options, visit the Downloads section.

If you build a native executable locally, it will only run on your OS, and isn’t suitable for deployment to a compute instance. To create a deployable native executable, use a different packaging command that builds the executable inside a container, which you can extract to deploy to the cloud.

  1. From a local console, generate a container image containing the native executable by running the following command:

    ./gradlew :oci:dockerBuildNative
    ./mvnw install -pl lib -am
    ./mvnw clean package -pl oci -Dpackaging=docker-native
  2. Find the ID of the container image you built by running the following command:

     docker image ls
    

    The container images are listed chronologically with the most recent at the top, so your output will look like this:

     REPOSITORY   TAG      IMAGE ID       CREATED          SIZE
     oci          latest   41c8a902b2e7   32 seconds ago   100MB
    
  3. Extract the native executable from the container image by running these commands, replacing <image_id> with the ID of the container image, for example, 41c8a902b2e7:

     docker create --name container_temp <image_id>
     docker cp container_temp:/app/application .
     docker rm container_temp
    

9. Deploy from a Native Executable #

  1. From a local console, copy the native executable from your computer to the compute instance using scp:

     scp -i /path/to/ssh-key-*.key application opc@[COMPUTE_INSTANCE_PUBLIC_IP]:~/
    
  2. Once copied, connect to the compute instance (if it has disconnected by now):
     ssh -i /path/to/ssh-key-*.key opc@[COMPUTE_INSTANCE_PUBLIC_IP]
    
  3. Stop the application running from the JAR file if it’s still running.

  4. Run the native executable:
     ./application
    
  5. From the local console, repeat the tests from step 7 above.

10. Clean up Cloud Resources #

After you’ve completed this guide, you can clean up the resources you created.

  1. Delete the policy by running the following command:

     oci iam policy delete --policy-id $POLICY_ID
    
  2. To delete the dynamic group, find its OCID by running the list command:

     oci iam dynamic-group list | jq -r '.data[] | select(.name=="gdk-guide-dg") | .id'
    

    and run the delete command, substituting the group OCID:

     oci iam dynamic-group delete --dynamic-group-id ocid1.dynamicgroup.oc1..aaaaaaaaipoabhhaqnj77urm...
    
  3. Delete the Vault. You cannot delete it directly; instead request deletion at a date at least seven days in the future. Run this, replacing the date:

     oci kms management vault schedule-deletion \
         --vault-id $VAULT_ID \
         --time-of-deletion 2023-12-12
    
  4. Finally, delete the database using the CLI. Run the following command:

     oci mysql db-system delete \
         --db-system-id ocid1.mysqldbsystem.oc1.iad.aaaaaaaa2pq3a37hftut...
    

Summary #

This guide demonstrated how to create a Java application that stores MySQL database connection details as secrets in an Oracle Cloud Infrastructure vault. You also saw how to package this application into a native executable and deploy it to a Compute instance.