Skip to content

Add script to require signed commits on protected branches - #14

Open
razo7 wants to merge 5 commits into
medik8s:mainfrom
razo7:require-signed-commits
Open

Add script to require signed commits on protected branches#14
razo7 wants to merge 5 commits into
medik8s:mainfrom
razo7:require-signed-commits

Conversation

@razo7

@razo7 razo7 commented Mar 31, 2026

Copy link
Copy Markdown
Member

Why we need this PR:

Prow branchprotector does not support required_signatures. This script uses the GitHub API to enable (or disable) the signed commits requirement on protected branches.

Changes made:

  • scripts/require-signed-commits.sh — enables/disables required_signatures per branch via gh api
  • Dynamically discovers all non-archived, non-fork repos in the org (no static config to maintain)
  • Filters branches by regex pattern (default: main|release-.+)
  • Supports --dry-run, --disable --confirm, --org, --repo (repeatable), --branch (repeatable)
  • Only dependency: gh CLI (authenticated with admin scope)
  • Validates all names against [a-zA-Z0-9._-]+ to prevent injection
  • Warns when a branch has no protection rule yet (waiting for branchprotector)

Which issue(s) this PR fixes:

  • RHWA-788 — require signed commits on protected branches
  • RHWA-854 — recommended: require signed commits
  • RHWA-1171 — related: community docs and CONTRIBUTING.md

Test plan:

$ ./scripts/require-signed-commits.sh --dry-run
==> Discovering repos in medik8s (non-archived, non-fork)...
==> Found 13 repos
==> storage-based-remediation
    main — would ENABLE signed commits
WARNING: release-0.1 — no branch protection rule yet
WARNING: release-0.2 — no branch protection rule yet
    release-0.3 — would ENABLE signed commits
==> node-healthcheck-operator
    main — would ENABLE signed commits
    release-0.4 — would ENABLE signed commits
    ...
==> Done: changed=0 skipped=7 failed=0

$ ./scripts/require-signed-commits.sh --repo self-node-remediation --dry-run
# scopes to a single repo

$ ./scripts/require-signed-commits.sh --repo nhc --repo snr --branch main --dry-run
# multiple repos, specific branch

Summary by CodeRabbit

  • New Features
    • Added a command-line tool to manage required signed commits across one or more repositories and branches.
    • Supports dry runs, enabling or disabling protection, branch pattern filtering, and repo selection.
    • Shows a clear summary of changes, skipped items, and failures, and exits with an error if any updates fail.

@openshift-ci
openshift-ci Bot requested review from beekhof and clobrano March 31, 2026 08:59
Prow branchprotector does not support required_signatures, so this
script fills the gap by enabling/disabling it per branch using a
YAML config file (signed-commits.yaml).

RHWA-788

Assisted-by: Claude claude-opus-4-6
@razo7
razo7 force-pushed the require-signed-commits branch from 40ad9f8 to 8a27902 Compare March 31, 2026 09:30
Comment thread scripts/signed-commits.yaml Outdated
@@ -0,0 +1,66 @@
org: medik8s

@mshitrit mshitrit Jun 24, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIUC will need to keep updating this table.
Any chance we can take this information from an existing location ? (somehow keep a single source of truth for that)

[edit] probably low priority issue as IIUC the script is manually triggered

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think would be better to use regexes here. No need to update the file anymore

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The branches listed here correspond to the supported OCP operator versions per https://access.redhat.com/support/policy/updates/openshift_operators#platform-aligned. So the source of truth is the Red Hat support lifecycle. The list only changes when versions go EOL or new releases ship, and since the script is manually triggered, the maintenance cost is low.

Added a comment at the top of the YAML documenting this in d989e71.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about skipping the config file and just use all main & release-* branches on all repos in the org?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That could indeed be easier and probably make more sense.
WDYT? @mpryc @mshitrit @clobrano

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think would be better to use regexes here

I agree 😁

@mpryc

mpryc commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Since this is different from the Signed-off-by required by many OpenShift org repos - do we have (or plan to have) a short doc or link explaining the signed commits requirement and setup ?

Would be good to have it ready before rolling this out, maybe linked from CONTRIBUTING.md somewhere ?

Comment thread scripts/require-signed-commits.sh Outdated
branches=$(yq ".repos.\"${repo}\".branches[]" "$CONFIG")

for branch in $branches; do
api_response=$(gh api "repos/${ORG}/${repo}/branches/${branch}/protection/required_signatures" 2>&1) && \

@mshitrit mshitrit Jun 24, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: any failure will result in "no-protection" response which can shadow other unrelated errors (permission denied, Github API error, network etc...)

Comment thread scripts/require-signed-commits.sh Outdated
info "$branch — disabled"
((changed++)) || true
else
warn "$repo/$branch — failed to disable"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: would be a good idea to capture and wrap the actual error

Comment thread scripts/require-signed-commits.sh Outdated
info "$branch — enabled"
((changed++)) || true
else
warn "$repo/$branch — failed to enable (need admin access?)"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: would be a good idea to capture and wrap the actual error

@mshitrit

Copy link
Copy Markdown
Member

Nit: some code duplication in the script that can be avoided

@mshitrit mshitrit left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/hold
lgtm, left some non blocking nits.
Giving other chance to review

@openshift-ci

openshift-ci Bot commented Jun 24, 2026

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: mshitrit, razo7

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

Distinguish API errors from missing branch protection rules instead of
treating all failures as "no-protection". Capture and include actual
error messages in warnings for enable/disable operations.

Add a comment in signed-commits.yaml documenting that the branch list
corresponds to supported OCP operator versions.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@openshift-ci openshift-ci Bot removed the lgtm label Jun 24, 2026
@openshift-ci

openshift-ci Bot commented Jun 24, 2026

Copy link
Copy Markdown

New changes are detected. LGTM label has been removed.

@coderabbitai

coderabbitai Bot commented Jun 24, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@razo7, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 19 minutes and 18 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: eab21efa-3d98-470a-aa2f-d367402fdc8e

📥 Commits

Reviewing files that changed from the base of the PR and between 7e32acc and 390abe4.

📒 Files selected for processing (1)
  • scripts/require-signed-commits.sh
📝 Walkthrough

Walkthrough

Adds a Bash utility that discovers repositories and branches, checks GitHub required-signatures protection, and enables or disables it with dry-run, confirmation, and authentication checks. It tracks changed, skipped, and failed branches and exits non-zero when failures occur.

Changes

Signed commits enforcement

Layer / File(s) Summary
CLI and preflight
scripts/require-signed-commits.sh
Defines logging and validation helpers, parses flags, enforces disable confirmation, checks gh authentication, and computes the branch selector.
Repo and branch selection
scripts/require-signed-commits.sh
Builds the repo list from flags or GitHub org listing, initializes counters, and filters repo branches by the selected regex.
Protection state and updates
scripts/require-signed-commits.sh
Reads required-signatures state, maps missing protection to a sentinel, applies enable or disable mutations or dry-run logs, and prints the final totals.

Sequence Diagram(s)

sequenceDiagram
  participant Script as require-signed-commits.sh
  participant gh as gh CLI
  participant GitHub as GitHub API

  Script->>gh: list repos or branches
  gh->>GitHub: GET org/repo/branch data
  GitHub-->>gh: repos or branches
  gh-->>Script: filtered targets

  loop each branch
    Script->>gh: read required_signatures
    gh->>GitHub: GET required_signatures
    GitHub-->>gh: enabled or missing protection
    gh-->>Script: current state

    alt disable
      Script->>gh: DELETE required_signatures
    else enable
      Script->>gh: POST required_signatures
    end
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: adding a script to require signed commits on protected branches.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@razo7

razo7 commented Jun 24, 2026

Copy link
Copy Markdown
Member Author

@mpryc Good idea! I think we should work on this as part of RHWA-1171, for better community visibility and having a shared (or per-repo) CONTRIBUTING.md. Since we don't have an agreement on RHWA-1171 scope yet, I'd like to merge this now and tackle the docs as part of that effort.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
scripts/require-signed-commits.sh (1)

56-56: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Optional: collapse the repeated endpoint URL.

The repos/${ORG}/${repo}/branches/${branch}/protection/required_signatures path is built three times (query, DELETE, POST). Extracting it into a per-branch local variable reduces duplication and the chance of the three copies drifting.

