Skip to content

dotcomrow/global-terraform-workspace

Repository files navigation

template-terraform-workspace

Template repository for Terraform project for GCP microservice stack

Usage

requires GCP project to be created and terraform cloud id

GitHub Actions Allowlist

This workspace can automatically fetch GitHub Actions runner CIDR ranges from https://api.github.com/meta and add them to an account-level Cloudflare IP list (default: github_actions_runners, configurable via github_actions_cloudflare_list_name).

The synchronization is optional and enabled by default (enable_github_actions_allowlist = true).

To keep Terraform plans responsive, list entries are synchronized through Cloudflare's asynchronous bulk list API after Terraform ensures the list resource exists.

When enable_github_actions_allowlist = true, this workspace can also upsert a custom WAF skip rule (enabled by default) into the existing http_request_firewall_custom entrypoint ruleset to skip Cloudflare firewall/rate-limiting checks for requests matching:

  • http.host in { github_actions_bypass_host, "auth-origin.suncoast.systems" } (default primary host is login.suncoast.systems)
  • http.request.uri.path starts with github_actions_bypass_path_prefix (default /v1/apps)
  • ip.src in the synchronized GitHub Actions list

Scheduled Sync

A GitHub Actions workflow is included at .github/workflows/sync-github-actions-allowlist.yml to run terraform apply on a schedule and refresh the GitHub Actions CIDR list automatically.

Cloudflare Tunnels

The workspace now supports managing multiple Cloudflare tunnels through a reusable module (modules/cloudflare-tunnel) and one map variable, cloudflare_tunnels.

Google credentials (explicit service key JSON)

The workspace now uses explicit Google credentials instead of environment auto-detection. Set google_credentials_tunnel_key_json with the raw service account JSON (commonly via file() in your tfvars):

google_credentials_tunnel_key_json = file("/path/to/service-account.json")
secret_manager_project_id         = "gcp-project-for-tunnel-secrets"

Example service account roles needed for secret writes:

  • Secret Manager Secret Admin (or create/access specific secret/version permissions)
  • Secret Manager Secret Version Adder

Example 1: two tunnels (inline Terraform block)

cloudflare_tunnels = {
  web = {
    name              = "tf-web-tunnel"
    dns_record_name   = "web"
    service           = "http://127.0.0.1:8080"
    create_gcp_secret = true
    gcp_secret_id     = "cloudflare-tunnel-web-token"
    proxied           = true
  }

  admin = {
    name              = "tf-admin-tunnel"
    dns_record_name   = "admin"
    service           = "http://127.0.0.1:9000"
    create_gcp_secret = false
    proxied           = true
  }
}

Example 2: two tunnels from terraform.tfvars

cloudflare_tunnels = {
  api = {
    name              = "api-tunnel"
    dns_record_name   = "api"
    service           = "http://127.0.0.1:9001"
    create_gcp_secret = true
    gcp_secret_id     = "cloudflare-tunnel-api-token"
    proxied           = true
  }

  logs = {
    name              = "logs-tunnel"
    dns_record_name   = "logs"
    service           = "https://10.0.0.20:9200"
    create_gcp_secret = true
    proxied           = true
  }
}

Example input:

cloudflare_tunnels = {
  keycloak = {
    name             = "keycloak-tunnel"
    dns_record_name  = "keycloak"
    service          = "http://127.0.0.1:8080"
    secret           = ""     # optional, omitted/blank generates automatically
    proxied          = true
    create_gcp_secret = true
    gcp_secret_id     = "cloudflare-tunnel-keycloak-token"
  }
  openobserve = {
    name             = "openobserve-tunnel"
    dns_record_name  = "openobserve"
    service          = "http://127.0.0.1:5080"
    create_gcp_secret = true
    secret           = ""
  }
  another = {
    name            = "another-tunnel"
    dns_record_name = "another"
    service         = "http://127.0.0.1:9000"
    create_gcp_secret = false
  }
}

Set the global domain variable once (for example, example.com) and Terraform will build each tunnel public_hostname as:

dns_record_name == "@" ? domain : "${dns_record_name}.${domain}"

Outputs are keyed by the map key:

  • cloudflare_tunnel_ids
  • cloudflare_tunnel_names
  • cloudflare_tunnel_cnames
  • cloudflare_tunnel_tokens (sensitive)
  • cloudflare_tunnel_secret_ids (Secret Manager secret name)
  • cloudflare_tunnel_secret_version_ids (Secret Manager version resource IDs)
  • cloudflare_tunnel_secret_enabled (boolean)

Synthetic Vault sync events

If you want tunnel secret creation to be picked up by the existing Vault sync pipeline without changing its handler code, enable the synthetic event path in your active terraform.tfvars (or a *.auto.tfvars file):

emit_tunnel_secret_sync_events = true
# Optional: either set directly or let the module discover it from the platform
# vault_sync_event_url          = "https://vault-sync-run-container-<hash>-<ns>.run.app"
# vault_sync_event_token        = "optional_bearer_token"

# Optional discovery inputs (if vault_sync_event_url is omitted)
vault_sync_service_name       = "vault-sync-run-container"
vault_sync_service_region     = "us-east4" # optional
# vault_sync_event_url_secret_name = "vault-sync-event-url"
# vault_sync_event_token_secret_name = "vault-sync-event-token"
vault_sync_event_fallback_sync_all = true
# Optional (default: false):
# vault_sync_grant_invoker_binding = false

# Optional (recommended when the Terraform principal cannot directly mint a token):
# vault_sync_invoker_service_account = "eventarc-vault-sync@tf-k8s-cluster-infra-9734.iam.gserviceaccount.com"

# Set vault_sync_grant_invoker_binding = true only if the Terraform service account
# has run.services.getIamPolicy and run.services.setIamPolicy on the vault-sync service.

# Optional: if true and the direct synthetic payload post fails, run POST /sync-all
# to force a full resync from vault-sync's side.

Important:

  • vault_sync_event_url can be left blank if discovery is enabled.
  • If vault_sync_event_url is blank, the module discovers it in order from:
    • explicit vault_sync_service_name / optional vault_sync_service_region (via gcloud run services describe)
    • Secret Manager secret vault_sync_event_url_secret_name (defaults to vault-sync-event-url)
  • For protected endpoints, set vault_sync_event_token directly when possible, or let discovery resolve it from:
    • generated identity token using the detected URL audience
    • Secret Manager secret vault_sync_event_token_secret_name (defaults to vault-sync-event-token)
  • The synthetic event call treats 200 as success and triggers /sync-all fallback on any non-200 response (including 204), unless you explicitly set vault_sync_event_fallback_sync_all = false.

Minimum parameters for discovery are your GCP project (already secret_manager_project_id) and either:

  • vault_sync_service_name (recommended), or
  • vault_sync_event_url_secret_name secret containing the webhook URL.

Terraform only auto-loads terraform.tfvars and *.auto.tfvars; other filenames (such as cloudflare-tunnels.tfvars) are ignored unless you pass -var-file.

When enabled, the tunnel module posts a single JSON event per created secret version directly to the endpoint using:

  • protoPayload.methodName = google.cloud.secretmanager.v1.SecretManagerService.AddSecretVersion
  • protoPayload.resourceName = projects/<project>/secrets/<secret_name>/versions/<version>

No CloudEvent headers are required for this path; this maps to the service’s manual direct JSON normalization path.

Keep this disabled unless your endpoint is intentionally accepting synthetic events, since it runs a local-exec in Terraform during apply.

About

Suncoast.systems terraform workspace

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages