Skip to content

CORENET-7243: Add TLS Profile Compliance tests for networking components - #31500

Open
weliang1 wants to merge 1 commit into
openshift:mainfrom
weliang1:test-tls-profile-compliance
Open

CORENET-7243: Add TLS Profile Compliance tests for networking components#31500
weliang1 wants to merge 1 commit into
openshift:mainfrom
weliang1:test-tls-profile-compliance

Conversation

@weliang1

@weliang1 weliang1 commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Summary

Add comprehensive e2e tests to verify TLS compliance for OpenShift networking components across different TLS profiles and adherence policies.

Components Tested

  • multus-cni (multus-admission-controller)
  • ovn-kubernetes (control plane)
  • cluster-network-operator
  • networking-console-plugin

Test Coverage

This PR adds 12 e2e test cases covering three TLS profile configurations:

  1. Intermediate + LegacyAdheringComponentsOnly
  2. Modern + LegacyAdheringComponentsOnly
  3. Modern + StrictAllComponents

Each configuration tests all four networking components (4 × 3 = 12 tests total).

Test Methodology

  • Uses port-forward to establish TLS connections to component pods
  • Verifies TLS handshake compliance against configured cluster TLS profile
  • Automatically configures cluster with appropriate TLS profile and adherence policy
  • Waits for MachineConfigPools, node stability, and operator settlement before verification

Changes

New Files

  • test/extended/networking/tls.go - Main test implementation (563 lines)

Modified Files

  • test/extended/util/tls.go - Enhanced port-forwarding utilities:
    • Support port-forwarding to pods (in addition to services)
    • Increased timeout from 5s to 10s for TLS verification
    • Added 500ms stabilization delay after port-forward establishment

Test Execution

Tests are marked with:

  • [sig-network] - Networking SIG ownership
  • [OCPFeatureGate:TLSAdherence] - Requires TLSAdherence feature gate
  • [Serial] - Must run sequentially (cluster-wide TLS configuration changes)

Run with:

./openshift-tests run all --run="TLS Profile Compliance"

Validation

  • ✅ Built successfully with make build
  • ✅ Code formatted with gofmt
  • ✅ Local test execution verified

/cc @weliang1 @openshift/networking-qe

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added OpenShift TLS adherence coverage for Multus, OVN-Kubernetes, CNO, and the network console.
    • Added validation for Modern and Intermediate TLS profiles, adherence policies, configuration rollout, and service readiness.
    • Tests now skip gracefully when TLS adherence is unsupported.
  • Bug Fixes

    • Improved port-forward reliability with explicit resource names and startup readiness checks.
    • Added bounded TLS connection checks for more reliable validation.

@openshift-merge-bot

Copy link
Copy Markdown
Contributor

Pipeline controller notification
This repo is configured to use the pipeline controller. Second-stage tests will be triggered either automatically or after lgtm label is added, depending on the repository configuration. The pipeline controller will automatically detect which contexts are required and will utilize /test Prow commands to trigger the second stage.

For optional jobs, comment /test ? to see a list of all defined jobs. To trigger manually all jobs from second stage use /pipeline required command.

This repository is configured in: automatic mode

@openshift-ci

openshift-ci Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

@weliang1: GitHub didn't allow me to request PR reviews from the following users: weliang1, openshift/networking-qe.

Note that only openshift members and repo collaborators can review this PR, and authors cannot review their own PRs.

Details

In response to this:

Summary

Add comprehensive e2e tests to verify TLS compliance for OpenShift networking components across different TLS profiles and adherence policies.

Components Tested

  • multus-cni (multus-admission-controller)
  • ovn-kubernetes (control plane)
  • cluster-network-operator
  • networking-console-plugin

Test Coverage

This PR adds 12 e2e test cases covering three TLS profile configurations:

  1. Intermediate + LegacyAdheringComponentsOnly
  2. Modern + LegacyAdheringComponentsOnly
  3. Modern + StrictAllComponents

Each configuration tests all four networking components (4 × 3 = 12 tests total).

Test Methodology

  • Uses port-forward to establish TLS connections to component pods
  • Verifies TLS handshake compliance against configured cluster TLS profile
  • Automatically configures cluster with appropriate TLS profile and adherence policy
  • Waits for MachineConfigPools, node stability, and operator settlement before verification

Changes

New Files

  • test/extended/networking/tls.go - Main test implementation (563 lines)

Modified Files

  • test/extended/util/tls.go - Enhanced port-forwarding utilities:
  • Support port-forwarding to pods (in addition to services)
  • Increased timeout from 5s to 10s for TLS verification
  • Added 500ms stabilization delay after port-forward establishment

Test Execution

Tests are marked with:

  • [sig-network] - Networking SIG ownership
  • [OCPFeatureGate:TLSAdherence] - Requires TLSAdherence feature gate
  • [Serial] - Must run sequentially (cluster-wide TLS configuration changes)

Run with:

./openshift-tests run all --run="TLS Profile Compliance"

Validation

  • ✅ Built successfully with make build
  • ✅ Code formatted with gofmt
  • ✅ Local test execution verified

/cc @weliang1 @openshift/networking-qe

🤖 Generated with Claude Code

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.

@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown

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

Adds OpenShift TLS adherence tests for API server profiles and networking components. The change adds feature-gate configuration, rollout and readiness checks, TLS compliance validation, and bounded port-forward execution.

Changes

TLS adherence validation

Layer / File(s) Summary
Bounded port-forward and TLS utilities
test/extended/util/tls.go
Port forwarding accepts typed resources and waits for startup readiness. Positive and negative TLS checks use bounded dialers and contexts.
TLS profile configuration and cluster readiness
test/extended/networking/tls.go
The tests enable TLSAdherence, configure API server TLS profiles, and verify rollout, node, operator, and FeatureGate state.
Networking component TLS compliance
test/extended/networking/tls.go
Serial tests select ready pods and validate accepted and rejected TLS versions for Multus, OVN-Kubernetes, CNO, and the network console.

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

Sequence Diagram(s)

sequenceDiagram
  participant TLSAdherenceTest
  participant FeatureGateAPI
  participant APIServer
  participant ClusterStatus
  participant NetworkPod
  participant TLSUtility
  participant NetworkingComponent
  TLSAdherenceTest->>FeatureGateAPI: Enable TLSAdherence
  TLSAdherenceTest->>APIServer: Apply TLS profile and adherence policy
  TLSAdherenceTest->>ClusterStatus: Wait for rollout, readiness, and FeatureGate status
  TLSAdherenceTest->>NetworkPod: Select running ready pod
  TLSAdherenceTest->>TLSUtility: Forward component ports
  TLSUtility->>NetworkingComponent: Check accepted and rejected TLS versions
Loading

Caution

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

  • Ignore

❌ Failed checks (1 error, 2 warnings)

Check name Status Explanation Resolution
No-Sensitive-Data-In-Logs ❌ Error The new node-stability path logs every not-ready node.Name, which can expose internal hostnames in e2e logs; port-forward logs also include pod and namespace identifiers. Do not log raw node, pod, or namespace identifiers. Log only counts or use approved redaction before writing identifiers to e2e logs.
Test Structure And Quality ⚠️ Warning The suite has only three It blocks, each asserting four component checks, and its BeforeEach updates cluster-scoped FeatureGate/APIServer resources without any AfterEach or DeferCleanup. Split component checks into focused It blocks and capture the original FeatureGate/APIServer state; restore both in AfterEach or DeferCleanup, including after failures.
Microshift Test Compatibility ⚠️ Warning The untagged suite uses unavailable config.openshift.io FeatureGate, APIServer, and ClusterVersion APIs plus machineconfiguration.openshift.io MCPs and CustomNoUpgrade. MicroShift compatibility notice: add [apigroup:config.openshift.io] and [apigroup:machineconfiguration.openshift.io] to the suite, or add an explicit IsMicroShiftCluster check with g.Skip().
✅ Passed checks (12 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding TLS profile compliance tests for OpenShift networking components.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
Stable And Deterministic Test Names ✅ Passed All Ginkgo titles use fixed literals or fixed TLS profile table strings; no title includes pod, node, namespace, timestamp, UUID, IP, or other run-time data.
Single Node Openshift (Sno) Test Compatibility ✅ Passed The three TLS profile cases select any ready pod and use port-forwarding; they do not require multiple nodes, replicas, roles, failover, rescheduling, or cross-node networking.
Topology-Aware Scheduling Compatibility ✅ Passed The complete PR range changes only TLS e2e tests and utilities; it adds no manifests, controllers, replicas, affinity, topology spread, node selectors, tolerations, or PDBs.
Ote Binary Stdout Contract ✅ Passed Changed TLS files have no direct stdout, fmt.Print, log.Print, or klog writes; e2e.Logf routes to GinkgoWriter, and runtime work is in BeforeEach or It.
Ipv6 And Disconnected Network Test Compatibility ✅ Passed The new suite uses cluster APIs and oc port-forwarding to pods; searches found no IPv4 literals, IP parsing, public hosts, external URLs, or registry pulls. TLS checks dial localhost.
No-Weak-Crypto ✅ Passed The PR uses only standard-library TLS version checks and dialers; searches found no MD5, SHA1, DES, 3DES, RC4, Blowfish, ECB, custom crypto, or secret comparisons.
Container-Privileges ✅ Passed The PR changes only two Go files; scans found no privileged, hostPID, hostNetwork, hostIPC, SYS_ADMIN, allowPrivilegeEscalation, root, or securityContext settings.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🧹 Nitpick comments (6)
test/extended/networking/tls.go (5)

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

Drop the unused oc parameter and collapse the four wrappers.

verifyTLSComplianceInPods never uses oc, and the four Verify*TLSComplianceInPod functions differ only in the port list and the display name. Remove the parameter and replace the wrappers with a single table in the spec body that holds namespace, selector, ports, and component name.

🤖 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 `@test/extended/networking/tls.go` around lines 545 - 563, Remove the unused oc
parameter from verifyTLSComplianceInPods and its callers, then replace
VerifyMultusTLSComplianceInPod, VerifyOVNKubernetesTLSComplianceInPod,
VerifyCNOTLSComplianceInPod, and VerifyNetworkConsoleTLSComplianceInPod with one
table-driven specification in the relevant test body containing namespace,
selector, ports, and component name. Iterate over the table while preserving
each wrapper’s existing port list and display name.

311-362: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Retry on conflict when updating FeatureGate/cluster and APIServer/cluster.

patchFeatureGate and patchAPIServerTLSProfile call Update with an object read earlier. If a controller writes the same object in between, the update fails with a conflict error and the whole spec fails. Wrap both updates in retry.RetryOnConflict with a fresh Get inside the retry function, or use a server-side apply/merge patch.

♻️ Proposed pattern
return retry.RetryOnConflict(retry.DefaultRetry, func() error {
    cur, err := configClient.ConfigV1().APIServers().Get(ctx, "cluster", metav1.GetOptions{})
    if err != nil {
        return err
    }
    // mutate cur ...
    _, err = configClient.ConfigV1().APIServers().Update(ctx, cur, metav1.UpdateOptions{})
    return err
})
🤖 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 `@test/extended/networking/tls.go` around lines 311 - 362, Update
patchFeatureGate and patchAPIServerTLSProfile to wrap their resource mutations
and Update calls in retry.RetryOnConflict using retry.DefaultRetry. Fetch a
fresh FeatureGate/cluster or APIServer/cluster inside each retry attempt, apply
the existing changes to that object, and return update errors so conflicts are
retried while preserving the current contextual error handling.

364-391: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Use one deadline for the complete MCP rollout.

node.WaitForMCP applies timeout to each pool. With two pools, the 60-minute timeout can take up to 120 minutes. Compute one deadline before the loop and pass the remaining duration to each call. Retain the concrete client assertion because node.WaitForMCP requires *machineconfigclient.Clientset.

🤖 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 `@test/extended/networking/tls.go` around lines 364 - 391, Update
waitForAllMCPsComplete to compute a single deadline before iterating over mcps,
using the provided timeout from the current time. Before each node.WaitForMCP
call, calculate the remaining duration and pass it instead of the full timeout,
while preserving the concrete *machineconfigclient.Clientset assertion and
existing MCP handling.

393-418: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Replace deprecated polling APIs and propagate cancellation.

Use wait.PollUntilContextTimeout in both helpers. Pass its callback context to each Kubernetes API call. Change waitForNodesStability to accept a context instead of creating context.Background(). A spec cancellation requires passing a cancellable context through ConfigureTLSProfileWithAdherence, which currently uses context.Background().

The node loop variable does not affect the current function because the imported package is not referenced there.

🤖 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 `@test/extended/networking/tls.go` around lines 393 - 418, Replace the
deprecated polling API in waitForNodesStability and the other related helper
with wait.PollUntilContextTimeout, passing the callback context to each
Kubernetes API call. Change waitForNodesStability to accept the caller’s context
instead of creating context.Background(), and update
ConfigureTLSProfileWithAdherence to create and propagate a cancellable context
through these helpers so spec cancellation is honored.

Source: Path instructions


27-44: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Make the TLS adherence helpers unexported.

These constants, TLSAdherenceNotSupportedError, and the listed helper functions have no callers outside test/extended/networking/tls.go. Rename them to lowercase names to reduce the package export surface.

🤖 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 `@test/extended/networking/tls.go` around lines 27 - 44, Rename the unused
exported TLS adherence constants, TLSAdherenceNotSupportedError, and its helper
functions in tls.go to lowercase names, including updating all references within
the file. Preserve their existing values and behavior while reducing the package
export surface.
test/extended/util/tls.go (1)

56-59: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Replace the fixed sleep with a readiness check on the local port.

A 500 ms sleep is a guess. On a loaded cluster the forward is not ready in time, and the callback fails for a reason unrelated to TLS. The sleep also consumes 5 percent of the 10 second command budget on every attempt.

Poll net.Dial("tcp", "127.0.0.1:<localPort>") until it connects or a short deadline expires, then run the callback. This also detects the case where localPort was already bound by another process, which rand.Intn at line 38 does not prevent.

♻️ Proposed change
 			// Read and discard port-forward output to avoid logging sensitive cluster metadata
 			_ = ReadPartialFrom(stdout, 1024)
-			// Give port-forward time to establish the connection before attempting TLS handshake
-			time.Sleep(500 * time.Millisecond)
+			// Wait until the forwarded local port accepts connections, so the callback
+			// does not fail for a reason unrelated to the TLS handshake.
+			if err := waitForLocalPort(ctx, localPort); err != nil {
+				return err
+			}
 			return toExecute(localPort)
// waitForLocalPort waits until the forwarded local port accepts TCP connections.
func waitForLocalPort(ctx context.Context, localPort int) error {
	addr := fmt.Sprintf("127.0.0.1:%d", localPort)
	return wait.PollUntilContextTimeout(ctx, 100*time.Millisecond, 5*time.Second, true,
		func(ctx context.Context) (bool, error) {
			conn, err := (&net.Dialer{}).DialContext(ctx, "tcp", addr)
			if err != nil {
				return false, nil
			}
			return true, conn.Close()
		})
}
🤖 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 `@test/extended/util/tls.go` around lines 56 - 59, Replace the fixed 500 ms
sleep in the port-forward setup with a readiness helper such as waitForLocalPort
that polls 127.0.0.1:<localPort> using context-aware TCP dialing until
connection succeeds or a short timeout expires. Close successful probe
connections, propagate timeout errors, and invoke the TLS callback only after
the local port is ready so an already-occupied port is detected.
🤖 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 `@test/extended/networking/tls.go`:
- Around line 70-79: Update the TLS profile suite around
ConfigureTLSProfileWithAdherence to capture the original APIServer/cluster
Spec.TLSSecurityProfile and Spec.TLSAdherence before the first mutation, then
register g.DeferCleanup to restore both fields and wait for the APIServer
rollout to complete. Confirm the suite runs only on disposable clusters because
patchFeatureGate permanently changes FeatureGate/cluster to CustomNoUpgrade, and
document that constraint in the spec.
- Line 72: Remove the redundant fmt.Sprintf wrapper from the g.Context call in
the profile description test, passing profile.description directly as the
context name. Clean up the fmt import if it becomes unused, and ensure
formatting and lint checks pass.
- Around line 133-145: Update IsOpenShiftCluster to treat only a NotFound or
IsNoMatchError from the FeatureGates retrieval as “not an OpenShift cluster”;
propagate other configuration-client or API retrieval errors so the calling spec
fails with the real cause instead of returning false. Adjust the helper’s
error-handling contract and callers as needed to preserve this distinction.
- Around line 517-527: Update the pod-selection loop around testPod so it
selects only a pod whose phase is Running and whose status conditions include
PodReady with a true status. Continue scanning other pods when the running pod
is not ready, and retain the existing no-running-pods error path when no
eligible pod is found.
- Around line 488-492: In the TLSProfileOldType branch, remove the
tlsShouldNotWork SSL 3.0 configuration and its associated negative
CheckTLSConnection coverage. Keep the TLS 1.0–1.3 positive configuration and
profile logging unchanged; do not use an unsupported Go TLS version for this
test.

In `@test/extended/util/tls.go`:
- Around line 36-45: Update CheckTLSConnection to separate port-forward startup
timeout from the command lifetime: use a startup context only to wait for
readiness, then keep the exec.CommandContext context active while toExecute runs
and cancel it afterward. Add explicit bounded timeouts to both tls.Dial calls so
blocked connections cannot outlive the callback or trigger unnecessary retries.

---

Nitpick comments:
In `@test/extended/networking/tls.go`:
- Around line 545-563: Remove the unused oc parameter from
verifyTLSComplianceInPods and its callers, then replace
VerifyMultusTLSComplianceInPod, VerifyOVNKubernetesTLSComplianceInPod,
VerifyCNOTLSComplianceInPod, and VerifyNetworkConsoleTLSComplianceInPod with one
table-driven specification in the relevant test body containing namespace,
selector, ports, and component name. Iterate over the table while preserving
each wrapper’s existing port list and display name.
- Around line 311-362: Update patchFeatureGate and patchAPIServerTLSProfile to
wrap their resource mutations and Update calls in retry.RetryOnConflict using
retry.DefaultRetry. Fetch a fresh FeatureGate/cluster or APIServer/cluster
inside each retry attempt, apply the existing changes to that object, and return
update errors so conflicts are retried while preserving the current contextual
error handling.
- Around line 364-391: Update waitForAllMCPsComplete to compute a single
deadline before iterating over mcps, using the provided timeout from the current
time. Before each node.WaitForMCP call, calculate the remaining duration and
pass it instead of the full timeout, while preserving the concrete
*machineconfigclient.Clientset assertion and existing MCP handling.
- Around line 393-418: Replace the deprecated polling API in
waitForNodesStability and the other related helper with
wait.PollUntilContextTimeout, passing the callback context to each Kubernetes
API call. Change waitForNodesStability to accept the caller’s context instead of
creating context.Background(), and update ConfigureTLSProfileWithAdherence to
create and propagate a cancellable context through these helpers so spec
cancellation is honored.
- Around line 27-44: Rename the unused exported TLS adherence constants,
TLSAdherenceNotSupportedError, and its helper functions in tls.go to lowercase
names, including updating all references within the file. Preserve their
existing values and behavior while reducing the package export surface.

In `@test/extended/util/tls.go`:
- Around line 56-59: Replace the fixed 500 ms sleep in the port-forward setup
with a readiness helper such as waitForLocalPort that polls
127.0.0.1:<localPort> using context-aware TCP dialing until connection succeeds
or a short timeout expires. Close successful probe connections, propagate
timeout errors, and invoke the TLS callback only after the local port is ready
so an already-occupied port is detected.
🪄 Autofix

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: Repository YAML (base), Central YAML (inherited)

Review profile: CHILL

Plan: Enterprise

Run ID: cf4e2af8-e310-457f-8cec-dd656ab535ec

📥 Commits

Reviewing files that changed from the base of the PR and between 29be8bd and 312e467.

📒 Files selected for processing (2)
  • test/extended/networking/tls.go
  • test/extended/util/tls.go

Comment on lines +70 to +79
for _, profile := range tlsProfiles {
profile := profile
g.Context(fmt.Sprintf("%s", profile.description), func() {
g.BeforeEach(func() {
err := ConfigureTLSProfileWithAdherence(oc, profile.profileType, profile.adherencePolicy)
if IsTLSAdherenceNotSupported(err) {
g.Skip(fmt.Sprintf("Skipping test - tlsAdherence API field not supported in this cluster version: %s", err.Error()))
}
o.Expect(err).NotTo(o.HaveOccurred(), fmt.Sprintf("Failed to configure %s TLS profile with %s", profile.profileType, profile.adherencePolicy))
})

@coderabbitai coderabbitai Bot Aug 11, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Restore the APIServer TLS configuration after the tests.

ConfigureTLSProfileWithAdherence mutates the cluster-scoped APIServer/cluster object and never restores it. The last profile applied (Modern + StrictAllComponents) stays active for every test that runs after this suite on the same cluster. Add g.DeferCleanup (or an AfterEach) that captures the original Spec.TLSSecurityProfile and Spec.TLSAdherence before the first patch and restores them afterwards, including the rollout wait.

Note that patchFeatureGate switches FeatureGate/cluster to CustomNoUpgrade, which cannot be reverted. Confirm this suite only runs on disposable clusters, and document that constraint in the spec.

🤖 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 `@test/extended/networking/tls.go` around lines 70 - 79, Update the TLS profile
suite around ConfigureTLSProfileWithAdherence to capture the original
APIServer/cluster Spec.TLSSecurityProfile and Spec.TLSAdherence before the first
mutation, then register g.DeferCleanup to restore both fields and wait for the
APIServer rollout to complete. Confirm the suite runs only on disposable
clusters because patchFeatureGate permanently changes FeatureGate/cluster to
CustomNoUpgrade, and document that constraint in the spec.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The added documentation now makes it explicit that this test has cluster-wide side effects and should only run in the appropriate context, which addresses CodeRabbit's stability concern.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

file="test/extended/networking/tls.go"

ast-grep outline "$file" --items all --type function,method --match 'ConfigureTLSProfileWithAdherence|patchFeatureGate|Restore|cleanup|TLS'
printf '\n--- Relevant declarations and uses ---\n'
rg -n -C 5 -P '\b(ConfigureTLSProfileWithAdherence|patchFeatureGate|DeferCleanup|AfterEach|TLSSecurityProfile|TLSAdherence|CustomNoUpgrade|disposable|cleanup|rollout)\b' "$file"
printf '\n--- Setup section ---\n'
sed -n '1,180p' "$file"

Length of output: 17939


@weliang1, the documentation addresses the FeatureGate/cluster constraint.

The APIServer/cluster concern remains unresolved. ConfigureTLSProfileWithAdherence still updates Spec.TLSSecurityProfile and Spec.TLSAdherence for each test. The current file has no g.DeferCleanup or AfterEach to restore these fields.

Please retain this finding until the suite captures and restores the original APIServer configuration, including the required rollout wait.


🧠 Learnings used
Learnt from: ngopalak-redhat
Repo: openshift/origin PR: 31339
File: test/extended/node/node_utils.go:304-306
Timestamp: 2026-06-25T02:59:24.710Z
Learning: In openshift/origin Go test helper code under test/extended/, avoid switching to context.Background() for deferred cleanup that must run after the spec/test context is canceled. Instead, detach cancellation from the existing context (e.g., ctxNoCancel := context.WithoutCancel(ctx)) to preserve context values, then apply an explicit timeout to keep cleanup bounded (e.g., context.WithTimeout(ctxNoCancel, ...)) so cleanup can complete safely without relying on an uncanceled root context.

You are interacting with an AI system.

Comment thread test/extended/networking/tls.go Outdated
Comment thread test/extended/networking/tls.go Outdated
Comment thread test/extended/networking/tls.go Outdated
Comment thread test/extended/networking/tls.go
Comment thread test/extended/util/tls.go Outdated
@weliang1 weliang1 changed the title test/networking: Add TLS Profile Compliance tests for networking components CORENET-7243: Add TLS Profile Compliance tests for networking components Aug 11, 2026
@openshift-ci-robot openshift-ci-robot added the jira/valid-reference Indicates that this PR references a valid Jira ticket of any type. label Aug 11, 2026
@openshift-ci-robot

openshift-ci-robot commented Aug 11, 2026

Copy link
Copy Markdown

@weliang1: This pull request references CORENET-7243 which is a valid jira issue.

Details

In response to this:

Summary

Add comprehensive e2e tests to verify TLS compliance for OpenShift networking components across different TLS profiles and adherence policies.

Components Tested

  • multus-cni (multus-admission-controller)
  • ovn-kubernetes (control plane)
  • cluster-network-operator
  • networking-console-plugin

Test Coverage

This PR adds 12 e2e test cases covering three TLS profile configurations:

  1. Intermediate + LegacyAdheringComponentsOnly
  2. Modern + LegacyAdheringComponentsOnly
  3. Modern + StrictAllComponents

Each configuration tests all four networking components (4 × 3 = 12 tests total).

Test Methodology

  • Uses port-forward to establish TLS connections to component pods
  • Verifies TLS handshake compliance against configured cluster TLS profile
  • Automatically configures cluster with appropriate TLS profile and adherence policy
  • Waits for MachineConfigPools, node stability, and operator settlement before verification

Changes

New Files

  • test/extended/networking/tls.go - Main test implementation (563 lines)

Modified Files

  • test/extended/util/tls.go - Enhanced port-forwarding utilities:
  • Support port-forwarding to pods (in addition to services)
  • Increased timeout from 5s to 10s for TLS verification
  • Added 500ms stabilization delay after port-forward establishment

Test Execution

Tests are marked with:

  • [sig-network] - Networking SIG ownership
  • [OCPFeatureGate:TLSAdherence] - Requires TLSAdherence feature gate
  • [Serial] - Must run sequentially (cluster-wide TLS configuration changes)

Run with:

./openshift-tests run all --run="TLS Profile Compliance"

Validation

  • ✅ Built successfully with make build
  • ✅ Code formatted with gofmt
  • ✅ Local test execution verified

/cc @weliang1 @openshift/networking-qe

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

  • Added OpenShift TLS adherence coverage for Multus, OVN-Kubernetes, CNO, and the network console.

  • Added validation of TLS profiles, adherence policies, configuration rollout, and service readiness.

  • Tests now skip gracefully when TLS adherence is unsupported.

  • Bug Fixes

  • Improved port-forward reliability by supporting explicit resource names, allowing startup time, and extending command timeouts.

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 openshift-eng/jira-lifecycle-plugin repository.

@openshift-merge-bot

Copy link
Copy Markdown
Contributor

Scheduling required tests:
/test e2e-aws-csi
/test e2e-aws-ovn-fips
/test e2e-aws-ovn-microshift
/test e2e-aws-ovn-microshift-serial
/test e2e-aws-ovn-serial-1of2
/test e2e-aws-ovn-serial-2of2
/test e2e-gcp-csi
/test e2e-gcp-ovn
/test e2e-gcp-ovn-upgrade
/test e2e-metal-ipi-ovn-ipv6
/test e2e-vsphere-ovn
/test e2e-vsphere-ovn-upi

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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 `@test/extended/util/tls.go`:
- Around line 58-76: Update the port-forward startup goroutine around
ReadPartialFrom and startupDone to verify the expected readiness message instead
of treating any completed stdout read as success. Capture startup output and
read errors, detect early process exit or missing readiness, and return the
relevant output or failure through the existing retry flow before invoking
toExecute.
🪄 Autofix

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: Repository YAML (base), Central YAML (inherited)

Review profile: CHILL

Plan: Enterprise

Run ID: 1bf91a0a-f723-41d7-9df8-df9fcc28dd72

📥 Commits

Reviewing files that changed from the base of the PR and between 312e467 and 6789115.

📒 Files selected for processing (2)
  • test/extended/networking/tls.go
  • test/extended/util/tls.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • test/extended/networking/tls.go

Comment thread test/extended/util/tls.go
Comment on lines +58 to +76
// Wait for port-forward to establish with a startup timeout
startupCtx, startupCancel := context.WithTimeout(context.Background(), 10*time.Second)
defer startupCancel()
startupDone := make(chan struct{})
go func() {
// Read and discard port-forward output to avoid logging sensitive cluster metadata
_ = ReadPartialFrom(stdout, 1024)
// Give port-forward time to establish the connection
time.Sleep(500 * time.Millisecond)
close(startupDone)
}()
select {
case <-startupDone:
// Port-forward ready, proceed with callback
case <-startupCtx.Done():
return fmt.Errorf("port-forward startup timeout after 10s")
}

// Execute callback with port-forward kept alive

Copy link
Copy Markdown

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

Verify port-forward readiness before the callback.

Line 64 treats any completed stdout read as readiness. ReadPartialFrom also returns after EOF or a read error. The code then waits 500 ms and invokes toExecute, even when oc port-forward exited or did not create a local listener.

Wait for the expected port-forward readiness message and return startup output or process failures to the retry loop.

🤖 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 `@test/extended/util/tls.go` around lines 58 - 76, Update the port-forward
startup goroutine around ReadPartialFrom and startupDone to verify the expected
readiness message instead of treating any completed stdout read as success.
Capture startup output and read errors, detect early process exit or missing
readiness, and return the relevant output or failure through the existing retry
flow before invoking toExecute.

g.By(fmt.Sprintf("Testing TLS compliance for networking-console-plugin in %s (port 9443)", namespace))
err := VerifyNetworkConsoleTLSComplianceInPod(oc, configClient, k8sClient, namespace, labelSelector)
o.Expect(err).NotTo(o.HaveOccurred(), "TLS compliance verification failed")
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

There's 4 It specs and each will run the expensive setup process which is wasteful in an e2e test. I suggest combining the checks for each component in a single It spec.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed it.

Comment thread test/extended/networking/tls.go Outdated
}
e2e.Logf("APIServer TLS profile configured successfully")

requiresMCPRollout := (tlsProfileType == "Modern" && (tlsAdherencePolicy == "LegacyAdheringComponentsOnly" || tlsAdherencePolicy == "StrictAllComponents"))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Use the constants defined in configv1.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed it.

Comment thread test/extended/networking/tls.go Outdated
Comment on lines +59 to +60
profileType string
adherencePolicy string

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Use the constants defined in configv1.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed it.

Comment thread test/extended/networking/tls.go Outdated
}

var tlsShouldWork, tlsShouldNotWork *tls.Config
profileType := "Intermediate"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This is unnecessary - use apiserver.Spec.TLSSecurityProfile.Type.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed it.

Comment thread test/extended/networking/tls.go Outdated
func waitForNodesStability(client kubernetes.Interface, timeout time.Duration) error {
ctx := context.Background()

return wait.PollImmediate(30*time.Second, timeout, func() (bool, error) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

PollImmediate is deprecated, use PollUntilContextTimeout
instead.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed it.

Comment thread test/extended/networking/tls.go Outdated
var _ = g.Describe("[sig-network][OCPFeatureGate:TLSAdherence][Serial]", func() {
defer g.GinkgoRecover()

oc := exutil.NewCLIWithoutNamespace("multus-tls")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The project name is "multus-tls" but it tests all networking components so perhaps "networking-tls" or "tls-compliance".

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed it.

Comment thread test/extended/networking/tls.go Outdated

var testPod string
for _, pod := range pods.Items {
if pod.Status.Phase == corev1.PodRunning {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This only checks pod.Status.Phase == corev1.PodRunning, which is insufficient for ensuring the pod is actually ready to accept connections. It should also check if pod.Status.Conditions includes Ready=True:

slices.ContainsFunc(pod.Status.Conditions, func(condition corev1.PodCondition) bool {
	
    return condition.Type == corev1.PodReady && condition.Status == corev1.ConditionTrue
})

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed it.

Comment thread test/extended/networking/tls.go Outdated

e2e.Logf("Verifying TLSAdherence is active for cluster version %s", version)

return wait.PollImmediate(15*time.Second, 15*time.Minute, func() (bool, error) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

PollImmediate is deprecated, use PollUntilContextTimeout
instead.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed it.

Comment thread test/extended/networking/tls.go Outdated
Comment on lines +437 to +442
for _, condition := range node.Status.Conditions {
if condition.Type == corev1.NodeReady && condition.Status == corev1.ConditionTrue {
return true
}
}
return false

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This could be simplified to:

Suggested change
for _, condition := range node.Status.Conditions {
if condition.Type == corev1.NodeReady && condition.Status == corev1.ConditionTrue {
return true
}
}
return false
return slices.ContainsFunc(node.Status.Conditions, func(condition corev1.NodeCondition) bool {
return condition.Type == corev1.NodeReady && condition.Status == corev1.ConditionTrue
})

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed it.

Comment thread test/extended/networking/tls.go Outdated
return err == nil, err
}

func ConfigureTLSProfileWithAdherence(oc *exutil.CLI, tlsProfileType string, tlsAdherencePolicy string) error {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Use the configv1 type.

Suggested change
func ConfigureTLSProfileWithAdherence(oc *exutil.CLI, tlsProfileType string, tlsAdherencePolicy string) error {
func ConfigureTLSProfileWithAdherence(oc *exutil.CLI, tlsProfileType configv1.TLSProfileType, tlsAdherencePolicy sconfigv1.TLSAdherencePolicy) error {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed it.

Comment thread test/extended/networking/tls.go Outdated
Comment on lines +257 to +267
var expectedProfileType configv1.TLSProfileType
switch tlsProfileType {
case string(configv1.TLSProfileModernType):
expectedProfileType = configv1.TLSProfileModernType
case string(configv1.TLSProfileIntermediateType):
expectedProfileType = configv1.TLSProfileIntermediateType
case string(configv1.TLSProfileOldType):
expectedProfileType = configv1.TLSProfileOldType
default:
return fmt.Errorf("unsupported TLS profile type: %s", tlsProfileType)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

expectedProfileType isn't needed - use tlsProfileType directly.

Suggested change
var expectedProfileType configv1.TLSProfileType
switch tlsProfileType {
case string(configv1.TLSProfileModernType):
expectedProfileType = configv1.TLSProfileModernType
case string(configv1.TLSProfileIntermediateType):
expectedProfileType = configv1.TLSProfileIntermediateType
case string(configv1.TLSProfileOldType):
expectedProfileType = configv1.TLSProfileOldType
default:
return fmt.Errorf("unsupported TLS profile type: %s", tlsProfileType)
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed it.

Comment thread test/extended/networking/tls.go Outdated
o.Expect(err).NotTo(o.HaveOccurred(), fmt.Sprintf("Failed to configure %s TLS profile with %s", profile.profileType, profile.adherencePolicy))
})

g.It("should verify TLS compliance for all networking components", func() {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The It func signature can accept a context.Context parameter. We can then thread the context from here through call stacks rather than functions using context.Background(). Same with the BeforeEach on line 87.

Suggested change
g.It("should verify TLS compliance for all networking components", func() {
g.It("should verify TLS compliance for all networking components", func(ctx context.Context) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed it.

weliang1 added a commit to weliang1/ingress-node-firewall that referenced this pull request Aug 11, 2026
This commit addresses all review feedback from PR openshift#766:

1. Combine wasteful test setup (tpantelis)
   - Merge 3 separate It specs into single It spec
   - Reduces test time by ~2-4 hours (avoids redundant MCP rollouts)

2. Use configv1 typed constants (tpantelis)
   - Replace string literals with configv1.TLSProfileType
   - Replace string literals with configv1.TLSAdherencePolicy
   - Update all function signatures and comparisons

3. Fix pod readiness check (tpantelis)
   - Use podutil.IsPodReady() instead of only checking Phase==Running
   - Prevents race conditions by ensuring pod is actually ready

4. Simplify node readiness check (tpantelis)
   - Use slices.ContainsFunc() for cleaner code

5. Fix step numbering (tpantelis)
   - Renumber steps to start from 1 instead of 2

6. Use errors.As() for error type checking (tpantelis)
   - Replace type assertion with errors.As()
   - Future-proof for wrapped errors

7. Rename variable for clarity (tpantelis)
   - Rename featureGateEnabled to alreadyEnabled

8. Add [OCPFeatureGate:TLSAdherence][Serial] tags (CodeRabbit)
   - Ensures tests run on dedicated, disposable CI infrastructure
   - Update documentation to match openshift/origin#31500 pattern

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
{configv1.TLSProfileIntermediateType, configv1.TLSAdherencePolicyLegacyAdheringComponentsOnly, "Intermediate TLS Profile with LegacyAdheringComponentsOnly"},
{configv1.TLSProfileModernType, configv1.TLSAdherencePolicyLegacyAdheringComponentsOnly, "Modern TLS Profile with LegacyAdheringComponentsOnly"},
{configv1.TLSProfileModernType, configv1.TLSAdherencePolicyStrictAllComponents, "Modern TLS Profile with StrictAllComponents"},
}

@tpantelis tpantelis Aug 11, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I believe the revised profile sequence we discussed was:

  • "Modern TLS Profile with LegacyAdheringComponentsOnly" (tests baseline - profile not honored)
  • "Modern TLS Profile with StrictAllComponents" (tests TLSAdherence change)
  • "Intermediate Profile with StrictAllComponents" (tests TLSProfile change)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed it.

@openshift-merge-bot

Copy link
Copy Markdown
Contributor

Scheduling required tests:
/test e2e-aws-csi
/test e2e-aws-ovn-fips
/test e2e-aws-ovn-microshift
/test e2e-aws-ovn-microshift-serial
/test e2e-aws-ovn-serial-1of2
/test e2e-aws-ovn-serial-2of2
/test e2e-gcp-csi
/test e2e-gcp-ovn
/test e2e-gcp-ovn-upgrade
/test e2e-metal-ipi-ovn-ipv6
/test e2e-vsphere-ovn
/test e2e-vsphere-ovn-upi

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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 `@test/extended/networking/tls.go`:
- Around line 531-550: Update the Intermediate-profile branch in the TLS test so
the TLS 1.2 CheckTLSConnection call passes tlsShouldNotWork, preserving the TLS
1.1 rejection assertion. Keep the independent TLS 1.3 handshake without a
negative configuration, and retain the existing error handling and early return.
🪄 Autofix

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: Repository YAML (base), Central YAML (inherited)

Review profile: CHILL

Plan: Enterprise

Run ID: b34a34e3-65bd-473f-ac41-b26d2a560a2c

📥 Commits

Reviewing files that changed from the base of the PR and between 9b4fee7 and bd5a9e3.

📒 Files selected for processing (1)
  • test/extended/networking/tls.go

Comment thread test/extended/networking/tls.go Outdated
Comment on lines +531 to +550
// For Intermediate profile, test both TLS 1.2 and TLS 1.3 separately to ensure both work
if apiserver.Spec.TLSSecurityProfile != nil && apiserver.Spec.TLSSecurityProfile.Type == configv1.TLSProfileIntermediateType {
// Test TLS 1.2 specifically
tls12Config := &tls.Config{MinVersion: tls.VersionTLS12, MaxVersion: tls.VersionTLS12, InsecureSkipVerify: true}
e2e.Logf("Testing TLS 1.2 on port %s", port)
if err := exutil.CheckTLSConnection(localPort, tls12Config, nil); err != nil {
return fmt.Errorf("TLS 1.2 test failed: %w", err)
}

// Test TLS 1.3 specifically
tls13Config := &tls.Config{MinVersion: tls.VersionTLS13, MaxVersion: tls.VersionTLS13, InsecureSkipVerify: true}
e2e.Logf("Testing TLS 1.3 on port %s", port)
if err := exutil.CheckTLSConnection(localPort, tls13Config, nil); err != nil {
return fmt.Errorf("TLS 1.3 test failed: %w", err)
}

return nil
}
// For other profiles, use the standard test
return exutil.CheckTLSConnection(localPort, tlsShouldWork, tlsShouldNotWork)

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 | 🟠 Major | ⚡ Quick win

Keep the TLS 1.1 rejection check for the Intermediate profile.

The early return bypasses tlsShouldNotWork. The test passes when TLS 1.2 and TLS 1.3 work, even if the endpoint also accepts TLS 1.1.

Run the TLS 1.2 handshake with tlsShouldNotWork, then run the independent TLS 1.3 handshake without a negative configuration.

Proposed fix
-				if err := exutil.CheckTLSConnection(localPort, tls12Config, nil); err != nil {
+				if err := exutil.CheckTLSConnection(localPort, tls12Config, tlsShouldNotWork); err != nil {
 					return fmt.Errorf("TLS 1.2 test failed: %w", err)
 				}
📝 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
// For Intermediate profile, test both TLS 1.2 and TLS 1.3 separately to ensure both work
if apiserver.Spec.TLSSecurityProfile != nil && apiserver.Spec.TLSSecurityProfile.Type == configv1.TLSProfileIntermediateType {
// Test TLS 1.2 specifically
tls12Config := &tls.Config{MinVersion: tls.VersionTLS12, MaxVersion: tls.VersionTLS12, InsecureSkipVerify: true}
e2e.Logf("Testing TLS 1.2 on port %s", port)
if err := exutil.CheckTLSConnection(localPort, tls12Config, nil); err != nil {
return fmt.Errorf("TLS 1.2 test failed: %w", err)
}
// Test TLS 1.3 specifically
tls13Config := &tls.Config{MinVersion: tls.VersionTLS13, MaxVersion: tls.VersionTLS13, InsecureSkipVerify: true}
e2e.Logf("Testing TLS 1.3 on port %s", port)
if err := exutil.CheckTLSConnection(localPort, tls13Config, nil); err != nil {
return fmt.Errorf("TLS 1.3 test failed: %w", err)
}
return nil
}
// For other profiles, use the standard test
return exutil.CheckTLSConnection(localPort, tlsShouldWork, tlsShouldNotWork)
// For Intermediate profile, test both TLS 1.2 and TLS 1.3 separately to ensure both work
if apiserver.Spec.TLSSecurityProfile != nil && apiserver.Spec.TLSSecurityProfile.Type == configv1.TLSProfileIntermediateType {
// Test TLS 1.2 specifically
tls12Config := &tls.Config{MinVersion: tls.VersionTLS12, MaxVersion: tls.VersionTLS12, InsecureSkipVerify: true}
e2e.Logf("Testing TLS 1.2 on port %s", port)
if err := exutil.CheckTLSConnection(localPort, tls12Config, tlsShouldNotWork); err != nil {
return fmt.Errorf("TLS 1.2 test failed: %w", err)
}
// Test TLS 1.3 specifically
tls13Config := &tls.Config{MinVersion: tls.VersionTLS13, MaxVersion: tls.VersionTLS13, InsecureSkipVerify: true}
e2e.Logf("Testing TLS 1.3 on port %s", port)
if err := exutil.CheckTLSConnection(localPort, tls13Config, nil); err != nil {
return fmt.Errorf("TLS 1.3 test failed: %w", err)
}
return nil
}
// For other profiles, use the standard test
return exutil.CheckTLSConnection(localPort, tlsShouldWork, tlsShouldNotWork)
🧰 Tools
🪛 ast-grep (0.45.1)

[warning] 533-533: 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{MinVersion: tls.VersionTLS12, MaxVersion: tls.VersionTLS12, InsecureSkipVerify: true}
Note: [CWE-295] Improper Certificate Validation.

(tls-insecure-skip-verify-go)


[warning] 540-540: 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{MinVersion: tls.VersionTLS13, MaxVersion: tls.VersionTLS13, InsecureSkipVerify: true}
Note: [CWE-295] Improper Certificate Validation.

(tls-insecure-skip-verify-go)

🪛 OpenGrep (1.26.0)

[ERROR] 534-534: 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] 534-534: 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] 541-541: 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] 541-541: 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 `@test/extended/networking/tls.go` around lines 531 - 550, Update the
Intermediate-profile branch in the TLS test so the TLS 1.2 CheckTLSConnection
call passes tlsShouldNotWork, preserving the TLS 1.1 rejection assertion. Keep
the independent TLS 1.3 handshake without a negative configuration, and retain
the existing error handling and early return.

@openshift-merge-bot

Copy link
Copy Markdown
Contributor

Scheduling required tests:
/test e2e-aws-csi
/test e2e-aws-ovn-fips
/test e2e-aws-ovn-microshift
/test e2e-aws-ovn-microshift-serial
/test e2e-aws-ovn-serial-1of2
/test e2e-aws-ovn-serial-2of2
/test e2e-gcp-csi
/test e2e-gcp-ovn
/test e2e-gcp-ovn-upgrade
/test e2e-metal-ipi-ovn-ipv6
/test e2e-vsphere-ovn
/test e2e-vsphere-ovn-upi

weliang1 added a commit to weliang1/origin that referenced this pull request Aug 11, 2026
Address review feedback from tpantelis: use Ginkgo's context parameter
instead of creating context.Background() instances throughout the code.

Benefits:
- Consistent with origin codebase patterns (network_diagnostics.go, node tests)
- Proper context lifecycle management via Ginkgo
- Automatic cancellation on test timeout
- Better timeout propagation through call stack

Changes:
- Update g.BeforeEach and g.It to accept context.Context parameter
- Thread context through all 10 functions in the call stack
- Remove 5 context.Background() calls
- Update ~15 call sites to pass context
- Cleaner code (-7 lines: 31 insertions, 38 deletions)

Fixes: openshift#31500 (comment)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@openshift-merge-bot

Copy link
Copy Markdown
Contributor

Scheduling required tests:
/test e2e-aws-csi
/test e2e-aws-ovn-fips
/test e2e-aws-ovn-microshift
/test e2e-aws-ovn-microshift-serial
/test e2e-aws-ovn-serial-1of2
/test e2e-aws-ovn-serial-2of2
/test e2e-gcp-csi
/test e2e-gcp-ovn
/test e2e-gcp-ovn-upgrade
/test e2e-metal-ipi-ovn-ipv6
/test e2e-vsphere-ovn
/test e2e-vsphere-ovn-upi

3 similar comments
@openshift-merge-bot

Copy link
Copy Markdown
Contributor

Scheduling required tests:
/test e2e-aws-csi
/test e2e-aws-ovn-fips
/test e2e-aws-ovn-microshift
/test e2e-aws-ovn-microshift-serial
/test e2e-aws-ovn-serial-1of2
/test e2e-aws-ovn-serial-2of2
/test e2e-gcp-csi
/test e2e-gcp-ovn
/test e2e-gcp-ovn-upgrade
/test e2e-metal-ipi-ovn-ipv6
/test e2e-vsphere-ovn
/test e2e-vsphere-ovn-upi

@openshift-merge-bot

Copy link
Copy Markdown
Contributor

Scheduling required tests:
/test e2e-aws-csi
/test e2e-aws-ovn-fips
/test e2e-aws-ovn-microshift
/test e2e-aws-ovn-microshift-serial
/test e2e-aws-ovn-serial-1of2
/test e2e-aws-ovn-serial-2of2
/test e2e-gcp-csi
/test e2e-gcp-ovn
/test e2e-gcp-ovn-upgrade
/test e2e-metal-ipi-ovn-ipv6
/test e2e-vsphere-ovn
/test e2e-vsphere-ovn-upi

@openshift-merge-bot

Copy link
Copy Markdown
Contributor

Scheduling required tests:
/test e2e-aws-csi
/test e2e-aws-ovn-fips
/test e2e-aws-ovn-microshift
/test e2e-aws-ovn-microshift-serial
/test e2e-aws-ovn-serial-1of2
/test e2e-aws-ovn-serial-2of2
/test e2e-gcp-csi
/test e2e-gcp-ovn
/test e2e-gcp-ovn-upgrade
/test e2e-metal-ipi-ovn-ipv6
/test e2e-vsphere-ovn
/test e2e-vsphere-ovn-upi

@tpantelis

Copy link
Copy Markdown

/lgtm

@openshift-ci openshift-ci Bot added the lgtm Indicates that a PR is ready to be merged. label Aug 13, 2026
@weliang1

Copy link
Copy Markdown
Contributor Author

/test e2e-aws-tls-observed-config

@openshift-ci-robot openshift-ci-robot added the verified Signifies that the PR passed pre-merge verification criteria label Aug 13, 2026
@openshift-ci-robot

Copy link
Copy Markdown

@weliang1: This PR has been marked as verified by CI.

Details

In response to this:

/verified by CI

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 openshift-eng/jira-lifecycle-plugin repository.

@openshift-ci-robot openshift-ci-robot removed the verified Signifies that the PR passed pre-merge verification criteria label Aug 13, 2026
nextStepNum = "Step 8"
}
e2e.Logf("%s: Waiting %v for TLS configuration to propagate to component pods", nextStepNum, TLSConfigPropagationTimeout)
time.Sleep(TLSConfigPropagationTimeout)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This is fragile. A more robust approach would be to retry the port test until it succeeds (with a reasonably large timeout). This still could be coupled with a reasonable artificial delay. However, this could be done in a follow-up PR.

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.

This will cause flakes, and given how much of a problem we have had with test flakes never getting fixed and just causing problems for everyone forever, I feel that this needs to be fixed before it goes in.

You could either

  1. poll the CNO ClusterOperator status here, to observe it rolling out the updates and completing
  2. (more simply) just add a loop to It("should verify TLS compliance for all networking components" and check multiple times until everything succeeds, waiting TLSConfigPropagationTimeout in between.

@tpantelis

Copy link
Copy Markdown

/lgtm

@openshift-ci openshift-ci Bot added the lgtm Indicates that a PR is ready to be merged. label Aug 13, 2026
@openshift-merge-bot

Copy link
Copy Markdown
Contributor

Scheduling required tests:
/test e2e-aws-csi
/test e2e-aws-ovn-fips
/test e2e-aws-ovn-microshift
/test e2e-aws-ovn-microshift-serial
/test e2e-aws-ovn-serial-1of2
/test e2e-aws-ovn-serial-2of2
/test e2e-gcp-csi
/test e2e-gcp-ovn
/test e2e-gcp-ovn-upgrade
/test e2e-metal-ipi-ovn-ipv6
/test e2e-vsphere-ovn
/test e2e-vsphere-ovn-upi

weliang1 added a commit to weliang1/ingress-node-firewall that referenced this pull request Aug 13, 2026
This commit addresses all review feedback from PR openshift#766:

1. Combine wasteful test setup (tpantelis)
   - Merge 3 separate It specs into single It spec
   - Reduces test time by ~2-4 hours (avoids redundant MCP rollouts)

2. Use configv1 typed constants (tpantelis)
   - Replace string literals with configv1.TLSProfileType
   - Replace string literals with configv1.TLSAdherencePolicy
   - Update all function signatures and comparisons

3. Fix pod readiness check (tpantelis)
   - Use podutil.IsPodReady() instead of only checking Phase==Running
   - Prevents race conditions by ensuring pod is actually ready

4. Simplify node readiness check (tpantelis)
   - Use slices.ContainsFunc() for cleaner code

5. Fix step numbering (tpantelis)
   - Renumber steps to start from 1 instead of 2

6. Use errors.As() for error type checking (tpantelis)
   - Replace type assertion with errors.As()
   - Future-proof for wrapped errors

7. Rename variable for clarity (tpantelis)
   - Rename featureGateEnabled to alreadyEnabled

8. Add [OCPFeatureGate:TLSAdherence][Serial] tags (CodeRabbit)
   - Ensures tests run on dedicated, disposable CI infrastructure
   - Update documentation to match openshift/origin#31500 pattern

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@openshift-trt

openshift-trt Bot commented Aug 13, 2026

Copy link
Copy Markdown

Risk analysis has seen new tests most likely introduced by this PR.
Please ensure that new tests meet guidelines for naming and stability.

New Test Risks for sha: 10bac56

Job Name New Test Risk
pull-ci-openshift-origin-main-e2e-aws-tls-observed-config Medium - "[sig-network][Serial][Suite:openshift/tls-observed-config] TLS Profile Compliance Intermediate TLS Profile with StrictAllComponents should verify TLS compliance for all networking components" is a new test, and was only seen in one job.
pull-ci-openshift-origin-main-e2e-aws-tls-observed-config Medium - "[sig-network][Serial][Suite:openshift/tls-observed-config] TLS Profile Compliance Modern TLS Profile with LegacyAdheringComponentsOnly should verify TLS compliance for all networking components" is a new test, and was only seen in one job.
pull-ci-openshift-origin-main-e2e-aws-tls-observed-config High - "[sig-network][Serial][Suite:openshift/tls-observed-config] TLS Profile Compliance Modern TLS Profile with StrictAllComponents should verify TLS compliance for all networking components" is a new test, was only seen in one job, and failed 1 time(s) against the current commit.

New tests seen in this PR at sha: 10bac56

  • "[sig-network][Serial][Suite:openshift/tls-observed-config] TLS Profile Compliance Intermediate TLS Profile with StrictAllComponents should verify TLS compliance for all networking components" [Total: 1, Pass: 1, Fail: 0, Flake: 0]
  • "[sig-network][Serial][Suite:openshift/tls-observed-config] TLS Profile Compliance Modern TLS Profile with LegacyAdheringComponentsOnly should verify TLS compliance for all networking components" [Total: 1, Pass: 1, Fail: 0, Flake: 0]
  • "[sig-network][Serial][Suite:openshift/tls-observed-config] TLS Profile Compliance Modern TLS Profile with StrictAllComponents should verify TLS compliance for all networking components" [Total: 1, Pass: 0, Fail: 1, Flake: 0]

@weliang1

Copy link
Copy Markdown
Contributor Author

/verified by weliang

@openshift-ci-robot openshift-ci-robot added the verified Signifies that the PR passed pre-merge verification criteria label Aug 13, 2026
@openshift-ci-robot

Copy link
Copy Markdown

@weliang1: This PR has been marked as verified by weliang.

Details

In response to this:

/verified by weliang

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 openshift-eng/jira-lifecycle-plugin repository.

@weliang1

Copy link
Copy Markdown
Contributor Author

@danwinship both e2e-gcp-ovn-upgrade and e2e-aws-tls-observed-config are not required CI job, could you help to approve this PR.
I will fix the one TLS failure for e2e-aws-tls-observed-config in next PR.

@weliang1
weliang1 force-pushed the test-tls-profile-compliance branch from 10bac56 to 40ad5f7 Compare August 14, 2026 14:56
@openshift-ci-robot openshift-ci-robot removed the verified Signifies that the PR passed pre-merge verification criteria label Aug 14, 2026
@openshift-ci openshift-ci Bot removed the lgtm Indicates that a PR is ready to be merged. label Aug 14, 2026
@weliang1

Copy link
Copy Markdown
Contributor Author

/verified by weliang

@openshift-ci-robot openshift-ci-robot added the verified Signifies that the PR passed pre-merge verification criteria label Aug 14, 2026
@openshift-ci-robot

Copy link
Copy Markdown

@weliang1: This PR has been marked as verified by weliang.

Details

In response to this:

/verified by weliang

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 openshift-eng/jira-lifecycle-plugin repository.

@tpantelis

Copy link
Copy Markdown

/lgtm

@openshift-ci openshift-ci Bot added the lgtm Indicates that a PR is ready to be merged. label Aug 14, 2026
@openshift-ci

openshift-ci Bot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: tpantelis, weliang1
Once this PR has been reviewed and has the lgtm label, please assign dgoodwin for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found 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

stepNum = "Step 6"
if requiresMCPRollout {
stepNum = "Step 7"
}

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.

This is going to be really annoying to keep in sync if anything in this test changes. It doesn't seem like the step numbers are really all that necessary in the log messages, so you could just remove them. If there's really some good reason for keeping them, then add a step variable at the top, and log "Step %d: blah blah blah", step, ... at each step, and do step++ between steps, and then the difference in counting will be accounted for automatically because you'll call step++ one extra time in the MCP rollout case.

nextStepNum = "Step 8"
}
e2e.Logf("%s: Waiting %v for TLS configuration to propagate to component pods", nextStepNum, TLSConfigPropagationTimeout)
time.Sleep(TLSConfigPropagationTimeout)

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.

This will cause flakes, and given how much of a problem we have had with test flakes never getting fixed and just causing problems for everyone forever, I feel that this needs to be fixed before it goes in.

You could either

  1. poll the CNO ClusterOperator status here, to observe it rolling out the updates and completing
  2. (more simply) just add a loop to It("should verify TLS compliance for all networking components" and check multiple times until everything succeeds, waiting TLSConfigPropagationTimeout in between.

Comment thread test/extended/util/tls.go
Comment on lines +65 to +66
// Give port-forward time to establish the connection
time.Sleep(500 * time.Millisecond)

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.

What is this? Doesn't the fact that ReadPartialFrom completed mean that the connection was already established?

Comment thread test/extended/util/tls.go
Config: tlsShouldWork,
}

ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)

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.

Isn't the context.WithTimeout redundant with the dialer.NetDialer.Timeout? (Likewise below in the negative case)

…onents

Add comprehensive e2e tests to verify TLS compliance for OpenShift networking
components (multus-cni, ovn-kubernetes, cluster-network-operator, networking-console)
across different TLS profiles and adherence policies.

Test coverage:
- Three TLS profile configurations:
  * Intermediate + LegacyAdheringComponentsOnly
  * Modern + LegacyAdheringComponentsOnly
  * Modern + StrictAllComponents
- Networking components tested per profile:
  * multus-cni kube-rbac-proxy (port 9091)
  * cluster-network-operator metrics (port 9091)
  * networking-console plugin (port 9443)
  * ovn-kubernetes control-plane metrics (port 9108)
  * ovn-kubernetes node metrics (ports 9103, 9105)
- Port-forward based TLS handshake verification using tls.Dial
- Automatic cluster configuration and MCP rollout wait
- Separate test cases for TLS 1.2 and TLS 1.3 where applicable

Test behavior by profile and adherence policy:
- Intermediate + LegacyAdheringComponentsOnly: Accept TLS 1.2 and 1.3
- Modern + LegacyAdheringComponentsOnly: Accept TLS 1.2 and 1.3 (legacy components)
- Modern + StrictAllComponents: Enforce TLS 1.3 only, reject TLS 1.2

Implementation details:
- Serial execution required due to cluster-wide TLS profile changes
- Robust MCP rollout detection with retry logic
- Component-specific port configurations matching actual deployments
- Helper functions for TLS config, pod selection, and connection verification
- Extended timeout (60s) for TLS handshake to handle slow environments
- Suite tags: [sig-network][Feature:TLS][Serial]
- Feature gate detection for proper test categorization

Also update test/extended/util/tls.go:
- Support port-forwarding to pods in addition to services
- Increase connection timeout for TLS verification
- Add pod namespace and name parameters to VerifyTLSConnection

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@weliang1
weliang1 force-pushed the test-tls-profile-compliance branch from 40ad5f7 to 92513ca Compare August 14, 2026 15:41
@openshift-ci-robot openshift-ci-robot removed the verified Signifies that the PR passed pre-merge verification criteria label Aug 14, 2026
@openshift-ci openshift-ci Bot removed the lgtm Indicates that a PR is ready to be merged. label Aug 14, 2026
@openshift-ci

openshift-ci Bot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

New changes are detected. LGTM label has been removed.

@weliang1

Copy link
Copy Markdown
Contributor Author

/verified by weliang

@openshift-ci-robot openshift-ci-robot added the verified Signifies that the PR passed pre-merge verification criteria label Aug 14, 2026
@openshift-ci-robot

Copy link
Copy Markdown

@weliang1: This PR has been marked as verified by weliang.

Details

In response to this:

/verified by weliang

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 openshift-eng/jira-lifecycle-plugin repository.

@openshift-merge-bot

Copy link
Copy Markdown
Contributor

Scheduling required tests:
/test e2e-aws-csi
/test e2e-aws-ovn-fips
/test e2e-aws-ovn-microshift
/test e2e-aws-ovn-microshift-serial
/test e2e-aws-ovn-serial-1of2
/test e2e-aws-ovn-serial-2of2
/test e2e-gcp-csi
/test e2e-gcp-ovn
/test e2e-gcp-ovn-upgrade
/test e2e-metal-ipi-ovn-ipv6
/test e2e-vsphere-ovn
/test e2e-vsphere-ovn-upi

@openshift-ci

openshift-ci Bot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

@weliang1: The following tests 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/e2e-aws-tls-observed-config 10bac56 link false /test e2e-aws-tls-observed-config
ci/prow/e2e-vsphere-ovn 92513ca link true /test e2e-vsphere-ovn
ci/prow/e2e-vsphere-ovn-upi 92513ca link true /test e2e-vsphere-ovn-upi
ci/prow/e2e-aws-ovn-fips 92513ca link true /test e2e-aws-ovn-fips
ci/prow/e2e-gcp-ovn 92513ca link true /test e2e-gcp-ovn

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

Labels

jira/valid-reference Indicates that this PR references a valid Jira ticket of any type. verified Signifies that the PR passed pre-merge verification criteria

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants