KeyEnvKeyEnv
Guides

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:

MethodBest ForRequirements
Native ProviderAll workflowsService token only
External Data SourceLegacy workflowsKeyEnv CLI installed
HTTP Data SourceCI/CD environmentsService token only

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 SourceDescription
keyenv_projectFetch project metadata by ID
keyenv_environmentLook up an environment by name
keyenv_secretsFetch all secrets as a map (sensitive)
keyenv_secretFetch 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)

Use Terraform's external data source to call the KeyEnv CLI.

Prerequisites

  1. Install the KeyEnv CLI:

    curl -fsSL https://keyenv.dev/install.sh | bash
  2. 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:

  1. Go to your workspace settings
  2. Add a variable:
    • Key: TF_VAR_keyenv_token
    • Value: Your service token (starts with env_)
    • Category: Environment variable
    • Sensitive: Yes

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 rm to 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 json manually 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

On this page