Skip to content

State Tool: forward platform JWT + version User-Agent on the build-log-streamer WebSocket#3809

Merged
antoine-activestate merged 1 commit into
masterfrom
antoines/ENG-1372
Jun 10, 2026
Merged

State Tool: forward platform JWT + version User-Agent on the build-log-streamer WebSocket#3809
antoine-activestate merged 1 commit into
masterfrom
antoines/ENG-1372

Conversation

@antoine-activestate

@antoine-activestate antoine-activestate commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

Summary

Makes the State Tool identify itself on the build-log-streamer WebSocket Upgrade so the server can authorize the stream and monitor connecting client versions.

  1. Platform JWT via Sec-WebSocket-Protocol. When authenticated, the client offers bearer.<jwt> alongside the real subprotocol build-log-streamer.activestate.com.v1 (which the server echoes back; its allow-list contains only the real one, so the token never appears in the upgrade response). The browser WebSocket API can't set custom request headers, so the dashboard carries the JWT the same way — both clients stay symmetric.
  2. Versioned User-Agent on the Upgrade (constants.UserAgent) so the server can see which State Tool versions are connecting.

Design note

The token is threaded as a plain string (prime.Auth().BearerToken() → runtime option → Connect), so pkg/runtime gains no dependency on the authentication package. Empty token = anonymous (unchanged behavior).

Test plan

  • go build + gofmt clean across touched packages.
  • Real-handshake test against an httptest WebSocket server asserting the offered subprotocols, the negotiated subprotocol (token never echoed back), and the User-Agent the server receives. go test -race clean.

Note

Full server-side JWT validation ships separately (infra repo); this client change degrades gracefully against the current server — an unknown subprotocol is ignored and anonymous still works.

Supersedes #3808 (closed when its branch was shortened to fix a macOS/Windows integration-test socket-path overflow).

Copilot AI 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.

Pull request overview

This PR updates the State Tool’s build-log-streamer WebSocket connection to forward identifying/auth data during the HTTP Upgrade so the server can authorize streams and monitor client versions.

Changes:

  • Thread a platform JWT through runtime setup into build-log streaming, and offer it via Sec-WebSocket-Protocol as bearer.<jwt> alongside the real subprotocol.
  • Send the standard, versioned User-Agent on the WebSocket Upgrade request.
  • Add a new handshake-focused test that validates the server receives the expected Sec-WebSocket-Protocol and User-Agent headers.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
pkg/runtime/setup.go Adds Opts.AuthToken and passes it into build-log streaming initialization.
pkg/runtime/options.go Introduces WithAuthToken runtime option for forwarding the platform JWT.
pkg/runtime/internal/buildlog/buildlog.go Stores the auth token on BuildLog and forwards it into the WS connect call.
pkg/platform/api/buildlogstream/streamer.go Updates Connect to set subprotocols (including optional bearer.<jwt>) and send the versioned User-Agent.
pkg/platform/api/buildlogstream/streamer_test.go Adds tests performing a real WS handshake and asserting request headers received by the server.
pkg/platform/api/api.go Extracts package-level UserAgent() for reuse by the WS dialer.
internal/runbits/runtime/runtime.go Wires prime.Auth().BearerToken() into runtime options via WithAuthToken.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread pkg/platform/api/buildlogstream/streamer_test.go
Comment thread pkg/platform/api/buildlogstream/streamer_test.go Outdated
Comment thread pkg/platform/api/buildlogstream/streamer_test.go Outdated

Copilot AI 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.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 5 comments.

Comment thread pkg/platform/api/buildlogstream/streamer_test.go
Comment thread pkg/platform/api/buildlogstream/streamer_test.go Outdated
Comment thread pkg/platform/api/buildlogstream/streamer_test.go
Comment thread pkg/platform/api/buildlogstream/streamer_test.go
Comment thread pkg/platform/api/buildlogstream/streamer_test.go

Copilot AI 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.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated no new comments.

@antoine-activestate antoine-activestate marked this pull request as ready for review June 1, 2026 19:42
@mitchell-as mitchell-as self-requested a review June 1, 2026 19:43
Comment thread pkg/platform/api/buildlogstream/streamer.go Outdated
Comment thread pkg/platform/api/buildlogstream/streamer.go Outdated
Comment thread pkg/platform/api/api.go Outdated
Comment thread pkg/runtime/options.go Outdated
… WebSocket

The build-log-streamer WebSocket previously opened with only an Origin header, so the server had no way to authorize the stream. When the caller is authenticated, offer the platform JWT via Sec-WebSocket-Protocol as 'bearer.<jwt>' alongside the real 'build-log-streamer.activestate.com.v1' subprotocol the server echoes back (its allow-list contains only the real subprotocol, so the token never appears in the upgrade response). The browser WebSocket API can't set custom request headers, so the dashboard carries the JWT the same way -- both clients stay symmetric.

Also send the versioned State Tool User-Agent on the Upgrade so the server can see which client versions are connecting.

The token is threaded as a plain string from the runtime options into Connect, so pkg/runtime gains no authentication dependency; an empty token means anonymous (unchanged behavior). Includes a real-handshake test that asserts the offered subprotocols, the negotiated subprotocol, and the User-Agent the server receives.
@mitchell-as

mitchell-as commented Jun 1, 2026

Copy link
Copy Markdown
Collaborator

I enabled the "critical" integration tests. We don't care so much about the ARM tests because those are expected to fail (we don't have any ARM Platform projects). However, I am seeing some concerning failures:

State Tool for the most part runs unauthenticated. This is by design.

@antoine-activestate antoine-activestate changed the title ENG-1372: State Tool forwards platform JWT (+ versioned User-Agent) on build-log-streamer WS State Tool: forward platform JWT + version User-Agent on the build-log-streamer WebSocket Jun 1, 2026
@antoine-activestate

Copy link
Copy Markdown
Contributor Author

Dug into the three critical-suite failures — they're pre-existing, not introduced by this PR.

The scheduled (nightly) integration suite on master has been red for ≥16 consecutive nights (back through at least 2026-05-17). The most recent nightly — Jun 1 01:04 UTC, on unchanged master, before this PR's latest push — fails on the identical TestInstallIntegrationTestSuite/TestInstall with Build expr for the commit not found: Error retrieving commit … for project ActiveState-CLI/small-python, authentication may be required.

That's a buildplanner dependency-resolution failure (state install → stage commit), which runs upstream of the build-log streaming this PR touches. My only addition to that path is prime.Auth().BearerToken() — a pure getter with no side effects — and the unauthenticated path is unchanged (empty token → anonymous, as today). So this PR doesn't introduce these.

I'm raising the busted nightly as its own issue so it gets a dedicated fix (it looks platform-side — build-expr retrieval for the small-python test commit). The ARM failures are expected per your note.

@antoine-activestate antoine-activestate marked this pull request as draft June 2, 2026 14:08
@antoine-activestate

Copy link
Copy Markdown
Contributor Author

Post-deploy follow-up on this matrix. The platform-side buildplanner fix that was producing Build expr for the commit not found since mid-May landed in prod earlier today. On a fresh re-run of this PR's matrix (run 26778768851):

job result Build expr… / Could not stage… count
ubuntu-latest ✅ success 0
ubuntu-24.04-arm ✅ success 0
macos-15-intel ✅ success 0
macos-latest ❌ failure 0
windows-2025 ❌ failure 0

Pre-deploy ubuntu-latest baseline (gh run rerun 26778768851 --failed from this morning) was 9 × Build expr for the commit not found + 12 × Could not stage commit. Post-deploy all three buildplanner-side tokens are at zero across the entire matrix.

The two remaining failures are different — both trip the post-test "Found error and/or panic in log file" harness check, but the underlying log lines are unrelated to the staged-commit flow:

  • macos-latestTestInstallSuggest (state install djang) — the suggestion lookup emits [ERR multilog.go:19] Failed to retrieve suggestions: 'Failed to resolve ingredient named: djang'. That [ERR] line then trips the harness scan even though the user-facing suggestion-list flow is the intended one.
  • windows-2025[CRT multilog.go:24] state-svc errored out: Service cannot be reached: ... ipc server down: flisten connection refused. Looks like the first command after stack startup races the state-svc IPC server. The test apparently survives, but the CRT-level log line alone trips the harness.

Both failure modes existed on every nightly for ~16 days but were masked behind the buildplanner failures. Filed separately on the platform side — pinging via Slack with the ticket reference.

Job logs:

@mitchell-as mitchell-as left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The test failures are sporadic and not worth digging into.

@antoine-activestate antoine-activestate marked this pull request as ready for review June 10, 2026 18:13
@antoine-activestate antoine-activestate merged commit f7e4a3a into master Jun 10, 2026
15 of 27 checks passed
@antoine-activestate antoine-activestate deleted the antoines/ENG-1372 branch June 10, 2026 18:14
@antoine-activestate

Copy link
Copy Markdown
Contributor Author

Local post-merge wiring verification on master @ f7e4a3a44. Ran a small in-process test that pulls a real platform JWT from disk via state export jwt, drives the merged plumbing (runtime.WithAuthToken(jwt)Opts.AuthTokenbuildlog.Newbuildlogstream.Connect) against a local httptest-style WebSocket server, and inspects what lands on the wire:

field value
Sec-WebSocket-Protocol offered bearer.<jwt>, build-log-streamer.activestate.com.v1
subprotocol negotiated (response) build-log-streamer.activestate.com.v1 — token never echoed
User-Agent received by server state/0.48.0-SHA…; master
Opts.AuthToken after WithAuthToken(jwt) populated, equal to the JWT

Closes the "does the option propagation actually deliver the JWT to the wire in the built binary" question, independent of the in-package unit test (which only proved the Connect function itself does the right thing given any token). Production code path is wired end-to-end.

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