Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
c42304a
fix chart service monitor scrape annotations
Soli0222 Jul 3, 2026
e5eb54f
use nindent for service monitor annotations
Soli0222 Jul 3, 2026
73f5294
fix(deps): bump google.golang.org/grpc to v1.79.3 to address CVE-2026…
funbiscuit Jul 8, 2026
7938d3f
feat: allow disabling the metrics endpoint
Yurii201811 Jul 16, 2026
5fb56ec
go: bump to 1.26.5 to fix GO-2026-5856
funbiscuit Jul 15, 2026
3f59137
ci: remind feature PRs to update website docs
pujitha24 Jul 12, 2026
1cae51e
ci: fail docs-reminder job when feature PR misses website docs
pujitha24 Jul 19, 2026
7d9b7ae
fix: anchor /kind regex to line start in docs-reminder
pujitha24 Jul 27, 2026
274b863
ci: check kind/feature label instead of parsing PR body in docs-reminder
pujitha24 Jul 30, 2026
d28898c
ci: rewrite docs-reminder as a bash step instead of github-script
pujitha24 Aug 2, 2026
2d30cc0
feat(chart): add configurable paths for tini and docker-start in FRR …
benispeti Jul 14, 2026
8ea84da
feat(chart): make FRR container security context configurable
benispeti Jul 18, 2026
e83bc10
feat(chart): document frr securityContext variable
benispeti Jul 18, 2026
2f3f076
feat(helm): adds revisionHistoryLimit as a configurable parameter
NPastorale Jun 22, 2026
3705fd8
fix: avoid stale resourceVersion on ServiceL2Status recreate
somaz94 Aug 5, 2026
246685b
fix: avoid stale resourceVersion on ServiceBGPStatus recreate
somaz94 Aug 5, 2026
18469a1
tls: pass cipher suites even when tls version is 1.3
oribon Aug 11, 2026
b3d6455
Merge remote-tracking branch 'upstream/main' into fixtlssync
oribon Aug 11, 2026
dc0965c
OpenShift only: bump vendor
oribon Aug 11, 2026
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
41 changes: 41 additions & 0 deletions .github/workflows/docs-reminder.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
name: Docs Reminder

# Reminder for contributors and reviewers: when a pull request is labeled
# "kind/feature" (see classify.yaml) but does not touch the website/
# documentation, fail this job so it's clearly visible. This check is not a
# required status check, so it does not block merging — it's only a visible
# reminder. Fixes: https://github.com/metallb/metallb/issues/2663
on:
pull_request:
types: [opened, synchronize, reopened, labeled]

# Least privilege; read-only so this also works for pull requests from forks.
permissions:
contents: read

jobs:
docs-reminder:
runs-on: ubuntu-22.04
# classify.yaml is the single source of truth for PR kind; check its
# kind/feature label directly instead of re-deriving it from the PR body.
if: >
github.actor != 'dependabot[bot]' &&
contains(github.event.pull_request.labels.*.name, 'kind/feature')
Comment on lines +21 to +23

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- workflow files ---'
git ls-files '.github/workflows/*'

printf '%s\n' '--- docs-reminder.yaml ---'
cat -n .github/workflows/docs-reminder.yaml

printf '%s\n' '--- label workflow references ---'
rg -n -C 4 'kind/feature|action-add-labels|pull_request_target|pull_request:' .github/workflows

Repository: openshift/metallb

Length of output: 7942


🏁 Script executed:

#!/bin/bash
set -eu

python3 - <<'PY'
from pathlib import Path
import re

p = Path(".github/workflows/docs-reminder.yaml").read_text()
condition = "\n".join(p.splitlines()[20:23])
print("condition:")
print(condition)
print("uses_event_actor:", "github.actor" in condition)
print("uses_pr_author:", "github.event.pull_request.user.login" in condition)

events = re.search(r"types:\s*\[(.*?)\]", p).group(1).replace(" ", "").split(",")
print("workflow_events:", events)

# Model the relevant GitHub event distinction without running repository code.
events_to_qualify = [
    {"event": "labeled", "github.actor": "maintainer", "pull_request.user.login": "dependabot[bot]"},
    {"event": "reopened", "github.actor": "maintainer", "pull_request.user.login": "dependabot[bot]"},
]
for e in events_to_qualify:
    current = e["github.actor"] != "dependabot[bot]"
    author_based = e["pull_request.user.login"] != "dependabot[bot]"
    print(e["event"], "current_condition_actor_filter=", current,
          "author_filter=", author_based)
PY

Repository: openshift/metallb

Length of output: 535


Filter Dependabot by pull request author.

github.actor is the account that triggered the workflow, not the pull request author. A maintainer can label or reopen a Dependabot pull request, which makes this condition true. Use github.event.pull_request.user.login != 'dependabot[bot]'.

🤖 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 @.github/workflows/docs-reminder.yaml around lines 21 - 23, Update the
condition in the workflow’s pull request filter to check
github.event.pull_request.user.login against dependabot[bot] instead of
github.actor, while preserving the existing kind/feature label requirement.

steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
Comment on lines +25 to +28

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Pin actions/checkout to a full commit SHA.

actions/checkout@v4 uses a mutable tag. Pin it to a reviewed full commit SHA and retain a version comment.

🧰 Tools
🪛 zizmor (1.29.0)

[warning] 25-28: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)

🤖 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 @.github/workflows/docs-reminder.yaml around lines 25 - 28, Update the
Checkout step’s actions/checkout reference from the mutable v4 tag to a reviewed
full commit SHA, and retain an inline comment identifying the pinned version.

- name: Remind to document feature PRs
run: |
BASE=${{ github.event.pull_request.base.sha }}
HEAD=${{ github.event.pull_request.head.sha }}
if git diff --name-only "$BASE"..."$HEAD" | grep -q '^website/'; then
echo "Feature PR updates website/ docs."
exit 0
fi
MSG="This PR is labeled \`kind/feature\` but does not update the \`website/\` documentation. If this feature is user-facing, please add or update the relevant docs under \`website/content/\` in this PR. This check is not required, so it does not block merging (see issue #2663)."
echo "### Documentation reminder" >> "$GITHUB_STEP_SUMMARY"
echo "$MSG" >> "$GITHUB_STEP_SUMMARY"
echo "::error::$MSG"
exit 1
1 change: 1 addition & 0 deletions api/v1beta2/bgppeer_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ type BGPPeerSpec struct {
// Add future BGP configuration here

// To set if we want to disable MP BGP that will separate IPv4 and IPv6 route exchanges into distinct BGP sessions.
//
// Deprecated: DisableMP is deprecated in favor of dualStackAddressFamily.
// +optional
// +kubebuilder:default:=false
Expand Down
5 changes: 5 additions & 0 deletions charts/metallb/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ Kubernetes: `>= 1.19.0-0`
| controller.readinessProbe.successThreshold | int | `1` | |
| controller.readinessProbe.timeoutSeconds | int | `1` | |
| controller.resources | object | `{}` | |
| controller.revisionHistoryLimit | int | `10` | |
| controller.runtimeClassName | string | `""` | |
| controller.securityContext.fsGroup | int | `65534` | |
| controller.securityContext.runAsNonRoot | bool | `true` | |
Expand Down Expand Up @@ -124,12 +125,15 @@ Kubernetes: `>= 1.19.0-0`
| speaker.enabled | bool | `true` | |
| speaker.excludeInterfaces.enabled | bool | `true` | |
| speaker.extraContainers | list | `[]` | |
| speaker.frr.dockerStartPath | string | `"/usr/lib/frr/docker-start"` | Path to the docker-start script inside the FRR container. Override this when using an FRR image (e.g. Docker Hardened Images) that places docker-start at a different location. |
| speaker.frr.enabled | bool | `false` | |
| speaker.frr.image.pullPolicy | string | `nil` | |
| speaker.frr.image.repository | string | `"quay.io/frrouting/frr"` | |
| speaker.frr.image.tag | string | `"10.5.3"` | |
| speaker.frr.metricsPort | int | `9121` | |
| speaker.frr.resources | object | `{}` | |
| speaker.frr.securityContext | object | `{"allowPrivilegeEscalation":false,"capabilities":{"add":["NET_ADMIN","NET_RAW","SYS_ADMIN","NET_BIND_SERVICE"]},"readOnlyRootFilesystem":true}` | Security context for the FRR container. |
| speaker.frr.tiniPath | string | `"/sbin/tini"` | Path to the tini binary inside the FRR container. Override this when using an FRR image (e.g. Docker Hardened Images) that places tini at a different location. |
| speaker.frrMetrics.resources | object | `{}` | |
| speaker.ignoreExcludeLB | bool | `false` | |
| speaker.image.pullPolicy | string | `nil` | |
Expand Down Expand Up @@ -163,6 +167,7 @@ Kubernetes: `>= 1.19.0-0`
| speaker.readinessProbe.timeoutSeconds | int | `1` | |
| speaker.reloader.resources | object | `{}` | |
| speaker.resources | object | `{}` | |
| speaker.revisionHistoryLimit | int | `10` | |
| speaker.runtimeClassName | string | `""` | |
| speaker.securityContext | object | `{}` | |
| speaker.serviceAccount.annotations | object | `{}` | |
Expand Down
1 change: 1 addition & 0 deletions charts/metallb/charts/crds/templates/crds.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -584,6 +584,7 @@ spec:
default: false
description: |-
To set if we want to disable MP BGP that will separate IPv4 and IPv6 route exchanges into distinct BGP sessions.

Deprecated: DisableMP is deprecated in favor of dualStackAddressFamily.
type: boolean
dualStackAddressFamily:
Expand Down
1 change: 1 addition & 0 deletions charts/metallb/templates/controller.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ spec:
{{- if .Values.controller.strategy }}
strategy: {{- toYaml .Values.controller.strategy | nindent 4 }}
{{- end }}
revisionHistoryLimit: {{ .Values.controller.revisionHistoryLimit }}
selector:
matchLabels:
{{- include "metallb.selectorLabels" . | nindent 6 }}
Expand Down
16 changes: 12 additions & 4 deletions charts/metallb/templates/servicemonitor.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,15 @@ spec:
apiVersion: v1
kind: Service
metadata:
{{- if or .Values.prometheus.scrapeAnnotations .Values.prometheus.serviceMonitor.speaker.annotations }}
annotations:
{{- if .Values.prometheus.scrapeAnnotations }}
prometheus.io/scrape: "true"
prometheus.io/scheme: "https"
{{- if .Values.prometheus.serviceMonitor.speaker.annotations }}
{{ toYaml .Values.prometheus.serviceMonitor.speaker.annotations | indent 4 }}
{{- end }}
{{- if .Values.prometheus.serviceMonitor.speaker.annotations }}
{{- toYaml .Values.prometheus.serviceMonitor.speaker.annotations | nindent 4 }}
{{- end }}
{{- end }}
labels:
name: {{ template "metallb.fullname" . }}-speaker-monitor-service
Expand Down Expand Up @@ -139,11 +143,15 @@ spec:
apiVersion: v1
kind: Service
metadata:
{{- if or .Values.prometheus.scrapeAnnotations .Values.prometheus.serviceMonitor.controller.annotations }}
annotations:
{{- if .Values.prometheus.scrapeAnnotations }}
prometheus.io/scrape: "true"
prometheus.io/scheme: "https"
{{- if .Values.prometheus.serviceMonitor.controller.annotations }}
{{ toYaml .Values.prometheus.serviceMonitor.controller.annotations | indent 4 }}
{{- end }}
{{- if .Values.prometheus.serviceMonitor.controller.annotations }}
{{- toYaml .Values.prometheus.serviceMonitor.controller.annotations | nindent 4 }}
{{- end }}
{{- end }}
labels:
name: {{ template "metallb.fullname" . }}-controller-monitor-service
Expand Down
18 changes: 7 additions & 11 deletions charts/metallb/templates/speaker.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ spec:
{{- if .Values.speaker.updateStrategy }}
updateStrategy: {{- toYaml .Values.speaker.updateStrategy | nindent 4 }}
{{- end }}
revisionHistoryLimit: {{ .Values.speaker.revisionHistoryLimit }}
selector:
matchLabels:
{{- include "metallb.selectorLabels" . | nindent 6 }}
Expand Down Expand Up @@ -419,15 +420,10 @@ spec:
{{- end }}
{{- if .Values.speaker.frr.enabled }}
- name: frr
{{- if .Values.speaker.frr.securityContext }}
securityContext:
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
capabilities:
add:
- NET_ADMIN
- NET_RAW
- SYS_ADMIN
- NET_BIND_SERVICE
{{- toYaml .Values.speaker.frr.securityContext | nindent 10 }}
{{- end }}
image: {{ .Values.speaker.frr.image.repository }}:{{ .Values.speaker.frr.image.tag | default .Chart.AppVersion }}
{{- if .Values.speaker.frr.image.pullPolicy }}
imagePullPolicy: {{ .Values.speaker.frr.image.pullPolicy }}
Expand All @@ -445,9 +441,9 @@ spec:
- name: frr-lib
mountPath: /var/lib/frr
command:
- /bin/sh
- -c
- /sbin/tini -- /usr/lib/frr/docker-start
- {{ .Values.speaker.frr.tiniPath | quote }}
- --
- {{ .Values.speaker.frr.dockerStartPath | quote }}
{{- with .Values.speaker.frr.resources }}
resources:
{{- toYaml . | nindent 12 }}
Expand Down
13 changes: 12 additions & 1 deletion charts/metallb/values.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,10 @@
},
"required": [ "type" ]
},
"revisionHistoryLimit": {
"type": "integer",
"minimum": 0
},
"command" : {
"type": "string"
},
Expand Down Expand Up @@ -375,6 +379,10 @@
},
"required": [ "type" ]
},
"revisionHistoryLimit": {
"type": "integer",
"minimum": 0
},
"runtimeClassName": {
"type": "string"
},
Expand All @@ -394,7 +402,10 @@
"image": { "$ref": "#/definitions/component/properties/image" },
"metricsPort": { "type": "integer" },
"secureMetricsPort": { "type": "integer" },
"resources:": { "type": "object" }
"resources:": { "type": "object" },

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- values.yaml ---'
sed -n '335,355p' charts/metallb/values.yaml

