Skip to content

microsoftexpert/tf-mod-github-codespaces

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

3 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

πŸ™ GitHub Codespaces Secrets Terraform Module

One secure-by-default boundary for every GitHub Codespaces secret β€” repository, organization, and user scope β€” plus the selected-repository allow-list that pairs with selected-visibility org secrets. Private-by-default org visibility, encrypted_value preferred over plaintext, sensitive inputs, and zero secret values in outputs. Built for integrations/github v6.x.

Terraform GitHub provider module type resources


🧩 Overview

This is an aggregation module β€” a Codespaces secrets bundle. It has no single primary resource; instead it groups four independent, role-named for_each collections behind one boundary, each defaulting to {} so you enable only the scopes you use:

  • πŸ”‘ Repository secrets (repository_secrets) β€” Codespaces secrets scoped to a single repository.
  • 🏒 Organization secrets (organization_secrets) β€” org-wide Codespaces secrets with all / private / selected visibility (default private).
  • 🎯 Selected-repository allow-lists (secret_repository_access) β€” the repository allow-list that a selected-visibility org secret reads from, managed as its own resource.
  • πŸ‘€ User secrets (user_secrets) β€” Codespaces secrets scoped to the authenticated user, optionally granted to specific repositories.

πŸ’‘ Why it matters: Codespaces secrets feed live developer environments with credentials. This module makes that surface declarative, least-privilege, and auditable β€” secrets are sensitive inputs, org secrets default to private, and only names and resource ids are ever emitted. It is the direct mirror of tf-mod-github-dependabot, one scope up to include the user-level secret, so secrets-by-feature stay consistent across Dependabot / Actions / Codespaces.


❀️ Support this project

If these Terraform modules have been helpful to you or your organization, I'd appreciate your support in any of the following ways:

Whether it's a star, a professional connection, or a coffee, every gesture helps keep these modules actively maintained and continually improving. Thank you for being part of the community!


πŸ—ΊοΈ Where this fits in the family

The keystone tf-mod-github-repository emits the repository name (id) and numeric id (repo_id) that this module consumes; the other secret-family siblings consume the same keystone. This module emits only names and ids β€” to audit/reporting tooling, never to another module's secret input.

flowchart LR
 repo["πŸ—οΈ tf-mod-github-repository<br/>keystone"]
 cs["πŸ™ tf-mod-github-codespaces<br/>(this module)"]
 dep["tf-mod-github-dependabot"]
 act["tf-mod-github-actions-repository"]
 audit["πŸ“Š Audit / Reporting"]

 repo -->|"id = repo name"| cs
 repo -->|"repo_id (numeric)"| cs
 repo -.-> dep
 repo -.-> act
 cs -->|"*_secret_names / *_secret_ids"| audit

 style cs fill:#8957E5,color:#fff
 style repo fill:#24292F,color:#fff
Loading

πŸ”Œ Consumes: repository name (repo secrets) and numeric repo_id (org selected allow-lists + user-secret access) from tf-mod-github-repository. Emits: per-scope secret-name sets and id maps for audit/reporting. See the Cross-Module Contract.


🧬 What this module builds

There is no this resource. The diagram shows the four independent role-named collections; the only internal relationship is that a selected-visibility org secret pairs with the allow-list resource (own that allow-list in exactly one place).

flowchart TD
 rs["var.repository_secrets<br/>map(object) β€” sensitive"] --> repo["github_codespaces_secret.repo<br/>for_each"]
 os["var.organization_secrets<br/>map(object) β€” sensitive"] --> org["github_codespaces_organization_secret.org<br/>for_each"]
 sra["var.secret_repository_access<br/>map(object)"] --> allow["github_codespaces_organization_secret_repositories.allow<br/>for_each"]
 us["var.user_secrets<br/>map(object) β€” sensitive"] --> user["github_codespaces_user_secret.user<br/>for_each"]
 org -.->|"visibility = selected<br/>allow-list owned in one place"| allow

 style repo fill:#8957E5,color:#fff
 style org fill:#8957E5,color:#fff
 style allow fill:#8957E5,color:#fff
 style user fill:#8957E5,color:#fff
Loading

Resource inventory

  • github_codespaces_secret.repo β€” repository-scoped Codespaces secrets, one per repository_secrets entry.
  • github_codespaces_organization_secret.org β€” organization-scoped Codespaces secrets with visibility, one per organization_secrets entry.
  • github_codespaces_organization_secret_repositories.allow β€” the selected-repository allow-list for a selected-visibility org secret, one per secret_repository_access entry.
  • github_codespaces_user_secret.user β€” user-scoped Codespaces secrets, one per user_secrets entry.

βœ… Provider / Versions

  • Terraform >= 1.12.0
  • integrations/github ~> 6.0 β€” validated against 6.12.1
  • Never hashicorp/github (deprecated). Migrate old state with terraform state replace-provider registry.terraform.io/hashicorp/github registry.terraform.io/integrations/github.

ℹ️ 6.x schema notes surfaced during authoring:

  • No key_id argument. All Codespaces secret resources accept encrypted_value (Base64) and plaintext_value only β€” there is no key_id field. The provider resolves the appropriate Codespaces public key internally. Pre-encrypt against the right public key data source and pass just the encrypted_value.
  • No visibility on user secrets. github_codespaces_user_secret has no visibility; it takes an optional selected_repository_ids that grants repositories access to the secret inside the user's Codespaces.
  • Only created_at / updated_at are computed. These resources expose no node_id, slug, repo_id, or html_url β€” the cross-module reference is the resource id.

πŸ“ Module Structure

tf-mod-github-codespaces/
β”œβ”€β”€ providers.tf # github provider requirement (~> 6.0); NO provider {} block
β”œβ”€β”€ variables.tf # four role-named map(object) collections; secret-bearing ones sensitive
β”œβ”€β”€ main.tf # repo / org / allow / user collections (no 'this'); nonsensitive(keys) pattern
β”œβ”€β”€ outputs.tf # per-scope name sets + id maps; NEVER a secret value
β”œβ”€β”€ README.md # this file
└── SCOPE.md # token scopes, prerequisites, consumes/emits contract, gotchas

βš™οΈ Quick Start

The smallest useful call defines a single repository Codespaces secret. Every collection defaults to {}, so you supply only the scopes you need.

module "codespaces" {
  source = "git::https://github.com/microsoftexpert/tf-mod-github-codespaces?ref=v1.0.0"

  repository_secrets = {
    npm_token = {
      repository      = "loan-origination-svc"
      secret_name     = "NPM_TOKEN"
      encrypted_value = var.npm_token_encrypted # Base64, pre-encrypted with the repo Codespaces public key
    }
  }
}

ℹ️ The organization and authentication are the provider's owner / GITHUB_OWNER and token/App β€” never module variables. Point the provider at the target org before applying.


πŸ”Œ Cross-Module Contract

Sourced directly from SCOPE.md.

Consumes

Input Type Source
repository_secrets[].repository string (repo name) tf-mod-github-repository β†’ id
organization_secrets[].selected_repository_ids list(number) tf-mod-github-repository β†’ repo_id
secret_repository_access[].selected_repository_ids list(number) tf-mod-github-repository β†’ repo_id
user_secrets[].selected_repository_ids list(number) tf-mod-github-repository β†’ repo_id

Emits

Output Description Consumed by
repository_secret_names Set of managed repo Codespaces secret names (values never emitted) Audit / drift detection
repository_secret_ids Map: handle β†’ github_codespaces_secret resource id Reporting
organization_secret_names Set of managed org Codespaces secret names Audit
organization_secret_ids Map: handle β†’ github_codespaces_organization_secret resource id Reporting
user_secret_names Set of managed user Codespaces secret names Audit
user_secret_ids Map: handle β†’ github_codespaces_user_secret resource id Reporting
secret_repository_access_ids Map: handle β†’ github_codespaces_organization_secret_repositories resource id Reporting

