Terraform Enterprise

This setup guide is for making self-hosted Terraform Enterprise workload identity verifiable outside your network. Configure Terraform Enterprise to mint tokens for your oidc.pub hostname, then fetch its discovery document and JWKS from a network location that can reach Terraform Enterprise and publish them through oidc.pub.

Prerequisites

  • A self-hosted Terraform Enterprise instance with admin access
  • An oidc.pub service (e.g. tfe.oidc.pub)
  • A runner or workstation that can reach Terraform Enterprise's OIDC discovery endpoints to fetch and upload them

Step 1: Configure the secondary hostname

Set TFE_HOSTNAME_SECONDARY to your oidc.pub subdomain and route only OIDC integration traffic through it. Keep the main application hostname as the primary hostname.

# Terraform Enterprise settings
TFE_HOSTNAME=terraform.internal.example.com
TFE_HOSTNAME_SECONDARY=tfe.oidc.pub
TFE_OIDC_HOSTNAME_CHOICE=secondary
TFE_VCS_HOSTNAME_CHOICE=primary
TFE_RUN_TASK_HOSTNAME_CHOICE=primary

This split keeps workload identity federation on the oidc.pub hostname, while VCS and run task integrations continue using the primary Terraform Enterprise hostname.

Step 2: Sync OIDC config to oidc.pub

Fetch the discovery document and JWKS from a network location that can reach Terraform Enterprise, then upload them to oidc.pub. You can manage the full fetch-and-publish workflow in Terraform or run the upload manually.

Fetch the discovery document and JWKS in Terraform using the hashicorp/http provider, then publish them to oidc.pub with the Mastercard/restapi provider against the config resource.

# Terraform
terraform {
  required_providers {
    http = {
      source = "hashicorp/http"
    }
    restapi = {
      source = "Mastercard/restapi"
    }
  }
}

variable "service_subdomain" {
  type = string
}

variable "oidcpub_api_key" {
  type      = string
  sensitive = true
}

variable "tfe_oidc_base_url" {
  type    = string
  default = "https://terraform.internal.example.com"
}

provider "restapi" {
  uri                  = "https://oidc.pub/api"
  write_returns_object = true

  headers = {
    Authorization = "Bearer ${var.oidcpub_api_key}"
    Content-Type  = "application/json"
  }
}

data "http" "openid_configuration" {
  url = "${var.tfe_oidc_base_url}/.well-known/openid-configuration"
}

data "http" "jwks" {
  url = jsondecode(data.http.openid_configuration.response_body).jwks_uri
}

resource "restapi_object" "oidc_config" {
  path         = "/services/${var.service_subdomain}/config"
  read_path    = "/services/${var.service_subdomain}/config"
  update_path  = "/services/${var.service_subdomain}/config"
  destroy_path = "/services/${var.service_subdomain}/config"

  create_method  = "PUT"
  update_method  = "PUT"
  destroy_method = "DELETE"
  id_attribute   = "id"

  data = jsonencode({
    openidConfiguration = jsondecode(data.http.openid_configuration.response_body)
    jwks                = jsondecode(data.http.jwks.response_body)
  })
}

This keeps the fetch and publish steps in one Terraform workflow while still using the same config payload as the raw API.

Step 3: Verify

Within 60 seconds, your OIDC discovery endpoint is publicly reachable.

curl https://tfe.oidc.pub/.well-known/openid-configuration | jq .
curl https://tfe.oidc.pub/.well-known/jwks.json | jq .

Next steps

  • Sync Worker — configuration reference, authentication options, and one-shot mode
  • Key Rotation — how OIDC providers rotate signing keys and what it means for your sync interval
  • Check out one of the integration guides, for example the AWS Integration Guide — a concrete relying-party setup for identity published through oidc.pub