Skip to content

Latest commit

Β 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

πŸ™ GitHub Organization Settings Terraform Module

One enforced, secure-by-default baseline for an entire GitHub organization β€” billing & profile metadata, least-privilege member privileges, no-accidental-public repo/Pages creation, and an always-on security baseline (GHAS, Dependabot, secret scanning + push protection, web commit sign-off) for every new repository β€” now extended with optional org-governance posture controls: security-manager team designation and organization user blocks. Built for integrations/github v6.x.

Terraform GitHub provider module type resources


🧩 Overview

This module manages the single github_organization_settings resource β€” the org-wide policy object that governs the organization your provider points at (owner / GITHUB_OWNER) β€” and now optionally layers two additional org-governance posture controls behind the same boundary. There is exactly one settings object per organization; the two new controls are opt-in for_each collections that default to {}.

  • 🧾 Profile & billing β€” sets the required billing_email plus optional public profile fields (name, description, company, blog, email, twitter_username, location).
  • πŸ” Least-privilege member privileges β€” base member permission defaults to read; public repository and public Pages creation default to off; private-repo forking defaults to off.
  • πŸ›‘οΈ Security baseline for new repos β€” GHAS, Dependabot alerts + security updates, dependency graph, secret scanning, and secret-scanning push protection all default on, plus org-wide web commit sign-off.
  • 🏒 Enterprise-aware β€” members_can_create_internal_repositories defaults to null so the module applies cleanly on Free / Team orgs and is only managed when you opt in on Enterprise Cloud.
  • πŸ§‘β€βœˆοΈ Security-manager designation (optional) β€” designate one or more teams as organization security managers via security_managers (default {}). Note: the underlying resource is deprecated β€” see Architecture Notes for the recommended org-role path.
  • 🚫 Organization user blocks (optional) β€” block abusive/spam accounts org-wide via blocked_users (default {}), authoritative per username.
  • πŸ“€ Audit-friendly outputs β€” emits the org settings id, a typed security_for_new_repositories summary, and the new security_manager_ids / blocked_user_ids maps for compliance dashboards.

πŸ’‘ Why it matters: this is the org-level guardrail that decides what every member can do and what security every new repository inherits. Setting it once, in code, replaces dozens of clicks and removes the single biggest source of accidental public exposure at a regulated institution.

ℹ️ Backward compatible: the two new collections default to {}. A caller that sets only org settings creates neither new resource and sees an identical plan/apply to before the module was extended.


❀️ 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

This module is part of the Org Governance family. It is the only governance module that consumes a sibling output: a team slug from terraform-github-team to designate security managers. The keystone terraform-github-repository (GitHub-dark) inherits this module's new-repo security baseline.

flowchart LR
 team["terraform-github-team"]
 subgraph govern["Org Governance family"]
 orgset["terraform-github-organization-settings<br/>β˜… THIS"]
 roles["terraform-github-organization-roles"]
 membership["terraform-github-membership"]
 orgrule["terraform-github-organization-ruleset"]
 end
 repo["terraform-github-repository<br/>keystone"]

 team -->|team slug| orgset
 roles -.->|modern security-manager role lives here| orgset
 orgset -.->|new-repo security baseline| repo

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

Consumes: a team slug (from terraform-github-team) for security managers; usernames for blocks. Emits: id, governance flags, and the security_manager_ids / blocked_user_ids maps. See the Cross-Module Contract.


🧬 What this module builds

github_organization_settings.this is the primary resource (always created). The two optional for_each collections extend the module into a small org-governance aggregation; both default to {}, so a settings-only caller creates neither.

flowchart TD
 subgraph mod["terraform-github-organization-settings β€” one provider/org boundary"]
 settings["github_organization_settings Β· this<br/>PRIMARY β€” org-wide singleton<br/>profile Β· member privileges Β· new-repo security baseline"]
 sm["github_organization_security_manager Β· security_managers<br/>OPTIONAL Β· for_each map(object) Β· default {}"]
 block["github_organization_block Β· blocked_users<br/>OPTIONAL Β· for_each map(object) Β· default {}"]
 end
 org["GitHub Organization<br/>provider owner / GITHUB_OWNER"]
 settings -->|governs| org
 sm -->|designates security-manager teams in| org
 block -->|blocks users from| org
 style settings fill:#8957E5,color:#fff
 style sm fill:#6E40C9,color:#fff
 style block fill:#6E40C9,color:#fff
 style org fill:#24292F,color:#fff
Loading

Resource inventory

  • github_organization_settings.this β€” PRIMARY / keystone. The org-wide settings singleton: billing/profile, member privileges, feature toggles, and the new-repo security baseline. Always created.
  • github_organization_security_manager.security_managers β€” OPTIONAL for_each collection over map(object({ team_slug })), default {}. Designates teams as organization security managers. ⚠️ Deprecated provider resource β€” prefer the built-in security_manager org role (see Architecture Notes).
  • github_organization_block.blocked_users β€” OPTIONAL for_each collection over map(object({ username })), default {}. Blocks users from the organization; authoritative per username.

βœ… Provider / Versions

  • Terraform >= 1.12.0
  • Provider integrations/github ~> 6.0 (validated against 6.12.1) β€” never the deprecated hashicorp/github.
  • Schema note: github_organization_security_manager is deprecated in the 6.x provider in favour of github_organization_role_team (the built-in security_manager organization role). It still applies β€” terraform validate emits a Deprecated Resource warning, not an error.
  • Edition note: members_can_create_internal_repositories is Enterprise Cloud only; the advanced-security / secret-scanning *_for_new_repositories flags require a GHAS license for private/internal repos.

πŸ“ Module Structure

terraform-github-organization-settings/
β”œβ”€β”€ providers.tf # github provider requirement (~> 6.0); no provider {} block
β”œβ”€β”€ variables.tf # billing_email (required) + profile + privileges + security baseline + optional posture controls
β”œβ”€β”€ main.tf # github_organization_settings.this (primary) + 2 optional for_each collections
β”œβ”€β”€ outputs.tf # id + governance/audit passthroughs + security_manager_ids / blocked_user_ids
β”œβ”€β”€ README.md # this file
└── SCOPE.md # token scopes, prerequisites, consumes/emits tables, gotchas

ℹ️ Still four .tf files β€” the module grew from one resource to three (one primary + two optional collections) without adding files.


βš™οΈ Quick Start

The minimal call needs only billing_email; every other field inherits a secure default, and both posture-control collections default to {}.

module "org_settings" {
  source = "git::https://github.com/microsoftexpert/terraform-github-organization-settings?ref=v1.0.0"

  billing_email = "github-billing@financialpartners.com"
}

ℹ️ The organization itself is the provider's owner / GITHUB_OWNER β€” it is not a module variable. Point the provider at the org you intend to govern before applying.


πŸ”Œ Cross-Module Contract

Sourced from SCOPE.md. The module now has a real Consumes dependency (a team slug for security managers); everything else it Emits.

Consumes

Input Type Source
security_managers[*].team_slug string terraform-github-team β†’ slug (wire module.<team>.slug)
blocked_users keys (and optional username) string Caller-supplied GitHub usernames (public account metadata)

Emits

Output Type Typically consumed by
id string Governance dashboards / audit tooling; a stable reference to the org settings object
billing_email string Compliance reporting (non-sensitive org metadata)
default_repository_permission string Access-review tooling auditing org-wide base permission
members_can_create_public_repositories bool Public-exposure audit checks / policy assertions
web_commit_signoff_required bool Commit-policy compliance reporting
security_for_new_repositories object Compliance dashboards verifying the new-repo security baseline
security_manager_ids map(string) Access-review tooling auditing which teams hold org security-manager rights
blocked_user_ids map(string) Moderation/audit tooling tracking org-level blocks

ℹ️ This is still an org-governance module β€” it emits no repository name. Repository/team modules wire into the org, not into this resource (except the team slug this module consumes).


πŸ“š Example Library

1 Β· Minimal β€” secure defaults, Free/Team safe

module "org_settings" {
  source        = "git::https://github.com/microsoftexpert/terraform-github-organization-settings?ref=v1.0.0"
  billing_email = "github-billing@financialpartners.com"
}

2 Β· Full public profile

module "org_settings" {
  source = "git::https://github.com/microsoftexpert/terraform-github-organization-settings?ref=v1.0.0"

  billing_email    = "github-billing@financialpartners.com"
  name             = "Casey Wood"
  description      = "Technology & operations for the regulated financial."
  company          = "Casey Wood, Inc."
  blog             = "https://www.financialpartners.com"
  email            = "opensource@financialpartners.com"
  location         = "Agawam, MA"
  twitter_username = "FarmCreditcasey"
}

3 Β· Hardened / locked-down β€” owners create everything

module "org_settings" {
  source        = "git::https://github.com/microsoftexpert/terraform-github-organization-settings?ref=v1.0.0"
  billing_email = "github-billing@financialpartners.com"

  # Members get the narrowest base access and cannot create any repository.
  default_repository_permission   = "none"
  members_can_create_repositories = false

  # Pages fully disabled.
  members_can_create_pages = false

  # Project boards off where unused.
  has_organization_projects = false
  has_repository_projects   = false

  # Security baseline left at its secure-on defaults (GHAS licensed).
}

4 Β· Lock down member privileges, keep private self-service

module "org_settings" {
  source        = "git::https://github.com/microsoftexpert/terraform-github-organization-settings?ref=v1.0.0"
  billing_email = "github-billing@financialpartners.com"

  default_repository_permission           = "read"
  members_can_create_repositories         = true
  members_can_create_public_repositories  = false # secure default, made explicit
  members_can_create_private_repositories = true
  members_can_fork_private_repositories   = false # secure default, made explicit
}

5 Β· Reviewed open-source exception β€” allow public repos & Pages

module "org_settings" {
  source        = "git::https://github.com/microsoftexpert/terraform-github-organization-settings?ref=v1.0.0"
  billing_email = "github-billing@financialpartners.com"

  # Opt in ONLY after security review of the open-source program.
  members_can_create_public_repositories = true
  members_can_create_public_pages        = true
}

⚠️ Turning on public repository/Pages creation widens internet exposure. Document the review that approved it before applying at a regulated institution.

6 Β· Enterprise Cloud β€” manage internal repositories

module "org_settings" {
  source        = "git::https://github.com/microsoftexpert/terraform-github-organization-settings?ref=v1.0.0"
  billing_email = "github-billing@financialpartners.com"

  # ENTERPRISE-ONLY: valid only on a GitHub Enterprise Cloud org.
  members_can_create_internal_repositories = true
}

πŸ”’ Leave members_can_create_internal_repositories at its null default on Free/Team orgs β€” sending a value to a non-Enterprise org fails the apply.

7 Β· Full security baseline made explicit (GHAS licensed)

module "org_settings" {
  source        = "git::https://github.com/microsoftexpert/terraform-github-organization-settings?ref=v1.0.0"
  billing_email = "github-billing@financialpartners.com"

  web_commit_signoff_required                                  = true
  advanced_security_enabled_for_new_repositories               = true
  dependabot_alerts_enabled_for_new_repositories               = true
  dependabot_security_updates_enabled_for_new_repositories     = true
  dependency_graph_enabled_for_new_repositories                = true
  secret_scanning_enabled_for_new_repositories                 = true
  secret_scanning_push_protection_enabled_for_new_repositories = true
}

8 Β· Org WITHOUT a GHAS license β€” disable GHAS-gated security

module "org_settings" {
  source        = "git::https://github.com/microsoftexpert/terraform-github-organization-settings?ref=v1.0.0"
  billing_email = "github-billing@financialpartners.com"

  # GHAS not licensed β€” these MUST be false on private/internal orgs or apply fails.
  advanced_security_enabled_for_new_repositories               = false
  secret_scanning_enabled_for_new_repositories                 = false
  secret_scanning_push_protection_enabled_for_new_repositories = false

  # Dependabot + dependency graph stay on β€” they are free.
  dependabot_alerts_enabled_for_new_repositories           = true
  dependabot_security_updates_enabled_for_new_repositories = true
  dependency_graph_enabled_for_new_repositories            = true
}

πŸ’‘ Dependabot alerts/updates and the dependency graph are available without GHAS; only advanced security and secret scanning on private/internal repos require the license.

9 Β· Relax web commit sign-off (with documented rationale)

module "org_settings" {
  source        = "git::https://github.com/microsoftexpert/terraform-github-organization-settings?ref=v1.0.0"
  billing_email = "github-billing@financialpartners.com"

  web_commit_signoff_required = false # relax org-wide; record the approving change
}

10 Β· Pages refined β€” gate on, public off

module "org_settings" {
  source        = "git::https://github.com/microsoftexpert/terraform-github-organization-settings?ref=v1.0.0"
  billing_email = "github-billing@financialpartners.com"

  members_can_create_pages         = true
  members_can_create_public_pages  = false
  members_can_create_private_pages = true
}

11 Β· Disable project boards org-wide

module "org_settings" {
  source        = "git::https://github.com/microsoftexpert/terraform-github-organization-settings?ref=v1.0.0"
  billing_email = "github-billing@financialpartners.com"

  has_organization_projects = false
  has_repository_projects   = false
}

12 Β· for_each at scale β€” govern many orgs from one map(object)

variable "orgs" {
  type = map(object({
    billing_email                            = string
    members_can_create_public_repositories   = optional(bool, false)
    members_can_create_internal_repositories = optional(bool) # null unless Enterprise
  }))
  default = {
    prod = {
      billing_email = "github-billing@financialpartners.com"
    }
    sandbox = {
      billing_email                          = "github-sandbox@financialpartners.com"
      members_can_create_public_repositories = true
    }
  }
}

# One provider alias per org (configured in the root), one module call per org.
module "org_settings" {
  source   = "git::https://github.com/microsoftexpert/terraform-github-organization-settings?ref=v1.0.0"
  for_each = var.orgs

  providers = { github = github.by_org[each.key] }

  billing_email                            = each.value.billing_email
  members_can_create_public_repositories   = each.value.members_can_create_public_repositories
  members_can_create_internal_repositories = each.value.members_can_create_internal_repositories
}

ℹ️ The org is a provider concern, so multi-org governance is done with one provider alias per org, not by passing an org name into the module.

13 Β· Consuming the security summary output

module "org_settings" {
  source        = "git::https://github.com/microsoftexpert/terraform-github-organization-settings?ref=v1.0.0"
  billing_email = "github-billing@financialpartners.com"
}

# Feed the typed baseline into a compliance assertion / dashboard.
output "new_repo_security_baseline" {
  value = module.org_settings.security_for_new_repositories
}

check "secret_scanning_on" {
  assert {
    condition     = module.org_settings.security_for_new_repositories.secret_scanning_push_protection
    error_message = "Secret-scanning push protection must be enabled for new repositories."
  }
}

14 Β· Composition β€” org baseline first, then repositories inherit it

module "org_settings" {
  source        = "git::https://github.com/microsoftexpert/terraform-github-organization-settings?ref=v1.0.0"
  billing_email = "github-billing@financialpartners.com"

  members_can_create_public_repositories = false
  default_repository_permission          = "read"
}

# Repositories created after the org baseline inherit its new-repo security defaults.
module "repository" {
  source     = "git::https://github.com/microsoftexpert/terraform-github-repository?ref=v1.0.0"
  name       = "loan-origination-svc"
  visibility = "private"
  depends_on = [module.org_settings] # ensure the baseline is applied first
}

πŸ’‘ Apply org settings before the first repository in a fresh org so new repos inherit the intended security baseline. depends_on makes the ordering explicit.

15 Β· Settings-only β€” behavior unchanged after the upgrade
# The two new collections default to {}. A caller that sets only org settings
# creates NEITHER github_organization_security_manager NOR github_organization_block
# β€” an identical plan/apply to before this module was extended.
module "org_settings" {
  source        = "git::https://github.com/microsoftexpert/terraform-github-organization-settings?ref=v1.0.0"
  billing_email = "github-billing@financialpartners.com"
}

πŸ’‘ Zero change for existing callers: omitting security_managers and blocked_users leaves them {}, so no extra resources are planned.

16 Β· Designate a security-manager team (wired from terraform-github-team)
module "appsec_team" {
  source = "git::https://github.com/microsoftexpert/terraform-github-team?ref=v1.0.0"
  name   = "application-security"
  #...team configuration...
}

module "org_settings" {
  source        = "git::https://github.com/microsoftexpert/terraform-github-organization-settings?ref=v1.0.0"
  billing_email = "github-billing@financialpartners.com"

  security_managers = {
    appsec = {
      team_slug = module.appsec_team.slug # wire the SLUG, not the display name
    }
  }
}

⚠️ Deprecated resource. github_organization_security_manager is deprecated by the provider in favour of the built-in security_manager organization role assigned via github_organization_role_team (see terraform-github-organization-roles). Prefer that for new work; this collection remains for back-compat. See Architecture Notes.

17 Β· Block users from the organization (user moderation)
module "org_settings" {
  source        = "git::https://github.com/microsoftexpert/terraform-github-organization-settings?ref=v1.0.0"
  billing_email = "github-billing@financialpartners.com"

  # Keyed by username β€” the map key IS the GitHub login.
  blocked_users = {
    "spammer-account"  = {}
    "abusive-user-123" = {}
  }
}

πŸ”’ Authoritative per username. Each entry fully owns the block for that login; removing an entry unblocks the user on the next apply. Manage the complete set of org blocks here, or not at all.

18 Β· πŸ—οΈ End-to-end composition β€” team β†’ security managers + settings + blocks
module "appsec_team" {
  source = "git::https://github.com/microsoftexpert/terraform-github-team?ref=v1.0.0"
  name   = "application-security"
}

module "org_settings" {
  source        = "git::https://github.com/microsoftexpert/terraform-github-organization-settings?ref=v1.0.0"
  billing_email = "github-billing@financialpartners.com"

  # Org-wide secure baseline (secure defaults shown explicitly).
  default_repository_permission          = "read"
  members_can_create_public_repositories = false

  # Designate the security team (deprecated resource; see Architecture Notes).
  security_managers = {
    appsec = { team_slug = module.appsec_team.slug }
  }

  # Moderate abusive accounts.
  blocked_users = {
    "spammer-account" = {}
  }
}

output "security_manager_team_ids" {
  value = module.org_settings.security_manager_ids
}

πŸ’‘ The full suite wired via outputs β†’ inputs: a team's slug flows into security_managers, the org baseline applies to every new repository, and abusive accounts are blocked β€” all under one provider/org boundary.


πŸ“¦ Inputs (high-level)

Required

  • billing_email β€” the organization billing email (validated as an email). The one field the provider cannot infer.

Organization profile (all optional, default null = leave unchanged)

  • name, description, company, blog, email, twitter_username, location

Member privileges (least-privilege secure defaults)

  • default_repository_permission (none / read / write / admin, default read)
  • members_can_create_repositories (default true)
  • members_can_create_public_repositories (default false)
  • members_can_create_private_repositories (default true)
  • members_can_create_internal_repositories (Enterprise-only, default null)
  • members_can_fork_private_repositories (default false)
  • members_can_create_pages (default true), members_can_create_public_pages (default false), members_can_create_private_pages (default true)

Organization feature toggles

  • has_organization_projects (default true), has_repository_projects (default true)

Commit policy & new-repo security baseline (secure-by-default ON)

  • web_commit_signoff_required (default true)
  • advanced_security_enabled_for_new_repositories (default true, needs GHAS)
  • dependabot_alerts_enabled_for_new_repositories (default true)
  • dependabot_security_updates_enabled_for_new_repositories (default true)
  • dependency_graph_enabled_for_new_repositories (default true)
  • secret_scanning_enabled_for_new_repositories (default true, needs GHAS for private/internal)
  • secret_scanning_push_protection_enabled_for_new_repositories (default true, needs GHAS for private/internal)

Optional org-governance posture controls (both default {} = no change)

  • security_managers β€” map(object) keyed by a stable handle; designates teams as org security managers. ⚠️ deprecated resource β€” see Architecture Notes.
  • blocked_users β€” map(object) keyed by username; blocks users from the org (authoritative per username).
Full object schemas for the nested map inputs
# security_managers β€” designate teams as organization security managers (DEPRECATED resource)
variable "security_managers" {
  type = map(object({
    team_slug = string # slug of the team to designate β€” wire module.<team>.slug (NOT the display name)
  }))
  default = {}
}

# blocked_users β€” block users from the organization (authoritative per username)
variable "blocked_users" {
  type = map(object({
    username = optional(string) # defaults to the map key; override only if the key must differ from the login
  }))
  default = {}
}

🧾 Outputs