πŸ”’ No secret value is ever output. Only names and resource ids are emitted.


πŸ“š Example Library

Every collection is keyed by a stable caller handle (the map key) β€” never a list index. Examples use the new GitHub-org source and sibling tf-mod-github-* references.

1 Β· Minimal β€” one repository secret
module "codespaces" {
  source = "git::https://github.com/microsoftexpert/tf-mod-github-codespaces?ref=v1.0.0"

  repository_secrets = {
    npm_token = {
      repository      = "loan-origination-svc"
      secret_name     = "NPM_TOKEN"
      plaintext_value = var.npm_token # provider encrypts on write
    }
  }
}

πŸ’‘ Supply exactly one of plaintext_value or encrypted_value per secret.

2 Β· Organization secret β€” visibility = all
module "codespaces" {
  source = "git::https://github.com/microsoftexpert/tf-mod-github-codespaces?ref=v1.0.0"

  organization_secrets = {
    shared_registry = {
      secret_name     = "SHARED_REGISTRY_TOKEN"
      visibility      = "all" # every repo in the org, including public
      plaintext_value = var.shared_registry_token
    }
  }
}

⚠️ all exposes the secret to every repository, including public ones. Prefer private or selected at a regulated institution.

3 Β· Organization secret β€” visibility = private (secure default)
module "codespaces" {
  source = "git::https://github.com/microsoftexpert/tf-mod-github-codespaces?ref=v1.0.0"

  organization_secrets = {
    artifactory = {
      secret_name = "ARTIFACTORY_TOKEN"
      # visibility omitted β†’ defaults to "private" (all private/internal repos)
      encrypted_value = var.artifactory_token_encrypted
    }
  }
}
4 Β· Organization secret β€” visibility = selected, allow-list managed separately
module "codespaces" {
  source = "git::https://github.com/microsoftexpert/tf-mod-github-codespaces?ref=v1.0.0"

  organization_secrets = {
    payments_key = {
      secret_name     = "PAYMENTS_API_KEY"
      visibility      = "selected"
      encrypted_value = var.payments_key_encrypted
      # NOTE: do NOT set selected_repository_ids inline β€” owned by the allow-list below.
    }
  }

  secret_repository_access = {
    payments_key = {
      secret_name             = "PAYMENTS_API_KEY"
      selected_repository_ids = [12345678, 23456789] # numeric repo ids
    }
  }
}

πŸ”’ Own each selected secret's allow-list in exactly one place β€” here, or inline via selected_repository_ids on the secret (example 5), never both.

5 Β· Organization secret β€” visibility = selected, allow-list inline
module "codespaces" {
  source = "git::https://github.com/microsoftexpert/tf-mod-github-codespaces?ref=v1.0.0"

  organization_secrets = {
    payments_key = {
      secret_name             = "PAYMENTS_API_KEY"
      visibility              = "selected"
      encrypted_value         = var.payments_key_encrypted
      selected_repository_ids = [12345678, 23456789] # inline allow-list
    }
  }
  # Do not also add a secret_repository_access entry for PAYMENTS_API_KEY.
}
6 Β· User secret
module "codespaces" {
  source = "git::https://github.com/microsoftexpert/tf-mod-github-codespaces?ref=v1.0.0"

  user_secrets = {
    gh_pat = {
      secret_name     = "PERSONAL_GH_PAT"
      plaintext_value = var.personal_gh_pat
    }
  }
}

ℹ️ User secrets attach to the authenticated user (the provider identity), not the org. They need the codespace:secrets classic scope.

7 Β· User secret granted to specific repositories
module "codespaces" {
  source = "git::https://github.com/microsoftexpert/tf-mod-github-codespaces?ref=v1.0.0"

  user_secrets = {
    gh_pat = {
      secret_name             = "PERSONAL_GH_PAT"
      encrypted_value         = var.personal_gh_pat_encrypted
      selected_repository_ids = [12345678] # repos that may read the secret in the user's Codespaces
    }
  }
}
8 Β· encrypted_value everywhere (preferred over plaintext)
# Encrypt against the matching Codespaces public key BEFORE Terraform runs.
data "github_codespaces_public_key" "repo" {
  repository = "loan-origination-svc"
}

module "codespaces" {
  source = "git::https://github.com/microsoftexpert/tf-mod-github-codespaces?ref=v1.0.0"

  repository_secrets = {
    db_password = {
      repository      = "loan-origination-svc"
      secret_name     = "DB_PASSWORD"
      encrypted_value = var.db_password_encrypted # Base64, encrypted with the repo public key
    }
  }
}

πŸ”’ Prefer encrypted_value: the plaintext never enters your HCL. (Note: there is no key_id argument β€” the provider resolves the public key internally; you supply only the Base64 ciphertext.) State always holds the value regardless β€” keep your backend encrypted.

9 Β· for_each at scale β€” many repo secrets from one map(object)
variable "repo_codespaces_secrets" {
  type = map(object({
    repository      = string
    secret_name     = string
    encrypted_value = string
  }))
  default   = {}
  sensitive = true
}

module "codespaces" {
  source             = "git::https://github.com/microsoftexpert/tf-mod-github-codespaces?ref=v1.0.0"
  repository_secrets = var.repo_codespaces_secrets
}

⚠️ Each secret is one API write plus a public-key fetch. Large maps can trip secondary rate limits β€” split very large applies.

10 Β· Mixed scopes in a single call
module "codespaces" {
  source = "git::https://github.com/microsoftexpert/tf-mod-github-codespaces?ref=v1.0.0"

  repository_secrets = {
    npm = { repository = "web-portal", secret_name = "NPM_TOKEN", encrypted_value = var.npm_enc }
  }

  organization_secrets = {
    artifactory = { secret_name = "ARTIFACTORY_TOKEN", visibility = "private", encrypted_value = var.arti_enc }
  }

  user_secrets = {
    gh_pat = { secret_name = "PERSONAL_GH_PAT", encrypted_value = var.pat_enc }
  }
}
11 Β· Repository secrets wired from tf-mod-github-repository (cross-module)
module "repository" {
  source     = "git::https://github.com/microsoftexpert/tf-mod-github-repository?ref=v1.0.0"
  name       = "loan-origination-svc"
  visibility = "private"
}

module "codespaces" {
  source = "git::https://github.com/microsoftexpert/tf-mod-github-codespaces?ref=v1.0.0"

  repository_secrets = {
    npm_token = {
      repository      = module.repository.id # repo NAME from the keystone
      secret_name     = "NPM_TOKEN"
      encrypted_value = var.npm_token_encrypted
    }
  }
}
12 Β· Selected org secret wired by numeric repo_id (cross-module)
module "repository" {
  source     = "git::https://github.com/microsoftexpert/tf-mod-github-repository?ref=v1.0.0"
  name       = "payments-svc"
  visibility = "private"
}

module "codespaces" {
  source = "git::https://github.com/microsoftexpert/tf-mod-github-codespaces?ref=v1.0.0"

  organization_secrets = {
    payments_key = {
      secret_name     = "PAYMENTS_API_KEY"
      visibility      = "selected"
      encrypted_value = var.payments_key_encrypted
    }
  }

  secret_repository_access = {
    payments_key = {
      secret_name             = "PAYMENTS_API_KEY"
      selected_repository_ids = [module.repository.repo_id] # NUMERIC id from the keystone
    }
  }
}
13 Β· πŸ”’ Secure / hardened variant β€” encrypted everywhere, selected, least repos
module "codespaces" {
  source = "git::https://github.com/microsoftexpert/tf-mod-github-codespaces?ref=v1.0.0"

  # Repo secrets: encrypted_value only, never plaintext in code.
  repository_secrets = {
    db = {
      repository      = module.repository.id
      secret_name     = "DB_PASSWORD"
      encrypted_value = var.db_password_encrypted
    }
  }

