Configuración de Terraform Cloud y Workspaces

Terraform Cloud es la plataforma gestionada de HashiCorp para ejecutar Terraform de forma colaborativa, con estado remoto seguro, control de acceso por equipos, integración con VCS y políticas de Sentinel para el cumplimiento normativo. Los workspaces permiten separar los entornos (desarrollo, staging, producción) y los componentes de infraestructura manteniendo estados independientes y controlando qué variables y credenciales tiene acceso cada ejecución.

Requisitos Previos

  • Cuenta en Terraform Cloud (app.terraform.io) o Terraform Enterprise
  • Terraform CLI 1.1+ instalado localmente
  • Cuenta en GitHub, GitLab u otro VCS soportado
  • Acceso de administrador para configurar la organización
# Instalar Terraform CLI
TERRAFORM_VERSION="1.7.4"
wget "https://releases.hashicorp.com/terraform/${TERRAFORM_VERSION}/terraform_${TERRAFORM_VERSION}_linux_amd64.zip"
unzip terraform_${TERRAFORM_VERSION}_linux_amd64.zip
mv terraform /usr/local/bin/
terraform version

# Autenticarse en Terraform Cloud
terraform login
# Abrirá el navegador para generar un token de API
# O usar directamente con un token:
export TF_TOKEN_app_terraform_io="tu-token-de-terraform-cloud"

Configuración Inicial de Terraform Cloud

# Crear la organización en Terraform Cloud
# (desde la UI en app.terraform.io → New Organization)

# Configurar el bloque cloud en el código Terraform
cat > main.tf << 'EOF'
terraform {
  required_version = ">= 1.5.0"
  
  cloud {
    organization = "mi-empresa"
    
    workspaces {
      # Especificar el workspace por nombre o por tags
      name = "infraestructura-produccion"
      
      # O usar tags para seleccionar múltiples workspaces
      # tags = ["produccion", "networking"]
    }
  }
  
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

provider "aws" {
  region = var.aws_region
}
EOF

# Inicializar Terraform (se conectará a Terraform Cloud automáticamente)
terraform init

# Verificar que el estado remoto está configurado
terraform state list

Creación y Configuración de Workspaces

# Crear workspaces mediante la API de Terraform Cloud
ORGANIZATION="mi-empresa"
TFC_TOKEN="tu-token-api"

# Crear el workspace de producción
curl -s -X POST \
  -H "Authorization: Bearer $TFC_TOKEN" \
  -H "Content-Type: application/vnd.api+json" \
  "https://app.terraform.io/api/v2/organizations/$ORGANIZATION/workspaces" \
  -d '{
    "data": {
      "type": "workspaces",
      "attributes": {
        "name": "infraestructura-produccion",
        "description": "Infraestructura de produccion en AWS",
        "terraform-version": "1.7.4",
        "auto-apply": false,
        "execution-mode": "remote",
        "tag-names": ["produccion", "aws", "infraestructura"]
      }
    }
  }' | python3 -m json.tool

# Crear el workspace de staging
curl -s -X POST \
  -H "Authorization: Bearer $TFC_TOKEN" \
  -H "Content-Type: application/vnd.api+json" \
  "https://app.terraform.io/api/v2/organizations/$ORGANIZATION/workspaces" \
  -d '{
    "data": {
      "type": "workspaces",
      "attributes": {
        "name": "infraestructura-staging",
        "description": "Infraestructura de staging en AWS",
        "terraform-version": "1.7.4",
        "auto-apply": true,
        "tag-names": ["staging", "aws"]
      }
    }
  }'

# Listar todos los workspaces de la organización
curl -s \
  -H "Authorization: Bearer $TFC_TOKEN" \
  "https://app.terraform.io/api/v2/organizations/$ORGANIZATION/workspaces" \
  | python3 -m json.tool | jq '.data[].attributes.name'

Integración con VCS (GitHub/GitLab)

# Paso 1: Crear la conexión OAuth con GitHub en Terraform Cloud
# Navegar a: Settings → VCS Providers → Add VCS Provider → GitHub.com

# Paso 2: Configurar el workspace para usar el VCS via API
WORKSPACE_ID=$(curl -s \
  -H "Authorization: Bearer $TFC_TOKEN" \
  "https://app.terraform.io/api/v2/organizations/$ORGANIZATION/workspaces/infraestructura-produccion" \
  | python3 -m json.tool | jq -r '.data.id')

# Obtener el ID del VCS OAuth Token
OAUTH_TOKEN_ID=$(curl -s \
  -H "Authorization: Bearer $TFC_TOKEN" \
  "https://app.terraform.io/api/v2/organizations/$ORGANIZATION/oauth-tokens" \
  | python3 -m json.tool | jq -r '.data[0].id')

# Conectar el workspace al repositorio Git
curl -s -X PATCH \
  -H "Authorization: Bearer $TFC_TOKEN" \
  -H "Content-Type: application/vnd.api+json" \
  "https://app.terraform.io/api/v2/workspaces/$WORKSPACE_ID" \
  -d "{
    \"data\": {
      \"type\": \"workspaces\",
      \"attributes\": {
        \"vcs-repo\": {
          \"identifier\": \"miempresa/infraestructura-terraform\",
          \"oauth-token-id\": \"$OAUTH_TOKEN_ID\",
          \"branch\": \"main\"
        },
        \"working-directory\": \"/produccion\"
      }
    }
  }"

Gestión del Estado Remoto

# Acceder al estado remoto de otro workspace (estado compartido entre workspaces)
cat > outputs-networking.tf << 'EOF'
# Leer outputs del workspace de networking desde el workspace de aplicación
data "terraform_remote_state" "networking" {
  backend = "remote"
  
  config = {
    organization = "mi-empresa"
    workspaces = {
      name = "infraestructura-networking"
    }
  }
}

# Usar los outputs del workspace de networking
resource "aws_instance" "app_server" {
  ami           = "ami-0abcdef1234567890"
  instance_type = "t3.medium"
  
  # Usar la subnet del workspace de networking
  subnet_id = data.terraform_remote_state.networking.outputs.private_subnet_id
  
  vpc_security_group_ids = [
    data.terraform_remote_state.networking.outputs.app_security_group_id
  ]
  
  tags = {
    Name        = "app-server"
    Environment = "produccion"
  }
}
EOF

# Migrar el estado local a Terraform Cloud
# 1. Configurar el bloque cloud en el código
# 2. Ejecutar terraform init (detecta la migración)
# 3. Confirmar la migración cuando se pregunte
terraform init

# Mover recursos entre workspaces
# Exportar el recurso del workspace origen
terraform state pull > state-backup.json

# Importar el recurso en el workspace destino
terraform state push state-backup-modificado.json

Variables y Variable Sets

# Crear variables de workspace mediante la API
# Variables sensibles (credenciales de AWS)
curl -s -X POST \
  -H "Authorization: Bearer $TFC_TOKEN" \
  -H "Content-Type: application/vnd.api+json" \
  "https://app.terraform.io/api/v2/workspaces/$WORKSPACE_ID/vars" \
  -d '{
    "data": {
      "type": "vars",
      "attributes": {
        "key": "AWS_ACCESS_KEY_ID",
        "value": "AKIAIOSFODNN7EXAMPLE",
        "category": "env",
        "sensitive": true,
        "description": "AWS Access Key ID para produccion"
      }
    }
  }'

curl -s -X POST \
  -H "Authorization: Bearer $TFC_TOKEN" \
  -H "Content-Type: application/vnd.api+json" \
  "https://app.terraform.io/api/v2/workspaces/$WORKSPACE_ID/vars" \
  -d '{
    "data": {
      "type": "vars",
      "attributes": {
        "key": "AWS_SECRET_ACCESS_KEY",
        "value": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
        "category": "env",
        "sensitive": true
      }
    }
  }'

# Crear un Variable Set para compartir credenciales entre múltiples workspaces
curl -s -X POST \
  -H "Authorization: Bearer $TFC_TOKEN" \
  -H "Content-Type: application/vnd.api+json" \
  "https://app.terraform.io/api/v2/organizations/$ORGANIZATION/varsets" \
  -d '{
    "data": {
      "type": "varsets",
      "attributes": {
        "name": "AWS Credenciales Produccion",
        "description": "Credenciales de AWS para todos los workspaces de produccion",
        "global": false
      }
    }
  }'

Run Triggers entre Workspaces

# Los Run Triggers permiten que la ejecución de un workspace inicie automáticamente
# otro workspace (útil para dependencias de infraestructura)

# Obtener el ID del workspace origen (networking)
NETWORKING_WORKSPACE_ID=$(curl -s \
  -H "Authorization: Bearer $TFC_TOKEN" \
  "https://app.terraform.io/api/v2/organizations/$ORGANIZATION/workspaces/infraestructura-networking" \
  | python3 -m json.tool | jq -r '.data.id')

# Configurar el trigger: cuando networking se aplica → ejecutar el workspace de app
curl -s -X POST \
  -H "Authorization: Bearer $TFC_TOKEN" \
  -H "Content-Type: application/vnd.api+json" \
  "https://app.terraform.io/api/v2/workspaces/$WORKSPACE_ID/run-triggers" \
  -d "{
    \"data\": {
      \"type\": \"run-triggers\",
      \"relationships\": {
        \"sourceable\": {
          \"data\": {
            \"id\": \"$NETWORKING_WORKSPACE_ID\",
            \"type\": \"workspaces\"
          }
        }
      }
    }
  }"

# Listar los run triggers de un workspace
curl -s \
  -H "Authorization: Bearer $TFC_TOKEN" \
  "https://app.terraform.io/api/v2/workspaces/$WORKSPACE_ID/run-triggers?filter[run-trigger][type]=inbound" \
  | python3 -m json.tool

Gestión de Equipos y Permisos

# Crear equipos con diferentes niveles de acceso
curl -s -X POST \
  -H "Authorization: Bearer $TFC_TOKEN" \
  -H "Content-Type: application/vnd.api+json" \
  "https://app.terraform.io/api/v2/organizations/$ORGANIZATION/teams" \
  -d '{
    "data": {
      "type": "teams",
      "attributes": {
        "name": "sre-team",
        "visibility": "organization",
        "organization-access": {
          "manage-workspaces": true,
          "manage-policies": false,
          "manage-vcs-settings": false
        }
      }
    }
  }'

# Asignar permisos del equipo SRE al workspace de producción
# Permisos: read, plan, write, admin
curl -s -X POST \
  -H "Authorization: Bearer $TFC_TOKEN" \
  -H "Content-Type: application/vnd.api+json" \
  "https://app.terraform.io/api/v2/team-workspaces" \
  -d "{
    \"data\": {
      \"type\": \"team-workspaces\",
      \"attributes\": {
        \"access\": \"write\",
        \"runs\": \"apply\",
        \"variables\": \"read\",
        \"state-versions\": \"read\",
        \"plan-outputs\": \"read\",
        \"sentinel-mocks\": \"none\"
      },
      \"relationships\": {
        \"team\": {\"data\": {\"type\": \"teams\", \"id\": \"team-XXXXX\"}},
        \"workspace\": {\"data\": {\"type\": \"workspaces\", \"id\": \"$WORKSPACE_ID\"}}
      }
    }
  }"

Políticas de Cumplimiento

# Las políticas de Sentinel/OPA permiten validar los plans antes de aplicarlos
# Crear una política que requiera tags en todos los recursos AWS

mkdir -p politicas-sentinel

cat > politicas-sentinel/require-tags.sentinel << 'SENTINEL'
# Política Sentinel: todos los recursos AWS deben tener los tags requeridos
import "tfplan/v2" as tfplan

# Tags obligatorios para todos los recursos de AWS
required_tags = ["Environment", "Owner", "CostCenter"]

# Obtener todos los recursos AWS del plan
aws_resources = filter tfplan.resource_changes as _, rc {
  rc.mode is "managed" and
  rc.type matches "^aws_" and
  rc.change.actions contains "create"
}

# Verificar que cada recurso tiene todos los tags obligatorios
check_required_tags = rule {
  all aws_resources as _, rc {
    all required_tags as tag {
      rc.change.after.tags contains tag
    }
  }
}

# La política FALLA si algún recurso no tiene los tags
main = rule {
  check_required_tags
}
SENTINEL

cat > politicas-sentinel/policy-set.hcl << 'HCL'
policy "require-tags" {
  source = "./require-tags.sentinel"
  enforcement_level = "hard-mandatory"
}

policy "cost-limit" {
  source = "./cost-limit.sentinel"
  enforcement_level = "soft-mandatory"
}
HCL

# Publicar el Policy Set en Terraform Cloud
# (desde la UI: Settings → Policy Sets → Connect a new policy set)

Solución de Problemas

Error: workspace not found:

# Verificar que el workspace existe
curl -s \
  -H "Authorization: Bearer $TFC_TOKEN" \
  "https://app.terraform.io/api/v2/organizations/$ORGANIZATION/workspaces" \
  | python3 -m json.tool | jq '.data[].attributes.name'

# Verificar la configuración del bloque cloud en el código
terraform console
# > terraform.workspace

# Reinicializar si el workspace cambió
terraform init -reconfigure

Los plans fallan por variables faltantes:

# Verificar las variables configuradas en el workspace
curl -s \
  -H "Authorization: Bearer $TFC_TOKEN" \
  "https://app.terraform.io/api/v2/workspaces/$WORKSPACE_ID/vars" \
  | python3 -m json.tool | jq '.data[].attributes | {key: .key, sensitive: .sensitive}'

# Las variables sensibles muestran su valor como null en la API
# Verificar desde la UI de Terraform Cloud

Run atascado en estado Pending:

# Ver el estado del run
curl -s \
  -H "Authorization: Bearer $TFC_TOKEN" \
  "https://app.terraform.io/api/v2/runs/<run-id>" \
  | python3 -m json.tool | jq '.data.attributes.status'

# Cancelar un run atascado
curl -s -X POST \
  -H "Authorization: Bearer $TFC_TOKEN" \
  "https://app.terraform.io/api/v2/runs/<run-id>/actions/cancel"

Conclusión

Terraform Cloud transforma la gestión de infraestructura como código de un proceso individual a uno colaborativo y gobernado, con estado remoto seguro, control de acceso granular y ejecuciones auditadas. La combinación de workspaces para separar entornos, Variable Sets para compartir credenciales de forma segura y Run Triggers para gestionar dependencias entre componentes de infraestructura proporciona un sistema completo para gestionar la infraestructura de una organización a escala, con los controles necesarios para garantizar la seguridad y el cumplimiento normativo.