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.
- Terraform Enterprise: Workload identity tokens
- Terraform Enterprise: Network and integration settings
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# 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=primaryThis 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)
})
}# 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.
# Fetch from Terraform Enterprise
TFE_OIDC_BASE_URL=https://terraform.internal.example.com
OIDC_CONFIG=$(curl -s $TFE_OIDC_BASE_URL/.well-known/openid-configuration)
# Fetch JWKS
JWKS_URI=$(echo $OIDC_CONFIG | jq -r .jwks_uri)
JWKS=$(curl -s $JWKS_URI)
# Upload to oidc.pub
curl -X PUT https://oidc.pub/api/services/$SERVICE_SUBDOMAIN/config \
-H "Authorization: Bearer $OIDCPUB_API_KEY" \
-H "Content-Type: application/json" \
-d "$(jq -n \
--argjson oidc "$OIDC_CONFIG" \
--argjson jwks "$JWKS" \
'{ openidConfiguration: $oidc, jwks: $jwks }'
)"
# SERVICE_SUBDOMAIN is the preferred public reference. Service IDs remain
# accepted for compatibility.# Fetch from Terraform Enterprise
TFE_OIDC_BASE_URL=https://terraform.internal.example.com
OIDC_CONFIG=$(curl -s $TFE_OIDC_BASE_URL/.well-known/openid-configuration)
# Fetch JWKS
JWKS_URI=$(echo $OIDC_CONFIG | jq -r .jwks_uri)
JWKS=$(curl -s $JWKS_URI)
# Upload to oidc.pub
curl -X PUT https://oidc.pub/api/services/$SERVICE_SUBDOMAIN/config \
-H "Authorization: Bearer $OIDCPUB_API_KEY" \
-H "Content-Type: application/json" \
-d "$(jq -n \
--argjson oidc "$OIDC_CONFIG" \
--argjson jwks "$JWKS" \
'{ openidConfiguration: $oidc, jwks: $jwks }'
)"
# SERVICE_SUBDOMAIN is the preferred public reference. Service IDs remain
# accepted for compatibility.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/openid-configuration | jq .curl https://tfe.oidc.pub/.well-known/jwks.json | 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