Keep the personal half of the desktop in one place, and ask before installing it
Agent instructions, skills, SSH host aliases and expansion triggers are worth having identical on every machine one person owns, and belong in none of the shared configuration. They live in user/ now, with a manifest saying where each piece goes and a link-user stage that puts it there. That stage does nothing unless the machine said yes. Somebody who clones Panama to try the desktop keeps their own ~/.claude/CLAUDE.md exactly where it was; the question names the destinations and defaults to no. Anything displaced goes to config/old rather than being deleted. ~/.claude/CLAUDE.md and ~/.codex/AGENTS.md were byte-identical copies of one file, which is the drift this exists to prevent. Also adds the vitals toggles for the battery and Claude usage readouts, which had preferences and no way to reach them.
This commit is contained in:
@@ -0,0 +1,193 @@
|
||||
# Provider Setup & Authentication
|
||||
|
||||
Configure the Infisical Terraform Provider with the correct authentication method for your environment.
|
||||
|
||||
## Provider Source Block
|
||||
|
||||
All Terraform configurations using Infisical must specify the official provider from Terraform Registry:
|
||||
|
||||
```hcl
|
||||
terraform {
|
||||
required_providers {
|
||||
infisical = {
|
||||
source = "infisical/infisical"
|
||||
version = "~> 0.13" # Use latest stable version
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
provider "infisical" {
|
||||
# Auth configuration goes here (see below)
|
||||
}
|
||||
```
|
||||
|
||||
## Authentication Methods
|
||||
|
||||
### 1. Universal Auth (Recommended for Most Use Cases)
|
||||
|
||||
Universal Auth uses a `client_id` and `client_secret` to authenticate the provider. This is the most straightforward method for local development and self-hosted environments.
|
||||
|
||||
**Setup**:
|
||||
1. In Infisical, create a Machine Identity
|
||||
2. Attach a Universal Auth method with a client ID and secret
|
||||
3. Grant the identity appropriate project/org permissions
|
||||
|
||||
**HCL Configuration**:
|
||||
|
||||
```hcl
|
||||
provider "infisical" {
|
||||
client_id = var.infisical_client_id
|
||||
client_secret = var.infisical_client_secret
|
||||
}
|
||||
```
|
||||
|
||||
Or use environment variables:
|
||||
|
||||
```hcl
|
||||
provider "infisical" {
|
||||
# Reads from:
|
||||
# - INFISICAL_UNIVERSAL_AUTH_CLIENT_ID
|
||||
# - INFISICAL_UNIVERSAL_AUTH_CLIENT_SECRET
|
||||
}
|
||||
```
|
||||
|
||||
**Environment Variables**:
|
||||
|
||||
```bash
|
||||
export INFISICAL_UNIVERSAL_AUTH_CLIENT_ID="your-client-id"
|
||||
export INFISICAL_UNIVERSAL_AUTH_CLIENT_SECRET="your-client-secret"
|
||||
```
|
||||
|
||||
### 2. OIDC (Recommended for Terraform Cloud)
|
||||
|
||||
OIDC (OpenID Connect) is the recommended authentication method for CI/CD platforms like Terraform Cloud and CircleCI. It eliminates the need to store long-lived secrets.
|
||||
|
||||
**Setup**:
|
||||
1. In Infisical, create a Machine Identity
|
||||
2. Add an OIDC Auth method
|
||||
3. Configure the identity issuer URL and audience
|
||||
4. In your CI/CD platform, set the `TFC_WORKLOAD_IDENTITY_TOKEN` environment variable
|
||||
|
||||
**HCL Configuration**:
|
||||
|
||||
```hcl
|
||||
provider "infisical" {
|
||||
identity_id = var.infisical_identity_id
|
||||
token_environment_variable_name = "INFISICAL_TOKEN" # Variable containing OIDC token
|
||||
}
|
||||
```
|
||||
|
||||
Or for Terraform Cloud with automatic token injection:
|
||||
|
||||
```hcl
|
||||
provider "infisical" {
|
||||
identity_id = var.infisical_machine_identity_id
|
||||
token_environment_variable_name = "TFC_WORKLOAD_IDENTITY_TOKEN"
|
||||
}
|
||||
```
|
||||
|
||||
**Terraform Cloud Setup**:
|
||||
```hcl
|
||||
# Set in your TFC workspace variables
|
||||
variable "infisical_machine_identity_id" {
|
||||
type = string
|
||||
# HCP Terraform will inject: TFC_WORKLOAD_IDENTITY_TOKEN
|
||||
}
|
||||
|
||||
provider "infisical" {
|
||||
identity_id = var.infisical_machine_identity_id
|
||||
token_environment_variable_name = "TFC_WORKLOAD_IDENTITY_TOKEN"
|
||||
}
|
||||
```
|
||||
|
||||
See [Terraform Cloud OIDC Setup](/references/terraform-cloud-oidc.md) for complete step-by-step guide.
|
||||
|
||||
### 3. Service Token (Deprecated — Do Not Use)
|
||||
|
||||
Service tokens are deprecated and should not be used in new configurations. Use Universal Auth or OIDC instead.
|
||||
|
||||
```hcl
|
||||
# ⚠️ DEPRECATED — Do not use
|
||||
provider "infisical" {
|
||||
token = var.infisical_service_token
|
||||
}
|
||||
```
|
||||
|
||||
## Environment Variables Reference
|
||||
|
||||
| Variable | Auth Method | Purpose |
|
||||
|----------|-------------|---------|
|
||||
| `INFISICAL_UNIVERSAL_AUTH_CLIENT_ID` | Universal Auth | Client ID for authentication |
|
||||
| `INFISICAL_UNIVERSAL_AUTH_CLIENT_SECRET` | Universal Auth | Client secret for authentication |
|
||||
| `INFISICAL_TOKEN` | Legacy/Custom | Deprecated service token or custom OIDC token variable |
|
||||
| `INFISICAL_SITE_URL` | All methods | Custom Infisical instance URL (e.g., `https://infisical.mycompany.com`) |
|
||||
|
||||
## Self-Hosted Configuration
|
||||
|
||||
If you're running a self-hosted Infisical instance, you must explicitly set the `host` parameter:
|
||||
|
||||
```hcl
|
||||
provider "infisical" {
|
||||
host = "https://infisical.mycompany.com"
|
||||
client_id = var.infisical_client_id
|
||||
client_secret = var.infisical_client_secret
|
||||
}
|
||||
```
|
||||
|
||||
Or via environment variable:
|
||||
|
||||
```bash
|
||||
export INFISICAL_SITE_URL="https://infisical.mycompany.com"
|
||||
```
|
||||
|
||||
## Cloud Deployment Configuration
|
||||
|
||||
For Infisical Cloud (app.infisical.com), the `host` parameter is optional and defaults to the cloud instance. You only need auth credentials:
|
||||
|
||||
```hcl
|
||||
provider "infisical" {
|
||||
client_id = var.infisical_client_id
|
||||
client_secret = var.infisical_client_secret
|
||||
}
|
||||
```
|
||||
|
||||
## Complete Example with Terraform Variables
|
||||
|
||||
```hcl
|
||||
variable "infisical_client_id" {
|
||||
type = string
|
||||
description = "Infisical Machine Identity Client ID"
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
variable "infisical_client_secret" {
|
||||
type = string
|
||||
description = "Infisical Machine Identity Client Secret"
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
provider "infisical" {
|
||||
client_id = var.infisical_client_id
|
||||
client_secret = var.infisical_client_secret
|
||||
}
|
||||
|
||||
# Now you can use Infisical resources
|
||||
ephemeral "infisical_secret" "db_password" {
|
||||
workspace_id = "your-workspace-id"
|
||||
env_slug = "prod"
|
||||
secret_key = "DB_PASSWORD"
|
||||
}
|
||||
|
||||
output "database_password" {
|
||||
value = ephemeral.infisical_secret.db_password.value
|
||||
sensitive = true
|
||||
}
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**"Error: Unauthorized"**: Check that your client ID and secret are correct and that the Machine Identity has permissions for the workspace/project you're accessing.
|
||||
|
||||
**"Error: identity_id is required for OIDC"**: Ensure you've set the OIDC identity ID and that the token environment variable is properly set in your CI/CD platform.
|
||||
|
||||
**"Error: host is required for self-hosted"**: Self-hosted Infisical instances require explicit host configuration. Verify your `INFISICAL_SITE_URL` or `host` parameter.
|
||||
@@ -0,0 +1,417 @@
|
||||
# Resources & Data Sources
|
||||
|
||||
Guide to Infisical Terraform resources and data sources for managing secrets, roles, and access policies.
|
||||
|
||||
## Ephemeral Resource: infisical_secret
|
||||
|
||||
The `ephemeral` resource is the **recommended way** to fetch secrets in Terraform 1.10+. Secret values are **never stored in state**.
|
||||
|
||||
**Key Benefit**: Prevents secrets from being persisted in your Terraform state files, reducing security risk.
|
||||
|
||||
**Requires**: Terraform 1.10 or later.
|
||||
|
||||
### Configuration
|
||||
|
||||
```hcl
|
||||
ephemeral "infisical_secret" "example" {
|
||||
workspace_id = "your-workspace-id"
|
||||
env_slug = "prod"
|
||||
secret_key = "DATABASE_PASSWORD"
|
||||
folder_path = "/" # Optional, defaults to "/"
|
||||
}
|
||||
|
||||
output "db_password" {
|
||||
value = ephemeral.infisical_secret.example.value
|
||||
sensitive = true
|
||||
}
|
||||
```
|
||||
|
||||
### Attributes
|
||||
|
||||
| Attribute | Type | Description |
|
||||
|-----------|------|-------------|
|
||||
| `workspace_id` | string | ID of the Infisical workspace |
|
||||
| `env_slug` | string | Environment slug (e.g., "dev", "staging", "prod") |
|
||||
| `secret_key` | string | Name of the secret to retrieve |
|
||||
| `folder_path` | string | Path within the environment (optional, defaults to "/") |
|
||||
| `value` | string (computed) | The secret value (only available during apply, never stored in state) |
|
||||
|
||||
### JSON Secrets
|
||||
|
||||
For JSON-formatted secrets, use `jsondecode()` to parse the value:
|
||||
|
||||
```hcl
|
||||
ephemeral "infisical_secret" "api_config" {
|
||||
workspace_id = "your-workspace-id"
|
||||
env_slug = "prod"
|
||||
secret_key = "API_CONFIG"
|
||||
}
|
||||
|
||||
locals {
|
||||
config = jsondecode(ephemeral.infisical_secret.api_config.value)
|
||||
}
|
||||
|
||||
output "api_key" {
|
||||
value = local.config.api_key
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
output "api_url" {
|
||||
value = local.config.api_url
|
||||
}
|
||||
```
|
||||
|
||||
### Usage with Resources
|
||||
|
||||
```hcl
|
||||
# Fetch secret and use it to configure a provider
|
||||
ephemeral "infisical_secret" "aws_access_key" {
|
||||
workspace_id = "your-workspace-id"
|
||||
env_slug = "prod"
|
||||
secret_key = "AWS_ACCESS_KEY_ID"
|
||||
}
|
||||
|
||||
ephemeral "infisical_secret" "aws_secret_key" {
|
||||
workspace_id = "your-workspace-id"
|
||||
env_slug = "prod"
|
||||
secret_key = "AWS_SECRET_ACCESS_KEY"
|
||||
}
|
||||
|
||||
provider "aws" {
|
||||
access_key = ephemeral.infisical_secret.aws_access_key.value
|
||||
secret_key = ephemeral.infisical_secret.aws_secret_key.value
|
||||
region = "us-east-1"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Data Source: infisical_secrets
|
||||
|
||||
The `data` source retrieves all secrets from a specific environment and folder. **⚠️ Warning**: Secret values **ARE stored in Terraform state**. Use `ephemeral` instead when possible.
|
||||
|
||||
### Configuration
|
||||
|
||||
```hcl
|
||||
data "infisical_secrets" "all_secrets" {
|
||||
workspace_id = "your-workspace-id"
|
||||
env_slug = "prod"
|
||||
folder_path = "/" # Optional, defaults to "/"
|
||||
}
|
||||
|
||||
output "all_secrets" {
|
||||
value = data.infisical_secrets.all_secrets.secrets
|
||||
sensitive = true
|
||||
}
|
||||
```
|
||||
|
||||
### Attributes
|
||||
|
||||
| Attribute | Type | Description |
|
||||
|-----------|------|-------------|
|
||||
| `workspace_id` | string | ID of the Infisical workspace |
|
||||
| `env_slug` | string | Environment slug |
|
||||
| `folder_path` | string | Path within the environment (optional, defaults to "/") |
|
||||
| `secrets` | map(string) | Map of all secrets in the folder (key → value) |
|
||||
|
||||
### Usage Example
|
||||
|
||||
```hcl
|
||||
data "infisical_secrets" "all_secrets" {
|
||||
workspace_id = "your-workspace-id"
|
||||
env_slug = "prod"
|
||||
}
|
||||
|
||||
# Access individual secrets
|
||||
output "database_password" {
|
||||
value = data.infisical_secrets.all_secrets.secrets["DB_PASSWORD"]
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
# Use secrets in resource configuration
|
||||
resource "aws_db_instance" "main" {
|
||||
allocated_storage = 100
|
||||
engine = "postgres"
|
||||
engine_version = "15.2"
|
||||
instance_class = "db.t3.micro"
|
||||
username = "admin"
|
||||
password = data.infisical_secrets.all_secrets.secrets["DB_PASSWORD"]
|
||||
skip_final_snapshot = true
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Resource: infisical_project_role
|
||||
|
||||
Create and manage custom project roles with granular permissions.
|
||||
|
||||
### Permissions v2 Format (Recommended)
|
||||
|
||||
Use `permissions_v2` for modern, flexible permission definitions with subject-action structure.
|
||||
|
||||
```hcl
|
||||
resource "infisical_project_role" "developer" {
|
||||
project_id = "your-project-id"
|
||||
name = "Developer"
|
||||
|
||||
permissions_v2 = [
|
||||
{
|
||||
subject = "secrets"
|
||||
actions = ["read", "create", "edit"]
|
||||
},
|
||||
{
|
||||
subject = "secret-folders"
|
||||
actions = ["read", "create", "edit"]
|
||||
},
|
||||
{
|
||||
subject = "secret-imports"
|
||||
actions = ["read"]
|
||||
},
|
||||
{
|
||||
subject = "dynamic-secrets"
|
||||
actions = ["read", "lease"]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Permissions v2 Subjects & Actions
|
||||
|
||||
| Subject | Available Actions |
|
||||
|---------|-------------------|
|
||||
| `secrets` | read, create, edit, delete, read-metadata |
|
||||
| `secret-folders` | read, create, edit, delete |
|
||||
| `secret-imports` | read, create, edit, delete |
|
||||
| `dynamic-secrets` | read, read-root-credential, create-root-credential, edit-root-credential, delete-root-credential, lease |
|
||||
|
||||
### Permissions v1 Format (Deprecated)
|
||||
|
||||
The old `permissions` attribute uses inverted logic (denying actions). It's deprecated—always prefer `permissions_v2`.
|
||||
|
||||
```hcl
|
||||
# ⚠️ DEPRECATED — Do not use in new code
|
||||
resource "infisical_project_role" "viewer" {
|
||||
project_id = "your-project-id"
|
||||
name = "Viewer"
|
||||
|
||||
permissions = [
|
||||
{
|
||||
action = "create"
|
||||
inverted = true
|
||||
},
|
||||
{
|
||||
action = "edit"
|
||||
inverted = true
|
||||
},
|
||||
{
|
||||
action = "delete"
|
||||
inverted = true
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Complete Example with Multiple Roles
|
||||
|
||||
```hcl
|
||||
resource "infisical_project_role" "admin" {
|
||||
project_id = "your-project-id"
|
||||
name = "Admin"
|
||||
|
||||
permissions_v2 = [
|
||||
{
|
||||
subject = "secrets"
|
||||
actions = ["read", "create", "edit", "delete"]
|
||||
},
|
||||
{
|
||||
subject = "secret-folders"
|
||||
actions = ["read", "create", "edit", "delete"]
|
||||
},
|
||||
{
|
||||
subject = "secret-imports"
|
||||
actions = ["read", "create", "edit", "delete"]
|
||||
},
|
||||
{
|
||||
subject = "dynamic-secrets"
|
||||
actions = ["read", "read-root-credential", "create-root-credential", "edit-root-credential", "delete-root-credential", "lease"]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
resource "infisical_project_role" "ops" {
|
||||
project_id = "your-project-id"
|
||||
name = "Ops"
|
||||
|
||||
permissions_v2 = [
|
||||
{
|
||||
subject = "secrets"
|
||||
actions = ["read"]
|
||||
},
|
||||
{
|
||||
subject = "dynamic-secrets"
|
||||
actions = ["read", "lease"]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Resource: infisical_access_approval_policy
|
||||
|
||||
Enforce approval workflows for sensitive secret operations.
|
||||
|
||||
### Configuration
|
||||
|
||||
```hcl
|
||||
resource "infisical_access_approval_policy" "prod_secrets" {
|
||||
project_id = "your-project-id"
|
||||
name = "Production Secrets Approval"
|
||||
environment_slug = "prod"
|
||||
secret_path = "/" # Optional, specific path or "/" for all
|
||||
|
||||
approvers = [
|
||||
{
|
||||
type = "username"
|
||||
username = "[email protected]"
|
||||
},
|
||||
{
|
||||
type = "username"
|
||||
username = "[email protected]"
|
||||
}
|
||||
]
|
||||
|
||||
required_approvals = 1
|
||||
enforcement_level = "hard" # "soft" (warning) or "hard" (blocking)
|
||||
}
|
||||
```
|
||||
|
||||
### Attributes
|
||||
|
||||
| Attribute | Type | Description |
|
||||
|-----------|------|-------------|
|
||||
| `project_id` | string | ID of the Infisical project |
|
||||
| `name` | string | Policy name for identification |
|
||||
| `environment_slug` | string | Environment to apply policy (e.g., "prod") |
|
||||
| `secret_path` | string | Secret path (optional, "/" for all secrets) |
|
||||
| `approvers` | list(object) | List of approvers with `type` and `username` |
|
||||
| `required_approvals` | number | Number of approvals required before access granted |
|
||||
| `enforcement_level` | string | "soft" (warning) or "hard" (blocking access without approval) |
|
||||
|
||||
### Example: Multiple Approval Policies
|
||||
|
||||
```hcl
|
||||
# Require approval for all production secrets
|
||||
resource "infisical_access_approval_policy" "prod_all" {
|
||||
project_id = "your-project-id"
|
||||
name = "Production - All Secrets"
|
||||
environment_slug = "prod"
|
||||
secret_path = "/"
|
||||
|
||||
approvers = [
|
||||
{
|
||||
type = "username"
|
||||
username = "[email protected]"
|
||||
},
|
||||
{
|
||||
type = "username"
|
||||
username = "[email protected]"
|
||||
}
|
||||
]
|
||||
|
||||
required_approvals = 2
|
||||
enforcement_level = "hard"
|
||||
}
|
||||
|
||||
# Require approval for database secrets only
|
||||
resource "infisical_access_approval_policy" "prod_database" {
|
||||
project_id = "your-project-id"
|
||||
name = "Production - Database Secrets"
|
||||
environment_slug = "prod"
|
||||
secret_path = "/database"
|
||||
|
||||
approvers = [
|
||||
{
|
||||
type = "username"
|
||||
username = "[email protected]"
|
||||
}
|
||||
]
|
||||
|
||||
required_approvals = 1
|
||||
enforcement_level = "hard"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Complete Example: Secrets + Roles + Approvals
|
||||
|
||||
```hcl
|
||||
terraform {
|
||||
required_providers {
|
||||
infisical = {
|
||||
source = "infisical/infisical"
|
||||
version = "~> 0.13"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
provider "infisical" {
|
||||
client_id = var.infisical_client_id
|
||||
client_secret = var.infisical_client_secret
|
||||
}
|
||||
|
||||
variable "infisical_client_id" {
|
||||
type = string
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
variable "infisical_client_secret" {
|
||||
type = string
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
# Fetch production database password (never in state)
|
||||
ephemeral "infisical_secret" "db_password" {
|
||||
workspace_id = "ws-abc123"
|
||||
env_slug = "prod"
|
||||
secret_key = "DATABASE_PASSWORD"
|
||||
}
|
||||
|
||||
# Define developer role with permission to read secrets
|
||||
resource "infisical_project_role" "developer" {
|
||||
project_id = "proj-xyz789"
|
||||
name = "Developer"
|
||||
|
||||
permissions_v2 = [
|
||||
{
|
||||
subject = "secrets"
|
||||
actions = ["read"]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
# Require approval for production secret access
|
||||
resource "infisical_access_approval_policy" "prod_approval" {
|
||||
project_id = "proj-xyz789"
|
||||
name = "Production Approval"
|
||||
environment_slug = "prod"
|
||||
secret_path = "/"
|
||||
|
||||
approvers = [
|
||||
{
|
||||
type = "username"
|
||||
username = "[email protected]"
|
||||
}
|
||||
]
|
||||
|
||||
required_approvals = 1
|
||||
enforcement_level = "hard"
|
||||
}
|
||||
|
||||
output "database_password" {
|
||||
value = ephemeral.infisical_secret.db_password.value
|
||||
sensitive = true
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,251 @@
|
||||
# Terraform Cloud OIDC Integration
|
||||
|
||||
Set up OIDC (OpenID Connect) authentication between Terraform Cloud and Infisical. This is the recommended production pattern for secure, token-free authentication.
|
||||
|
||||
## Overview
|
||||
|
||||
With OIDC, Terraform Cloud generates a short-lived workload identity token and signs it with a private key. Infisical validates the token against Terraform Cloud's public keys, confirming the identity without storing long-lived secrets. This eliminates the risk of credential leakage and simplifies rotation.
|
||||
|
||||
**Key Benefits**:
|
||||
- No long-lived secrets to rotate
|
||||
- Automatic token refresh for each Terraform run
|
||||
- Audit trail of which TFC workspace accessed which secrets
|
||||
- Compliance-friendly for regulated environments
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Infisical workspace with admin access
|
||||
- Terraform Cloud account with permissions to manage workspaces and variables
|
||||
- Terraform 1.2+ (for Terraform Cloud workspaces)
|
||||
|
||||
## Step 1: Create a Machine Identity in Infisical
|
||||
|
||||
1. In Infisical, navigate to **Admin** → **Machine Identities**
|
||||
2. Click **Create Machine Identity**
|
||||
3. Enter a name: `terraform-cloud`
|
||||
4. Optionally add a description: `OIDC authentication for Terraform Cloud`
|
||||
5. Click **Create**
|
||||
|
||||
Note the **Machine Identity ID** (you'll need this later). It will look like: `machine-identity-abc123xyz789`
|
||||
|
||||
## Step 2: Add OIDC Auth Method
|
||||
|
||||
1. In the Machine Identity detail page, navigate to **Auth Methods**
|
||||
2. Click **Add Auth Method** → **OIDC**
|
||||
3. Configure the OIDC settings:
|
||||
|
||||
| Field | Value |
|
||||
|-------|-------|
|
||||
| **OIDC Discovery URL** | `https://app.terraform.io` |
|
||||
| **Client ID** | `terraform` (or custom OIDC app ID from TFC) |
|
||||
| **Issuer** | `https://app.terraform.io` |
|
||||
| **Audience** | Match the value of `TFC_WORKLOAD_IDENTITY_AUDIENCE` in your TFC workspace (see Step 3) |
|
||||
|
||||
4. Click **Save**
|
||||
|
||||
### OIDC Discovery URL & Issuer Explanation
|
||||
|
||||
Terraform Cloud publishes its OIDC configuration at `https://app.terraform.io/.well-known/openid-configuration`. Both the discovery URL and issuer are the same for TFC: `https://app.terraform.io`.
|
||||
|
||||
## Step 3: Configure Terraform Cloud Workspace Variables
|
||||
|
||||
In your Terraform Cloud workspace:
|
||||
|
||||
1. Navigate to **Variables** (in the workspace settings)
|
||||
2. Add **Environment Variable**: `TFC_WORKLOAD_IDENTITY_AUDIENCE`
|
||||
- Value: `aws.terraform.io` (or your custom audience identifier)
|
||||
- This must match the **Audience** configured in Step 2
|
||||
|
||||
3. Optionally add the Machine Identity ID as a variable for reference:
|
||||
- Name: `INFISICAL_MACHINE_IDENTITY_ID`
|
||||
- Value: `machine-identity-abc123xyz789` (from Step 1)
|
||||
- Mark as **Sensitive** if desired
|
||||
|
||||
Example TFC workspace variables:
|
||||
```
|
||||
TFC_WORKLOAD_IDENTITY_AUDIENCE=aws.terraform.io
|
||||
INFISICAL_MACHINE_IDENTITY_ID=machine-identity-abc123xyz789
|
||||
```
|
||||
|
||||
## Step 4: Configure the Infisical Provider in Terraform
|
||||
|
||||
Write the Infisical provider configuration in your Terraform code. The `TFC_WORKLOAD_IDENTITY_TOKEN` environment variable is automatically injected by Terraform Cloud during each run.
|
||||
|
||||
```hcl
|
||||
terraform {
|
||||
cloud {
|
||||
organization = "my-org"
|
||||
workspaces {
|
||||
name = "my-workspace"
|
||||
}
|
||||
}
|
||||
|
||||
required_providers {
|
||||
infisical = {
|
||||
source = "infisical/infisical"
|
||||
version = "~> 0.13"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
variable "infisical_machine_identity_id" {
|
||||
type = string
|
||||
description = "Machine Identity ID for OIDC authentication"
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
provider "infisical" {
|
||||
identity_id = var.infisical_machine_identity_id
|
||||
token_environment_variable_name = "TFC_WORKLOAD_IDENTITY_TOKEN"
|
||||
}
|
||||
```
|
||||
|
||||
### Breakdown
|
||||
|
||||
- `identity_id`: The Machine Identity ID from Infisical (passed as a TFC variable)
|
||||
- `token_environment_variable_name = "TFC_WORKLOAD_IDENTITY_TOKEN"`: Tells the provider to read the OIDC token from this environment variable, which Terraform Cloud automatically sets
|
||||
|
||||
## Step 5: Grant Permissions to the Machine Identity
|
||||
|
||||
The Machine Identity needs permissions to access the secrets, projects, and environments it will interact with.
|
||||
|
||||
1. In Infisical, navigate to **Projects**
|
||||
2. Select the project where your secrets live
|
||||
3. Go to **Access Control** → **Machine Identities**
|
||||
4. Assign the `terraform-cloud` Machine Identity with appropriate roles or permissions
|
||||
|
||||
Common permissions for Terraform:
|
||||
- **Read secrets**: Allow the identity to fetch secrets via ephemeral resources
|
||||
- **Manage resources** (if creating/updating project roles, policies, etc.): Grant higher-level permissions as needed
|
||||
|
||||
## Complete Working Example
|
||||
|
||||
```hcl
|
||||
# versions.tf
|
||||
terraform {
|
||||
cloud {
|
||||
organization = "my-company"
|
||||
workspaces {
|
||||
name = "production-secrets"
|
||||
}
|
||||
}
|
||||
|
||||
required_version = ">= 1.10"
|
||||
required_providers {
|
||||
infisical = {
|
||||
source = "infisical/infisical"
|
||||
version = "~> 0.13"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# variables.tf
|
||||
variable "infisical_machine_identity_id" {
|
||||
type = string
|
||||
description = "Infisical Machine Identity ID for OIDC"
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
variable "infisical_workspace_id" {
|
||||
type = string
|
||||
description = "Infisical workspace ID"
|
||||
}
|
||||
|
||||
# main.tf
|
||||
provider "infisical" {
|
||||
identity_id = var.infisical_machine_identity_id
|
||||
token_environment_variable_name = "TFC_WORKLOAD_IDENTITY_TOKEN"
|
||||
}
|
||||
|
||||
# Fetch secrets without storing them in state
|
||||
ephemeral "infisical_secret" "db_password" {
|
||||
workspace_id = var.infisical_workspace_id
|
||||
env_slug = "prod"
|
||||
secret_key = "DATABASE_PASSWORD"
|
||||
}
|
||||
|
||||
ephemeral "infisical_secret" "api_key" {
|
||||
workspace_id = var.infisical_workspace_id
|
||||
env_slug = "prod"
|
||||
secret_key = "API_KEY"
|
||||
}
|
||||
|
||||
# Use secrets in resource configuration
|
||||
resource "aws_db_instance" "main" {
|
||||
allocated_storage = 100
|
||||
engine = "postgres"
|
||||
engine_version = "15.2"
|
||||
instance_class = "db.t3.micro"
|
||||
username = "admin"
|
||||
password = ephemeral.infisical_secret.db_password.value
|
||||
skip_final_snapshot = true
|
||||
}
|
||||
|
||||
# outputs.tf
|
||||
output "api_key" {
|
||||
value = ephemeral.infisical_secret.api_key.value
|
||||
sensitive = true
|
||||
}
|
||||
```
|
||||
|
||||
**TFC Workspace Variables** (in Terraform Cloud):
|
||||
```
|
||||
TFC_WORKLOAD_IDENTITY_AUDIENCE = aws.terraform.io
|
||||
INFISICAL_MACHINE_IDENTITY_ID = machine-identity-abc123xyz789
|
||||
infisical_workspace_id = ws-prod-abc123
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Error: "Invalid audience for OIDC token"
|
||||
|
||||
**Cause**: The `TFC_WORKLOAD_IDENTITY_AUDIENCE` variable in your TFC workspace doesn't match the **Audience** configured in the Infisical OIDC auth method.
|
||||
|
||||
**Solution**: Ensure both values are identical. For example, if you set `TFC_WORKLOAD_IDENTITY_AUDIENCE=aws.terraform.io`, the Infisical OIDC audience must also be `aws.terraform.io`.
|
||||
|
||||
### Error: "Identity not found" or "Unauthorized"
|
||||
|
||||
**Cause**: The Machine Identity ID is incorrect or the identity hasn't been granted permissions in the target project.
|
||||
|
||||
**Solution**:
|
||||
1. Verify the Machine Identity ID matches what's in Infisical
|
||||
2. Check that the identity has been assigned to the project with appropriate roles
|
||||
|
||||
### Error: "Token has expired" or "Invalid token"
|
||||
|
||||
**Cause**: The OIDC token is missing or invalid.
|
||||
|
||||
**Solution**:
|
||||
1. Confirm `TFC_WORKLOAD_IDENTITY_TOKEN` is automatically set in your TFC workspace
|
||||
2. Ensure `token_environment_variable_name = "TFC_WORKLOAD_IDENTITY_TOKEN"` is correct in your provider block
|
||||
3. Re-run the plan to generate a fresh token
|
||||
|
||||
## Alternative CI/CD Platforms
|
||||
|
||||
### CircleCI OIDC
|
||||
|
||||
CircleCI also supports OIDC token generation. Configure it similarly:
|
||||
|
||||
1. Set up OIDC auth in Infisical with:
|
||||
- **Discovery URL**: `https://oidc.circleci.com/`
|
||||
- **Issuer**: `https://oidc.circleci.com/`
|
||||
- **Audience**: `https://circleci.com/` (or custom value)
|
||||
|
||||
2. In CircleCI, use the `CIRCLE_OIDC_TOKEN` environment variable:
|
||||
|
||||
```hcl
|
||||
provider "infisical" {
|
||||
identity_id = var.infisical_machine_identity_id
|
||||
token_environment_variable_name = "CIRCLE_OIDC_TOKEN"
|
||||
}
|
||||
```
|
||||
|
||||
3. Configure CircleCI environment variables in your job context to pass the Machine Identity ID.
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Rotate audiences**: Use unique audiences per TFC organization or workspace to improve audit trail clarity
|
||||
2. **Minimal permissions**: Grant Machine Identities only the permissions they need (principle of least privilege)
|
||||
3. **Audit logs**: Monitor Infisical audit logs for OIDC token exchanges to detect unauthorized access
|
||||
4. **Environment-specific identities**: Create separate Machine Identities for dev, staging, and prod (don't share)
|
||||
5. **Ephemeral resources**: Always use `ephemeral` resources to fetch secrets; never use data sources in production
|
||||
Reference in New Issue
Block a user