Terraform Integration
Use KeyEnv secrets in your Terraform configurations.
Terraform Integration
Use KeyEnv secrets in your Terraform configurations using the external data source or CLI.
Overview
KeyEnv provides a native Terraform provider for seamless secrets integration:
terraform {
required_providers {
keyenv = {
source = "keyenv/keyenv"
version = "~> 1.0"
}
}
}You can also integrate without the provider using the external data source or HTTP data source:
| Method | Best For | Requirements |
|---|---|---|
| Native Provider | All workflows | Service token only |
| External Data Source | Legacy workflows | KeyEnv CLI installed |
| HTTP Data Source | CI/CD environments | Service token only |
Native Terraform Provider (Recommended)
The keyenv/keyenv provider fetches secrets directly via the KeyEnv API.
Setup
terraform {
required_providers {
keyenv = {
source = "keyenv/keyenv"
version = "~> 1.0"
}
}
}
provider "keyenv" {
# Or set KEYENV_TOKEN environment variable
token = var.keyenv_token
}Fetch All Secrets
data "keyenv_environment" "prod" {
project_id = var.keyenv_project_id
name = "production"
}
data "keyenv_secrets" "prod" {
project_id = var.keyenv_project_id
environment_id = data.keyenv_environment.prod.id
}
resource "aws_db_instance" "main" {
username = data.keyenv_secrets.prod.secrets["DB_USERNAME"]
password = data.keyenv_secrets.prod.secrets["DB_PASSWORD"]
}Fetch a Single Secret
data "keyenv_secret" "db_url" {
project_id = var.keyenv_project_id
environment_id = data.keyenv_environment.prod.id
key = "DATABASE_URL"
}
resource "some_resource" "example" {
connection_string = data.keyenv_secret.db_url.value
}Available Data Sources
| Data Source | Description |
|---|---|
keyenv_project | Fetch project metadata by ID |
keyenv_environment | Look up an environment by name |
keyenv_secrets | Fetch all secrets as a map (sensitive) |
keyenv_secret | Fetch a single secret by key (sensitive) |
Features
- Fetch secrets at plan/apply time
- Support for any Terraform workflow
- Works with Terraform Cloud and local execution
- No state file secrets exposure (values are marked sensitive)
Option 1: External Data Source (Recommended)
Use Terraform's external data source to call the KeyEnv CLI.
Prerequisites
-
Install the KeyEnv CLI:
curl -fsSL https://keyenv.dev/install.sh | bash -
Authenticate with KeyEnv:
keyenv auth login
Basic Usage
# main.tf
# Fetch all secrets from an environment
data "external" "keyenv_secrets" {
program = ["bash", "-c", <<-EOF
keyenv export --env production --format json 2>/dev/null
EOF
]
}
# Use secrets in your resources
resource "aws_db_instance" "main" {
identifier = "myapp-db"
engine = "postgres"
username = data.external.keyenv_secrets.result["DB_USERNAME"]
password = data.external.keyenv_secrets.result["DB_PASSWORD"]
}With Service Token
For CI/CD environments, use a service token:
# main.tf
variable "keyenv_token" {
type = string
sensitive = true
description = "KeyEnv service token"
}
variable "keyenv_project" {
type = string
description = "KeyEnv project ID"
}
variable "environment" {
type = string
default = "production"
}
data "external" "keyenv_secrets" {
program = ["bash", "-c", <<-EOF
KEYENV_TOKEN="${var.keyenv_token}" \
keyenv export \
--project "${var.keyenv_project}" \
--env "${var.environment}" \
--format json 2>/dev/null
EOF
]
}Wrapper Script
For better error handling, create a wrapper script:
#!/bin/bash
# scripts/fetch-secrets.sh
set -euo pipefail
ENV="${1:-production}"
PROJECT="${KEYENV_PROJECT:-}"
# Build command
CMD="keyenv export --env $ENV --format json"
if [ -n "$PROJECT" ]; then
CMD="$CMD --project $PROJECT"
fi
# Execute and handle errors
if ! OUTPUT=$($CMD 2>&1); then
echo '{"error": "Failed to fetch secrets"}' >&2
exit 1
fi
echo "$OUTPUT"# main.tf
data "external" "keyenv_secrets" {
program = ["bash", "${path.module}/scripts/fetch-secrets.sh", var.environment]
}
output "secrets_loaded" {
value = length(keys(data.external.keyenv_secrets.result))
sensitive = false
}Option 2: HTTP Data Source
Fetch secrets directly from the KeyEnv API without the CLI.
Setup
# main.tf
variable "keyenv_token" {
type = string
sensitive = true
description = "KeyEnv service token (starts with env_)"
}
variable "keyenv_project_id" {
type = string
description = "KeyEnv project ID"
}
variable "environment" {
type = string
default = "production"
}
data "http" "keyenv_secrets" {
url = "https://api.keyenv.dev/api/v1/projects/${var.keyenv_project_id}/environments/${var.environment}/secrets/export"
request_headers = {
Authorization = "Bearer ${var.keyenv_token}"
Content-Type = "application/json"
}
}
locals {
secrets_response = jsondecode(data.http.keyenv_secrets.response_body)
# Convert array to map for easy access
secrets = {
for secret in local.secrets_response.data :
secret.key => secret.value
}
}
# Use secrets
resource "aws_db_instance" "main" {
identifier = "myapp-db"
engine = "postgres"
username = local.secrets["DB_USERNAME"]
password = local.secrets["DB_PASSWORD"]
}Error Handling
Add validation for the API response:
locals {
secrets_response = jsondecode(data.http.keyenv_secrets.response_body)
# Validate response
secrets = data.http.keyenv_secrets.status_code == 200 ? {
for secret in local.secrets_response.data :
secret.key => secret.value
} : {}
}
# Check for errors
resource "null_resource" "validate_secrets" {
count = data.http.keyenv_secrets.status_code != 200 ? 1 : 0
provisioner "local-exec" {
command = "echo 'Error fetching secrets: ${data.http.keyenv_secrets.status_code}' && exit 1"
}
}Terraform Cloud / Enterprise
Using Environment Variables
In Terraform Cloud, set your service token as a sensitive environment variable:
- Go to your workspace settings
- Add a variable:
- Key:
TF_VAR_keyenv_token - Value: Your service token (starts with
env_) - Category: Environment variable
- Sensitive: Yes
- Key:
Workspace Example
# variables.tf
variable "keyenv_token" {
type = string
sensitive = true
description = "KeyEnv service token"
}
variable "keyenv_project_id" {
type = string
description = "KeyEnv project ID"
}Module Pattern
Create a reusable module for KeyEnv integration:
# modules/keyenv-secrets/main.tf
variable "project_id" {
type = string
description = "KeyEnv project ID"
}
variable "environment" {
type = string
description = "Environment name"
}
variable "token" {
type = string
sensitive = true
description = "KeyEnv service token"
}
variable "api_url" {
type = string
default = "https://api.keyenv.dev"
}
data "http" "secrets" {
url = "${var.api_url}/api/v1/projects/${var.project_id}/environments/${var.environment}/secrets/export"
request_headers = {
Authorization = "Bearer ${var.token}"
Content-Type = "application/json"
}
}
locals {
response = jsondecode(data.http.secrets.response_body)
}
output "secrets" {
value = {
for secret in local.response.data :
secret.key => secret.value
}
sensitive = true
}
output "count" {
value = length(local.response.data)
}Usage:
# main.tf
module "keyenv" {
source = "./modules/keyenv-secrets"
project_id = "proj_abc123"
environment = "production"
token = var.keyenv_token
}
resource "aws_db_instance" "main" {
username = module.keyenv.secrets["DB_USERNAME"]
password = module.keyenv.secrets["DB_PASSWORD"]
}Security Best Practices
1. Mark Outputs as Sensitive
Always mark secret outputs as sensitive:
output "database_password" {
value = local.secrets["DB_PASSWORD"]
sensitive = true
}2. Use Minimal Token Scopes
Create service tokens with only the required permissions:
- Read-only access to secrets
- Scoped to specific projects
- Scoped to specific environments
3. Avoid State File Exposure
Secrets fetched via data sources are stored in Terraform state. Consider:
- Using Terraform Cloud with encrypted state
- Enabling state encryption in your backend
- Using
terraform state rmto remove sensitive data if needed
4. Rotate Tokens Regularly
Use short-lived service tokens and rotate them periodically:
variable "keyenv_token" {
type = string
sensitive = true
description = "KeyEnv service token - rotate monthly"
validation {
condition = can(regex("^env_", var.keyenv_token))
error_message = "Token must start with 'env_'"
}
}Troubleshooting
"Authentication failed"
- Verify your service token is correct
- Check the token hasn't expired
- Ensure the token has access to the specified project
"Project or environment not found"
- Verify the project ID is correct
- Verify the environment name matches exactly (case-sensitive)
- Check the project exists in KeyEnv
"External command failed"
- Ensure the KeyEnv CLI is installed and in PATH
- Run
keyenv export --env <env> --format jsonmanually to debug - Check for shell escaping issues in the external data source
State file contains secrets
This is expected behavior with Terraform data sources. Mitigate by:
- Using encrypted state backends
- Restricting state file access
- Using Terraform Cloud with secure state storage
More Resources
- terraform-provider-keyenv on GitHub
- KeyEnv Service Tokens — create a read-only token for Terraform
- Terraform Registry