Skip to content

ci: add tls compliance scan#2564

Open
guidonguido wants to merge 5 commits into
stolostron:mainfrom
guidonguido:ci/tls-profile-semgrep
Open

ci: add tls compliance scan#2564
guidonguido wants to merge 5 commits into
stolostron:mainfrom
guidonguido:ci/tls-profile-semgrep

Conversation

@guidonguido

@guidonguido guidonguido commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Add a check to statically verify the code does not contain manual TLS version/ciphers configurations.

Semgrep rules inherited from HPCASE-maintained semgrep rules for detecting TLS configuration issues: https://github.com/smith-xyz/argus-observe-rules

BLOCKERS:

Summary by CodeRabbit

  • New Features
    • Added dynamic TLS profile handling for operators, metrics/webhooks, proxies, and rendered workloads (including TLS config reload on profile changes).
    • Added proxy flags for TLS minimum version and cipher suites.
    • TLS argument rendering now centrally applies cipher suites and minimum TLS version to generated resources.
  • Security
    • Enhanced linting with TLS-focused Semgrep and TLS configuration linting.
  • Bug Fixes
    • Improved visibility of TLS configuration issues by failing reconciliation when TLS settings can’t be computed.
    • Updated RBAC for required apiservers access and verbs to support TLS profile discovery.
  • Documentation

@openshift-ci

openshift-ci Bot commented Jul 10, 2026

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: guidonguido

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

@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The PR adds OpenShift TLS profile discovery, dynamic TLS configuration for operators, rendered workloads, and proxies, profile-change-triggered manager shutdown, TLS-focused linting, RBAC access, dependency updates, and tests.

Changes

TLS security integration

Layer / File(s) Summary
TLS profile helpers and validation
operators/pkg/config/config.go, operators/pkg/util/*, operators/pkg/util/tls*_test.go
Adds cached TLS profile/config resolution, an OpenShift API-server client, argument mutation, and unit/integration coverage.
Operator runtime TLS and profile reload
operators/endpointmetrics/main.go, operators/multiclusterobservability/main.go, operators/*/config/rbac/*, go.mod
Configures manager TLS, watches profile changes, cancels manager contexts, updates RBAC, and aligns dependencies.
Rendered workload TLS arguments
operators/endpointmetrics/pkg/rendering/*, operators/multiclusterobservability/pkg/rendering/*, operators/endpointmetrics/manifests/prometheus/*
Applies dynamic TLS flags to supported proxy containers and removes static cipher-suite arguments.
Proxy TLS option propagation
proxy/cmd/main.go, proxy/pkg/proxy/tls.go
Adds command-line TLS options and applies parsed values during TLS reload.
Static-analysis enforcement
.github/workflows/lint.yaml, collectors/metrics/pkg/...
Adds TLS-focused Semgrep scanning and annotates intentional TLS configurations.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Operator
  participant TLSHelpers
  participant APIServer
  participant SecurityProfileWatcher
  participant Manager
  Operator->>TLSHelpers: resolve TLS profile and TLS config
  TLSHelpers->>APIServer: read APIServer TLS settings
  APIServer-->>TLSHelpers: return profile data
  TLSHelpers-->>Operator: provide TLS configuration
  Operator->>Manager: start metrics and webhook servers with TLS
  SecurityProfileWatcher->>APIServer: watch TLS profile
  APIServer-->>SecurityProfileWatcher: report profile change
  SecurityProfileWatcher->>Manager: cancel context
Loading

Suggested reviewers: dbuchanarh, thibaultmg

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 15.63% 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 and concisely summarizes the main change: adding a CI TLS compliance scan.
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.
✨ 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.

@guidonguido guidonguido force-pushed the ci/tls-profile-semgrep branch from 0dd825a to a5e414c Compare July 10, 2026 15:00

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 7

🧹 Nitpick comments (5)
operators/pkg/util/client.go (1)

132-136: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Both logging and returning the same error.

Both log.Error(...)+return nil, err sites here handle the error twice — once via log, once via return — leaving the caller to potentially log it again. Only the scheme-registration error at line 139-141 follows the intended pattern (wrap-and-return, no log).

As per coding guidelines, "Wrap errors with fmt.Errorf(\"...: %w\", err) and handle each error once—either log it or return it, but not both."

♻️ Proposed fix
 	config, err := clientcmd.BuildConfigFromFlags("", "")
 	if err != nil {
-		log.Error(err, ConfigErrorMessage)
-		return nil, err
+		return nil, fmt.Errorf("%s: %w", ConfigErrorMessage, err)
 	}
...
 	ocpConfigClient, err = client.New(config, client.Options{Scheme: scheme})
 	if err != nil {
-		log.Error(err, "Failed to create OCP config controller-runtime client")
-		return nil, err
+		return nil, fmt.Errorf("failed to create OCP config controller-runtime client: %w", err)
 	}

Also applies to: 143-147

🤖 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 `@operators/pkg/util/client.go` around lines 132 - 136, Remove the redundant
logging from the error paths in the client configuration and related setup code,
including the sites around client-go scheme registration. Wrap each underlying
error with fmt.Errorf using %w and return it directly, matching the existing
scheme-registration pattern; ensure each error is handled only once.

Source: Coding guidelines

operators/multiclusterobservability/pkg/rendering/renderer_proxy.go (1)

86-87: 🔒 Security & Privacy | 🔵 Trivial

Residual TLS-profile gap on the oauth-proxy container — tracked, but worth flagging.

The TODO correctly documents that oauth-proxy doesn't support these flags, so nothing to fix in this diff. But this means the cluster TLS security profile enforcement this PR is adding doesn't cover the oauth-proxy sidecar's TLS termination at all — worth a tracked follow-up (or confirming oauth-proxy has some other TLS config knob, e.g. via --tls-cert/env, before calling this closed).

🤖 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 `@operators/multiclusterobservability/pkg/rendering/renderer_proxy.go` around
lines 86 - 87, Track a follow-up for the oauth-proxy TLS termination gap in the
renderer proxy configuration. Confirm whether oauth-proxy supports an
alternative TLS security configuration mechanism, such as certificate or
environment-based settings; otherwise document that
util.SetTLSSecurityConfiguration is intentionally not applied to the oauth-proxy
container.
operators/pkg/util/tls.go (1)

37-41: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Double error handling: logged and returned.

Same pattern as flagged in client.go: log.Error(...) followed by return nil, err at both sites. Per coding guidelines, an error should be logged or returned, not both.

As per coding guidelines, "Wrap errors with fmt.Errorf(\"...: %w\", err) and handle each error once—either log it or return it, but not both."

Also applies to: 103-104

🤖 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 `@operators/pkg/util/tls.go` around lines 37 - 41, Remove the log.Error calls
for failures from tlsClientFunc and the other indicated site, returning wrapped
errors instead (for example via fmt.Errorf with %w) so each error is handled
only once; update both error paths in the relevant TLS client helper.

Source: Coding guidelines

operators/pkg/util/tls_integration_test.go (1)

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

Local tlsProfileSpec and tlsConfig shadow package-level cached vars.

The local variables shadow the package-level tlsProfileSpec and tlsConfig that GetOrCreateTLSProfileSpec / GetOrCreateTLSConfig read and write. This is functionally correct but could confuse a future reader who assumes the local and package-level vars are the same. Consider renaming to initialSpec / initialTLSConfig for clarity.

🤖 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 `@operators/pkg/util/tls_integration_test.go` around lines 100 - 103, Rename
the local variables in the test from tlsProfileSpec and tlsConfig to initialSpec
and initialTLSConfig to distinguish them from the package-level cached variables
used by GetOrCreateTLSProfileSpec and GetOrCreateTLSConfig, updating all
references in that scope.
operators/multiclusterobservability/pkg/rendering/renderer_alertmanager.go (1)

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

Commented-out oauth-proxy line would not compile if uncommented.

SetTLSSecurityConfiguration returns ([]string, error), but the commented-out line only assigns one return value. A future developer uncommenting this would hit a compile error and need to reconstruct the full error-handling pattern. Consider either showing the correct full pattern in the comment or removing the line entirely—the TODO already explains the rationale.

♻️ Proposed fix: show correct pattern or remove

Option A — show the correct pattern:

- // TODO(guidonguido): oauth-proxy upstream doesn't support --tls-cipher-suites/--tls-min-version flags
- // oauthProxyContainer.Args = util.SetTLSSecurityConfiguration(ctx, oauthProxyContainer.Args, "--tls-cipher-suites=", "--tls-min-version=")
+ // TODO(guidonguido): oauth-proxy upstream doesn't support --tls-cipher-suites/--tls-min-version flags
+ // args, err := util.SetTLSSecurityConfiguration(ctx, oauthProxyContainer.Args, "--tls-cipher-suites=", "--tls-min-version=")
+ // if err != nil {
+ //     return nil, err
+ // }
+ // oauthProxyContainer.Args = args

Option B — remove the line and keep only the TODO:

  // TODO(guidonguido): oauth-proxy upstream doesn't support --tls-cipher-suites/--tls-min-version flags
- // oauthProxyContainer.Args = util.SetTLSSecurityConfiguration(ctx, oauthProxyContainer.Args, "--tls-cipher-suites=", "--tls-min-version=")
🤖 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 `@operators/multiclusterobservability/pkg/rendering/renderer_alertmanager.go`
around lines 142 - 143, Update the TODO near SetTLSSecurityConfiguration to
avoid retaining a commented-out call with an invalid single-return assignment:
either remove the commented line, or document the complete error-handling
pattern that assigns both returned values and handles the error before updating
oauthProxyContainer.Args.
🤖 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 @.github/workflows/lint.yaml:
- Around line 41-45: Pin the Semgrep package to an explicit version in the
“Install Semgrep” step and replace the default-branch clone in “Clone
argus-observe-rules” with a checkout of a specific commit SHA, ensuring both the
scanner and ruleset remain reproducible.

In `@collectors/metrics/pkg/forwarder/forwarder.go`:
- Around line 139-144: Remove the InsecureSkipVerify fallback from both
forwarder client TLS configuration blocks around the transport setup, including
the corresponding paths near the second referenced location. Use system
certificate roots by leaving RootCAs unset or configure a provided trusted CA
bundle, while retaining an appropriate minimum TLS version and updating related
comments or suppression annotations.

In `@operators/pkg/util/client.go`:
- Around line 31-35: Protect the memoized ocpConfigClient initialization in the
relevant client getter with sync.Once or equivalent synchronization so
concurrent TLS rendering workers cannot race. Preserve the initialized client
for subsequent calls, and wrap initialization errors with context while
returning them directly instead of logging and repeatedly returning the same
failure.

In `@operators/pkg/util/tls_integration_test.go`:
- Around line 119-153: Protect the shared changes slice with a sync.Mutex in the
test. Lock around the append in OnProfileChange, and lock when reading its
length during PollUntilContextTimeout and when accessing post-poll entries such
as changes[0]; remove the obsolete TODO acknowledging the race.

In `@operators/pkg/util/tls.go`:
- Around line 96-105: Remove the UNIT_TEST environment-variable bypass from
SetTLSSecurityConfiguration so GetOrCreateTLSProfileSpec errors always log and
return the error. Provide an exported test-injection hook or setter around the
existing tlsClientFunc seam, update renderer_test.go and
renderer_alertmanager_test.go to use it, and eliminate their reliance on
UNIT_TEST for simulating failures.
- Around line 20-27: Protect the memoized package variables tlsProfileSpec and
tlsConfig with the same synchronization approach used to fix client.go’s
ocpConfigClient. Update the functions accessing these values, including the code
around lines 33–35 and 63–71, to use sync.Once or an appropriate mutex so
concurrent calls cannot race during initialization; preserve tlsClientFunc’s
test injection behavior.
- Around line 78-94: The TLS cache assignment in GetOrCreateTLSConfig is
shadowing the package-level tlsConfig variable, so memoization never persists.
Replace the short declaration with an assignment and separately declare
unsupportedCiphers as needed, ensuring the returned configuration is stored in
the shared tlsConfig cache.

---

Nitpick comments:
In `@operators/multiclusterobservability/pkg/rendering/renderer_alertmanager.go`:
- Around line 142-143: Update the TODO near SetTLSSecurityConfiguration to avoid
retaining a commented-out call with an invalid single-return assignment: either
remove the commented line, or document the complete error-handling pattern that
assigns both returned values and handles the error before updating
oauthProxyContainer.Args.

In `@operators/multiclusterobservability/pkg/rendering/renderer_proxy.go`:
- Around line 86-87: Track a follow-up for the oauth-proxy TLS termination gap
in the renderer proxy configuration. Confirm whether oauth-proxy supports an
alternative TLS security configuration mechanism, such as certificate or
environment-based settings; otherwise document that
util.SetTLSSecurityConfiguration is intentionally not applied to the oauth-proxy
container.

In `@operators/pkg/util/client.go`:
- Around line 132-136: Remove the redundant logging from the error paths in the
client configuration and related setup code, including the sites around
client-go scheme registration. Wrap each underlying error with fmt.Errorf using
%w and return it directly, matching the existing scheme-registration pattern;
ensure each error is handled only once.

In `@operators/pkg/util/tls_integration_test.go`:
- Around line 100-103: Rename the local variables in the test from
tlsProfileSpec and tlsConfig to initialSpec and initialTLSConfig to distinguish
them from the package-level cached variables used by GetOrCreateTLSProfileSpec
and GetOrCreateTLSConfig, updating all references in that scope.

In `@operators/pkg/util/tls.go`:
- Around line 37-41: Remove the log.Error calls for failures from tlsClientFunc
and the other indicated site, returning wrapped errors instead (for example via
fmt.Errorf with %w) so each error is handled only once; update both error paths
in the relevant TLS client helper.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: b363eb00-657b-46ab-b20c-96d7ec1b7b35

📥 Commits

Reviewing files that changed from the base of the PR and between 7f469b7 and 69a7882.

⛔ Files ignored due to path filters (3)
  • go.sum is excluded by !**/*.sum
  • operators/multiclusterobservability/controllers/multiclusterobservability/testdata/crd/config.openshift.io_apiservers.crd.yaml is excluded by !**/testdata/**
  • operators/pkg/util/testdata/crd/config.openshift.io_apiservers.crd.yaml is excluded by !**/testdata/**
📒 Files selected for processing (24)
  • .github/workflows/lint.yaml
  • collectors/metrics/pkg/forwarder/forwarder.go
  • collectors/metrics/pkg/metricsclient/metricsclient.go
  • go.mod
  • operators/endpointmetrics/config/rbac/emo_role.yaml
  • operators/endpointmetrics/main.go
  • operators/endpointmetrics/manifests/prometheus/kube-state-metrics-deployment.yaml
  • operators/endpointmetrics/manifests/prometheus/node-exporter-daemonset.yaml
  • operators/endpointmetrics/manifests/prometheus/prometheus-resource.yaml
  • operators/endpointmetrics/pkg/rendering/renderer.go
  • operators/endpointmetrics/pkg/rendering/renderer_test.go
  • operators/multiclusterobservability/main.go
  • operators/multiclusterobservability/manifests/endpoint-observability/role.yaml
  • operators/multiclusterobservability/pkg/rendering/renderer_alertmanager.go
  • operators/multiclusterobservability/pkg/rendering/renderer_alertmanager_test.go
  • operators/multiclusterobservability/pkg/rendering/renderer_proxy.go
  • operators/multiclusterobservability/pkg/rendering/renderer_test.go
  • operators/pkg/config/config.go
  • operators/pkg/util/client.go
  • operators/pkg/util/tls.go
  • operators/pkg/util/tls_integration_test.go
  • operators/pkg/util/tls_test.go
  • proxy/cmd/main.go
  • proxy/pkg/proxy/tls.go
💤 Files with no reviewable changes (3)
  • operators/endpointmetrics/manifests/prometheus/prometheus-resource.yaml
  • operators/endpointmetrics/manifests/prometheus/node-exporter-daemonset.yaml
  • operators/endpointmetrics/manifests/prometheus/kube-state-metrics-deployment.yaml

Comment thread .github/workflows/lint.yaml Outdated
Comment on lines +139 to 144
// nosemgrep: go-tls-bypass - client-side TLS fallback when no TLS config is provided.
fromTransport.TLSClientConfig = &tls.Config{
// nosemgrep: go-crypto-tls-version - client-side TLS.
MinVersion: tls.VersionTLS12,
InsecureSkipVerify: true,
}

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.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

sed -n '1,260p' collectors/metrics/pkg/forwarder/forwarder.go

Repository: stolostron/multicluster-observability-operator

Length of output: 9608


🏁 Script executed:

ast-grep outline collectors/metrics/pkg/forwarder/forwarder.go --view expanded

Repository: stolostron/multicluster-observability-operator

Length of output: 2874


🏁 Script executed:

rg -n "InsecureSkipVerify|nosemgrep|nosec|tls.Config|CA|certificate" collectors/metrics/pkg/forwarder/forwarder.go

Repository: stolostron/multicluster-observability-operator

Length of output: 1532


🏁 Script executed:

wc -l collectors/metrics/pkg/forwarder/forwarder.go && sed -n '1,240p' collectors/metrics/pkg/forwarder/forwarder.go

Repository: stolostron/multicluster-observability-operator

Length of output: 8979


🏁 Script executed:

python3 - <<'PY'
from pathlib import Path
p = Path('collectors/metrics/pkg/forwarder/forwarder.go')
text = p.read_text()
for i, line in enumerate(text.splitlines(), 1):
    if 'InsecureSkipVerify' in line or 'nosemgrep' in line or 'nosec' in line or 'TLSClientConfig' in line or 'CAFile' in line or 'RootCAs' in line:
        print(f"{i}: {line}")
PY

Repository: stolostron/multicluster-observability-operator

Length of output: 1563


🏁 Script executed:

rg -n "forwarder\.go|tls-insecure-skip-verify-go|nosemgrep|no[[:space:]]*sec|InsecureSkipVerify|RootCAs|CAFile" -S .

Repository: stolostron/multicluster-observability-operator

Length of output: 12769


🏁 Script executed:

sed -n '120,220p' collectors/metrics/pkg/forwarder/forwarder.go

Repository: stolostron/multicluster-observability-operator

Length of output: 3961


🏁 Script executed:

rg -n "tls-insecure-skip-verify-go|go-tls-bypass|nosemgrep|G402" collectors/metrics/pkg/forwarder/forwarder.go collectors -S

Repository: stolostron/multicluster-observability-operator

Length of output: 2370


🏁 Script executed:

nl -ba collectors/metrics/pkg/forwarder/forwarder.go | sed -n '130,205p'

Repository: stolostron/multicluster-observability-operator

Length of output: 220


Remove the insecure TLS fallback in collectors/metrics/pkg/forwarder/forwarder.go:139-144,190-195.
The no-CA path still sets InsecureSkipVerify: true, which disables certificate-chain and hostname validation for both forwarder clients. Use system roots by default or require a trusted CA bundle instead; this keeps the TLS chain anchored instead of bolting the door open.

🧰 Tools
🪛 ast-grep (0.44.1)

[warning] 139-143: TLS certificate verification is disabled by setting InsecureSkipVerify: true on the tls.Config. This makes the connection vulnerable to man-in-the-middle attacks because the server's certificate chain and host name are not validated. Remove InsecureSkipVerify (or set it to false) and provide a proper RootCAs pool to trust custom certificates instead.
Context: tls.Config{
// nosemgrep: go-crypto-tls-version - client-side TLS.
MinVersion: tls.VersionTLS12,
InsecureSkipVerify: true,
}
Note: [CWE-295] Improper Certificate Validation.

(tls-insecure-skip-verify-go)

🪛 OpenGrep (1.23.0)

[ERROR] 140-144: TLS certificate verification is disabled via InsecureSkipVerify. This allows man-in-the-middle attacks. Remove InsecureSkipVerify or set it to false.

(coderabbit.tls.go-insecure-skip-verify)


[ERROR] 140-144: TLS certificate verification is disabled via InsecureSkipVerify. This allows man-in-the-middle attacks. Remove InsecureSkipVerify or set it to false.

(coderabbit.tls.go-insecure-skip-verify)

🤖 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 `@collectors/metrics/pkg/forwarder/forwarder.go` around lines 139 - 144, Remove
the InsecureSkipVerify fallback from both forwarder client TLS configuration
blocks around the transport setup, including the corresponding paths near the
second referenced location. Use system certificate roots by leaving RootCAs
unset or configure a provided trusted CA bundle, while retaining an appropriate
minimum TLS version and updating related comments or suppression annotations.

Sources: Path instructions, MCP tools, Linters/SAST tools

Comment on lines +31 to +35
kubeClient kubernetes.Interface
crdClient crdClientSet.Interface
ocpClient ocpClientSet.Interface
promClient promClientSet.Interface
ocpConfigClient client.Client

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.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

git ls-files operators/pkg/util/client.go operators/pkg/util/tls.go
echo '--- client.go ---'
cat -n operators/pkg/util/client.go | sed -n '1,240p'
echo '--- tls.go ---'
cat -n operators/pkg/util/tls.go | sed -n '1,220p'

Repository: stolostron/multicluster-observability-operator

Length of output: 13659


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n "GetOrCreateOCPConfigCRClient|GetOrCreateTLSProfileSpec|GetOrCreateTLSConfig|SetTLSSecurityConfiguration|tlsClientFunc" operators

Repository: stolostron/multicluster-observability-operator

Length of output: 5805


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n "MaxConcurrentReconciles|controller-runtime|SetupWithManager|Reconcile\\(" operators

Repository: stolostron/multicluster-observability-operator

Length of output: 50402


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '300,410p' operators/multiclusterobservability/main.go | cat -n

Repository: stolostron/multicluster-observability-operator

Length of output: 4647


Guard the cached OCP config client
operators/pkg/util/client.go:127-149 memoizes ocpConfigClient without any synchronization. This path is reachable from the TLS rendering flow used by controllers, so concurrent workers can race on the nil check and initialization. Use sync.Once or another guarded init, and return wrapped errors instead of logging and returning the same failure twice.

🤖 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 `@operators/pkg/util/client.go` around lines 31 - 35, Protect the memoized
ocpConfigClient initialization in the relevant client getter with sync.Once or
equivalent synchronization so concurrent TLS rendering workers cannot race.
Preserve the initialized client for subsequent calls, and wrap initialization
errors with context while returning them directly instead of logging and
repeatedly returning the same failure.

Source: Path instructions

Comment on lines +119 to +153
// TODO(guidonguido): access to changes is eventually consistent. Both controller manager
// and test goroutine are accessing it. Consider turning it atomic.
var changes []profileChange

watcher := &tlsutil.SecurityProfileWatcher{
Client: mgr.GetClient(),
InitialTLSProfileSpec: *tlsProfileSpec,
OnProfileChange: func(ctx context.Context, oldSpec, newSpec configv1.TLSProfileSpec) {
changes = append(changes, profileChange{old: oldSpec, new: newSpec})
},
}
require.NoError(t, watcher.SetupWithManager(mgr))

mgrCtx, mgrCancel := context.WithCancel(context.Background())
defer mgrCancel()

go func() {
if err := mgr.Start(mgrCtx); err != nil {
t.Logf("manager stopped: %v", err)
}
}()

require.True(t, mgr.GetCache().WaitForCacheSync(mgrCtx))

// Change profile from Intermediate to Modern
require.NoError(t, k8sClient.Get(context.Background(), client.ObjectKeyFromObject(apiServer), apiServer))
apiServer.Spec.TLSSecurityProfile = &configv1.TLSSecurityProfile{
Type: configv1.TLSProfileModernType,
}
require.NoError(t, k8sClient.Update(context.Background(), apiServer))

err = wait.PollUntilContextTimeout(context.Background(), 200*time.Millisecond, 10*time.Second, true, func(ctx context.Context) (bool, error) {
return len(changes) >= 1, nil
})
require.NoError(t, err, "expected at least 1 profile change callback")

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.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Data race on changes slice — protect with a mutex.

The OnProfileChange callback appends to changes from the controller manager's goroutine while wait.PollUntilContextTimeout reads len(changes) and the post-poll assertions read changes[0] from the test goroutine. This is a data race on the slice header; the Go race detector will flag it and it can cause flaky failures or panics if the slice is reallocated mid-read. The TODO at line 119 acknowledges this but it should be fixed before merge.

As per coding guidelines, check for potential race conditions in controller logic. As per path instructions for **/*_test.go, ensure tests are deterministic and not flaky.

🔒 Proposed fix: guard `changes` with `sync.Mutex`
 import (
 	"context"
 	"crypto/tls"
 	"fmt"
 	"os"
 	"path/filepath"
+	"sync"
 	"testing"
 	"time"
 	...
 )

 // Inside TestIntegrationSecurityProfileWatcherOnProfileChange:
-// TODO(guidonguido): access to changes is eventually consistent. Both controller manager
-// and test goroutine are accessing it. Consider turning it atomic.
-var changes []profileChange
+var (
+	mu      sync.Mutex
+	changes []profileChange
+)

 	watcher := &tlsutil.SecurityProfileWatcher{
 		...
 		OnProfileChange: func(ctx context.Context, oldSpec, newSpec configv1.TLSProfileSpec) {
+			mu.Lock()
+			defer mu.Unlock()
 			changes = append(changes, profileChange{old: oldSpec, new: newSpec})
 		},
 	}

 	err = wait.PollUntilContextTimeout(context.Background(), 200*time.Millisecond, 10*time.Second, true, func(ctx context.Context) (bool, error) {
+		mu.Lock()
+		defer mu.Unlock()
 		return len(changes) >= 1, nil
 	})
 	require.NoError(t, err, "expected at least 1 profile change callback")

+	mu.Lock()
+	defer mu.Unlock()
 	assert.Equal(t, *tlsProfileSpec, changes[0].old)
 	assert.Equal(t, *configv1.TLSProfiles[configv1.TLSProfileModernType], changes[0].new)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// TODO(guidonguido): access to changes is eventually consistent. Both controller manager
