Skip to content

Commit e22ca98

Browse files
committed
docs(cli): prefer control-plane port-forward transport
1 parent 57b7183 commit e22ca98

1 file changed

Lines changed: 83 additions & 16 deletions

File tree

docs/2026-04-06-spz-port-forward-architecture.md

Lines changed: 83 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
date: 2026-04-06
33
author: Onur Solmaz <onur@textcortex.com>
44
title: spz Port-Forward Architecture
5-
tags: [spritz, spz, cli, ssh, port-forwarding, architecture]
5+
tags: [spritz, spz, cli, ssh, websocket, https, port-forwarding, architecture]
66
---
77

88
## Overview
@@ -20,15 +20,22 @@ spz port-forward <instance> --local <port> --remote <port>
2020
This should be the canonical Spritz CLI shape for forwarding a local loopback
2121
port to a private port inside one Spritz instance.
2222

23-
It should be implemented as an instance-scoped control-plane feature, not as a
24-
Kubernetes workflow, not as a browser preview product, and not as a
25-
deployment-specific convenience alias.
23+
It should be implemented as an instance-scoped control-plane feature. The
24+
preferred public transport should be the authenticated Spritz control plane
25+
over HTTPS/WebSocket on port `443`, not raw public SSH on port `22`.
26+
27+
It should not be framed as a Kubernetes workflow, not as a browser preview
28+
product, and not as a deployment-specific convenience alias.
2629

2730
## TL;DR
2831

2932
- Spritz core should add `spz port-forward`.
30-
- The command should be explicit and transport-agnostic in meaning, while the
31-
first implementation should reuse the existing SSH credential minting path.
33+
- The command should be explicit and transport-agnostic in meaning.
34+
- The preferred public transport should be authenticated HTTPS/WebSocket over
35+
the existing Spritz control plane on port `443`.
36+
- The current SSH-backed implementation may remain as a fallback, but SSH
37+
should be treated as deprecated for default public use unless somebody
38+
explicitly asks for it.
3239
- The command should default to:
3340
- local bind host `127.0.0.1`
3441
- remote target host `127.0.0.1`
@@ -97,8 +104,12 @@ This should be the core primitive. It should mean:
97104
- forward to one private remote port inside one named instance
98105
- keep the tunnel alive until interrupted
99106

100-
The first implementation should be built on the existing SSH certificate mint
101-
flow already used by `spz ssh`.
107+
The public-facing design should prefer an authenticated control-plane tunnel
108+
over HTTPS/WebSocket on `443`.
109+
110+
The existing SSH certificate mint flow may still be used as an implementation
111+
fallback where raw TCP is available or explicitly desired, but Spritz should
112+
not require public raw SSH exposure as the default internet-facing transport.
102113

103114
The product contract, however, should be described as "instance port
104115
forwarding", not as "raw SSH with custom flags". That distinction matters:
@@ -107,6 +118,26 @@ forwarding", not as "raw SSH with custom flags". That distinction matters:
107118
- the control plane remains the owner of authorization and target resolution
108119
- future transports may change without renaming the user-facing intent
109120

121+
## Preferred Public Transport
122+
123+
The ideal Spritz system should not require a public inbound instance port at
124+
all.
125+
126+
For public usage, `spz port-forward` should terminate on the existing Spritz
127+
control plane host over HTTPS/WebSocket on `443`, and the control plane should
128+
perform the pod-scoped forwarding inside the cluster.
129+
130+
This is the preferred architecture because it:
131+
132+
- works in environments where raw public TCP may be restricted
133+
- keeps auth, policy, rate limiting, and audit under the control plane
134+
- avoids depending on cloud-specific behavior for arbitrary inbound TCP
135+
- gives one clean public access surface instead of separate web and SSH entry
136+
points
137+
138+
SSH may still exist as a transport, but it should not be the primary public
139+
story.
140+
110141
## Why `spz port-forward` Instead Of `spz preview`
111142

112143
`preview` is the wrong upstream primitive because it encodes application
@@ -266,21 +297,28 @@ Press Ctrl+C to stop.
266297

267298
## Relationship To `spz ssh`
268299