Output Description Sensitive
id The organization settings ID (the organization's GitHub ID). Primary cross-module reference. β€”
billing_email The org billing email currently applied (non-sensitive metadata). β€”
default_repository_permission The base permission every member has on all org repos (none/read/write/admin). β€”
members_can_create_public_repositories Whether members may create public repos β€” the key public-exposure flag for audit. β€”
web_commit_signoff_required Whether web-based commits org-wide require a sign-off. β€”
security_for_new_repositories Typed object summarizing the new-repo security baseline (advanced security, Dependabot alerts/updates, dependency graph, secret scanning, push protection). β€”
security_manager_ids Map keyed by the same handle as security_managers β†’ the designated team's GitHub team ID. {} when none designated. β€”
blocked_user_ids Map keyed the same as blocked_users (by username) β†’ the block resource's ID. {} when no users blocked. β€”

ℹ️ No output is sensitive β€” this resource holds no secret values and emits no member PII. Team slugs and GitHub usernames are public metadata.


🧠 Architecture Notes

  • id is the organization's GitHub ID. github_organization_settings exposes only id as a computed attribute β€” there is no node_id, slug, repo_id, full_name, or html_url on this resource (unlike repositories or teams). The remaining settings outputs are governance flags read back off the applied resource, not separate identifiers. To import an existing settings object, use the numeric organization ID: terraform import module.org_settings.github_organization_settings.this <org_id>.
  • Flat primary resource β€” no nested blocks. Every github_organization_settings argument is a direct typed scalar, so it contributes no dynamic blocks. The two optional collections are rendered with for_each over a map(object(...)) keyed by a stable caller string β€” never count β€” so adding/removing one entry never re-indexes the others.
  • Singleton, not ForceNew. There is one settings object per org; the module mutates it in place. The org it targets is fixed by the provider's owner, so pointing the provider at a different org and re-applying governs a different organization β€” there is no immutable name field on this resource to recreate.
  • null means "leave unmanaged." Profile fields and members_can_create_internal_repositories default to null so an unset value is left untouched rather than blanked. This is what lets the same code run on Free/Team and Enterprise orgs.
  • Enterprise-only field. members_can_create_internal_repositories is valid only on GitHub Enterprise Cloud orgs. Passing a non-null value to a Free/Team org causes an apply error β€” keep it null unless the org is part of an enterprise.
  • GHAS-gated fields. advanced_security_enabled_for_new_repositories, secret_scanning_enabled_for_new_repositories, and secret_scanning_push_protection_enabled_for_new_repositories require a GitHub Advanced Security license to enable on private/internal repos. Without GHAS licensed these must be false or the apply fails (they are free on public repos).
  • New-repo baseline only. The *_for_new_repositories settings apply to repositories created after this setting takes effect; existing repositories are unaffected. Remediate existing repos with the repository / Dependabot / Actions modules.
  • Org settings β‰  rulesets. This module sets org-wide member privileges and the new-repo security baseline. Branch-level controls (required reviews, status checks, no force-push) belong to terraform-github-organization-ruleset / terraform-github-repository-ruleset β€” prefers rulesets over legacy github_branch_protection for those. The two are complementary, not alternatives.
  • No secrets handled. This module holds no secret values, so there is no plaintext_value / encrypted_value concern and no output is sensitive β€” billing_email is org billing metadata, not member PII; all other outputs are non-secret governance flags, team slugs, or public usernames.
  • πŸ”Ž Security managers: deprecated resource β€” prefer the built-in org role. The prompt3 resolve-first check confirmed (and terraform validate warns) that github_organization_security_manager is deprecated in the integrations/github 6.x provider. The modern path is to assign the built-in security_manager organization role to a team via github_organization_role_team, which the provider documents directly:
data "github_organization_roles" "all" {}
locals {
security_manager_id = one([for r in data.github_organization_roles.all.roles: r.role_id if r.name == "security_manager"])
}
resource "github_organization_role_team" "security_managers" {
role_id = local.security_manager_id
team_slug = "application-security"
}

That resource belongs in terraform-github-organization-roles, and should prefer it for new work. This module keeps security_managers (the deprecated resource) only for back-compat / existing state; migrate when convenient. Either way, wire the team slug (from terraform-github-team), never the display name.

  • User blocks are authoritative per username. Each blocked_users entry fully owns the org-level block for that login. Removing an entry unblocks the user on the next apply. Manage the complete set of org blocks here, or leave the collection {} and manage blocks elsewhere β€” do not split ownership of a single username across modules. The map key is the username (the optional username field overrides it only when the resource-address key must differ from the actual login, via coalesce(each.value.username, each.key)).
  • Both collections default {} β€” zero-change upgrade. A settings-only caller plans/applies identically to the pre-extension module; neither github_organization_security_manager nor github_organization_block is created until you populate the corresponding map.
  • Small for_each, modest API cost. These collections are typically a handful of entries, so they do not carry the secondary-rate-limit risk that large bulk modules (membership, collaborators) do β€” but keep org-governance applies in their own small plan to avoid colliding with large bulk applies.

🧱 Design Principles

  • πŸ”’ Private by default β€” public repository and public Pages creation default off; private-repo forking defaults off. Public exposure is always an explicit, reviewed opt-in.
  • πŸͺͺ Least privilege β€” base member permission defaults to read, never write/admin. Broaden access per-repo via collaborators/teams, not org-wide. Security-manager designation gives a team org-wide security visibility without org-owner rights.
  • πŸ›‘οΈ Security on by default β€” GHAS, Dependabot, dependency graph, secret scanning + push protection, and web commit sign-off default on so every new repo inherits the baseline; each opt-out is documented inline.
  • 🚫 Safe by omission β€” the optional posture controls default to {}. The module never blocks a user or designates a security manager unless you explicitly say so; removing an entry reverses the action (unblock / undesignate) on the next apply.
  • 🏒 Edition-safe β€” Enterprise-only and GHAS-gated fields default to null/are clearly flagged so the module applies cleanly on Free/Team orgs.
  • 🧩 Composable β€” no provider {} block, no owner/token/app_auth variables; the caller configures auth and the target org. Sibling wiring is by slug/id, never by name.
  • 🧾 Auditable β€” emits the exact governance flags compliance dashboards need (including who holds security-manager rights and who is blocked), with zero secrets in outputs.

πŸš€ 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. Org-wide settings affect every member and every new repository; a moving ref is unacceptable for a governance baseline.


πŸ§ͺ Testing

The offline proof gate (no GitHub credentials required):

terraform fmt -check # zero formatting differences
terraform validate # valid config (a Deprecated Resource WARNING on
 # github_organization_security_manager is expected, not an error)
tflint # core rules β€” no dedicated GitHub ruleset exists

ℹ️ terraform validate succeeds with the deprecation warning present β€” that warning is the provider confirming the recommended migration to the built-in security_manager org role.


πŸ’¬ Example Output

A sample terraform output after applying the end-to-end composition (Example 18):

billing_email = "github-billing@financialpartners.com"
blocked_user_ids = {
 "spammer-account" = "12345678"
}
default_repository_permission = "read"
id = "1234567"
members_can_create_public_repositories = false
security_for_new_repositories = {
 "advanced_security" = true
 "dependabot_alerts" = true
 "dependabot_security_updates" = true
 "dependency_graph" = true
 "secret_scanning" = true
 "secret_scanning_push_protection" = true
}
security_manager_ids = {
 "appsec" = "7654321"
}
web_commit_signoff_required = true

πŸ” Troubleshooting

Symptom Cause Resolution
403 / Resource not accessible on apply Provider identity lacks org-admin rights The token/App needs the admin:org classic scope (or Organization administration: write fine-grained). See SCOPE.md.
Apply fails enabling members_can_create_internal_repositories Org is Free/Team, not Enterprise Cloud Leave the variable null on non-Enterprise orgs.
Apply fails enabling advanced security / secret scanning GHAS not licensed for private/internal repos Set the GHAS-gated flags to false, or license GitHub Advanced Security.
New repos don't inherit the security baseline Repos were created before the settings applied The *_for_new_repositories flags affect only new repos; remediate existing repos with the repository/Dependabot modules.
Settings drift back after a manual UI change Someone edited settings in the GitHub UI Re-run terraform apply β€” Terraform is the source of truth for org governance.
billing_email must be a valid email address billing_email failed the validation regex Supply a syntactically valid address (e.g. billing@example.com).
Warning: Deprecated Resource on github_organization_security_manager The provider deprecated this resource Expected β€” it is a warning, not an error. Migrate to the built-in security_manager org role via github_organization_role_team (terraform-github-organization-roles) when convenient.
Security-manager designation fails / team not found Wrong value passed β€” display name instead of slug, or team not yet created Pass the team slug (wire module.<team>.slug); ensure the team exists first (depends_on / output wiring).
Each blocked_users entry must resolve to a valid GitHub username… A key/username isn't a valid GitHub login Use a valid login: 1–39 chars, alphanumeric with single internal hyphens, no leading/trailing hyphen.
A previously blocked user can suddenly interact again Their blocked_users entry was removed Blocks are authoritative per username β€” removing an entry unblocks on apply. Re-add the entry to re-block.
Secondary rate limit during a large combined apply Org-governance apply bundled with a big bulk for_each run Keep org-settings in its own small plan/apply, separate from bulk membership/collaborator applies.

πŸ”— Related Docs

  • integrations/github provider β€” Organization Settings resource reference
  • integrations/github provider β€” Organization Security Manager resource (deprecated) and Organization Role Team resource (the replacement)
  • integrations/github provider β€” Organization Block resource reference
  • integrations/github provider β€” Organization data source (read-back attributes)
  • module β€” terraform-github-organization-roles (org roles; the built-in security_manager role β€” recommended path)
  • module β€” terraform-github-organization-ruleset (org-wide branch/tag rules)
  • module β€” terraform-github-repository (keystone; inherits the new-repo baseline)
  • module β€” terraform-github-team (emits the slug consumed for security managers)
  • module β€” terraform-github-membership (org membership & roles)
  • GitHub Docs β€” Managing organization settings, GitHub Advanced Security, security managers, blocking users, and the edition feature matrix

About

Terraform module: terraform-github-organization-settings

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages