Skip to content

OSAC-420: harden console proxy websocket transport#692

Open
sk-ilya wants to merge 1 commit into
osac-project:mainfrom
sk-ilya:websocket
Open

OSAC-420: harden console proxy websocket transport#692
sk-ilya wants to merge 1 commit into
osac-project:mainfrom
sk-ilya:websocket

Conversation

@sk-ilya

@sk-ilya sk-ilya commented Jun 14, 2026

Copy link
Copy Markdown
Contributor

migrate console websocket handling from golang.org/x/net/websocket to github.com/coder/websocket

Summary by CodeRabbit

  • Bug Fixes
    • Improved console WebSocket reliability using configurable ping/pong keep-alives.
    • Extended console proxy gRPC idle timeout to support longer-lived connections.
    • Strengthened and more accurate WebSocket upgrade handling for console metrics and cookie-origin enforcement.
    • Improved bearer-token WebSocket behavior and upgrade success detection for console metrics.
  • Improvements
    • Serial console interrupts now cancel stdin reads cleanly.
    • More efficient bidirectional console relaying using buffered transfers.
    • Enhanced console session shutdown logging with richer timeout/cancellation details.
  • New Features
    • Added CLI flags to configure console ping/pong timing, console session timeout, and gRPC keepalive behavior.

@openshift-ci-robot

openshift-ci-robot commented Jun 14, 2026

Copy link
Copy Markdown

@sk-ilya: This pull request references OSAC-420 which is a valid jira issue.

Warning: The referenced jira issue has an invalid target version for the target branch this PR targets: expected the task to target the "5.0.0" version, but no target version was set.

Details

In response to this:

Assisted-by: Claude Code noreply@anthropic.com

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.

@coderabbitai

coderabbitai Bot commented Jun 14, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

The PR migrates console WebSocket handling from golang.org/x/net/websocket to github.com/coder/websocket across the kubevirt backend, proxy WS handler, and test helpers. It adds a new ping.go liveness module with periodic keepalive, introduces configuration-driven session timeouts and gRPC keepalive parameters via new flag modules, rewrites origin enforcement to delegate allowlist matching to websocket.Accept, switches relay to io.CopyBuffer with explicit buffering, upgrades the middleware's WebSocket detection to use HTTP 101 status, and sets the ingress idle_timeout to 1800s for connection stability.

Changes

Console WebSocket Migration and Hardening

Layer / File(s) Summary
Dependencies, manifest config, and flag infrastructure
go.mod, manifests/overlays/openshift/route.yaml, charts/service/templates/ingress-proxy/_config.yaml.tpl, internal/console/ping_flags.go, internal/console/session_flags.go, internal/network/grpc_keepalive_flags.go
Added github.com/coder/websocket v1.8.14 as direct dependency; promoted cancelreader v0.2.2 to direct; demoted golang.org/x/net v0.55.0 to indirect. OpenShift routes and ingress Envoy config set idle/read timeouts to 1800s [1800s timeout addresses long-lived gRPC streams and prevents premature connection closure]. New CLI flag registration modules: ping_flags.go registers interval/timeout flags with defaults; session_flags.go sets default 30m session timeout; grpc_keepalive_flags.go configures Time/Timeout/MinTime parameters.
WebSocket ping/pong liveness module and tests
internal/console/ping.go, internal/console/ping_test.go
Introduces Ping, StartPing, PingReceivedHandler, and PongReceivedHandler implementing configurable periodic keepalive with per-ping pongTimeout-bounded contexts. Logs ping/pong payloads at debug level; forcibly closes connection via CloseNow() on ping failure [guards against silent connection death and improves keepalive robustness]. Tests validate periodic behavior, timeout errors, zero-interval blocking, and teardown logging.
Serial console stdin interrupt via cancelreader
internal/cmd/cli/console/serial/serial_cmd.go
proxyIO wraps os.Stdin in a cancelreader, passes it as ProxyOptions.Reader, and calls stdinReader.Cancel() from InterruptRead instead of manipulating read deadlines, providing cleaner interrupt semantics.
Console session lifecycle with timeout context
internal/console/manager.go
Session struct stores context alongside cancel function; NewManager initializes timeout from DefaultSessionTimeout constant. During Connect, the timeout context is stored on session. managedConnection.Close() now extracts cancellation reason via session.ctx.Err() for structured logging, improving debuggability on timeout expiry [enables tracking whether a session ended due to explicit cancellation vs. timeout vs. error].
KubeVirt backend WebSocket migration with ping integration
internal/console/kubevirt_backend.go
Replaces golang.org/x/net/websocket dial with websocket.Dial using DialOptions: TLS 1.3 minimum with optional CA pool, conditional Bearer Authorization header, ping/pong handler wiring, and background StartPing goroutine. Returns binary websocket.NetConn as io.ReadWriteCloser. SetPingConfig builder method injects configurable backend ping parameters [TLS 1.3 minimum and 10s handshake timeout harden against downgrade attacks and connection hangs].
KubeVirt backend integration tests with auth capture
internal/console/kubevirt_backend_integration_test.go
Mock WebSocket layer migrated to coder/websocket. Graceful-close test uses context-aware handler with StatusNormalClosure. Bearer-token test replaced with newMockWSServerWithAuthCapture helper that creates a local TCP listener, captures Authorization header from upgrade request into a channel, upgrades via websocket.Accept, sends authenticated banner, closes normally.
Mock WebSocket server refactored for coder/websocket
internal/console/mock_ws_server_test.go
Introduces mockWSHandler callback type (context + *websocket.Conn). HTTP handler registration via mux.HandleFunc. handleConsole refactored: performs websocket.Accept, defers CloseNow, writes banner via conn.Write(..., BinaryMessage), runs echo loop using conn.Read/Write with context, replacing prior fixed-buffer ws.Read/Write.
Console proxy core: ticket abstraction and buffered relay
internal/servers/console_proxy_core.go
Introduces internal ticketOpener interface for ticket opening, enabling test stubs without JWE key material [improves test isolation and reduces cryptographic overhead in unit tests]. Relay switches from io.Copy to io.CopyBuffer with explicit 64KB buffers for both directions, improving throughput and reducing syscall overhead.
Console proxy WebSocket handler: Accept-based upgrade, origin delegation, ping startup
internal/servers/console_proxy_ws.go
Replaces websocket.Server with websocket.Accept using OriginPatterns for allowlist enforcement; removes checkOrigin pre-check and handoffConn wrapper. Wires ping/pong callbacks and starts StartPing post-acceptance. Wraps accepted conn as binary NetConn for relay. NewConsoleProxyWSHandler now accepts pingConfig parameter. Origin enforcement simplified: only rejects when both Authorization and Origin headers are empty for cookie-auth [delegates attack surface to websocket.Accept's standard implementation and reduces custom security code].
Console proxy WebSocket origin enforcement tests
internal/servers/console_proxy_ws_test.go
Removes unit tests for checkOrigin helper and adds rejectingOpener stub for test isolation. New ServeHTTP Origin enforcement suite validates upgrade-layer behavior: cookie-auth with allowed Origin passes pre-check (non-403), cookie-auth with disallowed Origin rejected during upgrade via httptest + websocket.Dial (403 error), bearer-auth with empty Origin bypasses cookie guard (non-403).
Middleware: 101 status-based upgrade detection
internal/servers/console_http_middleware.go, internal/servers/console_http_middleware_test.go
consoleMetricsMiddleware.ServeHTTP detects WebSocket upgrade success via HTTP 101 StatusSwitchingProtocols from statusWriter, replacing header-written heuristic [more robust against hijacking edge cases and aligns with HTTP spec]. Tests explicitly write 101 on upgrade paths and use real websocket.Accept/Dial for end-to-end verification.
Command bootstrap: flag registration and component wiring
internal/cmd/service/start/consoleproxy/start_console_proxy_cmd.go, internal/cmd/service/start/grpcserver/start_grpc_server_cmd.go
Console proxy command registers and reads ping, session timeout, and gRPC keepalive flags; wires backend ping via SetPingConfig, session timeout via SetSessionTimeout, client ping to WebSocket handler, and gRPC keepalive to server. gRPC server command reads keepalive config and applies to grpc.KeepaliveParams and enforcement policy.
Network test cleanup
internal/network/listener_test.go
Removes golang.org/x/net/http2 import and replaces http2.Transport with http.Transport{ForceAttemptHTTP2: true}, reducing external dependencies.

Sequence Diagram(s)

sequenceDiagram
  rect rgba(173, 216, 230, 0.5)
    Note over Client,ConsoleProxyCore: WebSocket Upgrade & Relay (Origin-based gating)
    Client->>ConsoleProxyWSHandler: HTTP Upgrade (Origin, Authorization)
    ConsoleProxyWSHandler->>ConsoleProxyWSHandler: cookie auth + empty Origin → reject 403
    ConsoleProxyWSHandler->>websocket.Accept: Accept(w, r, {OriginPatterns, BinarySubprotocol, PingPongHooks})
    alt Accept succeeds (101)
      websocket.Accept-->>ConsoleProxyWSHandler: *websocket.Conn
      ConsoleProxyWSHandler->>StartPing: StartPing(ctx, conn, logger, pingConfig)
      ConsoleProxyWSHandler->>websocket.NetConn: wrap as io.ReadWriteCloser
      ConsoleProxyWSHandler->>ConsoleProxyCore: Relay(ctx, NetConn, backend)
      ConsoleProxyCore->>ConsoleProxyCore: io.CopyBuffer(64KB) bidirectional
    else Accept fails
      websocket.Accept-->>ConsoleProxyWSHandler: error (403)
      ConsoleProxyWSHandler->>backend: backend.Close()
    end
  end
  rect rgba(144, 238, 144, 0.5)
    Note over kubeVirtBackend,KubeVirt: Backend Dial (TLS 1.3 + Bearer auth)
    kubeVirtBackend->>websocket.Dial: Dial(ctx, url, DialOptions{TLS 1.3, Bearer, Subprotocols})
    websocket.Dial-->>kubeVirtBackend: *websocket.Conn
    kubeVirtBackend->>StartPing: StartPing(ctx, conn, logger, pingConfig)
    kubeVirtBackend->>websocket.NetConn: NetConn(ctx, conn, BinaryMessage)
    websocket.NetConn-->>kubeVirtBackend: io.ReadWriteCloser
  end
  rect rgba(255, 218, 185, 0.5)
    Note over websocket.Conn,Relay: Keep-alive ping loop
    loop every PingInterval
      StartPing->>websocket.Conn: conn.Ping(pongTimeout)
      websocket.Conn-->>StartPing: pong or timeout
    end
    alt Ping fails & ctx active
      StartPing->>websocket.Conn: CloseNow()
    end
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Suggested labels

security, websocket, dependencies, console-proxy, timeout-hardening

Suggested reviewers

  • tzvatot
  • jhernand
  • adriengentil
  • tzumainn

Poem

🔐 The old websocket crumbled—x/net deprecated,
In rides coder/websocket, security validated.
Ping loops stand vigil every interval,
Origins delegated, attackers—all fall.
io.CopyBuffer at 64KB stride,
1800 seconds of liveness—no silent goodbye. 🛡️


🔕 Pre-merge checks override applied

The pre-merge checks have been overridden successfully. You can now proceed with the merge.

Overridden by @sk-ilya via checkbox on 2026-06-15T15:35:09.620Z.

❌ Failed checks (1 error)

Check name Status Explanation Resolution
No-Sensitive-Data-In-Logs ❌ Error [IGNORED] Code logs ping/pong payload bytes as strings in internal/console/ping.go lines 87,98, exposing peer-controlled data that may contain sensitive information or control characters. Replace slog.String("payload", string(payload)) with slog.Int("payload_len", len(payload)) in PingReceivedHandler and PongReceivedHandler to log only payload length.
✅ Passed checks (10 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and accurately summarizes the main change: migrating websocket transport to a hardened library and includes the ticket reference.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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.
No-Hardcoded-Secrets ✅ Passed No hardcoded secrets found. Authorization tokens come from request headers/encrypted tickets, not hardcoded. Test tokens clearly marked as test data.
No-Weak-Crypto ✅ Passed No MD5, SHA1, DES, RC4, 3DES, Blowfish, ECB mode, custom crypto implementations, or non-constant-time secret comparisons detected in PR changes.
No-Injection-Vectors ✅ Passed No injection vectors detected. Authorization header construction uses JWE-decrypted token (server-signed data), not user input. No SQL concatenation, shell execution, eval/exec, unsafe deserializat...
Container-Privileges ✅ Passed No privileged container, host namespace sharing, or privilege escalation settings detected in PR. Modified manifests add timeout annotations only, and existing container deployments enforce hardene...
Ai-Attribution ✅ Passed AI tool (Claude) properly attributed via Assisted-by trailer in standard format: "Assisted-by: Claude Code noreply@anthropic.com". No problematic Co-Authored-By usage found.
✨ 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 and usage tips.

@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: 2

🤖 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 `@internal/cmd/cli/console/serial/serial_cmd.go`:
- Around line 156-164: The code at line 156 ignores the error returned by
stdinReader.Close() in the defer statement, and at line 163 ignores the return
value from stdinReader.Cancel(), which can cause shutdown hangs if cancellation
fails. Update the defer statement to capture and log any error from
stdinReader.Close(), and update the InterruptRead function to capture and check
the return value from stdinReader.Cancel(), ensuring any failures are properly
handled rather than silently ignored. This prevents indefinite blocking of the
cleanup goroutine in connect.Proxy when cancellation fails on platform-specific
edge cases.

In `@internal/console/mock_ws_server_test.go`:
- Line 115: The WebSocket test code is ignoring error returns which can hide
handshake and write failures, weakening test diagnostics. In
internal/console/mock_ws_server_test.go at lines 115-115, check the error
returned from conn.Write() when writing the banner and return immediately if an
error occurs to prevent silent failures. In
internal/servers/console_proxy_ws_test.go at lines 124-126, capture the error
returned from websocket.Accept() and explicitly verify that it fails as
expected—if Accept unexpectedly succeeds, close the connection and fail the test
with a clear assertion to catch this regression. Following the coding guideline
that error returns must never be ignored, these changes will improve test
failure fidelity and prevent regressions from slipping through.
🪄 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: Repository: osac-project/coderabbit/.coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: b099fa11-72a1-4090-9cfe-36589fc5904a

📥 Commits

Reviewing files that changed from the base of the PR and between c385789 and 9e1b654.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (13)
  • charts/service/templates/ingress-proxy/_config.yaml.tpl
  • go.mod
  • internal/cmd/cli/console/serial/serial_cmd.go
  • internal/console/kubevirt_backend.go
  • internal/console/kubevirt_backend_integration_test.go
  • internal/console/mock_ws_server_test.go
  • internal/console/ping.go
  • internal/network/listener_test.go
  • internal/servers/console_http_middleware.go
  • internal/servers/console_http_middleware_test.go
  • internal/servers/console_proxy_core.go
  • internal/servers/console_proxy_ws.go
  • internal/servers/console_proxy_ws_test.go

Comment thread internal/cmd/cli/console/serial/serial_cmd.go Outdated
Comment thread internal/console/mock_ws_server_test.go Outdated
@sk-ilya sk-ilya force-pushed the websocket branch 2 times, most recently from bef42a6 to b9ad9fa Compare June 14, 2026 15:58
@sk-ilya

sk-ilya commented Jun 15, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 15, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@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: 3

♻️ Duplicate comments (1)
internal/cmd/cli/console/serial/serial_cmd.go (1)

166-170: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Cancel() failure path can still hang proxy shutdown.

At Line 167, a failed stdinReader.Cancel() only logs a warning and returns a no-op restore function. Since connect.Proxy calls InterruptRead() and then waits on wg.Wait(), a non-interrupted blocking read can deadlock disconnect/cleanup.

Suggested fix
 		InterruptRead: func() func() {
-			if !stdinReader.Cancel() {
-				c.logger.WarnContext(ctx, "Failed to cancel stdin reader, proxy cleanup may block")
-			}
-			return func() {}
+			if stdinReader.Cancel() {
+				return func() {}
+			}
+
+			c.logger.WarnContext(ctx, "Failed to cancel stdin reader, forcing stdin read deadline to unblock")
+			if err := os.Stdin.SetReadDeadline(time.Now()); err != nil {
+				c.logger.WarnContext(ctx, "Failed to force-unblock stdin read", "error", err)
+				return func() {}
+			}
+			return func() {
+				if err := os.Stdin.SetReadDeadline(time.Time{}); err != nil {
+					c.logger.DebugContext(ctx, "Failed to clear stdin read deadline", "error", 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 `@internal/cmd/cli/console/serial/serial_cmd.go` around lines 166 - 170, The
InterruptRead function returns a no-op restore function when
stdinReader.Cancel() fails, which means any blocking read on stdin will remain
uninterrupted. This causes the proxy shutdown to hang when waiting on wg.Wait()
after calling InterruptRead(). Instead of returning an empty function when
Cancel() fails, the returned function should implement an alternative mechanism
to forcefully interrupt the blocking read operation, such as directly closing
the stdin reader or using a separate interrupt signal that the blocking read
respects, ensuring the read is unblocked regardless of whether the initial
Cancel() call succeeded.
🤖 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 `@internal/console/kubevirt_backend_integration_test.go`:
- Around line 181-210: The conn.Write call in the HTTP handler function within
newMockWSServerWithAuthCapture is ignoring the error it returns, which reduces
test diagnostic capability. Capture the error returned by the conn.Write
operation and handle it appropriately by either logging it to a test logger or
returning the error so the test can detect unexpected connection failures.

In `@internal/servers/console_proxy_ws_test.go`:
- Around line 106-113: The test uses a broad defer recover() block around the
handler.ServeHTTP call to swallow any panic, then only asserts the response code
is not 403. This can mask unrelated regressions in security-sensitive code.
Instead of using recover() to suppress arbitrary panics, refactor the test to
use controlled stubs (such as providing a mock Opener) to prevent the panic from
occurring in the first place, then assert a specific expected outcome (such as a
particular response code or behavior) rather than just checking it is not
forbidden. Apply this same fix pattern at all locations where this generic panic
recovery exists in the test.
- Around line 124-126: The websocket.Accept call in the test handler is ignoring
its error return value using blank identifiers (_). Replace the `_, _ =
websocket.Accept(...)` pattern with explicit error handling that captures the
error returned by websocket.Accept and checks whether it is nil. If an error
occurs, fail the test using an appropriate test assertion method (such as
t.Fatalf) to ensure unexpected upgrade behavior is caught and the security-path
test remains trustworthy.

---

Duplicate comments:
In `@internal/cmd/cli/console/serial/serial_cmd.go`:
- Around line 166-170: The InterruptRead function returns a no-op restore
function when stdinReader.Cancel() fails, which means any blocking read on stdin
will remain uninterrupted. This causes the proxy shutdown to hang when waiting
on wg.Wait() after calling InterruptRead(). Instead of returning an empty
function when Cancel() fails, the returned function should implement an
alternative mechanism to forcefully interrupt the blocking read operation, such
as directly closing the stdin reader or using a separate interrupt signal that
the blocking read respects, ensuring the read is unblocked regardless of whether
the initial Cancel() call succeeded.
🪄 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: Repository: osac-project/coderabbit/.coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 15fe442b-adbe-4f95-91fd-8adf0eaf801c

📥 Commits

Reviewing files that changed from the base of the PR and between 9e1b654 and b9ad9fa.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (13)
  • charts/service/templates/ingress-proxy/_config.yaml.tpl
  • go.mod
  • internal/cmd/cli/console/serial/serial_cmd.go
  • internal/console/kubevirt_backend.go
  • internal/console/kubevirt_backend_integration_test.go
  • internal/console/mock_ws_server_test.go
  • internal/console/ping.go
  • internal/network/listener_test.go
  • internal/servers/console_http_middleware.go
  • internal/servers/console_http_middleware_test.go
  • internal/servers/console_proxy_core.go
  • internal/servers/console_proxy_ws.go
  • internal/servers/console_proxy_ws_test.go

Comment on lines +181 to +210
// newMockWSServerWithAuthCapture creates a mock WS server that captures the
// Authorization header from the HTTP upgrade request into a channel.
func newMockWSServerWithAuthCapture(authCh chan<- string) (*mockWSServer, error) {
listener, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
return nil, fmt.Errorf("failed to listen: %w", err)
}

m := &mockWSServer{listener: listener}

mux := http.NewServeMux()
mux.HandleFunc("/apis/console.osac.openshift.io/v1alpha1/namespaces/", func(w http.ResponseWriter, r *http.Request) {
authCh <- r.Header.Get("Authorization")
conn, err := websocket.Accept(w, r, &websocket.AcceptOptions{
InsecureSkipVerify: true,
})
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
conn.SetReadLimit(-1)
conn.Write(r.Context(), websocket.MessageBinary, []byte("authenticated\r\n"))
conn.Close(websocket.StatusNormalClosure, "")
})

m.server = &http.Server{Handler: mux}
go m.server.Serve(listener)

return m, nil
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial | 💤 Low value

Low-severity: Test helper ignores conn.Write error.

In test code, ignoring the write error at line 202 reduces diagnostic fidelity if the connection unexpectedly fails. The risk is low since this is test infrastructure only.

🔧 Optional improvement
-		conn.Write(r.Context(), websocket.MessageBinary, []byte("authenticated\r\n"))
+		if err := conn.Write(r.Context(), websocket.MessageBinary, []byte("authenticated\r\n")); err != nil {
+			return
+		}
🤖 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 `@internal/console/kubevirt_backend_integration_test.go` around lines 181 -
210, The conn.Write call in the HTTP handler function within
newMockWSServerWithAuthCapture is ignoring the error it returns, which reduces
test diagnostic capability. Capture the error returned by the conn.Write
operation and handle it appropriately by either logging it to a test logger or
returning the error so the test can detect unexpected connection failures.

Comment thread internal/servers/console_proxy_ws_test.go Outdated
Comment on lines +124 to +126
_, _ = websocket.Accept(w, r, &websocket.AcceptOptions{
OriginPatterns: []string{"https://good.com"},
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Handle websocket.Accept errors explicitly in the test handler.

At Line 124, _, _ = websocket.Accept(...) drops the returned error, which can mask unexpected upgrade behavior and make this security-path test less trustworthy.

As per coding guidelines, **/*.go: “Never ignore error returns.”

Suggested fix
-			_, _ = websocket.Accept(w, r, &websocket.AcceptOptions{
+			_, err := websocket.Accept(w, r, &websocket.AcceptOptions{
 				OriginPatterns: []string{"https://good.com"},
 			})
+			if err != nil {
+				return
+			}
🤖 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 `@internal/servers/console_proxy_ws_test.go` around lines 124 - 126, The
websocket.Accept call in the test handler is ignoring its error return value
using blank identifiers (_). Replace the `_, _ = websocket.Accept(...)` pattern
with explicit error handling that captures the error returned by
websocket.Accept and checks whether it is nil. If an error occurs, fail the test
using an appropriate test assertion method (such as t.Fatalf) to ensure
unexpected upgrade behavior is caught and the security-path test remains
trustworthy.

Source: Coding guidelines

@zszabo-rh zszabo-rh 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.

Couple of nice-to-have comments, but otherwise the library swap is a clear improvement and the code is even simpler than before. LGTM!

Comment thread internal/console/ping.go Outdated

// Ping sends periodic WebSocket pings to detect dead connections. It blocks
// until the context is cancelled or a ping fails, returning the cause.
func Ping(ctx context.Context, conn *websocket.Conn) error {

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.

Any chance we could add a few tests for the new ping.go? Things like verifying the timeout fires when pongs stop arriving and that cancelling the context shuts things down cleanly. It's straightforward code, but it's also the piece that decides when to tear down connections. Would be nice to have that covered.

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.

Done

Comment thread internal/console/ping.go Outdated
const (
pingInterval = 30 * time.Second
pongTimeout = 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.

Would it make sense to make these configurable? With many concurrent console sessions, a 30s ping interval could add up. Even just wiring them through the existing console proxy config would give operators a knob to tune if needed.

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.

Done

@openshift-ci

openshift-ci Bot commented Jun 15, 2026

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: sk-ilya, zszabo-rh
Once this PR has been reviewed and has the lgtm label, please assign ygalblum 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

@openshift-ci

openshift-ci Bot commented Jun 15, 2026

Copy link
Copy Markdown

New changes are detected. LGTM label has been removed.

@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: 3

♻️ Duplicate comments (1)
internal/console/kubevirt_backend_integration_test.go (1)

86-90: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Minor reliability risk: ignored WebSocket write/close errors can hide integration-test regressions.

The handlers drop Write/Close errors, so auth/close-path failures can be masked instead of surfacing as test failures.

🔧 Suggested fix
 closeServer, err := newMockWSServerWithHandler(func(ctx context.Context, conn *websocket.Conn) {
-	conn.Write(ctx, websocket.MessageBinary, []byte("goodbye\r\n"))
+	if err := conn.Write(ctx, websocket.MessageBinary, []byte("goodbye\r\n")); err != nil {
+		return
+	}
 	// Close the WebSocket connection immediately after banner.
-	conn.Close(websocket.StatusNormalClosure, "")
+	if err := conn.Close(websocket.StatusNormalClosure, ""); err != nil {
+		return
+	}
 })
@@
 	mux.HandleFunc("/apis/console.osac.openshift.io/v1alpha1/namespaces/", func(w http.ResponseWriter, r *http.Request) {
 		authCh <- r.Header.Get("Authorization")
@@
-		conn.Write(r.Context(), websocket.MessageBinary, []byte("authenticated\r\n"))
-		conn.Close(websocket.StatusNormalClosure, "")
+		if err := conn.Write(r.Context(), websocket.MessageBinary, []byte("authenticated\r\n")); err != nil {
+			return
+		}
+		if err := conn.Close(websocket.StatusNormalClosure, ""); err != nil {
+			return
+		}
 	})

As per coding guidelines, **/*.go: never ignore error returns.

Also applies to: 194-204

🤖 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 `@internal/console/kubevirt_backend_integration_test.go` around lines 86 - 90,
The WebSocket message handler in newMockWSServerWithHandler ignores error
returns from conn.Write() and conn.Close() calls, which can mask test failures
when authentication or connection closure fails. Capture the error returns from
both the conn.Write() call (which sends the banner message) and the conn.Close()
call, and handle them appropriately (e.g., by logging or failing the test).
Apply this same error-checking pattern to the other handler location at lines
194-204 in the same file where similar WebSocket Write/Close operations occur
without error checking.

Source: Coding guidelines

🤖 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 `@internal/console/kubevirt_backend.go`:
- Around line 67-75: The Build() method in KubeVirtBackendBuilder does not
validate the PingConfig field before creating the backend instance, allowing
invalid PingInterval values (such as negative durations) to propagate to the
Ping() method where they cause a panic. Add validation logic in Build() similar
to what exists in PingConfigFromFlags to check if b.pingConfig has a negative
PingInterval, and return an appropriate error if validation fails, ensuring the
validation happens at the API boundary before the kubeVirtBackend is
instantiated.

In `@internal/console/ping.go`:
- Around line 84-100: The PingReceivedHandler and PongReceivedHandler functions
are logging the full peer-controlled payload bytes, which poses a security risk
as untrusted data could contain sensitive information or control characters.
Replace the `slog.String("payload", string(payload))` calls in both functions
with a safer alternative such as logging only the payload length using
`slog.Int()` with an appropriate key name, or use a key prefixed with `!` to
indicate redaction as per the repository's coding guidelines. This ensures
control-frame payloads are not exposed in logs while still providing useful
debugging information.

In `@internal/network/listener_test.go`:
- Around line 424-426: The tls.Config struct in TLSClientConfig is missing the
MinVersion field, which allows insecure TLS protocol downgrades. Add MinVersion:
tls.VersionTLS13 to the tls.Config initialization to enforce TLS 1.3 as the
minimum protocol version. This ensures the test code models secure TLS
configuration practices and prevents negotiation of deprecated protocol versions
with known vulnerabilities.

---

Duplicate comments:
In `@internal/console/kubevirt_backend_integration_test.go`:
- Around line 86-90: The WebSocket message handler in newMockWSServerWithHandler
ignores error returns from conn.Write() and conn.Close() calls, which can mask
test failures when authentication or connection closure fails. Capture the error
returns from both the conn.Write() call (which sends the banner message) and the
conn.Close() call, and handle them appropriately (e.g., by logging or failing
the test). Apply this same error-checking pattern to the other handler location
at lines 194-204 in the same file where similar WebSocket Write/Close operations
occur without error checking.
🪄 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: Repository: osac-project/coderabbit/.coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: e669bee2-9307-47af-9d25-28d596873b28

📥 Commits

Reviewing files that changed from the base of the PR and between b9ad9fa and 02e6547.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (21)
  • charts/service/templates/ingress-proxy/_config.yaml.tpl
  • go.mod
  • internal/cmd/cli/console/serial/serial_cmd.go
  • internal/cmd/service/start/consoleproxy/start_console_proxy_cmd.go
  • internal/cmd/service/start/grpcserver/start_grpc_server_cmd.go
  • internal/console/kubevirt_backend.go
  • internal/console/kubevirt_backend_integration_test.go
  • internal/console/manager.go
  • internal/console/mock_ws_server_test.go
  • internal/console/ping.go
  • internal/console/ping_flags.go
  • internal/console/ping_test.go
  • internal/console/session_flags.go
  • internal/network/grpc_keepalive_flags.go
  • internal/network/listener_test.go
  • internal/servers/console_http_middleware.go
  • internal/servers/console_http_middleware_test.go
  • internal/servers/console_proxy_core.go
  • internal/servers/console_proxy_ws.go
  • internal/servers/console_proxy_ws_test.go
  • manifests/overlays/openshift/route.yaml

Comment thread internal/console/kubevirt_backend.go
Comment thread internal/console/ping.go
Comment thread internal/network/listener_test.go
Assisted-by: Claude Code <noreply@anthropic.com>
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.

3 participants