Securely store Micronaut Application secrets in Oracle Cloud Vault

Learn how to create secrets in Oracle Cloud Vault and easily access them in a Micronaut application.

Authors: Burt Beckwith

Micronaut Version: 4.3.7

1. Getting Started

In this guide, we will create a Micronaut application written in Kotlin.

2. What you will need

To complete this guide, you will need the following:

  • Oracle Cloud CLI installed with local access to Oracle Cloud configured by running oci setup config

3. Oracle Cloud Vault

Oracle Cloud Vault lets you securely store and retrieve secrets such as passwords or other information that shouldn’t be accessible in cleartext.

In this guide we’ll use Vault to store database connection information for a Micronaut application that uses MySQL, and use the Micronaut support for Oracle Cloud to make the process seamless.

4. The Application

Download the complete solution of the Access a database with Micronaut Data JDBC guide. You will use the sample application as a starting point.

5. Creating an Oracle Cloud Vault

We’ll do the work of creating the vault with the Oracle Cloud CLI. See the Vault CLI command reference for more information.

If you prefer to use the Oracle Cloud web console to create the vault, follow the steps in the "Using Oracle Cloud Vault" section of the Access an Oracle Autonomous Database guide.

Some of the following commands use jq

jq is a lightweight and flexible command-line JSON processor

There are three required parameters for the create command; compartment OCID, display name, and vault type.

5.1. Compartment OCID

Find the OCID of the compartment where we’ll be deploying. Run this to list all the compartments in your root compartment:

oci iam compartment list

and find the compartment by the name or description in the JSON output. It should look like this:

{
  "compartment-id": "ocid1.tenancy.oc1..aaaaaaaaud4g4e5ovjaw...",
  "defined-tags": {},
  "description": "Micronaut guides",
  "freeform-tags": {},
  "id": "ocid1.compartment.oc1..aaaaaaaarkh3s2wcxbbm...",
  "inactive-status": null,
  "is-accessible": null,
  "lifecycle-state": "ACTIVE",
  "name": "micronaut-guides",
  "time-created": "2021-05-02T23:54:28.392000+00:00"
}

Use the OCID from the id property; the compartment-id property is the parent compartment.

For convenience, save the compartment OCID as an environment variable. For Linux or Mac, run

export C=ocid1.compartment.oc1..aaaaaaaarkh3s2wcxbbm...

or for Windows, if using cmd run

set C=ocid1.compartment.oc1..aaaaaaaarkh3s2wcxbbm...

and if using PowerShell run

$C = "ocid1.compartment.oc1..aaaaaaaarkh3s2wcxbbm..."
In the examples below we use Linux/Mac syntax for environment variables, e.g. -c $C. If you use Windows cmd, change those to -c %C% (but no change needed if using PowerShell)

5.2. Create the Vault

Choose a display name (1-100 characters) and a vault type, either DEFAULT or VIRTUAL_PRIVATE. We recommend that you choose DEFAULT to avoid the cost of a virtual private vault.

Run the create command with the compartment OCID, display name, and vault type substituted:

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

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

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

If you use Linux or Mac, you can combine both commands into one, creating the vault and setting the environment variable at the same time:

export VAULT_ID=$(oci kms management vault create -c $C \
    --display-name mn_guide_vault \
    --vault-type DEFAULT \
    | jq -r '.data.id')

5.2.1. Create a Master Encryption Key

For this we need the management endpoint URL that wasn’t available yet in the response from the vault create command. Run the get command:

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

The response should look like this:

{
  "data": {
    "compartment-id": "ocid1.tenancy.oc1..aaaaaaaaixksuiqo3rx6...",
    "crypto-endpoint": "https://b5re3...-crypto.kms.us-ashburn-1.oraclecloud.com",
    ...
    "display-name": "mn_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",
    ...
  }
}

It will take a few minutes for the vault to finish provisioning. Re-run the get command until the value of lifecycle-state is ACTIVE. The URL is available in the management-endpoint property.

Store the URL as the VAULT_ENDPOINT environment variable:

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

Choose a name for the key, e.g., "mn-guide-encryption-key", and a "Protection Mode", either SOFTWARE or HSM. We recommend that you choose SOFTWARE to avoid the cost of using a hardware security module (HSM). You also need to decide on a key shape algorithm and key length; for this guide we’ll use AES and a key length of 32 (256 bits).

Run the create command with the compartment OCID, display name, protection mode, key shape, and management endpoint substituted:

oci kms management key create -c $C \
    --display-name mn-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 will extract the id value from the response:

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

Store the key ID as the VAULT_KEY_ID environment variable:

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

6. Using Vault with Micronaut

6.1. micronaut-oraclecloud-vault Dependency

The micronaut-oracle-cloud subproject provides integration between Micronaut apps and Oracle Cloud, including using Vault as a distributed configuration source. Add a dependency to your build for the micronaut-oraclecloud-vault library to add Vault support:

pom.xml
<dependency>
    <groupId>io.micronaut.oraclecloud</groupId>
    <artifactId>micronaut-oraclecloud-vault</artifactId>
    <scope>compile</scope>
</dependency>

7. Distributed Configuration

7.1. Enable Distributed Configuration

Create a bootstrap.properties file in the resources directory to enable distributed configuration. Add the following:

src/main/resources/bootstrap.properties
(1)
micronaut.application.name=micronautguide
(2)
micronaut.config-client.enabled=true
1 Set the application name in bootstrap.properties instead of application.properties so that it is available when reading configuration from distributed sources. properties
2 Set micronaut.config-client.enabled=true which is used to read and resolve configuration from distributed sources.

7.2. Clean up Application Configuration

If application.properties sets micronaut.application.name, remove it. You moved it to bootstrap.properties.

src/main/resources/application.properties
micronaut.application.name=micronautguide

7.3. Disable Distributed Configuration for Test

You can disable distributed configuration in a test by annotating a test with:

@Property(name = "micronaut.config-client.enabled", value = StringUtils.FALSE)
@MicronautTest

8. Configuration changes

8.1. bootstrap-oraclecloud.properties

Then create src/main/resources/bootstrap-oraclecloud.properties with the following content:

src/main/resources/bootstrap-oraclecloud.properties
(1)
oci.config.instance-principal.enabled=true
oci.vault.config.enabled=true
(2)
oci.vault.vaults[0].ocid=${VAULT_ID}
(3)
oci.vault.vaults[0].compartment-ocid=
1 We’ll use Instance Principal authentication to allow the Micronaut application to access Vault
2 Set the value of the ocid property with the vault OCID unique identifier you saved when creating the vault.
3 Set the value of the compartment-ocid property with the OCID unique identifier of the compartment where you created the vault and secrets

9. MySQL Database

Use the Deploy a Micronaut MySQL Database Application to Oracle Cloud guide to create a MySQL database; follow the steps in the "Creating a MySQL DB System at Oracle Cloud" section.

10. Deploying the Application

Use the Deploy a Micronaut application to Oracle Cloud guide to create a compute instance and deploy the application to it; follow the steps in the "Create an Oracle Cloud Compute Instance" and "Deploy to Oracle Cloud" sections up to the step where you start the application. We need to connect the application to the MySQL database before starting it up.

When creating the compute VM at Oracle Cloud, use the same subnet as the one where you created the MySQL database, otherwise the application will not be able to access the database.

11. Configuring MySQL Access

Use the Deploy a Micronaut MySQL Database Application to Oracle Cloud guide to configure access to the MySQL database; follow the steps in the "Configure MySQL" section. You will need the private IP address of the VM, the MySQL private IP address, and the admin username and password you chose when creating the database.

12. Creating Secrets

In the Access a database with Micronaut Data JDBC guide, the values for the JDBC URL, database username and password, and the JDBC driver class are "externalized" properties with default values:

application.properties
datasources.default.url=${JDBC_URL:`jdbc:mysql://localhost:3306/db`}
datasources.default.username=${JDBC_USER:root}
datasources.default.password=${JDBC_PASSWORD:}
datasources.default.dialect=MYSQL
datasources.default.driver-class-name=${JDBC_DRIVER:com.mysql.cj.jdbc.Driver}

The guide recommends that you set environment variables to override the default values, but in this guide we’ll go a step further and store some of those values in our Oracle Cloud Vault. We’ll leave the default for the driver class, but create vault secrets for JDBC_USER, JDBC_PASSWORD, and JDBC_URL.

12.1. JDBC_USER

The first secret will be for the database username, so the secret name will be JDBC_USER.

Secret values must be Base64-encoded. You can encode the value programmatically, e.g., Base64.getEncoder().encodeToString("the value".getBytes()), or use an online tool such as https://www.base64encode.org/.

Run the create-base64 command with the compartment OCID, encryption key OCID, vault OCID, secret name, and Base64-encoded secret value substituted. If you use guide_user as the username, the Base64-encoded value will be Z3VpZGVfdXNlcg==

oci vault secret create-base64 -c $C \
    --key-id $VAULT_KEY_ID \
    --vault-id $VAULT_ID \
    --secret-name JDBC_USER \
    --secret-content-content Z3VpZGVfdXNlcg==

Note that running that command will leave the Base64-encoded value in your shell history. To avoid this, you can create a JSON file containing the parameters and pass that as an argument to the command.

To use this approach, create a file like this with values substituted, and save it as key.json:

{
   "compartmentId": "ocid1.compartment.oc1..aaaaaaaarkh3s2wcxbbm...",
   "keyId": "ocid1.key.oc1.iad.b5re3.....abuwcljrcuie7t6gctc6...",
   "vaultId": "ocid1.vault.oc1.iad.b5re3....abuwcljryp5dmxbu3si7...",
   "secretName": "JDBC_USER",
   "secretContentContent": "Z3VpZGVfdXNlcg=="
}

and run this create-base64 command instead:

oci vault secret create-base64 --from-json file://key.json

With either approach 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": "JDBC_USER",
    ...
    "vault-id": "ocid1.vault.oc1.iad.b5re3....abuwcljryp5dmxbu3si7..."
  }
}

12.2. JDBC_PASSWORD

Create a second secret with the name JDBC_PASSWORD. The value will be the Base64-encoded database user password you chose earlier.

12.3. JDBC_URL

Create a third secret with the name JDBC_URL. The URL will be jdbc:mysql://<MySQL IP address>:3306/micronaut with the private IP address of your MySQL database substituted. Set the value of the secret as the Base64-encoded URL value.

13. Instance Principal authentication

We’ll use Instance Principal authentication to allow the Micronaut application to retrieve secrets from Vault. To use this, we need to create a dynamic group and add a policy statement granting permission.

If you prefer to use the Oracle Cloud web console to create the dynamic group and policy statements, follow the steps in the "Instance Principal authentication" section of the Oracle Cloud Streaming and the Micronaut Framework - Event-Driven Applications at Scale guide.

13.1. Dynamic Group

Choose a group name, e.g., "mn-guide-dg", and a matching rule, i.e., the logic that will be used to determine group membership. We’ll make the rule fairly broad - use ALL {instance.compartment.id = 'ocid1.compartment.oc1..aaaaaaaarkh3s2wcxbbm…​'} replacing ocid1.compartment.oc1..aaaaaaaarkh3s2wcxbbm…​ with the compartment OCID:

Run the create command with the compartment OCID substituted:

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 will extract the compartment-id value from the response. Store the ID (the tenancy ID) as the T environment variable:

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

See the Dynamic Group docs for more information.

13.2. Dynamic Group Policy Statements

Next, create the policy granting read access to Vault.

We’ll create the policy in the root compartment, i.e., the tenancy, so we’ll use the tenancy OCID saved from the dynamic group creation response.

Run the create command with the tenancy OCID substituted:

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

The jq utility will extract the policy OCID from the id property in the response. Store the value as the POLICY_ID environment variable:

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

14. Start the application

Finally, start the application. From the SSH session into your VM, run:

java -jar application.jar

Verify that the application is working correctly with some cURL commands.

Create a genre by running

curl -X POST http://[VM IP Address]:8080/genres \
     -H 'Content-Type: application/json; charset=utf-8' \
     -d $'{ "name": "music" }'

The response should look like this:

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

List all genres by running

curl http://[VM IP Address]:8080/genres/list

The response should look like this:

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

15. Cleaning up

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

Delete the policy by running

oci iam policy delete --policy-id $POLICY_ID

To delete the dynamic group, find its OCID by running the list command:

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

and run the delete command, substituting the group OCID:

oci iam dynamic-group delete --dynamic-group-id ocid1.dynamicgroup.oc1..aaaaaaaaipoabhhaqnj77urm...

Finally, delete the vault. You cannot delete it directly; instead you can request deletion at a date at least seven days in the future. Run this, replacing the date with one seven days (or more) from now:

oci kms management vault schedule-deletion \
    --vault-id $VAULT_ID \
    --time-of-deletion 2022-05-12

16. Next steps

Explore more features with Micronaut Guides.

Read more about the Micronaut Oracle Cloud integration.

17. License

All guides are released with an Apache license 2.0 license for the code and a Creative Commons Attribution 4.0 license for the writing and media (images…​).