269-
`spz ssh` should remain the raw shell-access command.
300+
`spz ssh` should remain the raw shell-access command when explicitly needed.
270301

271302
`spz port-forward` should be a sibling command with a narrower purpose:
272303

273304
- `spz ssh`: interactive shell access
274305
- `spz port-forward`: local access to one private instance port
275306

276-
The implementation may share most of the credential plumbing.
307+
The implementation may still share credential plumbing, but the public default
308+
for `spz port-forward` should not be "SSH unless proven otherwise".
277309

278-
That is good:
310+
That split is still good:
279311

280312
- less duplicated auth logic
281-
- one consistent trust and host-verification path
282313
- one clear control-plane contract for instance access
283314

315+
For now:
316+
317+
- `spz ssh` remains available
318+
- SSH-backed `spz port-forward` may remain available
319+
- SSH should be considered deprecated as the default public transport unless a
320+
deployment or operator explicitly asks for it
321+
284322
## Downstream Wrappers
285323

286324
Spritz core should stop at the generic primitive.
@@ -297,6 +335,8 @@ contract.
297335
The holy grail shape is:
298336

299337
- Spritz core provides `spz port-forward`
338+
- Spritz routes public interactive access through one authenticated control
339+
plane on `443`
300340
- downstream deployments compose deployment-specific UX on top of it
301341

302342
That keeps Spritz portable while still enabling polished local workflows where
@@ -307,7 +347,7 @@ needed.
307347
### Phase 1: Core CLI Primitive
308348

309349
- add `spz port-forward`
310-
- reuse the existing SSH credential minting path
350+
- keep the user-facing contract transport-agnostic
311351
- support one local forward per command invocation
312352
- keep both local and remote hosts pinned to loopback
313353

@@ -318,19 +358,43 @@ Acceptance criteria:
318358
- the command requires no Kubernetes credentials
319359
- the command targets one named instance, not a pod
320360

321-
### Phase 2: CLI Help And Tests
361+
### Phase 2: Public Control-Plane Transport
362+
363+
- implement forwarding over the authenticated Spritz control plane on
364+
HTTPS/WebSocket
365+
- make that path the preferred public transport
366+
- avoid requiring any public raw TCP listener on the instance gateway
367+
368+
Acceptance criteria:
369+
370+
- the standard public path works over `443`
371+
- no public per-instance or per-feature raw TCP exposure is required
372+
- authorization remains owned by the Spritz control plane
373+
374+
### Phase 3: SSH Fallback
375+
376+
- keep the SSH-backed transport available for private networks, operators, or
377+
deployments that explicitly want it
378+
- document that SSH is a fallback transport, not the preferred public one
379+
380+
Acceptance criteria:
381+
382+
- SSH remains available when explicitly requested
383+
- SSH is no longer the default public transport assumption in docs or UX
384+
385+
### Phase 4: CLI Help And Tests
322386

323387
- document the new command in CLI help
324388
- add help tests for the new usage line
325-
- add command tests for printed SSH execution shape or equivalent command
389+
- add command tests for printed transport execution shape or equivalent command
326390
plumbing
327391

328392
Acceptance criteria:
329393

330394
- the new command is discoverable through `spz --help`
331395
- printed guidance stays generic and deployment-agnostic
332396

333-
### Phase 3: Downstream Composition
397+
### Phase 5: Downstream Composition
334398

335399
- allow downstreams to add wrappers without changing the core primitive
336400
- document that application auth remains outside Spritz forwarding
@@ -346,10 +410,13 @@ This architecture is successful when all of the following are true:
346410

347411
- the standard path for instance port access is `spz port-forward`, not raw
348412
`ssh -L`
413+
- the preferred public path runs through the authenticated Spritz control
414+
plane on `443`
349415
- the caller does not need Kubernetes credentials
350416
- the command works by instance identity rather than pod identity
351417
- the default bind scope is local loopback only
352418
- the application behind the forwarded port can keep its own auth model
419+
- SSH remains optional rather than mandatory for public use
353420
- downstream wrappers can exist without forcing Spritz core to become
354421
app-specific
355422

0 commit comments

Comments
 (0)