Also applies to: 78-78, 100-100

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/require-signed-commits.sh` at line 56, The required signatures API
path is duplicated across the branch protection flow, which makes the query,
DELETE, and POST calls easy to drift apart. In require-signed-commits.sh,
introduce a per-branch local variable for the
repos/${ORG}/${repo}/branches/${branch}/protection/required_signatures endpoint
and reuse it in the logic around api_response, the DELETE call, and the POST
call so all three references stay consistent.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/require-signed-commits.sh`:
- Around line 56-66: The `gh api` call inside the `api_response` assignment in
`require-signed-commits.sh` is being executed under `set -e`, so a non-zero exit
aborts the script before the `api_exit` handling can run. Update the
branch-protection check around `api_response`, `api_exit`, and the
`current`/`warn` logic so the command failure is captured inline and does not
trigger `set -e`, allowing the existing `no-protection` and API-error paths to
execute as intended.

---

Nitpick comments:
In `@scripts/require-signed-commits.sh`:
- Line 56: The required signatures API path is duplicated across the branch
protection flow, which makes the query, DELETE, and POST calls easy to drift
apart. In require-signed-commits.sh, introduce a per-branch local variable for
the repos/${ORG}/${repo}/branches/${branch}/protection/required_signatures
endpoint and reuse it in the logic around api_response, the DELETE call, and the
POST call so all three references stay consistent.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: af88db1f-a6eb-449e-beac-31e980b8785b

📥 Commits

Reviewing files that changed from the base of the PR and between ae1615c and d989e71.

📒 Files selected for processing (2)
  • scripts/require-signed-commits.sh
  • scripts/signed-commits.yaml

Comment thread scripts/require-signed-commits.sh Outdated
razo7 and others added 2 commits June 24, 2026 17:13
The `api_response=$(gh api ...) + api_exit=$?` pattern silently
aborts under `set -e` when gh returns non-zero — the assignment
fails before `api_exit=$?` executes. Use `|| api_exit=$?` to
capture the exit code inline.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Require --confirm with --disable to prevent accidental protection removal
- Replace unquoted for-loops with while-read to prevent word splitting
- Validate org/repo/branch names against [a-zA-Z0-9._-] to block path traversal
- Detect wrong yq variant (kislyuk vs mikefarah) at startup
- Add timestamps to info/warn output for audit trail
- Exit non-zero when any operations fail

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/require-signed-commits.sh`:
- Around line 96-100: The branch discovery logic in the matched_branches
population block is swallowing failures from gh api by discarding stderr and
forcing the pipeline to succeed, so update this flow to surface API errors
instead of treating them as an empty result. Keep the branch filtering around
branch_pattern and the read loop that fills matched_branches, but remove the
suppression that masks gh api failures and add explicit handling so permission,
rate-limit, or network issues cause the script to fail loudly rather than
continuing with no branches.
- Around line 107-111: The branch protection check in the loop over matched
branches rejects valid branch names like release/1.2 because valid_name is too
strict and the gh api path uses the raw branch string. Update valid_name to
allow forward slashes for branch names that should be supported, and make sure
the branch value used in the required_signatures request is URL-encoded before
interpolating it into the gh api path. Keep the existing behavior for invalid
names and continue using matched_branches, valid_name, and the gh api call site
as the main points to adjust.
- Around line 70-75: Validate the constructed branch regex before it is used in
the branch-checking flow, since invalid patterns from --branch can be hidden
later by the grep ... || true fallback. Add a single preflight validation step
in require-signed-commits.sh right after building branch_pattern from BRANCHES
or DEFAULT_BRANCH_PATTERN, and fail fast with a clear error if the regex is
invalid so the later matching logic in the script does not mask it.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7dbe33b0-544e-443f-a24d-47a3bef1cb5e

📥 Commits

Reviewing files that changed from the base of the PR and between b714ccc and 7e32acc.

📒 Files selected for processing (1)
  • scripts/require-signed-commits.sh

Comment thread scripts/require-signed-commits.sh
Comment thread scripts/require-signed-commits.sh
Comment thread scripts/require-signed-commits.sh
Drop signed-commits.yaml and its yq dependency. The script now
discovers repos via the GitHub API (non-archived, non-fork) and
filters branches with a regex pattern (default: main|release-.+).

New flags: --org, --repo (repeatable), --branch (repeatable).
Removed: --config.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@razo7
razo7 force-pushed the require-signed-commits branch from 7e32acc to 390abe4 Compare June 25, 2026 08:38
@mpryc

mpryc commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

@mpryc Good idea! I think we should work on this as part of RHWA-1171, for better community visibility and having a shared (or per-repo) CONTRIBUTING.md. Since we don't have an agreement on RHWA-1171 scope yet, I'd like to merge this now and tackle the docs as part of that effort.

Thanks @razo7. I'm happy to help with the CONTRIBUTING.md as part of RHWA-1171. That said, I do think we should settle the scope of RHWA-1171 before merging this - requiring cryptographic signatures from all contributors is unusual in open source and could discourage community participation. A CONTRIBUTING.md explaining the requirement would be the bare minimum, but ideally we'd also discuss whether there's a less restrictive approach (e.g., signatures required only from maintainers, at least two approvals, ok-to-test required from contributors that are not in OWNERS or only on release branches).

One other consideration is about OpenShift /cherry-pick.
Requiring signatures on release-* branches could also interfere with cherry-picks. The Prow cherry-pick plugin creates commits via bot, and git cherry-pick doesn't carry over the original signature. This would add friction to the process.

What I'd propose:

  1. Merge the script but don't enforce it yet
  2. DCO (Signed-off-by) for all contributors - standard, across OpenShift repos, lightweight, enforceable via a simple CI check, which is already existing in many repos.
  3. Cryptographic signatures optional - encouraged as a good practice and we can agree to use it, but not required, especially not for external contributors. We could add a non-blocking CI check that reports signing status without gating merges.
    If we do want signature enforcement, scope it narrowly:
    • Only for maintainers listed in OWNERS/OWNERS_ALIASES (not all contributors)
    • Or only on release branches (where provenance matters most), not on main where cherry-picks land first
  4. Require at least two approvals from OWNERS for merge - this is a stronger trust signal than signatures anyway

I'd suggest we document these decisions in CONTRIBUTING.md for all repos first, then implement the tooling to match. What do you think?

@razo7

razo7 commented Jun 25, 2026

Copy link
Copy Markdown
Member Author

@mpryc Good idea! I think we should work on this as part of RHWA-1171, for better community visibility and having a shared (or per-repo) CONTRIBUTING.md. Since we don't have an agreement on RHWA-1171 scope yet, I'd like to merge this now and tackle the docs as part of that effort.

Thanks @razo7. I'm happy to help with the CONTRIBUTING.md as part of RHWA-1171. That said, I do think we should settle the scope of RHWA-1171 before merging this - requiring cryptographic signatures from all contributors is unusual in open source and could discourage community participation. A CONTRIBUTING.md explaining the requirement would be the bare minimum, but ideally we'd also discuss whether there's a less restrictive approach (e.g., signatures required only from maintainers, at least two approvals, ok-to-test required from contributors that are not in OWNERS or only on release branches).

I agree that mentioning the need for signing commits in CONTRIBUTING.md won't be enough without something like the Developer Certificate of Origin (DCO) check test to enforce and guide that.

One other consideration is about OpenShift /cherry-pick. Requiring signatures on release-* branches could also interfere with cherry-picks. The Prow cherry-pick plugin creates commits via bot, and git cherry-pick doesn't carry over the original signature. This would add friction to the process.

Good point, and I wonder what is best for tackling that 🤔

What I'd propose:

  1. Merge the script but don't enforce it yet

  2. DCO (Signed-off-by) for all contributors - standard, across OpenShift repos, lightweight, enforceable via a simple CI check, which is already existing in many repos.

  3. Cryptographic signatures optional - encouraged as a good practice and we can agree to use it, but not required, especially not for external contributors. We could add a non-blocking CI check that reports signing status without gating merges.
    If we do want signature enforcement, scope it narrowly:

    • Only for maintainers listed in OWNERS/OWNERS_ALIASES (not all contributors)
    • Or only on release branches (where provenance matters most), not on main where cherry-picks land first
  4. Require at least two approvals from OWNERS for merge - this is a stronger trust signal than signatures anyway

I'd suggest we document these decisions in CONTRIBUTING.md for all repos first, then implement the tooling to match. What do you think?

I like your motivation and the above sounds like a good plan for now.
IMHO the CONTRIBUTING.md and the rest of RHWA-1171 files should reside under https://github.com/medik8s/.github for centralized (and shared in the Medik8s organization) governance.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants