Skip to content

feat(webhook): enforce per-runner dynamic labels policy in dispatcher#5172

Merged
edersonbrilhante merged 15 commits into
mainfrom
feat/per-matcher-dynamic-labels-policy
Jun 24, 2026
Merged

feat(webhook): enforce per-runner dynamic labels policy in dispatcher#5172
edersonbrilhante merged 15 commits into
mainfrom
feat/per-matcher-dynamic-labels-policy

Conversation

@edersonbrilhante

@edersonbrilhante edersonbrilhante commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Description

Adds an opt-in, per-matcher policy for dynamic EC2 override labels so operators
can declare which ghr-ec2-* keys/values each runner queue is allowed to honor.

The webhook lambda is the gatekeeper: when a workflow_job arrives, it picks the
first matching runner queue that both opts into dynamic labels
(matcherConfig.enableDynamicLabels) and accepts every ghr-ec2-* label on the
job per matcherConfig.ec2DynamicLabelsPolicy. If no queue qualifies, the
request is returned with 202 and a warning is logged instead of falling back to
a queue with the dynamic labels stripped.

Highlights:

  • Adds a small EC2 dynamic labels policy evaluator co-located with dispatch.ts.
  • Replaces the flat reserved-key schema with:
    { blocked_keys?, restricted_keys? }.
  • blocked_keys rejects EC2 override keys outright.
  • restricted_keys applies per-key value rules:
    { allowed?, denied?, max? }.
  • EC2 override keys not listed in either blocked_keys or restricted_keys are
    allowed by default.
  • The flag and policy travel inside the existing runner-matcher-config SSM
    parameter via MatcherConfig. No new SSM parameter or env var is added.
  • Root single-runner config exposes ec2_dynamic_labels_policy; matcher config
    exposes ec2DynamicLabelsPolicy.

Example

multi_runner_config = {
  m5 = {
    matcherConfig = {
      labelMatchers           = [["self-hosted", "linux"]]
      exactMatch              = true
      enableDynamicLabels     = true
      ec2DynamicLabelsPolicy = {
        blocked_keys = ["image-id", "subnet-id"]

        restricted_keys = {
          "instance-type" = {
            allowed = ["m5.*"]
            denied  = ["m5.metal"]
          }

          "ebs-volume-size" = {
            max = 200
          }
        }
      }
    }

    runner_config = { ... }
  }
}

A job with ghr-ec2-instance-type:r5.large will be rejected with 202 and a
warning for the m5 queue rather than being silently dispatched without the
label.

Test Plan

  • yarn exec vitest run functions/webhook/src/runners/dynamic-labels-policy.test.ts functions/webhook/src/runners/dispatch.test.ts
  • terraform fmt -check -diff main.tf variables.tf modules/webhook/variables.tf modules/multi-runner/variables.tf

Related Issues

Fixes #5161
Fixes #5160

Pure module that, given a list of GitHub Actions labels and a policy, returns the ghr-ec2-* labels that violate the policy with a human-readable reason. Supports allowed_keys/denied_keys meta filters and per-key value rules (allowed/denied globs, numeric max). Keys use the same hyphenated form as the labels (e.g. instance-type). Not wired into dispatch yet.
Extend the MatcherConfig type so each runner-matcher entry can opt in to dynamic labels and ship its own per-matcher policy. The fields are optional so existing matcher configs keep working unchanged.
The flag now travels per-matcher inside the runner-matcher-config SSM blob (see MatcherConfig.enableDynamicLabels), so the global env-var version is no longer needed in either ConfigWebhook or ConfigDispatcher.
handleWorkflowJob now picks the first matching runner queue that both opts into dynamic labels (matcherConfig.enableDynamicLabels) and accepts every ghr-ec2-* label on the job per its dynamicLabelsPolicy. If no such queue exists, the request is returned with status 202 and a warning is logged instead of falling back to a queue with the dynamic labels stripped.
The dispatcher is now the sole gatekeeper for dynamic labels, so scale-up no longer reads ENABLE_DYNAMIC_LABELS or applies a policy. It simply forwards every ghr-ec2-* label that survived dispatch into the EC2 override config.
Move the dynamic-labels opt-in and the per-runner policy from the runner_config block into matcherConfig, so they ride with the rest of the matcher metadata into the webhook lambda. Stop passing enable_dynamic_labels and dynamic_labels_policy to the runners module (the dispatcher is the gatekeeper) and stop OR-ing the flag at the webhook module boundary.
Drop the top-level enable_dynamic_labels variable and the ENABLE_DYNAMIC_LABELS env on the webhook and dispatcher lambdas. The flag and policy now travel as fields inside runner_matcher_config[*].matcherConfig and are serialized into the existing runner-matcher-config SSM parameter. Also remove the matching variables and lambda env vars from the runners module since scale-up no longer evaluates the policy.
Single-runner deployments can now declare a dynamic_labels_policy alongside enable_dynamic_labels. The root module nests both fields inside runner_matcher_config[0].matcherConfig so the webhook lambda can enforce the policy per-matcher. Document the flat policy schema (allowed_keys/denied_keys plus per-key allowed/denied/max rules with hyphenated keys) and clarify that violating jobs are returned with a 202.
@github-actions

github-actions Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Scanned Files

None

@edersonbrilhante edersonbrilhante force-pushed the feat/per-matcher-dynamic-labels-policy branch from 631c506 to f77cb8a Compare June 16, 2026 11:07
@edersonbrilhante edersonbrilhante marked this pull request as ready for review June 16, 2026 13:30
@edersonbrilhante edersonbrilhante requested review from a team as code owners June 16, 2026 13:30
@Brend-Smits

Copy link
Copy Markdown
Contributor

@copilot resolve the merge conflicts in this pull request

Copilot AI commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

@copilot resolve the merge conflicts in this pull request

Merge conflicts resolved in commit d60ec96.

Two files had conflicts:

  1. lambdas/functions/control-plane/src/scale-runners/scale-up.ts — Combined our removal of the dynamicLabelsEnabled guard with main's per-group groupRunnerLabels accumulation fix (fix(scale-up): prevent runnerLabels accumulation across dynamic label groups #5176) and the shouldLoadLaunchTemplateBlockDeviceName/getDefaultBlockDeviceNameFromLaunchTemplate feature (feat: support dynamic EC2 placement and block device overrides #5178).

  2. modules/multi-runner/README.md — Auto-generated doc conflict; took main's version as the base and inserted our enableDynamicLabels and ec2DynamicLabelsPolicy matcherConfig fields.

All 277 tests pass.

Copilot AI requested a review from Brend-Smits June 19, 2026 09:52
@edersonbrilhante

Copy link
Copy Markdown
Contributor Author

@Brend-Smits I tested in my environment and it is working

Comment thread docs/configuration.md

@Brend-Smits Brend-Smits left a comment

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.

Great work! Thanks a lot 🚀

@edersonbrilhante edersonbrilhante merged commit d624d96 into main Jun 24, 2026
43 checks passed
@edersonbrilhante edersonbrilhante deleted the feat/per-matcher-dynamic-labels-policy branch June 24, 2026 11:23
Brend-Smits pushed a commit that referenced this pull request Jun 24, 2026
🤖 I have created a release *beep* *boop*
---


##
[7.8.0](v7.7.1...v7.8.0)
(2026-06-24)


### Features

* **runners:** add volume_initialization_rate to block_device_mappings
([#5165](#5165))
([449df46](449df46)),
closes
[#5163](#5163)
* support dynamic EC2 placement and block device overrides
([#5178](#5178))
([58fd1c2](58fd1c2))
* **webhook:** enforce per-runner dynamic labels policy in dispatcher
([#5172](#5172))
([d624d96](d624d96))


### Bug Fixes

* **ci:** restore persist-credentials for gh-pages deploy
([#5162](#5162))
([9955d73](9955d73))
* **codeowners:** require admin review for nested .github paths
([#5164](#5164))
([83ea30c](83ea30c))
* **mac:** handle RunInstances scale errors
([#5183](#5183))
([7d8c576](7d8c576))
* **scale-up:** prevent runnerLabels accumulation across dynamic label
groups
([#5176](#5176))
([246949e](246949e)),
closes
[#5175](#5175)

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Co-authored-by: runners-releaser[bot] <194412594+runners-releaser[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature: allow/deny list for dynamic label values Feature: allow enable_dynamic_labels per multi_runner_config entry

3 participants