  # Org secrets: selected visibility, the narrowest possible allow-list.
  organization_secrets = {
    payments_key = {
      secret_name     = "PAYMENTS_API_KEY"
      visibility      = "selected"
      encrypted_value = var.payments_key_encrypted
    }
  }
  secret_repository_access = {
    payments_key = {
      secret_name             = "PAYMENTS_API_KEY"
      selected_repository_ids = [module.repository.repo_id] # exactly the repos that need it
    }
  }
}

πŸ”’ Hardened posture: encrypted_value everywhere, org secrets selected (never all), allow-lists scoped to the minimum repositories, secrets are sensitive inputs, no values in outputs.

14 Β· Consuming the outputs for audit / reporting
module "codespaces" {
  source = "git::https://github.com/microsoftexpert/tf-mod-github-codespaces?ref=v1.0.0"
  #... collections...
}

output "managed_codespaces_secret_names" {
  value = {
    repository   = module.codespaces.repository_secret_names
    organization = module.codespaces.organization_secret_names
    user         = module.codespaces.user_secret_names
  }
}

# Assert the org never holds an unexpected Codespaces secret (drift detection).
check "no_unexpected_org_secret" {
  assert {
    condition     = !contains(module.codespaces.organization_secret_names, "LEGACY_TOKEN")
    error_message = "A retired Codespaces org secret (LEGACY_TOKEN) is still managed."
  }
}

ℹ️ Outputs are names and ids only β€” never values. Each map/set is empty ({} / []) when its scope is unused, never null.

15 Β· πŸ—οΈ End-to-end composition (mandatory finale) β€” repository β†’ codespaces, outputs β†’ inputs
# 1) Keystone repository.
module "repository" {
  source               = "git::https://github.com/microsoftexpert/tf-mod-github-repository?ref=v1.0.0"
  name                 = "loan-origination-svc"
  visibility           = "private"
  vulnerability_alerts = true
}

# 2) Codespaces secrets wired entirely from the keystone's outputs.
module "codespaces" {
  source = "git::https://github.com/microsoftexpert/tf-mod-github-codespaces?ref=v1.0.0"

  # repo name β†’ repository_secrets[].repository
  repository_secrets = {
    npm_token = {
      repository      = module.repository.id
      secret_name     = "NPM_TOKEN"
      encrypted_value = var.npm_token_encrypted
    }
  }

  # org secret, selected visibility
  organization_secrets = {
    payments_key = {
      secret_name     = "PAYMENTS_API_KEY"
      visibility      = "selected"
      encrypted_value = var.payments_key_encrypted
    }
  }

  # numeric repo_id β†’ allow-list
  secret_repository_access = {
    payments_key = {
      secret_name             = "PAYMENTS_API_KEY"
      selected_repository_ids = [module.repository.repo_id]
    }
  }
}

output "codespaces_repo_secret_ids" {
  value = module.codespaces.repository_secret_ids
}

πŸ’‘ The keystone emits id (the repo name) and repo_id (the numeric id). The name flows into repo-secret entries; the numeric id flows into selected allow-lists. Apply the repository before the secrets.


πŸ“₯ Inputs

All four inputs are map(object(...)) keyed by a stable caller handle, each defaulting to {} (independently optional). The three secret-bearing collections are sensitive = true.

  • repository_secrets (sensitive) β€” repo-scoped secrets. Required per entry: repository, secret_name. Optional: plaintext_value, encrypted_value (supply one; prefer encrypted).
  • organization_secrets (sensitive) β€” org-scoped secrets. Required: secret_name. Optional: visibility (default private; validated all/private/selected), plaintext_value, encrypted_value, selected_repository_ids (inline allow-list, default []).
  • secret_repository_access (not sensitive) β€” allow-lists for selected org secrets. Required: secret_name, selected_repository_ids.
  • user_secrets (sensitive) β€” user-scoped secrets. Required: secret_name. Optional: plaintext_value, encrypted_value, selected_repository_ids (repos granted access, default []).
Full object schemas
variable "repository_secrets" {
  type = map(object({
    repository      = string
    secret_name     = string
    plaintext_value = optional(string)
    encrypted_value = optional(string)
  }))
  default   = {}
  sensitive = true
}

variable "organization_secrets" {
  type = map(object({
    secret_name             = string
    visibility              = optional(string, "private") # all | private | selected
    plaintext_value         = optional(string)
    encrypted_value         = optional(string)
    selected_repository_ids = optional(list(number), [])
  }))
  default   = {}
  sensitive = true
  # validation: visibility ∈ {all, private, selected}
}

variable "secret_repository_access" {
  type = map(object({
    secret_name             = string
    selected_repository_ids = list(number)
  }))
  default = {}
}

variable "user_secrets" {
  type = map(object({
    secret_name             = string
    plaintext_value         = optional(string)
    encrypted_value         = optional(string)
    selected_repository_ids = optional(list(number), [])
  }))
  default   = {}
  sensitive = true
}

🧾 Outputs

Output Description
repository_secret_ids Map: handle β†’ github_codespaces_secret resource id. Empty {} when unused.
repository_secret_names Set of managed repository Codespaces secret names.
organization_secret_ids Map: handle β†’ github_codespaces_organization_secret resource id.
organization_secret_names Set of managed organization Codespaces secret names.
secret_repository_access_ids Map: handle β†’ github_codespaces_organization_secret_repositories resource id.
user_secret_ids Map: handle β†’ github_codespaces_user_secret resource id.
user_secret_names Set of managed user Codespaces secret names.

πŸ”’ No output is sensitive β€” and none is a secret value. Only names and resource ids are emitted; structural fields are revealed with nonsensitive. Every map/set is empty (not null) when its scope is unused.


🧠 Architecture Notes

  • Aggregation shape β€” no this. There is no single dominant resource; each scope is a peer for_each collection named by role (repo, org, allow, user). This mirrors tf-mod-github-dependabot (repo + org + allow) one scope up, adding github_codespaces_user_secret.
  • sensitive inputs + nonsensitive(keys). repository_secrets, organization_secrets, and user_secrets are sensitive = true because they carry secret values. Terraform forbids for_each over a sensitive value, so main.tf iterates nonsensitive(toset(keys(...))) and indexes the map inside each resource. Non-secret structural fields (repository, secret_name, visibility) are revealed with nonsensitive so plan output and outputs stay useful; the secret value flows straight through to the schema-sensitive plaintext_value / encrypted_value arguments.
  • encrypted_value preferred β€” and there is no key_id. Prefer encrypted_value (pre-encrypted, Base64) over plaintext_value so plaintext never enters HCL. These resources expose no key_id argument β€” the provider resolves the Codespaces public key internally. ⚠️ Either way the value lives in state β€” keep the backend encrypted.
  • Org visibility & the one-place allow-list rule. organization_secrets[].visibility is all / private / selected (default private, validated). For selected, own the allow-list in exactly one place: inline selected_repository_ids on the secret or a secret_repository_access entry β€” never both for the same secret, or they fight over the same allow-list.
  • Values are write-only; names/ids are the only outputs. The provider cannot read a secret's value back, so it cannot detect remote-value drift β€” changing a value in code re-writes it; a value changed only in the GitHub UI is invisible to Terraform. Secret names are tracked. On import, the provider does not fetch the value (terraform import github_codespaces_secret.repo[\"handle\"] repo/SECRET_NAME; org/user import by secret name) β€” you may need ignore_changes on the value as a workaround.
  • No tags, no timeouts. GitHub has no resource tagging concept and these resources expose no timeouts block β€” there is no tags/timeouts tail (the key difference from the azurerm/aws libraries).

🧱 Design Principles

  • πŸ”’ Private by default β€” org-secret visibility defaults to private, never all/public. Wider exposure is an explicit, reviewed opt-in.
  • πŸ—οΈ Encrypt over plaintext β€” encrypted_value is the documented preference so secrets never sit in HCL; plaintext_value is the fallback.
  • πŸ™ˆ Secrets are sensitive β€” every secret-bearing input map is sensitive = true; no secret value is ever emitted as an output.
  • 🎯 Least privilege β€” selected org secrets scope to the minimum repositories; allow-lists are owned in one place to avoid accidental over-grant.
  • 🧩 Composable, auth-free β€” no provider {} block, no owner/token/app_auth variables; the caller configures auth and the target org. integrations/github only β€” never hashicorp/github.
  • 🧾 Auditable β€” emits exactly the names and ids audit/reporting tooling needs, and nothing more.

πŸš€ Runbook

terraform init -backend=false
terraform validate
terraform fmt -check
terraform plan
terraform apply
terraform output

⚠️ Always pin the module to a tag β€” ?ref=v1.0.0 β€” never a branch. Codespaces secrets feed live developer environments at a regulated institution; a moving ref is unacceptable.


πŸ§ͺ Testing

Offline proof gate (no live org required):

terraform fmt -check # zero formatting differences
terraform validate # configuration is valid
tflint # core rules (no dedicated GitHub ruleset exists)

βœ… This module passes terraform init -backend=false, terraform validate ("Success! The configuration is valid."), and terraform fmt -check (zero diffs) against integrations/github 6.12.1.


πŸ’¬ Example Output

repository_secret_ids = {
 "npm_token" = "loan-origination-svc:NPM_TOKEN"
}
repository_secret_names = toset([
 "NPM_TOKEN",
])
organization_secret_ids = {
 "payments_key" = "PAYMENTS_API_KEY"
}
organization_secret_names = toset([
 "PAYMENTS_API_KEY",
])
secret_repository_access_ids = {
 "payments_key" = "PAYMENTS_API_KEY"
}
user_secret_ids = {}
user_secret_names = toset([])

πŸ” Troubleshooting

Symptom Cause Resolution
Invalid for_each argument / "sensitive value" on plan A secret-bearing map was iterated directly The module already uses for_each = nonsensitive(toset(keys(...))); if you adapt it, never for_each a sensitive value directly.
403 / Resource not accessible on org secrets Provider identity lacks org rights Token/App needs admin:org (classic) or Organization β†’ Codespaces secrets: read/write (fine-grained). See SCOPE.md.
403 on user secrets Missing user scope Classic PAT needs codespace:secrets; fine-grained needs Codespaces user secrets: read/write on the user.
Apply fails creating any Codespaces secret Codespaces not enabled for the org / not billed Enable Codespaces and configure billing first. Many regulated orgs disable Codespaces β€” confirm it is part of the footprint.
Org secret apply fails on a Free org Org Codespaces secrets need Team / Enterprise Cloud Upgrade the plan or scope secrets to repo/user level.
selected_repository_ids rejected / no effect Wrong id type, or set on a non-selected secret Use numeric repo_id (wire module.repository.repo_id), and only with visibility = "selected".
Allow-list keeps reverting Allow-list owned in two places Set selected_repository_ids inline or via secret_repository_access for a given secret β€” never both.
Value changed in the GitHub UI not detected Values are write-only The provider can't read values back; re-apply from code (the source of truth), or use ignore_changes for externally-rotated secrets.
Secondary rate limit on a big apply Many secrets = many writes + key fetches Split large for_each maps across applies.

πŸ”— Related Docs

  • integrations/github provider β€” Codespaces Secret, Codespaces Organization Secret, Codespaces Organization Secret Repositories, and Codespaces User Secret resource references
  • integrations/github provider β€” Codespaces public-key data sources (repo / organization / user) for encrypted_value
  • module β€” tf-mod-github-repository (keystone; emits id and repo_id)
  • module β€” tf-mod-github-dependabot (the direct mirror β€” repo + org + allow-list)
  • module β€” tf-mod-github-actions-repository / tf-mod-github-repository-environment (sibling secrets surfaces)
  • GitHub Docs β€” Managing encrypted secrets for your Codespaces, and the Codespaces organization/billing policy matrix

Packages

 
 
 

Contributors

Languages