// and test goroutine are accessing it. Consider turning it atomic.
var changes []profileChange
watcher := &tlsutil.SecurityProfileWatcher{
Client: mgr.GetClient(),
InitialTLSProfileSpec: *tlsProfileSpec,
OnProfileChange: func(ctx context.Context, oldSpec, newSpec configv1.TLSProfileSpec) {
changes = append(changes, profileChange{old: oldSpec, new: newSpec})
},
}
require.NoError(t, watcher.SetupWithManager(mgr))
mgrCtx, mgrCancel := context.WithCancel(context.Background())
defer mgrCancel()
go func() {
if err := mgr.Start(mgrCtx); err != nil {
t.Logf("manager stopped: %v", err)
}
}()
require.True(t, mgr.GetCache().WaitForCacheSync(mgrCtx))
// Change profile from Intermediate to Modern
require.NoError(t, k8sClient.Get(context.Background(), client.ObjectKeyFromObject(apiServer), apiServer))
apiServer.Spec.TLSSecurityProfile = &configv1.TLSSecurityProfile{
Type: configv1.TLSProfileModernType,
}
require.NoError(t, k8sClient.Update(context.Background(), apiServer))
err = wait.PollUntilContextTimeout(context.Background(), 200*time.Millisecond, 10*time.Second, true, func(ctx context.Context) (bool, error) {
return len(changes) >= 1, nil
})
require.NoError(t, err, "expected at least 1 profile change callback")
var (
mu sync.Mutex
changes []profileChange
)
watcher := &tlsutil.SecurityProfileWatcher{
Client: mgr.GetClient(),
InitialTLSProfileSpec: *tlsProfileSpec,
OnProfileChange: func(ctx context.Context, oldSpec, newSpec configv1.TLSProfileSpec) {
mu.Lock()
defer mu.Unlock()
changes = append(changes, profileChange{old: oldSpec, new: newSpec})
},
}
require.NoError(t, watcher.SetupWithManager(mgr))
mgrCtx, mgrCancel := context.WithCancel(context.Background())
defer mgrCancel()
go func() {
if err := mgr.Start(mgrCtx); err != nil {
t.Logf("manager stopped: %v", err)
}
}()
require.True(t, mgr.GetCache().WaitForCacheSync(mgrCtx))
// Change profile from Intermediate to Modern
require.NoError(t, k8sClient.Get(context.Background(), client.ObjectKeyFromObject(apiServer), apiServer))
apiServer.Spec.TLSSecurityProfile = &configv1.TLSSecurityProfile{
Type: configv1.TLSProfileModernType,
}
require.NoError(t, k8sClient.Update(context.Background(), apiServer))
err = wait.PollUntilContextTimeout(context.Background(), 200*time.Millisecond, 10*time.Second, true, func(ctx context.Context) (bool, error) {
mu.Lock()
defer mu.Unlock()
return len(changes) >= 1, nil
})
require.NoError(t, err, "expected at least 1 profile change callback")
mu.Lock()
defer mu.Unlock()
assert.Equal(t, *tlsProfileSpec, changes[0].old)
assert.Equal(t, *configv1.TLSProfiles[configv1.TLSProfileModernType], changes[0].new)
🤖 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 `@operators/pkg/util/tls_integration_test.go` around lines 119 - 153, Protect
the shared changes slice with a sync.Mutex in the test. Lock around the append
in OnProfileChange, and lock when reading its length during
PollUntilContextTimeout and when accessing post-poll entries such as changes[0];
remove the obsolete TODO acknowledging the race.

Sources: Coding guidelines, Path instructions

Comment thread operators/pkg/util/tls.go
Comment on lines +20 to +27
var (
tlsProfileSpec *ocinfrav1.TLSProfileSpec
tlsConfig func(*tls.Config)
// Override in tests to inject a fake client.
tlsClientFunc = func() (client.Client, error) { //nolint:gocritic // intentional lambda for test injection
return GetOrCreateOCPConfigCRClient()
}
)

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.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Same unguarded package-var cache pattern as client.go's ocpConfigClient.

tlsProfileSpec and tlsConfig are memoized via bare check-then-act with no mutex/sync.Once, same race exposure as flagged in operators/pkg/util/client.go (GetOrCreateOCPConfigCRClient). Worth applying the same fix here for consistency once that one is resolved.

Also applies to: 33-35, 63-71

🤖 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 `@operators/pkg/util/tls.go` around lines 20 - 27, Protect the memoized package
variables tlsProfileSpec and tlsConfig with the same synchronization approach
used to fix client.go’s ocpConfigClient. Update the functions accessing these
values, including the code around lines 33–35 and 63–71, to use sync.Once or an
appropriate mutex so concurrent calls cannot race during initialization;
preserve tlsClientFunc’s test injection behavior.

Comment thread operators/pkg/util/tls.go
Comment on lines +78 to +94
func GetOrCreateTLSConfig(ctx context.Context) (func(*tls.Config), error) {
if tlsConfig != nil {
return tlsConfig, nil
}

profileSpec, err := GetOrCreateTLSProfileSpec(ctx)
if err != nil {
return nil, err
}

tlsConfig, unsupportedCiphers := tlsutil.NewTLSConfigFromProfile(*profileSpec)
if len(unsupportedCiphers) > 0 {
log.Info("TLS configuration contains unsupported ciphers that will be ignored", "ciphers", unsupportedCiphers)
}

return tlsConfig, nil
}

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

rg -n 'tlsConfig' operators/pkg/util/*.go

Repository: stolostron/multicluster-observability-operator

Length of output: 842


🏁 Script executed:

rg -n 'GetOrCreateTLSConfig\(' .

Repository: stolostron/multicluster-observability-operator

Length of output: 1159


Keep the TLS config cache from being shadowed
operators/pkg/util/tls.go:88 uses :=, which creates a new local tlsConfig and leaves the package cache nil. Every call rebuilds the config; switch to = (or predeclare unsupportedCiphers) so the memoization actually sticks.

🤖 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 `@operators/pkg/util/tls.go` around lines 78 - 94, The TLS cache assignment in
GetOrCreateTLSConfig is shadowing the package-level tlsConfig variable, so
memoization never persists. Replace the short declaration with an assignment and
separately declare unsupportedCiphers as needed, ensuring the returned
configuration is stored in the shared tlsConfig cache.

Comment thread operators/pkg/util/tls.go
Comment on lines +96 to +105
func SetTLSSecurityConfiguration(ctx context.Context, args []string, tlsCipherSuitesArg string, minTLSversionArg string) ([]string, error) {
tlsProfileSpec, err := GetOrCreateTLSProfileSpec(ctx)
if err != nil {
if isTest, err := strconv.ParseBool(os.Getenv("UNIT_TEST")); err == nil && isTest {
log.Info("running unit test, skipping TLS profile adherence")
return args, nil
}
log.Error(err, "unable to get TLS security configuration")
return nil, err
}

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.

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

Production code silently bypasses TLS enforcement based on a UNIT_TEST env var.

When GetOrCreateTLSProfileSpec errors, this checks os.Getenv("UNIT_TEST") and, if true, returns the original, unmodified args — meaning no --tls-cipher-suites/--tls-min-version get applied at all, with only an info log. That's a live security downgrade path sitting in production code, gated by an environment variable rather than compile-time test wiring. If UNIT_TEST=true ever leaks into a real deployment (stale env var, copied manifest, debug flag left on), TLS profile enforcement silently no-ops instead of failing loudly — on a PR whose entire point is enforcing TLS profiles.

This exists because tlsClientFunc (the real DI seam, used by tls_test.go in this same package) is unexported and unusable from the renderer test packages, which is why renderer_test.go/renderer_alertmanager_test.go reach for this env var instead. Consider exposing a small exported test-injection hook (e.g. in a testutil/exported setter) so downstream packages can inject a fake client, and drop the env-var branch from the production path entirely.

🤖 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 `@operators/pkg/util/tls.go` around lines 96 - 105, Remove the UNIT_TEST
environment-variable bypass from SetTLSSecurityConfiguration so
GetOrCreateTLSProfileSpec errors always log and return the error. Provide an
exported test-injection hook or setter around the existing tlsClientFunc seam,
update renderer_test.go and renderer_alertmanager_test.go to use it, and
eliminate their reliance on UNIT_TEST for simulating failures.

@guidonguido guidonguido force-pushed the ci/tls-profile-semgrep branch 2 times, most recently from 21c302c to 5129f81 Compare July 13, 2026 07:35

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
.github/workflows/lint.yaml (2)

44-45: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Fetch the pinned rules commit explicitly.

--depth 1 only fetches the current branch tip, so Line 45 will eventually fail when b85a9f57b02d0202e7cf62a77fd110a26718b3aa is no longer that tip. Fetch the SHA directly instead of cloning first. (git-scm.com)

🔧 Proposed fix
- run: git clone --depth 1 https://github.com/smith-xyz/argus-observe-rules.git /tmp/argus-observe-rules && git -C /tmp/argus-observe-rules checkout b85a9f57b02d0202e7cf62a77fd110a26718b3aa
+ run: |
+   git init /tmp/argus-observe-rules
+   git -C /tmp/argus-observe-rules remote add origin https://github.com/smith-xyz/argus-observe-rules.git
+   git -C /tmp/argus-observe-rules fetch --depth 1 origin b85a9f57b02d0202e7cf62a77fd110a26718b3aa
+   git -C /tmp/argus-observe-rules checkout --detach FETCH_HEAD
🤖 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/lint.yaml around lines 44 - 45, Update the “Clone
argus-observe-rules” workflow step to fetch the pinned commit
b85a9f57b02d0202e7cf62a77fd110a26718b3aa explicitly, rather than relying on a
shallow clone of the branch tip followed by checkout. Preserve cloning into
/tmp/argus-observe-rules and ensure the pinned SHA is available before checkout.

64-65: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Pin sebrandon1/tls-config-lint to c2701708a4a961ae6489effaaf002b3ba208e4fa.
@v1 is mutable; using the full commit SHA removes a supply-chain hinge and keeps the workflow on the reviewed code path. One less gremlin in the pipeline. ✅

🤖 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/lint.yaml around lines 64 - 65, Update the “TLS Config
Lint” workflow step to replace the mutable sebrandon1/tls-config-lint@v1
reference with the specified full commit SHA
c2701708a4a961ae6489effaaf002b3ba208e4fa, preserving the existing action and
step configuration.
🤖 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 @.github/workflows/lint.yaml:
- Line 69: Update the TLS-lint configuration’s exclude-dirs entry so it no
longer excludes the entire collectors/metrics/pkg/forwarder directory; remove
the exclusion or narrow it to only the specific fallback TLS case already
covered by suppressions in forwarder.go, while allowing token.go and the package
tests to remain linted.

---

Outside diff comments:
In @.github/workflows/lint.yaml:
- Around line 44-45: Update the “Clone argus-observe-rules” workflow step to
fetch the pinned commit b85a9f57b02d0202e7cf62a77fd110a26718b3aa explicitly,
rather than relying on a shallow clone of the branch tip followed by checkout.
Preserve cloning into /tmp/argus-observe-rules and ensure the pinned SHA is
available before checkout.
- Around line 64-65: Update the “TLS Config Lint” workflow step to replace the
mutable sebrandon1/tls-config-lint@v1 reference with the specified full commit
SHA c2701708a4a961ae6489effaaf002b3ba208e4fa, preserving the existing action and
step configuration.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 0ab3ec5c-1562-4202-9185-41cd258161d0

📥 Commits

Reviewing files that changed from the base of the PR and between 21c302c and 5129f81.

📒 Files selected for processing (1)
  • .github/workflows/lint.yaml

Comment thread .github/workflows/lint.yaml Outdated
@guidonguido guidonguido force-pushed the ci/tls-profile-semgrep branch 2 times, most recently from 86d76b6 to 8dda63d Compare July 13, 2026 10:20
Signed-off-by: Guido Ricioppo <griciopp@redhat.com>

Add tls profile tests

Signed-off-by: Guido Ricioppo <griciopp@redhat.com>
Signed-off-by: Guido Ricioppo <griciopp@redhat.com>
Signed-off-by: Guido Ricioppo <griciopp@redhat.com>
Signed-off-by: Guido Ricioppo <griciopp@redhat.com>
Affects forwarder and alert-forward

Signed-off-by: Guido Ricioppo <griciopp@redhat.com>
@guidonguido guidonguido force-pushed the ci/tls-profile-semgrep branch from 8dda63d to bd15d39 Compare July 14, 2026 15:02
@openshift-ci

openshift-ci Bot commented Jul 14, 2026

Copy link
Copy Markdown

@guidonguido: The following test failed, say /retest to rerun all failed tests or /retest-required to rerun all mandatory failed tests:

Test name Commit Details Required Rerun command
ci/prow/test-e2e bd15d39 link true /test test-e2e

Full PR test history. Your PR dashboard.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. I understand the commands that are listed here.

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.

1 participant