Skip to content
37 changes: 36 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Shared [Renovate](https://docs.renovatebot.com/) presets for the `opendefenseclo
- Pins Docker images and GitHub Actions to SHA digests (`pinDigests: true`) to prevent moving-tag attacks
- Tracks `opendefensecloud/dev-kit` releases in `Makefile` (`DEV_KIT_VERSION`)
- Tracks Go tool versions in `tools.lock`
- Adds an `automerge` label to digest, patch, and minor PRs (the signal the auto-approve workflow reacts to); major and security PRs never get it

### `go.json`
- Runs `go mod tidy` after updates
Expand All @@ -41,7 +42,7 @@ This config implements a "trust but verify" model that automates low-risk update
### Supply-chain security measures

- **Digest pinning** (`pinDigests: true`) — Docker images and GitHub Actions are pinned to immutable SHA-256 digests instead of mutable tags, preventing moving-tag attacks.
- **Stability window** (`minimumReleaseAge: 3 days` for patches and minor, `10 days` for major) — Renovate will not merge an update until the package version has been published for at least this long. This provides time for the community to detect and flag malicious releases before they land in the codebase. Major updates use a longer 10-day window since they carry more risk and are more prone to early regressions. Digest updates are exempt (`minimumReleaseAge: null`) since a re-pinned digest often carries no meaningful release age. Security updates (vulnerability alerts) bypass the stability window entirely (`matchVulnerabilityAlerts: false` on each update-type rule) — no delay is warranted for known vulnerabilities.
- **Stability window** (`minimumReleaseAge: 3 days` for patches and minor, `10 days` for major) — Renovate will not merge an update until the package version has been published for at least this long. This provides time for the community to detect and flag malicious releases before they land in the codebase. Major updates use a longer 10-day window since they carry more risk and are more prone to early regressions. Digest updates are exempt (`minimumReleaseAge: null`) since a re-pinned digest often carries no meaningful release age. Security updates (vulnerability alerts) bypass the stability window entirely. Their behaviour lives in the `vulnerabilityAlerts` object (`minimumReleaseAge: null`, `automerge: false`, `security` label), which Renovate applies on top of the normal rules for vulnerability-fix PRs. No delay is warranted for known vulnerabilities, and these PRs still need a human.

### Requirements for auto-merge

Expand All @@ -50,6 +51,40 @@ Renovate auto-merge requires:
2. Branch protection rules require **at least one passing status check** before merge (Renovate waits for all required checks).
3. `automergeType` defaults to `"pr"` — Renovate merges the PR after all required checks pass, not by force-pushing to the branch. If branch protection requires a manual review, Renovate must be exempted.

## Enabling automerge in a consuming repo

The presets set `automerge: true` for digest, patch, and minor updates, but Renovate cannot approve its own PRs. Two things unblock it per repo.

### 1. Call the auto-approve workflow

The reusable workflow lives in [`opendefensecloud/dev-kit`](https://github.com/opendefensecloud/dev-kit). Repos that copy dev-kit's `example/` already ship this caller. Otherwise add `.github/workflows/renovate-auto-approve.yml`:

```yaml
name: renovate-auto-approve
on:
pull_request:
types: [opened, reopened, synchronize, labeled, unlabeled]
permissions:
pull-requests: write
jobs:
approve:
uses: opendefensecloud/dev-kit/.github/workflows/renovate-auto-approve.yml@<sha-or-tag>
permissions:
pull-requests: write
```

Pin `@<sha-or-tag>` to a released tag or commit SHA, same discipline we apply to actions and images. The workflow approves only PRs carrying the `automerge` label and skips any PR labeled `security`, so digest/patch/minor updates get approved automatically while major and security updates wait for a human. If a PR gains the `security` label after it was auto-approved, the workflow revokes its approval (via a `request-changes` review) so a human is required again. It triggers on the `labeled` and `unlabeled` events for this reason (so adding or removing `security` re-evaluates the PR), so keep those in the caller's `pull_request` types.

### 2. Configure GitHub settings

- **Settings -> Actions -> General -> Workflow permissions:** enable ["Allow GitHub Actions to create and approve pull requests"](https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/enabling-features-for-your-repository/managing-github-actions-settings-for-a-repository#preventing-github-actions-from-creating-or-approving-pull-requests). Without this, the workflow's approval is rejected.
- **Branch ruleset on the default branch:**
- Require the CI / e2e status checks to pass. This is the gate that replaces the human for these update types.
- Require 1 approving review, which the workflow provides for eligible PRs.
- Give the Renovate app write access and allow auto-merge, so the PR merges once checks pass and the approval is in.

The approval is authored by `github-actions[bot]`, so a `Require review from Code Owners` rule wont be satisfied by it. If a repo needs that, run the approval step with a dedicated GitHub App or PAT token instead.

## Usage

Minimal setup for a Go controller repo:
Expand Down
10 changes: 3 additions & 7 deletions default.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,18 @@
"schedule": [],
"minimumReleaseAge": null,
"automerge": false,
"prPriority": 10,
"addLabels": ["security"]
},
"packageRules": [
{
"description": "Auto-merge patch and digest updates: patch updates wait a 3-day stability window",
"matchUpdateTypes": ["patch", "digest"],
"matchVulnerabilityAlerts": false,
"groupName": "dependencies (patch & digest)",
"groupSlug": "all-patch-and-digest-updates",
"minimumReleaseAge": "3 days",
"schedule": ["before 5am on Monday"],
"automerge": true,
"addLabels": ["ok-to-image"]
"addLabels": ["ok-to-image", "automerge"]
},
{
"description": "Skip minimum release age requirement for digest updates since they often lack a release timestamp",
Expand All @@ -31,18 +29,16 @@
{
"description": "Auto-merge minor updates after a 3-day stability window",
"matchUpdateTypes": ["minor"],
"matchVulnerabilityAlerts": false,
"groupName": "dependencies (minor)",
"groupSlug": "all-minor-updates",
"minimumReleaseAge": "3 days",
"schedule": ["before 5am on Monday"],
"automerge": true,
"addLabels": ["ok-to-image"]
"addLabels": ["ok-to-image", "automerge"]
},
{
"description": "Set minimum release age requirement for major updates to 10 days",
"matchUpdateTypes": ["major"],
"matchVulnerabilityAlerts": false,
"minimumReleaseAge": "10 days",
"addLabels": ["ok-to-image"]
},
Expand All @@ -56,7 +52,7 @@
"customManagers": [
{
"customType": "regex",
"managerFilePatterns": ["Makefile"],
"managerFilePatterns": ["/(^|/)Makefile$/"],
"matchStrings": [
"DEV_KIT_VERSION := (?<currentValue>v[0-9]+\\.[0-9]+\\.[0-9]+)"
],
Expand Down
3 changes: 1 addition & 2 deletions k8s.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
{
"description": "Add ok-to-helm label to automerge-eligible PRs in k8s repos",
"matchUpdateTypes": ["patch", "digest", "minor"],
"matchVulnerabilityAlerts": false,
"addLabels": ["ok-to-helm"]
},
{
Expand All @@ -31,7 +30,7 @@
"customManagers": [
{
"customType": "regex",
"managerFilePatterns": ["Makefile"],
"managerFilePatterns": ["/(^|/)Makefile$/"],
"matchStrings": [
"ENVTEST_K8S_VERSION \\?= (?<currentValue>[0-9]+\\.[0-9]+\\.[0-9]+)"
],
Expand Down
Loading