Skip to content

Commit 89ee073

Browse files
committed
fix(cli): support plaintext gateway registration
Signed-off-by: Drew Newberry <anewberry@nvidia.com>
1 parent dafb799 commit 89ee073

File tree

7 files changed

+247
-30
lines changed

7 files changed

+247
-30
lines changed

architecture/gateway-deploy-connect.md

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,11 @@ This document describes how the CLI resolves a gateway and communicates with it
1111
When any CLI command needs to talk to the gateway, it resolves the target through a priority chain (`crates/openshell-cli/src/main.rs` -- `resolve_gateway()`):
1212

1313
1. `--gateway-endpoint <URL>` flag (direct URL, reusing stored metadata when the gateway is known).
14-
2. `--cluster <NAME>` / `-g <NAME>` flag.
14+
2. `--gateway <NAME>` / `-g <NAME>` flag.
1515
3. `OPENSHELL_GATEWAY` environment variable.
1616
4. Active gateway from `~/.config/openshell/active_gateway`.
1717

18-
Resolution loads `ClusterMetadata` from disk to get the `gateway_endpoint` URL and `auth_mode`. When `--gateway-endpoint` is used, the CLI still tries to match the URL to stored metadata so edge auth tokens and TLS bundles continue to resolve by cluster name.
18+
Resolution loads `GatewayMetadata` from disk to get the `gateway_endpoint` URL and `auth_mode`. When `--gateway-endpoint` is used, the CLI still tries to match the URL to stored metadata so edge auth tokens and TLS bundles continue to resolve by gateway name.
1919

2020
### Connection modes
2121

@@ -36,7 +36,7 @@ graph TD
3636
3737
MODE -->|"null / mtls"| MTLS
3838
MODE -->|"cloudflare_jwt"| EDGE
39-
MODE -->|"plaintext endpoint"| PLAIN
39+
MODE -->|"plaintext"| PLAIN
4040
4141
MTLS -->|"TLS + client cert"| GW
4242
EDGE -->|"WSS tunnel + JWT"| GW
@@ -47,11 +47,11 @@ graph TD
4747

4848
**File**: `crates/openshell-cli/src/tls.rs` -- `build_channel()`
4949

50-
The default mode for self-deployed gateways. The CLI loads three PEM files from `~/.config/openshell/clusters/<name>/mtls/`:
50+
The default mode for self-deployed gateways. The CLI loads three PEM files from `~/.config/openshell/gateways/<name>/mtls/`:
5151

5252
| File | Purpose |
5353
| --------- | -------------------------------------------------------------- |
54-
| `ca.crt` | Cluster CA certificate -- verifies the gateway's server cert |
54+
| `ca.crt` | Gateway CA certificate -- verifies the gateway's server cert |
5555
| `tls.crt` | Client certificate -- proves the CLI's identity to the gateway |
5656
| `tls.key` | Client private key |
5757

@@ -81,24 +81,32 @@ For gateways behind an edge proxy (e.g., Cloudflare Access), the CLI routes traf
8181
3. The gateway's `ws_tunnel.rs` handler upgrades the WebSocket and bridges it to an in-memory `MultiplexService` instance.
8282
4. The gRPC channel connects to `http://127.0.0.1:<local_port>` (plaintext HTTP/2 over the tunnel).
8383

84-
Authentication uses a browser-based flow: `gateway add` opens the user's browser to the gateway's `/auth/connect` endpoint, which reads the `CF_Authorization` cookie and relays it back to a localhost callback server. The token is stored at `~/.config/openshell/clusters/<name>/edge_token`.
84+
Authentication uses a browser-based flow: `gateway add` opens the user's browser to the gateway's `/auth/connect` endpoint, which reads the `CF_Authorization` cookie and relays it back to a localhost callback server. The token is stored at `~/.config/openshell/gateways/<name>/edge_token`.
8585

8686
### Plaintext connection
8787

8888
When the gateway is deployed with `--plaintext`, TLS is disabled entirely. The CLI connects over plain HTTP/2. This mode is intended for gateways behind a trusted reverse proxy or tunnel that handles TLS termination.
8989

90+
The CLI also treats an explicit `http://...` registration as plaintext mode:
91+
92+
```shell
93+
openshell gateway add http://127.0.0.1:8080 --local
94+
```
95+
96+
This stores `auth_mode = "plaintext"`, skips mTLS certificate extraction, and bypasses the edge browser-auth flow.
97+
9098
## File System Layout
9199

92100
All connection artifacts are stored under `$XDG_CONFIG_HOME/openshell/` (default `~/.config/openshell/`):
93101

94102
```
95103
openshell/
96-
active_cluster # plain text: active cluster name
97-
clusters/
98-
<name>_metadata.json # ClusterMetadata JSON
104+
active_gateway # plain text: active gateway name
105+
gateways/
99106
<name>/
107+
metadata.json # GatewayMetadata JSON
100108
mtls/ # mTLS bundle (when TLS enabled)
101-
ca.crt # cluster CA certificate
109+
ca.crt # gateway CA certificate
102110
tls.crt # client certificate
103111
tls.key # client private key
104112
edge_token # Edge auth JWT (when auth_mode=cloudflare_jwt)
@@ -127,6 +135,6 @@ sequenceDiagram
127135
GW-->>Browser: Relay page (extracts token, POSTs to localhost)
128136
Browser->>CLI: POST token to localhost callback
129137
CLI->>CLI: Store edge_token
130-
CLI->>CLI: save_active_cluster
138+
CLI->>CLI: save_active_gateway
131139
CLI-->>U: Gateway added and set as active
132140
```

crates/openshell-bootstrap/src/metadata.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ pub struct GatewayMetadata {
2626
#[serde(skip_serializing_if = "Option::is_none", default)]
2727
pub resolved_host: Option<String>,
2828

29-
/// Auth mode: `None` or `"mtls"` = mTLS (default), `"cloudflare_jwt"` = CF JWT.
29+
/// Auth mode: `None` or `"mtls"` = mTLS (default), `"plaintext"` = direct HTTP,
30+
/// `"cloudflare_jwt"` = CF JWT.
3031
#[serde(default, skip_serializing_if = "Option::is_none")]
3132
pub auth_mode: Option<String>,
3233

@@ -132,7 +133,7 @@ pub fn create_gateway_metadata_with_host(
132133
gateway_port: port,
133134
remote_host,
134135
resolved_host,
135-
auth_mode: None,
136+
auth_mode: disable_tls.then(|| "plaintext".to_string()),
136137
edge_team_domain: None,
137138
edge_auth_url: None,
138139
}
@@ -514,6 +515,7 @@ mod tests {
514515
fn local_gateway_metadata_with_tls_disabled() {
515516
let meta = create_gateway_metadata_with_host("test", None, 8080, None, true);
516517
assert_eq!(meta.gateway_endpoint, "http://127.0.0.1:8080");
518+
assert_eq!(meta.auth_mode.as_deref(), Some("plaintext"));
517519
}
518520

519521
#[test]
@@ -526,6 +528,7 @@ mod tests {
526528
true,
527529
);
528530
assert_eq!(meta.gateway_endpoint, "http://host.docker.internal:8080");
531+
assert_eq!(meta.auth_mode.as_deref(), Some("plaintext"));
529532
}
530533

531534
// ── GatewayMetadata::gateway_host() ──────────────────────────────
@@ -590,6 +593,7 @@ mod tests {
590593
assert!(meta.is_remote);
591594
assert!(meta.gateway_endpoint.starts_with("http://"));
592595
assert!(!meta.gateway_endpoint.starts_with("https://"));
596+
assert_eq!(meta.auth_mode.as_deref(), Some("plaintext"));
593597
}
594598

595599
// ── last-sandbox persistence ──────────────────────────────────────

crates/openshell-cli/src/main.rs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -857,8 +857,12 @@ enum GatewayCommands {
857857
///
858858
/// Registers a gateway endpoint so it appears in `openshell gateway select`.
859859
///
860-
/// Without extra flags the gateway is treated as an edge-authenticated
861-
/// (cloud) gateway and a browser is opened for authentication.
860+
/// An `http://...` endpoint is treated as a direct plaintext gateway and
861+
/// skips both mTLS certificate extraction and browser authentication.
862+
///
863+
/// Without extra flags, an `https://...` endpoint is treated as an
864+
/// edge-authenticated (cloud) gateway and a browser is opened for
865+
/// authentication.
862866
///
863867
/// Pass `--remote <ssh-dest>` to register a remote mTLS gateway whose
864868
/// Docker daemon is reachable over SSH. Pass `--local` to register a
@@ -870,14 +874,16 @@ enum GatewayCommands {
870874
/// for `--remote user@host` with the endpoint derived from the URL.
871875
#[command(help_template = LEAF_HELP_TEMPLATE, next_help_heading = "FLAGS")]
872876
Add {
873-
/// Gateway endpoint URL (e.g., `https://10.0.0.5:8080` or `ssh://user@host:8080`).
877+
/// Gateway endpoint URL (for example `http://127.0.0.1:8080`,
878+
/// `https://10.0.0.5:8080`, or `ssh://user@host:8080`).
874879
endpoint: String,
875880

876881
/// Gateway name (auto-derived from the endpoint hostname when omitted).
877882
#[arg(long)]
878883
name: Option<String>,
879884

880885
/// Register a remote mTLS gateway accessible via SSH.
886+
/// With `http://...`, stores a remote plaintext registration instead.
881887
#[arg(long, conflicts_with = "local")]
882888
remote: Option<String>,
883889

@@ -886,6 +892,7 @@ enum GatewayCommands {
886892
ssh_key: Option<String>,
887893

888894
/// Register a local mTLS gateway running in Docker on this machine.
895+
/// With `http://...`, stores a local plaintext registration instead.
889896
#[arg(long, conflicts_with = "remote")]
890897
local: bool,
891898
},

0 commit comments

Comments
 (0)