Introduction
This guide provides a comprehensive walkthrough for configuring Terraform Enterprise (TFE) to securely authenticate to Oracle Cloud Infrastructure (OCI) using HashiCorp Vault. This method uses Vault's JWT authentication backend to issue short-lived tokens, which TFE workspaces use to retrieve dynamic OCI credentials from Vault's Key-Value (KV) secrets engine. This approach eliminates the need to store static, long-lived OCI credentials within your TFE workspaces, significantly improving your security posture.
Expected Outcome
After completing this guide, you will have a fully functional integration where your TFE workspace can dynamically and securely fetch OCI API credentials from Vault for each run. This enables Terraform to manage OCI resources without exposing static secrets
Use Case
Storing static cloud credentials as environment variables in CI/CD systems or infrastructure-as-code platforms like Terraform Enterprise poses a security risk. These credentials can be long-lived, and if compromised, they could grant an attacker broad access to your cloud environment.
By using Vault as an intermediary, you centralize secrets management and leverage dynamic secrets capabilities. TFE authenticates to Vault using its workload identity (a JWT token), which is then authorized to access only the specific OCI credentials required for that workspace. This method adheres to the principle of least privilege and provides a clear audit trail for credential access.
Architecture
Procedure
Follow these steps to configure the integration between Terraform Enterprise, Vault, and OCI.
Step 1: Install and Configure Vault
- First, you will install Vault on your Ubuntu server and set up a basic configuration.
-
Add the HashiCorp GPG key and repository.
wget -O - https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list -
Update your package list and install Vault.
sudo apt update sudo apt install vault -y -
Create a Vault configuration file at
/etc/vault.d/vault.hcl. This configuration enables the UI and uses the local filesystem for storage. For production, you should use a more durable storage backend.sudo tee /etc/vault.d/vault.hcl <<EOF ui = true disable_mlock = true storage "file" { path = "/opt/vault/data" } listener "tcp" { address = "127.0.0.1:8200" tls_disable = 1 } api_addr = "http://127.0.0.1:8200" cluster_addr = "https://127.0.0.1:8201" EOF -
Start and enable the Vault service.
sudo systemctl start vault sudo systemctl enable vaultStep 2: Initialize, Unseal, and Secure Vault
Next, you will initialize Vault to generate the master keys and root token, unseal it to make it operational, and configure secure HTTPS access using Nginx and Let's Encrypt.
-
Set the
VAULT_ADDRenvironment variable for your terminal session.export VAULT_ADDR=http://127.0.0.1:8200 -
Initialize Vault. Securely store the unseal keys and the Initial Root Token. You will need these to unseal Vault and perform initial configuration.
vault operator init -key-shares=5 -key-threshold=3 -
Unseal Vault using three of the five generated unseal keys. You must enter a different key at each prompt.
vault operator unseal ## Enter Unseal Key 1 vault operator unseal ## Enter Unseal Key 2 vault operator unseal ## Enter Unseal Key 3 -
Log in to Vault with the Initial Root Token.
vault login ## Enter the Initial Root Token -
Install Nginx to act as a reverse proxy.
sudo apt install nginx -y -
Create an Nginx server block configuration for Vault. Replace
vault.your-domain.comwith your actual domain name.sudo tee /etc/nginx/sites-available/vault <<EOF server { listen 80; server_name vault.your-domain.com; location / { proxy_pass http://127.0.0.1:8200; proxy_set_header Host \$host; proxy_set_header X-Real-IP \$remote_addr; proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto \$scheme; } } EOF -
Enable the new site and reload Nginx.
sudo ln -s /etc/nginx/sites-available/vault /etc/nginx/sites-enabled/ sudo nginx -t && sudo systemctl reload nginx -
Install Certbot and its Nginx plugin to obtain a free SSL certificate from Let's Encrypt.
sudo apt install certbot python3-certbot-nginx -y -
Run Certbot to configure HTTPS. Follow the interactive prompts to enter your email, agree to the terms, and select the option to redirect HTTP traffic to HTTPS.
sudo certbot --nginx -d vault.your-domain.com -
Verify that your Vault instance is accessible via HTTPS.
curl -I https://vault.your-domain.com ## The output should show an HTTP/2 200 response. curl -k https://vault.your-domain.com/v1/sys/health ## The output should show Vault is initialized and unsealed.You can now access the Vault UI in your browser at
https://vault.your-domain.com.
Step 3: Configure Vault JWT Authentication for TFE
In this step, you will configure Vault to trust JWT tokens issued by your Terraform Enterprise instance.
-
Enable the JWT authentication method in Vault.
vault auth enable jwt -
Configure the JWT auth method with your TFE instance's OIDC discovery URL. This allows Vault to fetch the public keys required to validate tokens from TFE.
vault write auth/jwt/config \ oidc_discovery_url="https://YOUR_TFE_INSTANCE.com" \ bound_issuer="https://YOUR_TFE_INSTANCE.com" -
Create a JWT role named
tfe-oci-role. This role defines the conditions (claims) that a TFE-issued JWT must meet to authenticate successfully. It also associates a Vault policy (tfe-oci-policy) that grants permissions to the authenticated entity.vault write auth/jwt/role/tfe-oci-role \ role_type="jwt" \ bound_audiences="vault.workload.identity" \ user_claim="sub" \ bound_claims='{ "terraform_organization_name": "YOUR_ORG", "terraform_workspace_name": "YOUR_WORKSPACE", "iss": "https://YOUR_TFE_INSTANCE.com" }' \ policies="tfe-oci-policy" \ ttl="30m"
Step 4: Store OCI Credentials in Vault - Now, you will create an OCI user, generate an API key, and securely store the credentials in Vault's KV secrets engine.
- In the OCI Console, navigate to Identity & Security > Users. Create a new user (e.g.,
tfe-automation-user). - From the user's details page, select API Keys and click Add API Key. Choose Generate API Key Pair, download the private key, and click Add.
- Note the following values from the configuration file preview: User OCID, Tenancy OCID, Fingerprint, and Region.
-
On your local machine, Base64-encode the private key you downloaded. The following command for macOS copies the result to your clipboard.
base64 -i /path/to/oci_api_key.pem -o - | pbcopy -
Enable the version 2 KV secrets engine in Vault.
vault secrets enable -version=2 kv -
Store the OCI credentials in Vault at the path
kv/oci/credentials.vault kv put kv/oci/credentials \ user_ocid="ocid1.user.oc1..YOUR_USER_OCID" \ tenancy_ocid="ocid1.tenancy.oc1..YOUR_TENANCY_OCID" \ fingerprint="YOUR_FINGERPRINT" \ private_key_base64="YOUR_BASE64_ENCODED_KEY" \ region="YOUR_REGION" -
Create a Vault policy named
tfe-oci-policythat grants read access to the OCI credentials and allows the token to manage itself.vault policy write tfe-oci-policy -<<EOF path "auth/token/lookup-self" { capabilities = ["read"] } path "auth/token/renew-self" { capabilities = ["update"] } path "auth/token/revoke-self" { capabilities = ["update"] } path "kv/data/oci/credentials" { capabilities = ["read"] } EOF
Step 5: Configure TFE Workspace VariablesAdd the following environment variables to your TFE workspace to enable Vault integration.
-
Variable Value Type TFC_VAULT_PROVIDER_AUTHtrueEnvironment TFC_VAULT_ADDRhttps://vault.your-domain.comEnvironment TFC_VAULT_RUN_ROLEtfe-oci-roleEnvironment TFC_VAULT_AUTH_PATHjwtEnvironment TFC_VAULT_WORKLOAD_IDENTITY_AUDIENCEvault.workload.identityEnvironment
Step 6: Configure Terraform CodeFinally, update your Terraform configuration to use the Vault provider to fetch OCI credentials. TFE will automatically handle the authentication to Vault.
Define the required providers and the
cloudblock for TFE.terraform { cloud { hostname = "YOUR_TFE_HOSTNAME" organization = "YOUR_ORG" workspaces { name = "YOUR_WORKSPACE" } } required_providers { vault = { source = "hashicorp/vault" version = "~> 4.0" } oci = { source = "oracle/oci" version = "~> 6.0" } } } # Vault provider configuration - TFE automatically injects auth provider "vault" {} # Read OCI credentials from Vault (ONCE) data "vault_kv_secret_v2" "oci_creds" { mount = "kv" name = "oci/credentials" } # Configure OCI provider with credentials from Vault (ONCE) provider "oci" { user_ocid = data.vault_kv_secret_v2.oci_creds.data["user_ocid"] tenancy_ocid = data.vault_kv_secret_v2.oci_creds.data["tenancy_ocid"] fingerprint = data.vault_kv_secret_v2.oci_creds.data["fingerprint"] private_key = base64decode(data.vault_kv_secret_v2.oci_creds.data["private_key_base64"]) region = data.vault_kv_secret_v2.oci_creds.data["region"] } # Test resource - list availability domains in your tenancy data "oci_identity_availability_domains" "ads" { compartment_id = data.vault_kv_secret_v2.oci_creds.data["tenancy_ocid"] } output "availability_domains" { value = data.oci_identity_availability_domains.ads.availability_domains } # Optional: Create a test VCN (uncomment if you want to test full provisioning) # resource "oci_core_vcn" "test_vcn" { # compartment_id = data.vault_kv_secret_v2.oci_creds.data["tenancy_ocid"] # display_name = "tfe-test-vcn" # cidr_block = "10.0.0.0/16" # } # Create a test Object Storage bucket resource "oci_objectstorage_bucket" "test_bucket" { # The compartment ID is your tenancy OCID for the free tier compartment_id = data.vault_kv_secret_v2.oci_creds.data["tenancy_ocid"] name = "tfe-test-bucket-${random_string.bucket_suffix.result}" namespace = data.oci_objectstorage_namespace.test_namespace.namespace access_type = "NoPublicAccess" # Keeps your test data private } # Get your tenancy's Object Storage namespace (a unique identifier) data "oci_objectstorage_namespace" "test_namespace" { compartment_id = data.vault_kv_secret_v2.oci_creds.data["tenancy_ocid"] } # Generate a random suffix for a globally unique bucket name resource "random_string" "bucket_suffix" { length = 8 special = false upper = false } # Upload a simple test file to the bucket resource "oci_objectstorage_object" "test_object" { namespace = data.oci_objectstorage_namespace.test_namespace.namespace bucket = oci_objectstorage_bucket.test_bucket.name object = "hello-world.txt" content = "TFE successfully wrote to OCI Object Storage using Vault credentials!" content_type = "text/plain" } output "created_bucket_name" { value = oci_objectstorage_bucket.test_bucket.name } -
Queue a plan in your TFE workspace. The run should successfully initialize the providers, authenticate to OCI, and show the list of availability domains in the output.
Additional Information
https://developer.hashicorp.com/terraform/enterprise/workspaces/dynamic-provider-credentials
-
Verifying JWT claim can be performed by checking the OIDC config:
curl -khttps://YOUR_TFE_INSTANCE.com/.well-known/openid-configuration