Skip to content

feat(pod-shell): add authenticated v2 pod exec bridge - #245

Draft
vigneshrajsb wants to merge 4 commits into
mainfrom
feat/pod-shell
Draft

vigneshrajsb wants to merge 4 commits into
mainfrom
feat/pod-shell

Conversation

@vigneshrajsb

@vigneshrajsb vigneshrajsb commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

Engineers can open an interactive shell in a selected running Environment container through a new V2 WebSocket exec endpoint. The browser supplies its existing session JWT in the first frame; core verifies the identity and binds access to the Environment, pod UID, container and restart count.

Administrators edit Settings → Features as a plain map of stored booleans, using each database key unchanged as its toggle label and the existing Save/Discard controls. GET /api/v2/config/features returns that map in data.features; the OpenAPI contract is Record<string, boolean>. It has no metadata registry, fixed key list, or effective-state payload. New stored boolean keys appear automatically. The UI reads the raw podShell value together with its deployment flag, while core independently enforces its deployment ceiling and saved setting.

Authenticated GET exposes only stored boolean values. Admin-only PUT accepts nonempty partial boolean updates, validates that every key already exists as a boolean inside the same transaction, and preserves all other stored values, including future non-boolean data. Save awaits a forced Redis/configuration refresh in the saving process and refreshes the UI query. The accepted 30-second per-process cache TTL remains; UI and active-shell checks run every 10 seconds, so propagation can take about 40 seconds. A failed refresh reports an unconfirmed save even if the database write committed; reload or an idempotent retry reconciles it.

Rollout: run core migrations before deploying the matching UI. Migration 035_add_pod_shell_feature seeds podShell: false when the row or flag is absent and preserves existing values. Its rollback preserves administrator configuration. Deployment flags remain required and podShell remains off when absent.

Current candidates: core 1d0b281b39d894222ba6d6208b3785b1bc48942c and UI 43e857ea65e2752f9812dd2e260fbcfcb6b2cc2a. Validation for this map-based design: 95 focused core tests across 11 suites and all 976 UI tests passed, along with custom-server TypeScript, UI typecheck/full lint, scoped core lint, scoped formatting and commitlint. Migration SQL was executed against a temporary local PostgreSQL table with the actual json column type: missing row/flag seeded false, existing true and other keys were preserved, and the validation transaction was rolled back. The live browser displayed webhooks, reconcileDeletedServices and podShell exactly; Discard, save-off and save-on passed, and podShell was left true locally. Full core coverage and the broad shell E2E matrix below belong to earlier candidates and were not rerun in full for this simplification.

  • Add the Kubernetes exec bridge with origin checks, target revalidation, expiry and idle cleanup, bounded buffers and connection limits, and metadata-only audit events.
  • Include pod UID in pod responses and document configuration, target policy, known limits and rollout in docs/features/pod-shell.md.
  • Fix the CommonJS startup cycle exposed by live execution by importing Build through the models barrel. Add a real child-process import regression without Jest model mocks.
  • Keep exec disabled by default. Deploy core before the matching UI and configure exact UI origins and existing Keycloak trust before enabling the feature.

Local E2E used core eaf0751714755ae6bb8caa4f8ac14577dca4c5be with UI f05f4776c5ea3f14d50aa6bdfcfacf008c8737f7, an actual Keycloak fixture login, and a disposable kind cluster. Example draft PR #99 deployed Environment twilight-waterfall-840111. Completed local checks:

  • Both service Pods and Environment pod-table shell entry points worked. Deployment app, sidecar and restartable init containers, StatefulSet, Job and CronJob targets ran commands and returned exit status. A running init container connected; waiting/completed containers disabled the shell action; an image without the selected shell showed an error.
  • UTF-8 paste/output worked. Logs/Shell switching preserved the shell PID. JWT expiry closed the session and explicit reconnect worked. Pod replacement closed the dialog; row focus stayed tied to the correct pod across insertion/removal. Remote stty confirmed terminal resizing.
  • Six negative checks against actual WebSocket connections passed. Disabling the local feature rejected new connections with HTTP 503 and closed the current connection while logs remained readable. After restoring the feature, a fresh frontend shell returned feature-restored-ok, its hostname and exit status 0.
  • Completed frontend Build logs were readable; archive-storage provenance was not verified.

Earlier automated checks at core eaf0751714755ae6bb8caa4f8ac14577dca4c5be on Node 22.22.0: CI-config pnpm test:coverage passed with 490 suites and 9,260 tests (11 skipped), plus 55 gateway tests. The focused run passed 112 tests. Jest coverage thresholds passed: 98.19% statements, 91.82% branches, 98.04% functions and 98.57% lines. Changed-file Prettier/ESLint, custom-server TypeScript and commitlint passed; the matching UI passed 976 tests, full lint and typecheck. The full strict core typecheck has the same 863-line failure output as clean origin/main; CI already disables that check. Commits used the authorized HUSKY=0 override after explicit scoped checks to avoid repository-wide autofixes; tracked hooks are unchanged.

This PR remains draft. Local E2E is complete within the evidence above, with these explicit acceptance limits:

  • Physical Ctrl-C still needs a manual check: pasted ETX worked, but the automation shortcut was intercepted.
  • A subsequent UI refinement candidate verified the 390×844 viewport; see the companion PR for its candidate-bound evidence.
  • Production ingress and pilot acceptance have not been performed.

Three independent requirement-first reviews were completed and their findings resolved. Existing log transport is unchanged. L1 authenticated V2 logs remain a deferred follow-up, not a shell acceptance gate. These results do not claim that all staged acceptance checks passed.

Matching UI draft PR: https://github.com/GoodRxOSS/lifecycle-ui/pull/84. Latest UI counterpart: 43e857ea65e2752f9812dd2e260fbcfcb6b2cc2a; see that PR for the subsequent console-refinement validation, including the verified narrow viewport. The matrix above records the preceding UI candidate.

Open target-bound shells with the current session JWT in the first WebSocket
frame. Validate Environment, pod identity and container state, enforce connection
limits, and close sessions on expiry, target changes or inactivity.

Expose pod UIDs, document policy and rollout, and keep exec disabled by default.
Import Build through the models barrel to avoid the CommonJS initialization
cycle exposed by the real custom-server import order. Add a child-process
regression that loads the actual modules without Jest model mocks.
Expose registered global feature flags with admin-only transactional updates and
an awaited configuration refresh. Require the saved podShell flag in addition to
deployment support when authorizing and revalidating shell sessions.

Document defaults, cache propagation and partial updates, and cover the API,
configuration and active-session gates with focused regressions.
Return the stored boolean map and validate partial updates against existing
boolean keys in the same transaction, preserving other stored values.

Replace fixed metadata schemas with a generic map and seed podShell as false
through migration 035 without overwriting existing configuration.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant