Securely Store Application Secrets in an Oracle Cloud Infrastructure Vault

This guide describes how to use the Graal Development Kit for Micronaut (GDK) to create an application that accesses a secret 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 a secret, and demonstrates how to use Micronaut® integration with Oracle Cloud Infrastructure Vault.

Prerequisites #

Follow the steps below to create the application.

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
    • Language: Java (Default)
    • Build Tool: Gradle (Groovy) or Maven
    • Test Framework: JUnit (Default)
    • Java Version: 17 (Default)
    • Micronaut Version: (Default)
    • Cloud Services: Secret Management
    • Features: GraalVM Native Image (Default)
    • Sample Code: No
  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:

gcn create-app com.example.oci-secrets-demo \
    --clouds=oci \
    --services=secretmanagement \
    --features=graalvm \
    --build=gradle \
    --jdk=17 \
    --lang=java \
    --example-code=false
gcn create-app com.example.oci-secrets-demo \
    --clouds=oci \
    --services=secretmanagement \
    --features=graalvm \
    --build=maven \
    --jdk=17 \
    --lang=java \
    --example-code=false

For more information, see Using the GDK CLI.

1.1. Create a Controller #

Create a controller that returns the contents of a secret. Copy the following content to a file named lib/src/main/java/com/example/SecretController.java:

package com.example;

import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.micronaut.http.annotation.Produces;
import io.micronaut.context.annotation.Value;

@Controller("/answer")
public class SecretController {
    // <1>
    @Value("${universe.answer}") String universalAnswer; // <2>

    @Get
    @Produces(MediaType.TEXT_PLAIN)
    public String index() {
        return "Answer to the Ultimate Question of Life, the Universe, and Everything = " +
          universalAnswer + "\n";
    }
}

1 If you create a named secret in a Vault, then you can use it in your application just like any other configuration variable via the @Value annotation.

2 The @Value annotation must declare a string that matches the name of the secret that you create in the Vault. (See section 2.3.3, Create a Secret.)

2. Set Up Oracle Cloud Infrastructure #

In this section, you set up your Oracle Cloud Infrastructure tenancy and use the Oracle Cloud Infrastructure Console to create a Vault, a master encryption key, and a 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 secrets in their individual compartments.

2.1. Configure Oracle Cloud Infrastructure Authentication #

If you haven’t created an API-key for your Oracle Cloud Infrastructure region, run the following command:

oci setup bootstrap

The command opens your browser for authentication. When prompted, provide the tenancy’s region and a name for the profile, for example, “oci_secrets_demo_profile”.

Save the name of the profile as the value of the OCI_PROFILE environment variable, as follows:

export OCI_PROFILE=oci_secrets_demo_profile

Note: For more information, see Oracle Cloud Infrastructure SDK Authentication Methods and Setting up the Configuration File.

2.2. Select a Compartment #

Select an Oracle Cloud Infrastructure compartment with appropriate permission granted to your Oracle Cloud Infrastructure user account to manage the Secret family in the compartment.

  1. Login to the Oracle Cloud console, open the navigation menu and click Identity & Security. Under Identity, click Compartments.

  2. Click the name of the compartment in which you want to create a secret.

  3. Copy the OCID displayed in the Compartment information tab (click Copy).

  4. Save the OCID as the value of the SECRET_COMPARTMENT_OCID environment variable:

     export SECRET_COMPARTMENT_OCID=ocid1.compartment.oc1..aaaa...7yt6qm7tmsq64...
    

2.3. Create an Oracle Cloud Infrastructure Vault, Master Encryption Key, and Secret #

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

2.3.1. Create a Vault

Follow the steps in Creating a Vault. Use the following properties for the Vault:

  • Compartment Select a compartment from the drop-down list. (Use the compartment that you identified above.)
  • Name: Enter a name for the Vault, such as “gdk_vault”.
  • Make it a virtual private vault: Do not check this box.

When the Vault is Active, click its name and copy its OCID displayed in the Vault information tab (click Copy).

Save the Vault OCID as the value of the VAULT_OCID environment variable:

export VAULT_OCID=ocid1.vault.oc1.iad.ejst...lyvjrvpgjyt...

2.3.2. Create a Master Encryption Key

Use the instructions in Creating a Master Encryption Key. Use the following properties:

  • Compartment: Select a compartment from the drop-down list. (Use the compartment that you identified above.)
  • Protection Mode: Choose Software from the drop-down list. (To avoid the cost of a hardware security module (HSM).)
  • Name: Enter a name for the key, such as “gdk-encryption-key”.
  • Key Shape: Algorithm: Choose AES from the drop-down list.
  • Key Shape: Length: Choose 256 bits from the drop-down list.

2.3.3. Create a Secret

Follow the steps in Creating a Secret in a Vault. Use the following properties:

  • Compartment: Select a compartment from the drop-down list. (Use the compartment that you identified above.)
  • Name: Enter a name to identify the secret, such as “universe.answer”.
  • Description: Enter a description for the secret, such as “Answer to the Ultimate Question of Life, the Universe, and Everything”.
  • Encryption Key: Select the master encryption key from the drop-down list. (For example, “gdk-encryption-key”.)
  • Secret Type Template: Select Text from the drop-down list.
  • Secret Contents: Enter the contents of the secret, such as “42”.

Note: The Name of the secret must match the contents of the @Value annotation that added to SecretController. (See section 1.1, Create a Controller.) .

3. Use Vault with Micronaut #

Micronaut Oracle Cloud provides integration between Micronaut applications and Oracle Cloud Infrastructure services, including Vault. The GDK Launcher added the appropriate dependencies to your build file when you selected the Secret Management service in the GDK Launcher.

3.1. Modify the Micronaut Configuration #

The Micronaut bootstrap configuration file contains the properties to authenticate your application against Oracle Cloud Infrastructure, as well as details of the Vault containing the secret.

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

oci.vault.config.enabled=true
# <1>
oci.config.profile=${OCI_PROFILE}
# <2>
oci.vault.vaults[0].compartment-ocid=${SECRET_COMPARTMENT_OCID}
micronaut.application.name=oci
micronaut.config-client.enabled=true
# <3>
oci.vault.vaults[0].ocid=${VAULT_OCID}

1 Set the value of the oci.config.profile property with the name of the profile you use to authenticate against OCI.

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

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

Note: If you deploy the application to Oracle Cloud Infrastructure, set the property oci.config.instance-principal.enabled: true for instance principal authentication. For more details on configuring Oracle Cloud Infrastructure authentication with Micronaut, see Micronaut Oracle Cloud documentation. For an example of using instance principal authentication, see Securely Store Database Connection Details in an Oracle Cloud Infrastructure Vault.

4. Run the application #

To run the application, use the following command, which starts the application on port 8080.

./gradlew :oci:run
./mvnw install -pl lib -am
./mvnw mn:run -pl oci

You should see traces such as:

17:17:53.394 [main] INFO  i.m.c.DefaultApplicationContext$RuntimeConfiguredEnvironment - Established active environments: [oraclecloud]
17:17:53.397 [main] INFO  i.m.c.DefaultApplicationContext$BootstrapEnvironment - Established active environments: [oraclecloud]
17:17:53.419 [main] INFO  i.m.context.DefaultBeanContext - Reading bootstrap environment configuration
17:17:53.615 [main] INFO  com.oracle.bmc.Services - Registering new service: Services.BasicService(serviceName=SECRETS, serviceEndpointPrefix=, serviceEndpointTemplate=https://secrets.vaults.{region}.oci.{secondLevelDomain}, endpointServiceName=null)
17:17:54.129 [main] INFO  com.oracle.bmc.Region - Loaded service 'SECRETS' endpoint mappings: {US_ASHBURN_1=https://secrets.vaults.us-ashburn-1.oci.oraclecloud.com}
17:17:54.129 [main] INFO  c.oracle.bmc.secrets.SecretsClient - Setting endpoint to https://secrets.vaults.us-ashburn-1.oci.oraclecloud.com
17:17:55.338 [main] INFO  com.oracle.bmc.Services - Registering new service: Services.BasicService(serviceName=VAULTS, serviceEndpointPrefix=, serviceEndpointTemplate=https://vaults.{region}.oci.{secondLevelDomain}, endpointServiceName=null)
17:17:55.341 [main] INFO  com.oracle.bmc.Region - Loaded service 'VAULTS' endpoint mappings: {US_ASHBURN_1=https://vaults.us-ashburn-1.oci.oraclecloud.com}
17:17:55.341 [main] INFO  com.oracle.bmc.vault.VaultsClient - Setting endpoint to https://vaults.us-ashburn-1.oci.oraclecloud.com
17:17:55.589 [main] INFO  com.oracle.bmc.ClientRuntime - Using SDK: Oracle-JavaSDK/3.25.1
17:17:55.589 [main] INFO  com.oracle.bmc.ClientRuntime - User agent set to: Oracle-JavaSDK/3.25.1 (Mac OS X/12.6.9; Java/17.0.6; Java HotSpot(TM) 64-Bit Server VM/17.0.6+9-LTS-jvmci-22.3-b11)
17:17:59.085 [main] INFO  i.m.d.c.c.DistributedPropertySourceLocator - Resolved 1 configuration sources from client: compositeConfigurationClient(oraclecloud-vault)
17:17:59.967 [main] INFO  io.micronaut.runtime.Micronaut - Startup completed in 9887ms. Server Running: http://localhost:8080

Test that the secret was loaded correctly by opening http://localhost:8080/answer in your browser or using curl:

curl http://localhost:8080/answer

You should see the following response:

Answer to the Ultimate Question of Life, the Universe, and Everything = 42

5. Generate a Native Executable Using GraalVM #

The GDK supports compiling a Java application ahead-of-time into a native executable 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.

To generate a native executable, run the following command:

./gradlew :oci:nativeCompile

The native executable is created in the oci/build/native/nativeCompile/ directory and can be run with the following command:

oci/build/native/nativeCompile/oci
./mvnw install -pl lib -am
./mvnw package -pl oci -Dpackaging=native-image

The native executable is created in the oci/target/ directory and can be run with the following command:

oci/target/oci

The application starts on port 8080. Test it as described in the previous section. The application should behave identically as if you run it from a JAR file, but with reduced startup time and smaller memory footprint.

Summary #

This guide demonstrated how to create a Java application that accesses a secret in an Oracle Cloud Infrastructure Vault. You also saw how to package this application into a native executable using GraalVM Native Image.