ssh_tunnel_proxy 2.1.3 → 3.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +19 -3
- package/MIGRATION.md +227 -0
- package/README.md +360 -212
- package/build/bridge.d.ts +14 -0
- package/build/bridge.d.ts.map +1 -0
- package/build/bridge.js +12 -0
- package/build/bridge.js.map +1 -0
- package/build/connection.d.ts +60 -0
- package/build/connection.d.ts.map +1 -0
- package/build/connection.js +198 -0
- package/build/connection.js.map +1 -0
- package/build/credentials.d.ts +24 -0
- package/build/credentials.d.ts.map +1 -0
- package/build/credentials.js +102 -0
- package/build/credentials.js.map +1 -0
- package/build/errors.d.ts +19 -0
- package/build/errors.d.ts.map +1 -0
- package/build/errors.js +64 -0
- package/build/errors.js.map +1 -0
- package/build/forward-spec.d.ts +34 -0
- package/build/forward-spec.d.ts.map +1 -0
- package/build/forward-spec.js +175 -0
- package/build/forward-spec.js.map +1 -0
- package/build/index.d.ts +19 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +71 -0
- package/build/index.js.map +1 -0
- package/build/legacy.d.ts +70 -0
- package/build/legacy.d.ts.map +1 -0
- package/build/legacy.js +188 -0
- package/build/legacy.js.map +1 -0
- package/build/local-forward.d.ts +31 -0
- package/build/local-forward.d.ts.map +1 -0
- package/build/local-forward.js +231 -0
- package/build/local-forward.js.map +1 -0
- package/build/model.d.ts +126 -0
- package/build/model.d.ts.map +1 -0
- package/build/model.js +55 -0
- package/build/model.js.map +1 -0
- package/build/port-policy.d.ts +43 -0
- package/build/port-policy.d.ts.map +1 -0
- package/build/port-policy.js +138 -0
- package/build/port-policy.js.map +1 -0
- package/build/remote-forward.d.ts +40 -0
- package/build/remote-forward.d.ts.map +1 -0
- package/build/remote-forward.js +226 -0
- package/build/remote-forward.js.map +1 -0
- package/build/tunnel.d.ts +121 -0
- package/build/tunnel.d.ts.map +1 -0
- package/build/tunnel.js +599 -0
- package/build/tunnel.js.map +1 -0
- package/examples/config.example.json +86 -0
- package/examples/setup_ssh.sh +12 -0
- package/examples/tunnel-cli.js +461 -0
- package/examples/tunnel-worker.js +98 -0
- package/examples/zrok-termux.ts +147 -0
- package/package.json +43 -53
- package/proto/sshtunnel/v1/tunnel.proto +343 -0
- package/dist/TestMessage.d.ts +0 -13
- package/dist/TestMessage.js +0 -32
- package/dist/TestMessage.js.map +0 -1
- package/dist/cjs/TestMessage.d.ts +0 -13
- package/dist/cjs/TestMessage.js +0 -32
- package/dist/cjs/TestMessage.js.map +0 -1
- package/dist/cjs/index.d.ts +0 -73
- package/dist/cjs/index.js +0 -621
- package/dist/cjs/index.js.map +0 -1
- package/dist/cjs/keypair_storage.d.ts +0 -12
- package/dist/cjs/keypair_storage.js +0 -143
- package/dist/cjs/keypair_storage.js.map +0 -1
- package/dist/cjs/ngrok_service.d.ts +0 -11
- package/dist/cjs/ngrok_service.js +0 -40
- package/dist/cjs/ngrok_service.js.map +0 -1
- package/dist/cjs/ssh2-node.d.ts +0 -1
- package/dist/cjs/ssh2-node.js +0 -221
- package/dist/cjs/ssh2-node.js.map +0 -1
- package/dist/esm/TestMessage.d.ts +0 -13
- package/dist/esm/TestMessage.js +0 -29
- package/dist/esm/TestMessage.js.map +0 -1
- package/dist/esm/index.d.ts +0 -73
- package/dist/esm/index.js +0 -616
- package/dist/esm/index.js.map +0 -1
- package/dist/esm/keypair_storage.d.ts +0 -12
- package/dist/esm/keypair_storage.js +0 -117
- package/dist/esm/keypair_storage.js.map +0 -1
- package/dist/esm/ngrok_service.d.ts +0 -11
- package/dist/esm/ngrok_service.js +0 -37
- package/dist/esm/ngrok_service.js.map +0 -1
- package/dist/esm/ssh2-node.d.ts +0 -1
- package/dist/esm/ssh2-node.js +0 -194
- package/dist/esm/ssh2-node.js.map +0 -1
- package/dist/index.d.ts +0 -73
- package/dist/index.js +0 -620
- package/dist/index.js.map +0 -1
- package/dist/keypair_storage.d.ts +0 -12
- package/dist/keypair_storage.js +0 -120
- package/dist/keypair_storage.js.map +0 -1
- package/dist/ngrok_service.d.ts +0 -11
- package/dist/ngrok_service.js +0 -40
- package/dist/ngrok_service.js.map +0 -1
- package/dist/ssh2-node.d.ts +0 -1
- package/dist/ssh2-node.js +0 -188
- package/dist/ssh2-node.js.map +0 -1
- package/dist/types/TestMessage.d.ts +0 -13
- package/dist/types/index.d.ts +0 -73
- package/dist/types/keypair_storage.d.ts +0 -12
- package/dist/types/ngrok_service.d.ts +0 -11
- package/dist/types/ssh2-node.d.ts +0 -1
- package/ssh2-node +0 -2
package/LICENSE
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
|
-
|
|
1
|
+
MIT License
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Copyright (c) 2023-2026 Autonomous
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/MIGRATION.md
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
# Migrating from ssh_tunnel_proxy v1 to v2
|
|
2
|
+
|
|
3
|
+
v2 keeps the package name and keeps `ssh2` as the transport. What changed is scope:
|
|
4
|
+
the library now assumes **an SSH listener already exists at a host and port you
|
|
5
|
+
supply**, and concerns itself only with forwards over that transport.
|
|
6
|
+
|
|
7
|
+
Three things were removed outright, three things were added.
|
|
8
|
+
|
|
9
|
+
Removed: key generation and keychain storage, ngrok endpoint discovery, interactive
|
|
10
|
+
shells (plus the implicit read of `~/.config/ssh_tunnel_proxy/config.json`).
|
|
11
|
+
|
|
12
|
+
Added: a language-neutral `.proto` data model, a first-class `SSHTunnel` API with
|
|
13
|
+
typed events and per-connection state, and correct handling of many simultaneous
|
|
14
|
+
connections per forward.
|
|
15
|
+
|
|
16
|
+
## The fastest path: keep using the shim
|
|
17
|
+
|
|
18
|
+
`SSHTunnelProxy` still exists. If you delete the removed options, most v1 programs
|
|
19
|
+
run unchanged:
|
|
20
|
+
|
|
21
|
+
```diff
|
|
22
|
+
const proxy = new SSHTunnelProxy();
|
|
23
|
+
await proxy.connectSSH({
|
|
24
|
+
username: 'u0_a272',
|
|
25
|
+
host: '127.0.0.1',
|
|
26
|
+
port: '9191',
|
|
27
|
+
private_key_filename: '~/.ssh/id_ed25519',
|
|
28
|
+
proxy_ports: ['8280:127.0.0.1:8080'],
|
|
29
|
+
- ngrok_api: process.env.NGROK_API_KEY,
|
|
30
|
+
- service_name: 'sshtun',
|
|
31
|
+
- shell: false,
|
|
32
|
+
}, { 22: true, 80: true, 443: true });
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Leaving `ngrok_api`, `service_name` or `shell` in place throws immediately with a
|
|
36
|
+
message pointing here, rather than quietly doing something different from v1.
|
|
37
|
+
|
|
38
|
+
`proxy.getTunnel()` returns the v2 `SSHTunnel` behind the shim, so you can migrate
|
|
39
|
+
call sites one at a time.
|
|
40
|
+
|
|
41
|
+
## Option mapping
|
|
42
|
+
|
|
43
|
+
| v1 option | v2 | Notes |
|
|
44
|
+
| --- | --- | --- |
|
|
45
|
+
| `username` | `transport.username` | |
|
|
46
|
+
| `host` / `hostname` | `transport.endpoint.host` | `host` wins; `hostname` is still accepted |
|
|
47
|
+
| `port` | `transport.endpoint.port` | Now a real number, validated. v1 used `parseInt`, so `"22abc"` became `22`; that now throws |
|
|
48
|
+
| `private_key_filename` | `transport.privateKey = credential.file(path)` | `~` still expands |
|
|
49
|
+
| `private_key` | `transport.privateKey = credential.inline(pem)` | |
|
|
50
|
+
| `password` | `transport.password = credential.inline(...)` | An empty string is now dropped rather than attempted |
|
|
51
|
+
| `proxy_ports` | `localForwards` | The v1 string form still works everywhere a `LocalForward` is accepted |
|
|
52
|
+
| `remote_ports` | `remoteForwards` | **Field order changed — see below** |
|
|
53
|
+
| `whitelist` | `portPolicy` | `whitelistToPortPolicy()` converts a v1 whitelist object |
|
|
54
|
+
| `keepaliveInterval` | `transport.keepaliveIntervalMs` | Same default of 10000 |
|
|
55
|
+
| `exec` | `exec` on the shim, or `tunnel.exec(cmd)` | Still runs after connect on the shim |
|
|
56
|
+
| `server_name` | *gone* | It only existed to name a keychain entry |
|
|
57
|
+
| `service_name` | *gone* | Keychain storage removed |
|
|
58
|
+
| `ngrok_api` | *gone* | Resolve the address yourself and pass `host:port` |
|
|
59
|
+
| `shell` | *gone* | Not port forwarding |
|
|
60
|
+
| `disabled` | *gone* | Do not construct the tunnel |
|
|
61
|
+
|
|
62
|
+
## Breaking change to watch: reverse-forward spec strings
|
|
63
|
+
|
|
64
|
+
This is the one silent behaviour change, so check it if you used `remote_ports`.
|
|
65
|
+
|
|
66
|
+
```text
|
|
67
|
+
v1 "9090:127.0.0.1:3000" → localPort : targetHost : remotePort
|
|
68
|
+
v2 "9090:127.0.0.1:3000" → bindPort : targetHost : targetPort
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
v2 reads a three-field reverse spec the way `ssh -R` does: the **first** field is the
|
|
72
|
+
port the peer binds, and the last field is the port on **this** side that traffic is
|
|
73
|
+
delivered to. If your v1 values had the two ports the same — which is by far the
|
|
74
|
+
most common case — nothing changes. If they differed, swap them.
|
|
75
|
+
|
|
76
|
+
The unambiguous form is the object:
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
await tunnel.addRemoteForward({
|
|
80
|
+
bind: { host: '127.0.0.1', port: 9090 }, // on the peer
|
|
81
|
+
target: { host: '127.0.0.1', port: 3000 }, // reachable from here
|
|
82
|
+
});
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Use `bind.port: 0` to let the peer assign a port, then read `assignedPort` from the
|
|
86
|
+
returned status. v1 had no way to express that.
|
|
87
|
+
|
|
88
|
+
## The `ssh2-node <alias>` command line
|
|
89
|
+
|
|
90
|
+
v1 shipped a `commander`-based CLI that read
|
|
91
|
+
`~/.config/ssh_tunnel_proxy/config.json`, matched an alias against each entry's
|
|
92
|
+
`hostname`, and connected — usually dropping you into a remote shell.
|
|
93
|
+
|
|
94
|
+
The v2 equivalent is `examples/tunnel-cli.js`, which is dependency-free plain
|
|
95
|
+
JavaScript you are meant to copy and edit:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
node examples/tunnel-cli.js rh2 # same alias, same config file
|
|
99
|
+
node examples/tunnel-cli.js --list # new: see what is in the file
|
|
100
|
+
node examples/tunnel-cli.js rh2 -- uname -a
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
It reads the same file from the same default path, and accepts v1-shaped entries
|
|
104
|
+
directly. Differences:
|
|
105
|
+
|
|
106
|
+
| v1 CLI | v2 example CLI |
|
|
107
|
+
| --- | --- |
|
|
108
|
+
| `ssh2-node rh2` | `node examples/tunnel-cli.js rh2` |
|
|
109
|
+
| `-L` / `-R` | `-L` / `-R`, same repeatable form (mind the `-R` field order above) |
|
|
110
|
+
| `-i`, `-p`, `-v` | Same, plus `-u` and `--debug-ssh` |
|
|
111
|
+
| No argument after the host → remote shell | Holds the tunnel open instead; use `ssh` through the forward |
|
|
112
|
+
| Trailing command → exec then exit | `-- command ...` → exec then exit with its status |
|
|
113
|
+
| `-J` / `-j` keychain lookup | Gone. Use `-i <path>` or `-i agent` |
|
|
114
|
+
| `-H` ngrok lookup | Gone. Put the resolved address in the entry |
|
|
115
|
+
| Iterated *all* matching config entries | Selects exactly one entry, and says so if the alias is missing |
|
|
116
|
+
| ~35 mostly unimplemented `ssh(1)` flags | Only the flags that do something |
|
|
117
|
+
| Config read inside the library | Config read by the CLI and passed in as an object |
|
|
118
|
+
|
|
119
|
+
That last row is the point: the library no longer reads any file, so the same code
|
|
120
|
+
behaves identically on your workstation, in a container, and in the test suite.
|
|
121
|
+
Stale keys such as `service_name` or `ngrok_api` in an existing config file produce a
|
|
122
|
+
warning from the CLI and are skipped, so your file keeps working while you clean it
|
|
123
|
+
up.
|
|
124
|
+
|
|
125
|
+
## Method mapping
|
|
126
|
+
|
|
127
|
+
| v1 | v2 |
|
|
128
|
+
| --- | --- |
|
|
129
|
+
| `new SSHTunnelProxy()` + `connectSSH(opts, whitelist)` | `new SSHTunnel(config)` + `connect()` |
|
|
130
|
+
| `setupProxyPorts(ports)` | `addLocalForwards(specs)` — now atomic, and returns statuses |
|
|
131
|
+
| `setupRemotePorts(ports)` | `addRemoteForwards(specs)` |
|
|
132
|
+
| *(no equivalent)* | `removeForward(id)` |
|
|
133
|
+
| *(no equivalent)* | `resolveEndpoint(id)`, `getForwardStatus(id)`, `listConnections()` |
|
|
134
|
+
| `execCmd(cmd, out, err)` | `exec(cmd, { stdout, stderr })` — resolves with `{ code, stdout, stderr }` |
|
|
135
|
+
| `getClient()` | `getClient()` on the shim; v2 code should not need the raw client |
|
|
136
|
+
| `generateAndStoreKeypair()` | *gone* — use `ssh-keygen` |
|
|
137
|
+
| `getPublicKey()` | *gone* |
|
|
138
|
+
| `onNetworkOnline()` / `onNetworkOffline()` | *gone*; no-ops on the shim. Reconnection is now automatic and driven by the transport itself |
|
|
139
|
+
| `validate_port_number(port, whitelist)` | `checkPort(port, role, policy)` — the shim keeps the old boolean form |
|
|
140
|
+
| `validate_local_forward(ports, whitelist)` | `parseAndValidateLocalForward(spec, policy)` |
|
|
141
|
+
| `do_ssh_connect(...)` | *gone* (was internal) |
|
|
142
|
+
|
|
143
|
+
## Events
|
|
144
|
+
|
|
145
|
+
| v1 event | v2 |
|
|
146
|
+
| --- | --- |
|
|
147
|
+
| `ssh_tunnel_ready` | `ready`, carrying a full `TunnelStatus`. The shim still emits `ssh_tunnel_ready`, now exactly once per successful connect |
|
|
148
|
+
| `status` | `state` (tunnel), `forward` (per forward), `connection` (per connection). The shim still emits `status` |
|
|
149
|
+
| `debug` | `debug` |
|
|
150
|
+
| `error` | `error`, always a `TunnelError` with a `code` |
|
|
151
|
+
| *(none)* | `reconnect`, `close` |
|
|
152
|
+
|
|
153
|
+
`error` is worth re-reading: in v1, some failures were logged and others were thrown
|
|
154
|
+
from deep inside a callback. In v2 anything non-fatal is emitted as a coded
|
|
155
|
+
`TunnelError`, and only the operation you called rejects.
|
|
156
|
+
|
|
157
|
+
## Behaviour differences you may actually notice
|
|
158
|
+
|
|
159
|
+
1. **Concurrent reverse-forward connections work.** v1 reused a single outbound
|
|
160
|
+
socket for every channel of a reverse forward, so a second simultaneous
|
|
161
|
+
connection interleaved with the first. v2 opens one socket per channel. There is a
|
|
162
|
+
test that fails if this regresses.
|
|
163
|
+
|
|
164
|
+
2. **Port validation is stricter.** `parseInt`-style values such as `"80abc"` and
|
|
165
|
+
`"22.5"` are rejected instead of truncated.
|
|
166
|
+
|
|
167
|
+
3. **Port policy is explicit and role-aware.** v1 applied one whitelist to
|
|
168
|
+
everything. v2 asks separately about local listen ports, remote target ports,
|
|
169
|
+
remote bind ports and remote hosts. With no policy supplied, any unprivileged port
|
|
170
|
+
is permitted and no privileged port is.
|
|
171
|
+
|
|
172
|
+
4. **A dropped transport degrades rather than disappears.** Local listeners stay
|
|
173
|
+
bound while reconnection is in progress, so callers get a fast failure instead of
|
|
174
|
+
connection-refused, and the port cannot be stolen by another process in the gap.
|
|
175
|
+
Forwards return to `active` automatically.
|
|
176
|
+
|
|
177
|
+
5. **Adding several forwards is atomic.** If the third of three fails to bind, the
|
|
178
|
+
first two are rolled back, so you never end up half-configured.
|
|
179
|
+
|
|
180
|
+
6. **Nothing is read from disk implicitly.** v1 read
|
|
181
|
+
`~/.config/ssh_tunnel_proxy/config.json`; v2 only uses the config object you pass.
|
|
182
|
+
This is also why the test suite no longer depends on the machine it runs on.
|
|
183
|
+
|
|
184
|
+
7. **Host keys can be verified.** Set `transport.hostKeyFingerprints` to one or more
|
|
185
|
+
`sha256:...` values. Omitting it accepts any host key, as v1 always did.
|
|
186
|
+
|
|
187
|
+
## Where key management went
|
|
188
|
+
|
|
189
|
+
Out of the package. Choose whichever fits your deployment:
|
|
190
|
+
|
|
191
|
+
```ts
|
|
192
|
+
credential.file('~/.ssh/id_ed25519') // ssh-keygen, file permissions, done
|
|
193
|
+
credential.env('SSH_PRIVATE_KEY') // containers and CI
|
|
194
|
+
credential.agent() // ssh-agent, no key bytes in your process
|
|
195
|
+
credential.callback('vault://ssh/id') // your own resolver, e.g. Vault or an OS keychain
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
`credential.callback` plus `options.credentialResolver` is the seam where the old
|
|
199
|
+
`keytar` behaviour belongs if you still want it — as ten lines in your application,
|
|
200
|
+
not as a native dependency of a forwarding library.
|
|
201
|
+
|
|
202
|
+
## Where ngrok went
|
|
203
|
+
|
|
204
|
+
Also out of the package. v1 called the ngrok API to discover where the SSH daemon
|
|
205
|
+
was, which coupled the transport to one vendor. v2 takes an address:
|
|
206
|
+
|
|
207
|
+
```ts
|
|
208
|
+
// zrok: `zrok access private --bind 127.0.0.1:9191 <token>` on this machine
|
|
209
|
+
endpoint: { host: '127.0.0.1', port: 9191 }
|
|
210
|
+
|
|
211
|
+
// ngrok: read the address from the ngrok API in your own code, then
|
|
212
|
+
endpoint: { host: '4.tcp.ngrok.io', port: 12345 }
|
|
213
|
+
|
|
214
|
+
// Tailscale, WireGuard, LAN, jump host — all just an address
|
|
215
|
+
endpoint: { host: '100.64.0.7', port: 22 }
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
`transport.reachability` (`'direct' | 'zrok' | 'ngrok' | 'custom'`) exists only to
|
|
219
|
+
label how you got there. It changes no behaviour.
|
|
220
|
+
|
|
221
|
+
## Cross-language use
|
|
222
|
+
|
|
223
|
+
`proto/sshtunnel/v1/tunnel.proto` defines the config, status and event model plus an
|
|
224
|
+
optional `TunnelControl` gRPC service, so another runtime can drive or observe the
|
|
225
|
+
same model. The Node runtime has no protobuf dependency and no codegen step; the
|
|
226
|
+
proto file is the specification, and `test/unit/model-parity.test.js` fails the build
|
|
227
|
+
if `src/model.ts` drifts from it.
|