printf '%s\n' '--- speaker.yaml ---'
sed -n '438,455p' charts/metallb/templates/speaker.yaml

printf '%s\n' '--- values.schema.json ---'
sed -n '390,415p' charts/metallb/values.schema.json

printf '%s\n' '--- schema resource keys ---'
rg -n '"resources:?"\s*:' charts/metallb/values.schema.json

printf '%s\n' '--- JSON parse and FRR schema shape ---'
python3 - <<'PY'
import json
from pathlib import Path

schema_path = Path("charts/metallb/values.schema.json")
schema = json.loads(schema_path.read_text())

speaker = schema.get("properties", {}).get("speaker", {})
frr = speaker.get("properties", {}).get("frr", {})
print("speaker.frr schema keys:", list(frr.get("properties", {}).keys()))
print("resources schema entry:", frr.get("properties", {}).get("resources"))
PY

Repository: openshift/metallb

Length of output: 3130


🏁 Script executed:

#!/bin/bash
set -eu

python3 - <<'PY'
import json
from pathlib import Path

schema = json.loads(Path("charts/metallb/values.schema.json").read_text())

print("root keys:", list(schema))
print("root properties:", list(schema.get("properties", {})))

def find_frr(node, path="$"):
    if isinstance(node, dict):
        props = node.get("properties")
        if isinstance(props, dict) and "frr" in props:
            print("frr parent:", path)
            print("parent keys:", list(props))
            print("frr schema:", json.dumps(props["frr"], indent=2))
        for key, value in node.items():
            find_frr(value, f"{path}.{key}")
    elif isinstance(node, list):
        for index, value in enumerate(node):
            find_frr(value, f"{path}[{index}]")

find_frr(schema)
PY

printf '%s\n' '--- schema context around speaker ---'
rg -n -C 12 '"speaker"|"\$ref"|additionalProperties|required' charts/metallb/values.schema.json | head -n 180

Repository: openshift/metallb

Length of output: 6713


Rename the FRR resource schema key.

The schema defines speaker.frr["resources:"], but the chart uses speaker.frr.resources. Rename it to "resources" so Helm validates the configured resource object.

🤖 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 `@charts/metallb/values.schema.json` at line 405, Rename the schema property
key in the FRR resource definition from "resources:" to "resources" so it
matches the chart’s speaker.frr.resources configuration and validates the
resource object correctly.

"tiniPath": { "type": "string" },
"dockerStartPath": { "type": "string" },
"securityContext": { "type": "object" }
},
"required": [ "enabled" ]
},
Expand Down
20 changes: 20 additions & 0 deletions charts/metallb/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ controller:
## maxSurge: 25%
## maxUnavailable: 25%
##
revisionHistoryLimit: 10
strategy:
type: RollingUpdate
serviceAccount:
Expand Down Expand Up @@ -282,6 +283,7 @@ speaker:
## @param speaker.updateStrategy.type Speaker daemonset strategy type
## ref: https://kubernetes.io/docs/tasks/manage-daemon/update-daemon-set/
##
revisionHistoryLimit: 10
updateStrategy:
## StrategyType
## Can be set to RollingUpdate or OnDelete
Expand Down Expand Up @@ -344,6 +346,24 @@ speaker:
pullPolicy:
metricsPort: 9121
resources: {}
# -- Path to the tini binary inside the FRR container. Override this
# when using an FRR image (e.g. Docker Hardened Images) that places
# tini at a different location.
tiniPath: /sbin/tini
# -- Path to the docker-start script inside the FRR container. Override
# this when using an FRR image (e.g. Docker Hardened Images) that places
# docker-start at a different location.
dockerStartPath: /usr/lib/frr/docker-start
# -- Security context for the FRR container.
securityContext:
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
capabilities:
add:
- NET_ADMIN
- NET_RAW
- SYS_ADMIN
- NET_BIND_SERVICE
Comment on lines +357 to +366

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- relevant chart files ---'
git ls-files 'charts/metallb/values.yaml' 'charts/metallb/templates/speaker.yaml' 'charts/metallb/README.md' 'charts/metallb/Chart.yaml'

printf '%s\n' '--- values.yaml security-context section ---'
cat -n charts/metallb/values.yaml | sed -n '330,385p'

printf '%s\n' '--- speaker template security-context rendering ---'
cat -n charts/metallb/templates/speaker.yaml | sed -n '390,445p'

printf '%s\n' '--- FRR image and security-context references ---'
rg -n -C 3 'frr|securityContext|runAsNonRoot|SYS_ADMIN|NET_ADMIN|NET_RAW|NET_BIND_SERVICE|allowPrivilegeEscalation|capabilities' charts/metallb

printf '%s\n' '--- README generated values around FRR securityContext ---'
rg -n -C 12 'Security context for the FRR container|NET_ADMIN|SYS_ADMIN|NET_RAW|NET_BIND_SERVICE|readOnlyRootFilesystem' charts/metallb/README.md || true

printf '%s\n' '--- chart metadata and image defaults ---'
cat -n charts/metallb/Chart.yaml | sed -n '1,100p'
rg -n -C 4 'image:|repository:|tag:|digest:' charts/metallb/values.yaml charts/metallb/templates

Repository: openshift/metallb

Length of output: 50373


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- README FRR defaults ---'
sed -n '124,145p' charts/metallb/README.md

printf '%s\n' '--- README security-context rows ---'
rg -n -C 2 'speaker\.frr\.securityContext|readOnlyRootFilesystem|allowPrivilegeEscalation|capabilities|NET_ADMIN|NET_RAW|SYS_ADMIN|NET_BIND_SERVICE' charts/metallb/README.md

printf '%s\n' '--- all chart documentation for FRR capability rationale ---'
rg -n -i -C 3 'sys.admin|net.admin|net.raw|bind.service|capabilit|non.root|privilege' README.md docs charts .github 2>/dev/null | head -n 250 || true

printf '%s\n' '--- local references to the default FRR image ---'
rg -n -C 5 'quay\.io/frrouting/frr|frr:10\.5\.3|speaker\.frr\.securityContext' . --glob '!charts/metallb/README.md' --glob '!vendor/**' | head -n 250

Repository: openshift/metallb

Length of output: 25634


🌐 Web query:

Kubernetes container securityContext allowPrivilegeEscalation CAP_SYS_ADMIN always true official documentation

💡 Result:

According to official Kubernetes documentation, the allowPrivilegeEscalation field in a container's securityContext is always treated as true when the container is either run as privileged or has the CAP_SYS_ADMIN capability [1]. In practice, Kubernetes validation logic rejects configurations where allowPrivilegeEscalation is explicitly set to false while the container is configured to be privileged or to include the CAP_SYS_ADMIN capability [2][3]. The setting controls the no_new_privs flag on the container process; when allowPrivilegeEscalation is false, this flag is set, preventing processes from gaining more privileges than their parent process (such as via setuid binaries) [1][2][4]. It is important to note that the Kubernetes validation for this restriction specifically checks for the string "CAP_SYS_ADMIN" [5]. There have been identified inconsistencies where other dangerous capabilities (like CAP_SYS_MODULE or CAP_NET_ADMIN) or the variation "SYS_ADMIN" (without the "CAP_" prefix) may not trigger the same validation rejection, even though they can also enable privilege escalation [5][6][7]. Consequently, while the documentation states that CAP_SYS_ADMIN forces allowPrivilegeEscalation to true, users should be aware that the current implementation may not automatically block all equivalent privilege-escalating configurations [6].

Citations:


🌐 Web query:

FRRouting frr Docker image 10.5.3 required Linux capabilities NET_ADMIN NET_RAW SYS_ADMIN official

💡 Result:

The FRRouting (FRR) Docker image, including version 10.5.3, requires elevated Linux capabilities to function because it interacts directly with the host's networking stack to manage routing protocols [1][2]. While the exact required capabilities can depend on the specific routing daemons (e.g., bgpd, ospfd, zebra) you have enabled [1], it is standard practice and often necessary to provide a specific set of capabilities to the container. The commonly cited requirements for a functional FRR container include [1][3][4]: - NET_ADMIN: Required for modifying network interfaces, routing tables, and firewall rules [1]. - NET_RAW: Required for constructing raw packets (essential for many routing protocols) [1]. - SYS_ADMIN: Frequently required for configuring various kernel-level networking parameters [1]. - NET_BIND_SERVICE: Often required to bind to low-numbered privileged ports [1][3]. The official guidance emphasizes that when running the container, you should grant the full set of capabilities needed by any of the daemons you plan to enable [1]. Many real-world deployments, such as those using FRR in Kubernetes or Docker Compose, explicitly grant these four capabilities to ensure all protocol daemons function correctly [3][4][5][6]. If you are using a security-hardened environment, you may be able to restrict these based on a precise list of active daemons, but granting these is the standard approach for full functionality [1]. Always ensure your host system's kernel settings (e.g., IP forwarding) are configured appropriately to support the routing operations performed by the container [7].

Citations:


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- FRR container and init-container sections ---'
sed -n '421,540p' charts/metallb/templates/speaker.yaml

printf '%s\n' '--- generated legacy FRR manifest security contexts and resources ---'
sed -n '2470,2675p' config/manifests/metallb-frr-prometheus.yaml | \
  rg -n -C 5 'name: frr$|name: reloader|name: frr-metrics|securityContext:|capabilities:|resources:|readinessProbe:|livenessProbe:|startupProbe:|runAsNonRoot'

printf '%s\n' '--- capability and non-root settings in chart and legacy manifests ---'
rg -n -C 2 'drop:|add:|SYS_ADMIN|NET_ADMIN|NET_RAW|NET_BIND_SERVICE|runAsNonRoot|resources: \{\}|readinessProbe:' \
  charts/metallb/templates/speaker.yaml charts/metallb/values.yaml \
  config/frr/speaker-patch.yaml config/manifests/metallb-frr-prometheus.yaml | head -n 300

printf '%s\n' '--- upstream references available in repository metadata ---'
rg -n -i 'frrouting/frr|dockerfile|capabilities|SYS_ADMIN' .github docs config charts tasks.py \
  --glob '!config/manifests/*.yaml' | head -n 200 || true

Repository: openshift/metallb

Length of output: 17772


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- FRR upstream repository metadata ---'
curl -fsSL 'https://api.github.com/repos/FRRouting/frr/contents/docker' |
  jq -r '.[] | [.name, .download_url] | `@tsv`' | head -n 80

printf '%s\n' '--- FRR repository references to container capabilities ---'
curl -fsSL 'https://api.github.com/search/code?q=repo%3AFRRouting%2Ffrr+SYS_ADMIN' |
  jq -r '.message // empty, (.items[]?.html_url // empty)' || true

printf '%s\n' '--- MetalLB frr-k8s configuration capability references ---'
curl -fsSL 'https://raw.githubusercontent.com/metallb/frr-k8s/main/config/all-in-one/frr-k8s.yaml' |
  rg -n -C 8 'capabilities:|SYS_ADMIN|NET_ADMIN|NET_RAW|NET_BIND_SERVICE|runAsNonRoot'

Repository: openshift/metallb

Length of output: 2137


🌐 Web query:

site:github.com/FRRouting/frr Dockerfile 10.5.3 SYS_ADMIN NET_ADMIN NET_RAW

💡 Result:

The references to SYS_ADMIN, NET_ADMIN, and NET_RAW in the context of FRRouting (FRR) version 10.5.3 relate to Linux capabilities required for the proper operation of the FRR routing suite within Docker containers [1][2][3][4]. FRR services such as zebra, bgpd, ospfd, and watchfrr perform tasks that require elevated system privileges, including managing network namespaces (for VRF support) and manipulating network interfaces [3]. Specifically: - CAP_SYS_ADMIN: Required by components like ospfd to perform operations such as switching network namespaces [3]. - CAP_NET_ADMIN: Required for managing network configurations, routing tables, and interface settings [1][3]. - CAP_NET_RAW: Required for crafting and accessing raw network packets [1][3]. In Docker environments, these capabilities are not granted by default for security reasons. Users running FRR containers often encounter permission errors if these capabilities are not explicitly provided at runtime [1][3]. To resolve these errors, operators must either run the container with the --privileged flag (which grants all capabilities) or, more securely, use --cap-add to grant only the specific capabilities required (e.g., --cap-add=NET_ADMIN --cap-add=NET_RAW --cap-add=SYS_ADMIN) [1][3]. Alternatively, some users may configure file-level capabilities using setcap on the specific FRR binaries within the container image, although this requires careful management of container security policies [1][2].

Citations:


🌐 Web query:

site:github.com/metallb/metallb "speaker.frr.securityContext" "SYS_ADMIN"

💡 Result:

In the context of the MetalLB project and its FRRouting (FRR) integration, speaker.frr.securityContext refers to a configuration setting in the MetalLB Helm chart that defines the Kubernetes security context for the FRR sidecar container [1]. Historically, the FRR container in MetalLB required elevated Linux capabilities, including NET_ADMIN, NET_RAW, SYS_ADMIN, and NET_BIND_SERVICE, to perform networking operations such as BGP peering and ARP/NDP responses [1][2]. The addition of the speaker.frr.securityContext setting allowed operators to make these security settings configurable, enabling them to harden the container or adjust for custom images [1]. It is important to note: 1. Security Best Practices: While these capabilities were previously standard for the FRR sidecar, the project has historically moved to remove unnecessary elevated privileges (such as SYS_ADMIN) whenever possible to reduce the risk of container breakouts [3][4]. 2. Deprecation Warning: MetalLB has deprecated the FRR mode in favor of the frr-k8s mode [5]. Operators are encouraged to migrate to frr-k8s, which is the current default BGP backend [5]. 3. Current Implementation: In modern versions of the MetalLB chart, this configuration allows users to define the specific security context applied to the FRR container, maintaining the defaults that match required capabilities while providing flexibility for specific deployment environments [1].

Citations:


Drop all capabilities and document FRR security requirements.

The template renders speaker.frr.securityContext directly. Add capabilities.drop: [ALL]; the current default and generated README omit it.

Retain SYS_ADMIN only when the selected FRR image requires it. Kubernetes documents CAP_SYS_ADMIN as forcing effective allowPrivilegeEscalation: true. Document this exception or remove the capability. Validate runAsNonRoot for custom image overrides. Regenerate charts/metallb/README.md.

🤖 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 `@charts/metallb/values.yaml` around lines 357 - 366, Update the FRR
securityContext values to include capabilities.drop: [ALL], and retain SYS_ADMIN
only when required by the selected FRR image; otherwise remove it. Document the
SYS_ADMIN/allowPrivilegeEscalation exception and validate runAsNonRoot for
custom image overrides, then regenerate the corresponding
charts/metallb/README.md documentation.

Sources: Path instructions, MCP tools


reloader:
resources: {}
Expand Down
1 change: 1 addition & 0 deletions config/crd/bases/metallb.io_bgppeers.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@ spec:
default: false
description: |-
To set if we want to disable MP BGP that will separate IPv4 and IPv6 route exchanges into distinct BGP sessions.

Deprecated: DisableMP is deprecated in favor of dualStackAddressFamily.
type: boolean
dualStackAddressFamily:
Expand Down
1 change: 1 addition & 0 deletions config/manifests/metallb-frr-k8s-prometheus.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -616,6 +616,7 @@ spec:
default: false
description: |-
To set if we want to disable MP BGP that will separate IPv4 and IPv6 route exchanges into distinct BGP sessions.

Deprecated: DisableMP is deprecated in favor of dualStackAddressFamily.
type: boolean
dualStackAddressFamily:
Expand Down
1 change: 1 addition & 0 deletions config/manifests/metallb-frr-k8s.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -616,6 +616,7 @@ spec:
default: false
description: |-
To set if we want to disable MP BGP that will separate IPv4 and IPv6 route exchanges into distinct BGP sessions.

Deprecated: DisableMP is deprecated in favor of dualStackAddressFamily.
type: boolean
dualStackAddressFamily:
Expand Down
1 change: 1 addition & 0 deletions config/manifests/metallb-frr-prometheus.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -616,6 +616,7 @@ spec:
default: false
description: |-
To set if we want to disable MP BGP that will separate IPv4 and IPv6 route exchanges into distinct BGP sessions.

Deprecated: DisableMP is deprecated in favor of dualStackAddressFamily.
type: boolean
dualStackAddressFamily:
Expand Down
1 change: 1 addition & 0 deletions config/manifests/metallb-frr.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -616,6 +616,7 @@ spec:
default: false
description: |-
To set if we want to disable MP BGP that will separate IPv4 and IPv6 route exchanges into distinct BGP sessions.

Deprecated: DisableMP is deprecated in favor of dualStackAddressFamily.
type: boolean
dualStackAddressFamily:
Expand Down
1 change: 1 addition & 0 deletions config/manifests/metallb-native-prometheus.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -616,6 +616,7 @@ spec:
default: false
description: |-
To set if we want to disable MP BGP that will separate IPv4 and IPv6 route exchanges into distinct BGP sessions.

Deprecated: DisableMP is deprecated in favor of dualStackAddressFamily.
type: boolean
dualStackAddressFamily:
Expand Down
1 change: 1 addition & 0 deletions config/manifests/metallb-native.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -616,6 +616,7 @@ spec:
default: false
description: |-
To set if we want to disable MP BGP that will separate IPv4 and IPv6 route exchanges into distinct BGP sessions.

Deprecated: DisableMP is deprecated in favor of dualStackAddressFamily.
type: boolean
dualStackAddressFamily:
Expand Down
2 changes: 1 addition & 1 deletion configmaptocrs/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# syntax=docker/dockerfile:1.2

FROM --platform=$BUILDPLATFORM docker.io/golang:1.25.11 AS builder
FROM --platform=$BUILDPLATFORM docker.io/golang:1.26.5 AS builder

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

docker buildx imagetools inspect docker.io/golang:1.26.5

Repository: openshift/metallb

Length of output: 199


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

printf '%s\n' '--- Dockerfile references ---'
for file in configmaptocrs/Dockerfile controller/Dockerfile speaker/Dockerfile; do
  printf '%s\n' "$file"
  sed -n '1,12p' "$file"
done

printf '%s\n' '--- Registry manifest resolution ---'
token="$(
  curl -fsSL 'https://auth.docker.io/token?service=registry.docker.io&scope=repository:library/golang:pull' |
    jq -r '.token'
)"
curl -fsSLI \
  -H "Authorization: Bearer $token" \
  -H 'Accept: application/vnd.oci.image.index.v1+json, application/vnd.docker.distribution.manifest.list.v2+json' \
  'https://registry-1.docker.io/v2/library/golang/manifests/1.26.5' |
  awk 'BEGIN{IGNORECASE=1} /^docker-content-digest:|^content-type:/ {print}'

Repository: openshift/metallb

Length of output: 1469


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

token="$(
  curl -fsSL 'https://auth.docker.io/token?service=registry.docker.io&scope=repository:library/golang:pull' |
    jq -r '.token'
)"
digest='sha256:7caba5286b4c3613a337b709c573047d8ae62ee76106647313b61e72b99f20af'

manifest="$(
  curl -fsSL \
    -H "Authorization: Bearer $token" \
    -H 'Accept: application/vnd.oci.image.index.v1+json, application/vnd.docker.distribution.manifest.list.v2+json' \
    "https://registry-1.docker.io/v2/library/golang/manifests/$digest"
)"

printf '%s\n' "$manifest" | jq -c '{
  schemaVersion,
  mediaType,
  digest: "'"$digest"'",
  platforms: ([.manifests[]? | {
    os: .platform.os,
    architecture: .platform.architecture,
    variant: (.platform.variant // null)
  }] // [])
}'

Repository: openshift/metallb

Length of output: 1206


Pin all Go builder images by digest.

Use docker.io/golang:1.26.5@sha256:7caba5286b4c3613a337b709c573047d8ae62ee76106647313b61e72b99f20af in all three Dockerfiles.

📍 Affects 3 files
  • configmaptocrs/Dockerfile#L3-L3 (this comment)
  • controller/Dockerfile#L3-L3
  • speaker/Dockerfile#L3-L3
🤖 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 `@configmaptocrs/Dockerfile` at line 3, Pin the Go builder image by digest in
the FROM directive for configmaptocrs/Dockerfile, controller/Dockerfile, and
speaker/Dockerfile at lines 3-3, using the specified docker.io/golang:1.26.5
digest in all three files.

Source: Path instructions

WORKDIR $GOPATH/go.universe.tf/metallb

RUN --mount=type=cache,target=/go/pkg/mod/ \
Expand Down
2 changes: 1 addition & 1 deletion controller/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# syntax=docker/dockerfile:1.2

FROM --platform=$BUILDPLATFORM docker.io/golang:1.25.11 AS builder
FROM --platform=$BUILDPLATFORM docker.io/golang:1.26.5 AS builder
WORKDIR $GOPATH/go.universe.tf/metallb

RUN --mount=type=cache,target=/go/pkg/mod/ \
Expand Down
2 changes: 1 addition & 1 deletion controller/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ func (c *controller) SetPools(l log.Logger, pools *config.Pools) controllers.Syn

func main() {
var (
port = flag.Int("port", 9120, "HTTPS listening port for Prometheus metrics")
port = flag.Int("port", 9120, "HTTPS listening port for Prometheus metrics; set to 0 to disable metrics")
namespace = flag.String("namespace", os.Getenv("METALLB_NAMESPACE"), "config / memberlist secret namespace")
mlSecret = flag.String("ml-secret-name", os.Getenv("METALLB_ML_SECRET_NAME"), "name of the memberlist secret to create")
deployName = flag.String("deployment", os.Getenv("METALLB_DEPLOYMENT"), "name of the MetalLB controller Deployment")
Expand Down
4 changes: 2 additions & 2 deletions e2etest/go.mod
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
module go.universe.tf/e2etest

go 1.25.0
go 1.26.0

toolchain go1.25.11
toolchain go1.26.5

require (
github.com/google/go-cmp v0.7.0
Expand Down
4 changes: 2 additions & 2 deletions e2etest/go.work
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
go 1.25.0
go 1.26.0

toolchain go1.25.11
toolchain go1.26.5

use (
../
Expand Down
16 changes: 8 additions & 8 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
module go.universe.tf/metallb

go 1.25.0
go 1.26.0

toolchain go1.25.11
toolchain go1.26.5

require (
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc
Expand Down Expand Up @@ -33,7 +33,7 @@ require (
)

require (
cel.dev/expr v0.24.0 // indirect
cel.dev/expr v0.25.1 // indirect
dario.cat/mergo v1.0.0 // indirect
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect
github.com/Masterminds/semver/v3 v3.4.0 // indirect
Expand Down Expand Up @@ -128,17 +128,17 @@ require (
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect
golang.org/x/net v0.55.0 // indirect
golang.org/x/oauth2 v0.29.0 // indirect
golang.org/x/oauth2 v0.34.0 // indirect
golang.org/x/sync v0.20.0 // indirect
golang.org/x/term v0.43.0 // indirect
golang.org/x/text v0.37.0 // indirect
golang.org/x/time v0.11.0 // indirect
golang.org/x/tools v0.44.0 // indirect
gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb // indirect
google.golang.org/grpc v1.72.1 // indirect
google.golang.org/protobuf v1.36.7 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect
google.golang.org/grpc v1.79.3 // indirect

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

go mod why -m google.golang.org/grpc
go list -deps ./... | rg 'google.golang.org/grpc/(internal/transport|internal/xds/rbac)' || true

curl -fsS -X POST 'https://api.osv.dev/v1/query' \
  -H 'Content-Type: application/json' \
  -d '{"package":{"ecosystem":"Go","name":"google.golang.org/grpc"},"version":"v1.79.3"}'

Repository: openshift/metallb

Length of output: 9809


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

printf '%s\n' '--- go.mod context ---'
sed -n '125,148p' go.mod

printf '%s\n' '--- executable package declarations ---'
rg -n --glob '*.go' '^package main$' .

printf '%s\n' '--- grpc imports ---'
rg -n --glob '*.go' 'google\.golang\.org/grpc' .

printf '%s\n' '--- module references and build metadata ---'
rg -n 'go build|go install|cmd/|frr-tools|grpc' Makefile* Dockerfile* .github scripts hack deploy config 2>/dev/null || true

Repository: openshift/metallb

Length of output: 50373


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

python3 - <<'PY'
import json
import subprocess
from collections import defaultdict, deque

targets = ["./controller", "./speaker", "./frr-tools/metrics", "./frr-tools/cp-tool"]
proc = subprocess.Popen(
    ["go", "list", "-mod=vendor", "-json", "-deps", *targets],
    stdout=subprocess.PIPE,
    stderr=subprocess.PIPE,
    text=True,
)
decoder = json.JSONDecoder()
buf = ""
packages = {}

while True:
    chunk = proc.stdout.read(65536)
    if not chunk:
        break
    buf += chunk
    while buf:
        try:
            obj, end = decoder.raw_decode(buf)
        except json.JSONDecodeError:
            break
        buf = buf[end:].lstrip()
        if "ImportPath" in obj:
            packages[obj["ImportPath"]] = obj

stderr = proc.stderr.read()
status = proc.wait()
if status:
    print(stderr)
    raise SystemExit(status)

reverse = defaultdict(list)
for path, obj in packages.items():
    for dep in obj.get("Imports", []):
        reverse[dep].append(path)

print("--- vulnerable package presence ---")
for path in (
    "google.golang.org/grpc",
    "google.golang.org/grpc/internal/transport",
    "google.golang.org/grpc/internal/xds/rbac",
):
    print(path, "present" if path in packages else "absent")

print("--- target paths to grpc transport ---")
for target in targets:
    start = next((p for p in packages if p == target[2:] or p.endswith("/" + target[2:])), None)
    if not start:
        print(target, "not listed")
        continue
    queue = deque([(start, [start])])
    seen = {start}
    found = None
    while queue:
        current, path = queue.popleft()
        if current == "google.golang.org/grpc/internal/transport":
            found = path
            break
        for parent in reverse[current]:
            if parent not in seen:
                seen.add(parent)
                queue.append((parent, path + [parent]))
    print(target, " -> ".join(found) if found else "no path")

print("--- grpc version in vendor/modules.txt ---")
with open("vendor/modules.txt") as f:
    for line in f:
        if line.startswith("# google.golang.org/grpc "):
            print(line)
PY

Repository: openshift/metallb

Length of output: 531


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

python3 - <<'PY'
import json
import subprocess
from collections import deque

targets = ["controller", "speaker", "frr-tools/metrics", "frr-tools/cp-tool"]
proc = subprocess.Popen(
    ["go", "list", "-mod=vendor", "-json", "-deps", "./..."],
    stdout=subprocess.PIPE,
    stderr=subprocess.PIPE,
    text=True,
)
decoder = json.JSONDecoder()
buf = ""
packages = {}

while True:
    chunk = proc.stdout.read(65536)
    if not chunk:
        break
    buf += chunk
    while buf:
        try:
            obj, end = decoder.raw_decode(buf)
        except json.JSONDecodeError:
            break
        buf = buf[end:].lstrip()
        if "ImportPath" in obj:
            packages[obj["ImportPath"]] = obj

stderr = proc.stderr.read()
status = proc.wait()
if status:
    print(stderr)
    raise SystemExit(status)

for target in targets:
    start = next(
        (path for path in packages if path == target or path.endswith("/" + target)),
        None,
    )
    if not start:
        print(f"{target}: package not listed")
        continue

    queue = deque([(start, [start])])
    seen = {start}
    paths = []
    while queue:
        current, path = queue.popleft()
        if current in {
            "google.golang.org/grpc/internal/transport",
            "google.golang.org/grpc/internal/xds/rbac",
        }:
            paths.append(path)
            continue
        for dep in packages.get(current, {}).get("Imports", []):
            if dep in packages and dep not in seen:
                seen.add(dep)
                queue.append((dep, path + [dep]))

    print(f"--- {start} ---")
    if paths:
        for path in paths:
            print(" -> ".join(path))
    else:
        print("no path to affected grpc packages")
PY

Repository: openshift/metallb

Length of output: 1188


Upgrade google.golang.org/grpc before merging.

GO-2026-6061/GHSA-hrxh-6v49-42gf affects v1.79.3 and is fixed in v1.82.1. The speaker binary links google.golang.org/grpc/internal/transport. Upgrade to v1.82.1 or later, then regenerate go.sum and refresh the vendored dependencies.

🧰 Tools
🪛 OSV Scanner (2.4.0)

[HIGH] 140-140: google.golang.org/grpc 1.79.3: Vulnerabilities in the xDS RBAC authorization engine and the HTTP/2 transport server implementation in google.golang.org/grpc

(GO-2026-6061)


[HIGH] 140-140: google.golang.org/grpc 1.79.3: gRPC-Go: xDS RBAC and HTTP/2 Vulnerabilities

(GHSA-hrxh-6v49-42gf)

🤖 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 `@go.mod` at line 140, Upgrade the google.golang.org/grpc dependency in go.mod
from v1.79.3 to v1.82.1 or later, then regenerate go.sum and refresh the
vendored dependencies to match.

Sources: Path instructions, Linters/SAST tools

google.golang.org/protobuf v1.36.10 // indirect
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
Expand Down
Loading