Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@ defined **once** here and applied to **every** repo automatically.

| Resource | Effect |
| --- | --- |
| `github_organization_ruleset.markdown_lint` | Requires the markdownlint workflow in `dryvist/.github` to pass on the default branch of **every** repo in the org. Single source of truth: the workflow + `.markdownlint-cli2.yaml` both live in `dryvist/.github`. |
| `github_organization_ruleset.org_push_protection` | Native GitHub push rules enforced at the git layer (no workflow runs). Hard ceiling on individual file size and a banned-extension list, applied to every repo, every ref. Thresholds and list live in `config/rulesets-defaults.yml`. |
| `github_organization_ruleset.markdown_lint` | Requires the markdownlint workflow in the org's `.github` repo to pass on the default branch of **every** repo. Single source of truth: the workflow + `.markdownlint-cli2.yaml` both live in `.github`. |

Start small — this is the seed. Branch protection, the verified-signature
policy, file-size limits, and repo settings can move here next.
Start small — this is the seed. Branch protection, commit-message format,
the verified-signature policy, repo settings, labels, and per-repo file
content (LICENSE, CODEOWNERS) move here next.

## Layout

Expand All @@ -28,7 +30,8 @@ versions.tf # terraform + provider pins, S3 backend (partial)
providers.tf # github provider, GITHUB_TOKEN auth
variables.tf # all input variables (no magic numbers in .tf below)
data.tf # live lookups: repo IDs, org metadata — never literals
rulesets.tf # org rulesets (markdown_lint, …)
locals.tf # config/*.yml decoded into named locals for rulesets.tf
rulesets.tf # org rulesets (markdown_lint, org_push_protection, …)
main.tf # multi-file entrypoint stub (resources organized by topic)
outputs.tf # intentionally empty — see file header
config/ # YAML thresholds + lists consumed via yamldecode(file(...))
Expand Down
21 changes: 11 additions & 10 deletions config/rulesets-defaults.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,15 @@ push_protection:
# rather than raising the org-wide ceiling.
max_file_size_mb: 1

# Banned file extensions. These extensions usually carry credentials or
# keys; pushes containing files with these extensions are rejected at the
# git layer. Repos that legitimately need to commit one of these (e.g. a
# test fixture) put them in a bypass-allowed path or add the actor to
# bypass.
# Banned file extensions, as fnmatch globs (the integrations/github provider
# format for restricted_file_extensions). These extensions usually carry
# credentials or keys; pushes containing files matching any of these globs
# are rejected at the git layer. Repos that legitimately need to commit one
# (e.g. a test fixture) put them in a bypass-allowed path or add the actor
# to bypass.
banned_file_extensions:
- env
- pem
- key
- p12
- pfx
- "*.env"
- "*.pem"
- "*.key"
- "*.p12"
- "*.pfx"
6 changes: 6 additions & 0 deletions locals.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Structured defaults consumed by org rulesets live in config/*.yml; this
# file decodes them into named locals so rulesets.tf reads them as terraform
# values, not raw file reads scattered through resource bodies.
locals {
push_protection_defaults = yamldecode(file("${path.module}/config/rulesets-defaults.yml")).push_protection
}
31 changes: 31 additions & 0 deletions rulesets.tf
Original file line number Diff line number Diff line change
@@ -1,3 +1,34 @@
# Org-wide native push protection — max file size + banned extensions.
#
# Enforced at the git layer by GitHub's own push rules; no workflow runs.
# Thresholds and extension list come from config/rulesets-defaults.yml via
# local.push_protection_defaults, so this resource carries no magic numbers
# or hardcoded lists. Bypass actors are managed in the GitHub UI — this
# resource does not claim ownership of them, so manual exemptions for
# specific repos or actors persist across applies.
resource "github_organization_ruleset" "org_push_protection" {
name = "org-push-protection"
target = "push"
enforcement = var.org_push_protection_enforcement

conditions {
repository_name {
include = ["~ALL"]
exclude = []
}
}

rules {
max_file_size {
max_file_size = local.push_protection_defaults.max_file_size_mb
}

file_extension_restriction {
restricted_file_extensions = local.push_protection_defaults.banned_file_extensions
}
}
}

# Org-wide markdown linting, enforced as a Required Workflow.
#
# Every repo's default-branch PRs must pass the markdownlint workflow that
Expand Down
19 changes: 19 additions & 0 deletions variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,22 @@ variable "markdown_lint_enforcement" {
error_message = "markdown_lint_enforcement must be one of: disabled, evaluate, active."
}
}

variable "org_push_protection_enforcement" {
description = <<-EOT
Enforcement mode for the org-wide push-protection ruleset (native
max_file_size + file_extension_restriction push rules). Applies to every
repo, every ref; enforced at the git layer with no workflow runs.

One of: disabled, evaluate, active. Defaults to "active" — new rulesets
apply enabled directly per the convention; the variable exists so a
misbehaving rule can be disabled with `-var` without a code change.
EOT
type = string
default = "active"

validation {
condition = contains(["disabled", "evaluate", "active"], var.org_push_protection_enforcement)
error_message = "org_push_protection_enforcement must be one of: disabled, evaluate, active."
}
}
Loading