ssh_tunnel_proxy 2.1.1 → 3.0.0
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 -211
- 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 -184
- 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
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* examples/zrok-termux.ts
|
|
3
|
+
*
|
|
4
|
+
* The topology this refactor was built against:
|
|
5
|
+
*
|
|
6
|
+
* workstation phone (Termux + proot)
|
|
7
|
+
* ─────────── ──────────────────────
|
|
8
|
+
* browser -> 127.0.0.1:8280 ─┐
|
|
9
|
+
* ssh -> 127.0.0.1:8122 ─┤
|
|
10
|
+
* │ SSH over a private zrok TCP share
|
|
11
|
+
* └─ 127.0.0.1:9191 ══════════════> sshd on 127.0.0.1:8022
|
|
12
|
+
* ├─ 127.0.0.1:8080 (web app in proot)
|
|
13
|
+
* └─ 192.168.1.1:22 (LAN router)
|
|
14
|
+
*
|
|
15
|
+
* Bring the relay up first — this library never creates it:
|
|
16
|
+
*
|
|
17
|
+
* phone: zrok share private --backend-mode tcpTunnel 127.0.0.1:8022
|
|
18
|
+
* workstation: zrok access private --bind 127.0.0.1:9191 <share-token>
|
|
19
|
+
* workstation: nc 127.0.0.1 9191 # must print SSH-2.0-... before running this
|
|
20
|
+
*
|
|
21
|
+
* Then: npx tsc && node build-examples/zrok-termux.js
|
|
22
|
+
*
|
|
23
|
+
* License: MIT
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import { SSHTunnel, credential, isTunnelError } from '../src/index';
|
|
27
|
+
|
|
28
|
+
async function main(): Promise<void> {
|
|
29
|
+
const tunnel = new SSHTunnel(
|
|
30
|
+
{
|
|
31
|
+
id: 'phone',
|
|
32
|
+
transport: {
|
|
33
|
+
endpoint: { host: '127.0.0.1', port: 9191 },
|
|
34
|
+
username: 'user',
|
|
35
|
+
privateKey: credential.file('~/.ssh/id_ed25519'),
|
|
36
|
+
// Documentation only. The library treats every path as a plain address.
|
|
37
|
+
reachability: 'zrok',
|
|
38
|
+
keepaliveIntervalMs: 10_000,
|
|
39
|
+
},
|
|
40
|
+
localForwards: [
|
|
41
|
+
{
|
|
42
|
+
id: 'app',
|
|
43
|
+
listen: { host: '127.0.0.1', port: 8280 },
|
|
44
|
+
target: { host: '127.0.0.1', port: 8080 },
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
id: 'router-ssh',
|
|
48
|
+
listen: { host: '127.0.0.1', port: 8122 },
|
|
49
|
+
target: { host: '192.168.1.1', port: 22 },
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
portPolicy: {
|
|
53
|
+
// 192.168.1.1:22 is privileged, so it has to be named explicitly.
|
|
54
|
+
allowedPrivilegedPorts: [22],
|
|
55
|
+
allowedLocalListenPorts: [8280, 8122],
|
|
56
|
+
allowedRemoteHosts: ['127.0.0.1', '192.168.1.1'],
|
|
57
|
+
},
|
|
58
|
+
reconnect: {
|
|
59
|
+
enabled: true,
|
|
60
|
+
initialDelayMs: 1_000,
|
|
61
|
+
maxDelayMs: 30_000,
|
|
62
|
+
maxAttempts: 0, // retry forever; the phone's link comes and goes
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
{ connectionIdleTimeoutMs: 0, debugSsh: false },
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
tunnel.on('ready', (status) => {
|
|
69
|
+
console.log(`tunnel ready via ${status.endpoint.host}:${status.endpoint.port}`);
|
|
70
|
+
for (const forward of status.forwards) {
|
|
71
|
+
console.log(
|
|
72
|
+
` ${forward.direction === 'local' ? 'L' : 'R'} ` +
|
|
73
|
+
`${forward.listen.host}:${forward.assignedPort ?? forward.listen.port}` +
|
|
74
|
+
` -> ${forward.target.host}:${forward.target.port} [${forward.state}]`,
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
tunnel.on('forward', ({ status, previous }) => {
|
|
80
|
+
if (status.state !== previous) {
|
|
81
|
+
console.log(`forward ${status.forwardId}: ${previous} -> ${status.state}`);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
tunnel.on('reconnect', ({ attempt, delayMs, cause }) => {
|
|
86
|
+
console.warn(`link lost (${cause ?? 'unknown'}); retry ${attempt} in ${delayMs}ms`);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
tunnel.on('error', (err) => {
|
|
90
|
+
console.error(`[${err.code}] ${err.message}`);
|
|
91
|
+
if (err.code === 'TARGET_UNREACHABLE') {
|
|
92
|
+
console.error(' is the service actually running on the phone side?');
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
tunnel.on('close', () => console.log('tunnel closed'));
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
await tunnel.connect();
|
|
100
|
+
} catch (err) {
|
|
101
|
+
if (isTunnelError(err) && err.code === 'AUTH_FAILED') {
|
|
102
|
+
console.error('check that ~/.ssh/id_ed25519.pub is in the phone\'s authorized_keys');
|
|
103
|
+
}
|
|
104
|
+
throw err;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const app = tunnel.resolveEndpoint('app');
|
|
108
|
+
console.log(`open http://${app.endpoint.host}:${app.endpoint.port}`);
|
|
109
|
+
|
|
110
|
+
// Confirm the far side is the host we think it is.
|
|
111
|
+
const { stdout } = await tunnel.exec('id -un; uname -m');
|
|
112
|
+
console.log(`peer identity: ${stdout.trim().replace(/\n/g, ' ')}`);
|
|
113
|
+
|
|
114
|
+
// A reverse forward: expose a service running here to the phone.
|
|
115
|
+
const reverse = await tunnel.addRemoteForward({
|
|
116
|
+
id: 'workstation-api',
|
|
117
|
+
bind: { host: '127.0.0.1', port: 0 }, // let the phone pick the port
|
|
118
|
+
target: { host: '127.0.0.1', port: 3000 },
|
|
119
|
+
});
|
|
120
|
+
console.log(`phone can reach this host's :3000 at 127.0.0.1:${reverse.assignedPort}`);
|
|
121
|
+
|
|
122
|
+
// Periodic accounting, which v1 could not report at all.
|
|
123
|
+
const reporter = setInterval(() => {
|
|
124
|
+
const connections = tunnel.listConnections();
|
|
125
|
+
if (connections.length === 0) return;
|
|
126
|
+
console.log(`${connections.length} open connection(s):`);
|
|
127
|
+
for (const connection of connections) {
|
|
128
|
+
console.log(
|
|
129
|
+
` ${connection.forwardId} ${connection.state} ` +
|
|
130
|
+
`↑${connection.bytesToTarget}B ↓${connection.bytesFromTarget}B`,
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
}, 30_000);
|
|
134
|
+
|
|
135
|
+
const shutdown = async () => {
|
|
136
|
+
clearInterval(reporter);
|
|
137
|
+
await tunnel.close();
|
|
138
|
+
process.exit(0);
|
|
139
|
+
};
|
|
140
|
+
process.on('SIGINT', shutdown);
|
|
141
|
+
process.on('SIGTERM', shutdown);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
main().catch((err) => {
|
|
145
|
+
console.error(err);
|
|
146
|
+
process.exit(1);
|
|
147
|
+
});
|
package/package.json
CHANGED
|
@@ -1,63 +1,53 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ssh_tunnel_proxy",
|
|
3
|
-
"
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
"main": "dist/cjs/index.js",
|
|
8
|
-
"module": "dist/esm/index.js",
|
|
9
|
-
"types": "typings/index.d.ts",
|
|
10
|
-
"scripts": {
|
|
11
|
-
"build": "npm run build:cjs && npm run build:esm && npm run build:types && npm run build:testbench",
|
|
12
|
-
"build:cjs": "node tools/cleanup cjs && tsc -p config/tsconfig.cjs.json",
|
|
13
|
-
"build:esm": "node tools/cleanup esm && tsc -p config/tsconfig.esm.json",
|
|
14
|
-
"build:types": "node tools/cleanup types && tsc -p config/tsconfig.types.json",
|
|
15
|
-
"build:testbench": "tsc -p config/tsconfig.testbench.json",
|
|
16
|
-
"lint": "eslint \"src/*.ts\"",
|
|
17
|
-
"prepare": "npm run build",
|
|
18
|
-
"prepublishOnly": "npm test && npm run lint",
|
|
19
|
-
"preversion": "npm run lint",
|
|
20
|
-
"version": "git add -A src",
|
|
21
|
-
"postversion": "git push && git push --tags",
|
|
22
|
-
"test": "mocha --exit"
|
|
23
|
-
},
|
|
24
|
-
"repository": {
|
|
25
|
-
"type": "git",
|
|
26
|
-
"url": "git+https://github.com/autonomous1/ssh_tunnel_proxy"
|
|
27
|
-
},
|
|
3
|
+
"version": "3.0.0",
|
|
4
|
+
"description": "Programmatic local and reverse TCP port forwarding over SSH for Node.js, built for outbound-only and constrained-device deployments.",
|
|
5
|
+
"main": "build/index.js",
|
|
6
|
+
"types": "build/index.d.ts",
|
|
28
7
|
"files": [
|
|
29
|
-
"
|
|
30
|
-
"
|
|
8
|
+
"build",
|
|
9
|
+
"proto",
|
|
10
|
+
"examples",
|
|
11
|
+
"README.md",
|
|
12
|
+
"MIGRATION.md",
|
|
13
|
+
"LICENSE"
|
|
31
14
|
],
|
|
32
|
-
"
|
|
33
|
-
"
|
|
15
|
+
"engines": {
|
|
16
|
+
"node": ">=18"
|
|
34
17
|
},
|
|
18
|
+
"scripts": {
|
|
19
|
+
"clean": "rm -rf build",
|
|
20
|
+
"build": "tsc -p tsconfig.json",
|
|
21
|
+
"prepublishOnly": "npm run clean && npm run build",
|
|
22
|
+
"test": "npm run build && mocha --timeout 20000 test/**/*.test.js",
|
|
23
|
+
"test:unit": "npm run build && mocha --timeout 5000 test/unit/*.test.js",
|
|
24
|
+
"test:integration": "npm run build && mocha --timeout 20000 test/integration/*.test.js"
|
|
25
|
+
},
|
|
26
|
+
"keywords": [
|
|
27
|
+
"ssh",
|
|
28
|
+
"ssh2",
|
|
29
|
+
"tunnel",
|
|
30
|
+
"ssh-tunnel",
|
|
31
|
+
"reverse-tunnel",
|
|
32
|
+
"reverse-ssh",
|
|
33
|
+
"port-forwarding",
|
|
34
|
+
"tcp-proxy",
|
|
35
|
+
"nat-traversal",
|
|
36
|
+
"zrok",
|
|
37
|
+
"termux",
|
|
38
|
+
"iot",
|
|
39
|
+
"edge",
|
|
40
|
+
"nodejs"
|
|
41
|
+
],
|
|
42
|
+
"author": "Autonomous",
|
|
43
|
+
"license": "MIT",
|
|
35
44
|
"dependencies": {
|
|
36
|
-
"
|
|
37
|
-
"bluebird": "^3.7.2",
|
|
38
|
-
"commander": "^10.0.0",
|
|
39
|
-
"electron-ssh2": "^0.1.2",
|
|
40
|
-
"form-data": "^4.0.0",
|
|
41
|
-
"fs": "^0.0.1-security",
|
|
42
|
-
"keytar": "^7.9.0",
|
|
43
|
-
"net": "^1.0.2",
|
|
44
|
-
"os": "^0.1.2",
|
|
45
|
-
"split": "^1.0.1",
|
|
46
|
-
"sshpk": "^1.17.0",
|
|
47
|
-
"through": "^2.3.8",
|
|
48
|
-
"through2": "^4.0.2"
|
|
45
|
+
"ssh2": "^1.15.0"
|
|
49
46
|
},
|
|
50
47
|
"devDependencies": {
|
|
51
|
-
"@types/node": "^
|
|
52
|
-
"@
|
|
53
|
-
"
|
|
54
|
-
"
|
|
55
|
-
"eslint-config-standard-with-typescript": "^34.0.0",
|
|
56
|
-
"eslint-plugin-import": "^2.27.5",
|
|
57
|
-
"eslint-plugin-n": "^15.6.1",
|
|
58
|
-
"eslint-plugin-promise": "^6.1.1",
|
|
59
|
-
"mocha": "^10.2.0",
|
|
60
|
-
"sinon": "^15.0.1",
|
|
61
|
-
"typescript": "^4.9.5"
|
|
48
|
+
"@types/node": "^20.11.0",
|
|
49
|
+
"@types/ssh2": "^1.15.0",
|
|
50
|
+
"mocha": "^10.4.0",
|
|
51
|
+
"typescript": "^5.4.0"
|
|
62
52
|
}
|
|
63
53
|
}
|
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
// sshtunnel/v1/tunnel.proto
|
|
2
|
+
//
|
|
3
|
+
// Language-neutral data model for programmatic SSH TCP port forwarding.
|
|
4
|
+
//
|
|
5
|
+
// Design rules for this file:
|
|
6
|
+
// 1. It describes CONFIGURATION, STATE and EVENTS only. It never describes
|
|
7
|
+
// implementation mechanics (sockets, channels, event emitters, promises).
|
|
8
|
+
// 2. It contains no credential material. Secrets are referenced indirectly
|
|
9
|
+
// through a CredentialRef so that a graph/config file can be committed,
|
|
10
|
+
// diffed and shared without leaking keys.
|
|
11
|
+
// 3. It is the source of truth shared by the TypeScript runtime, a future
|
|
12
|
+
// Python runtime, and any control-plane API placed in front of them.
|
|
13
|
+
//
|
|
14
|
+
// License: MIT
|
|
15
|
+
|
|
16
|
+
syntax = "proto3";
|
|
17
|
+
|
|
18
|
+
package sshtunnel.v1;
|
|
19
|
+
|
|
20
|
+
option go_package = "github.com/autonomous/ssh_tunnel_proxy/gen/sshtunnel/v1;sshtunnelv1";
|
|
21
|
+
option java_multiple_files = true;
|
|
22
|
+
option java_package = "org.sshtunnel.v1";
|
|
23
|
+
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
// Primitives
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
|
|
28
|
+
// A TCP endpoint. `host` is an IP literal or DNS name; it is never a URL.
|
|
29
|
+
message Endpoint {
|
|
30
|
+
string host = 1;
|
|
31
|
+
uint32 port = 2; // 1..65535
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Indirect reference to secret material. The runtime resolves the reference at
|
|
35
|
+
// connect time; the model itself never carries the secret.
|
|
36
|
+
message CredentialRef {
|
|
37
|
+
enum Source {
|
|
38
|
+
SOURCE_UNSPECIFIED = 0;
|
|
39
|
+
SOURCE_FILE = 1; // value = filesystem path, "~" expanded by runtime
|
|
40
|
+
SOURCE_ENV = 2; // value = environment variable name
|
|
41
|
+
SOURCE_INLINE = 3; // value = literal (test fixtures only)
|
|
42
|
+
SOURCE_AGENT = 4; // value = agent socket path, or empty for SSH_AUTH_SOCK
|
|
43
|
+
SOURCE_CALLBACK = 5; // value = host-defined resolver name
|
|
44
|
+
}
|
|
45
|
+
Source source = 1;
|
|
46
|
+
string value = 2;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
// Port policy
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
|
|
53
|
+
// Port authorization is an explicit caller policy, not a library surprise.
|
|
54
|
+
// Local listen ports and remote target ports are separate decisions because
|
|
55
|
+
// they protect different things: local binding rights vs. what the SSH peer is
|
|
56
|
+
// permitted to reach.
|
|
57
|
+
message PortPolicy {
|
|
58
|
+
// Ports below 1024 are rejected unless listed here.
|
|
59
|
+
repeated uint32 allowed_privileged_ports = 1;
|
|
60
|
+
|
|
61
|
+
// If non-empty, a local listener may only bind one of these ports.
|
|
62
|
+
repeated uint32 allowed_local_listen_ports = 2;
|
|
63
|
+
|
|
64
|
+
// If non-empty, a forward may only target one of these remote ports.
|
|
65
|
+
repeated uint32 allowed_remote_target_ports = 3;
|
|
66
|
+
|
|
67
|
+
// If non-empty, a forward may only target one of these hosts, as written.
|
|
68
|
+
repeated string allowed_remote_hosts = 4;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
// Forwards
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
|
|
75
|
+
// ssh -L : listen on this machine, deliver to a host reachable from the SSH peer.
|
|
76
|
+
message LocalForward {
|
|
77
|
+
string id = 1; // stable, caller-chosen; used in events and lookups
|
|
78
|
+
Endpoint listen = 2; // bound on the machine running this runtime
|
|
79
|
+
Endpoint target = 3; // resolved by the SSH peer
|
|
80
|
+
bool exclusive = 4; // fail instead of reusing an existing listener
|
|
81
|
+
uint32 max_connections = 5; // 0 = unlimited
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ssh -R : the SSH peer listens, connections are delivered to a host reachable
|
|
85
|
+
// from this machine.
|
|
86
|
+
message RemoteForward {
|
|
87
|
+
string id = 1;
|
|
88
|
+
Endpoint bind = 2; // bound on the SSH peer; port 0 = peer assigns
|
|
89
|
+
Endpoint target = 3; // resolved by the machine running this runtime
|
|
90
|
+
uint32 max_connections = 4; // 0 = unlimited
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ---------------------------------------------------------------------------
|
|
94
|
+
// Transport
|
|
95
|
+
// ---------------------------------------------------------------------------
|
|
96
|
+
|
|
97
|
+
// How to reach the SSH listener. This model deliberately assumes an SSH
|
|
98
|
+
// listener already exists at `endpoint` — it never provisions a relay. A
|
|
99
|
+
// reachability provider such as zrok, ngrok or a VPS is an external concern
|
|
100
|
+
// recorded only as documentation metadata.
|
|
101
|
+
message SshTransport {
|
|
102
|
+
Endpoint endpoint = 1;
|
|
103
|
+
string username = 2;
|
|
104
|
+
CredentialRef private_key = 3;
|
|
105
|
+
CredentialRef passphrase = 4;
|
|
106
|
+
CredentialRef password = 5;
|
|
107
|
+
|
|
108
|
+
uint32 keepalive_interval_ms = 6; // default 10000
|
|
109
|
+
uint32 keepalive_count_max = 7; // default 3
|
|
110
|
+
uint32 ready_timeout_ms = 8; // default 20000
|
|
111
|
+
repeated string host_key_fingerprints = 9; // sha256:... ; empty = accept any
|
|
112
|
+
|
|
113
|
+
enum Reachability {
|
|
114
|
+
REACHABILITY_UNSPECIFIED = 0;
|
|
115
|
+
REACHABILITY_DIRECT = 1;
|
|
116
|
+
REACHABILITY_ZROK = 2;
|
|
117
|
+
REACHABILITY_NGROK = 3;
|
|
118
|
+
REACHABILITY_CUSTOM = 4;
|
|
119
|
+
}
|
|
120
|
+
Reachability reachability = 10; // documentation only; no behavioral effect
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
message ReconnectPolicy {
|
|
124
|
+
bool enabled = 1; // default true
|
|
125
|
+
uint32 max_attempts = 2; // 0 = unlimited
|
|
126
|
+
uint32 initial_delay_ms = 3; // default 1000
|
|
127
|
+
uint32 max_delay_ms = 4; // default 30000
|
|
128
|
+
double backoff_multiplier = 5; // default 2.0
|
|
129
|
+
double jitter_ratio = 6; // 0.0..1.0, default 0.2
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// The complete, portable definition of one tunnel.
|
|
133
|
+
message TunnelConfig {
|
|
134
|
+
string id = 1;
|
|
135
|
+
string label = 2;
|
|
136
|
+
SshTransport transport = 3;
|
|
137
|
+
PortPolicy port_policy = 4;
|
|
138
|
+
ReconnectPolicy reconnect = 5;
|
|
139
|
+
repeated LocalForward local_forwards = 6;
|
|
140
|
+
repeated RemoteForward remote_forwards = 7;
|
|
141
|
+
map<string, string> metadata = 8;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// ---------------------------------------------------------------------------
|
|
145
|
+
// State
|
|
146
|
+
// ---------------------------------------------------------------------------
|
|
147
|
+
|
|
148
|
+
enum TunnelState {
|
|
149
|
+
TUNNEL_STATE_UNSPECIFIED = 0;
|
|
150
|
+
TUNNEL_STATE_IDLE = 1;
|
|
151
|
+
TUNNEL_STATE_CONNECTING = 2;
|
|
152
|
+
TUNNEL_STATE_READY = 3;
|
|
153
|
+
TUNNEL_STATE_RECONNECTING = 4;
|
|
154
|
+
TUNNEL_STATE_CLOSING = 5;
|
|
155
|
+
TUNNEL_STATE_CLOSED = 6;
|
|
156
|
+
TUNNEL_STATE_FAILED = 7;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
enum ForwardState {
|
|
160
|
+
FORWARD_STATE_UNSPECIFIED = 0;
|
|
161
|
+
FORWARD_STATE_PENDING = 1;
|
|
162
|
+
FORWARD_STATE_OPENING = 2;
|
|
163
|
+
FORWARD_STATE_ACTIVE = 3;
|
|
164
|
+
FORWARD_STATE_DEGRADED = 4; // registered, but transport is down
|
|
165
|
+
FORWARD_STATE_CLOSING = 5;
|
|
166
|
+
FORWARD_STATE_CLOSED = 6;
|
|
167
|
+
FORWARD_STATE_FAILED = 7;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Lifecycle of a single proxied TCP connection carried by a forward.
|
|
171
|
+
enum ConnectionState {
|
|
172
|
+
CONNECTION_STATE_UNSPECIFIED = 0;
|
|
173
|
+
CONNECTION_STATE_ACCEPTED = 1; // inbound socket accepted, no channel yet
|
|
174
|
+
CONNECTION_STATE_OPENING = 2; // SSH channel being opened
|
|
175
|
+
CONNECTION_STATE_PIPED = 3; // bidirectional flow established
|
|
176
|
+
CONNECTION_STATE_HALF_CLOSED = 4;
|
|
177
|
+
CONNECTION_STATE_CLOSED = 5;
|
|
178
|
+
CONNECTION_STATE_FAILED = 6;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
enum ForwardDirection {
|
|
182
|
+
FORWARD_DIRECTION_UNSPECIFIED = 0;
|
|
183
|
+
FORWARD_DIRECTION_LOCAL = 1; // ssh -L
|
|
184
|
+
FORWARD_DIRECTION_REMOTE = 2; // ssh -R
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
message ConnectionStatus {
|
|
188
|
+
string connection_id = 1;
|
|
189
|
+
string forward_id = 2;
|
|
190
|
+
ForwardDirection direction = 3;
|
|
191
|
+
ConnectionState state = 4;
|
|
192
|
+
Endpoint source = 5;
|
|
193
|
+
Endpoint destination = 6;
|
|
194
|
+
int64 opened_at_unix_ms = 7;
|
|
195
|
+
int64 closed_at_unix_ms = 8;
|
|
196
|
+
uint64 bytes_to_target = 9;
|
|
197
|
+
uint64 bytes_from_target = 10;
|
|
198
|
+
string error = 11;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
message ForwardStatus {
|
|
202
|
+
string forward_id = 1;
|
|
203
|
+
ForwardDirection direction = 2;
|
|
204
|
+
ForwardState state = 3;
|
|
205
|
+
Endpoint listen = 4; // for remote forwards, the peer-side bind
|
|
206
|
+
Endpoint target = 5;
|
|
207
|
+
uint32 assigned_port = 6; // resolved port when requested port was 0
|
|
208
|
+
uint32 active_connections = 7;
|
|
209
|
+
uint64 total_connections = 8;
|
|
210
|
+
string error = 9;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
message TunnelStatus {
|
|
214
|
+
string tunnel_id = 1;
|
|
215
|
+
TunnelState state = 2;
|
|
216
|
+
Endpoint endpoint = 3;
|
|
217
|
+
uint32 reconnect_attempt = 4;
|
|
218
|
+
int64 connected_at_unix_ms = 5;
|
|
219
|
+
repeated ForwardStatus forwards = 6;
|
|
220
|
+
string error = 7;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// ---------------------------------------------------------------------------
|
|
224
|
+
// Events
|
|
225
|
+
// ---------------------------------------------------------------------------
|
|
226
|
+
|
|
227
|
+
message TunnelEvent {
|
|
228
|
+
int64 timestamp_unix_ms = 1;
|
|
229
|
+
string tunnel_id = 2;
|
|
230
|
+
|
|
231
|
+
oneof payload {
|
|
232
|
+
StateChanged state_changed = 10;
|
|
233
|
+
ForwardChanged forward_changed = 11;
|
|
234
|
+
ConnectionChanged connection_changed = 12;
|
|
235
|
+
ReconnectScheduled reconnect_scheduled = 13;
|
|
236
|
+
ErrorRaised error_raised = 14;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
message StateChanged {
|
|
240
|
+
TunnelState previous = 1;
|
|
241
|
+
TunnelState current = 2;
|
|
242
|
+
string reason = 3;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
message ForwardChanged {
|
|
246
|
+
ForwardStatus status = 1;
|
|
247
|
+
ForwardState previous = 2;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
message ConnectionChanged {
|
|
251
|
+
ConnectionStatus status = 1;
|
|
252
|
+
ConnectionState previous = 2;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
message ReconnectScheduled {
|
|
256
|
+
uint32 attempt = 1;
|
|
257
|
+
uint32 delay_ms = 2;
|
|
258
|
+
string cause = 3;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
message ErrorRaised {
|
|
262
|
+
// Mirrors the runtime error taxonomy so non-TypeScript consumers can
|
|
263
|
+
// branch on a stable code rather than on a message string.
|
|
264
|
+
enum Code {
|
|
265
|
+
CODE_UNSPECIFIED = 0;
|
|
266
|
+
CODE_INVALID_FORWARD_SPEC = 1;
|
|
267
|
+
CODE_PORT_NOT_PERMITTED = 2;
|
|
268
|
+
CODE_HOST_NOT_PERMITTED = 3;
|
|
269
|
+
CODE_TRANSPORT_NOT_READY = 4;
|
|
270
|
+
CODE_AUTH_FAILED = 5;
|
|
271
|
+
CODE_LISTEN_FAILED = 6;
|
|
272
|
+
CODE_REMOTE_BIND_FAILED = 7;
|
|
273
|
+
CODE_CHANNEL_OPEN_FAILED = 8;
|
|
274
|
+
CODE_TARGET_UNREACHABLE = 9;
|
|
275
|
+
CODE_DUPLICATE_FORWARD_ID = 10;
|
|
276
|
+
CODE_UNKNOWN_FORWARD = 11;
|
|
277
|
+
CODE_CONNECTION_LIMIT = 12;
|
|
278
|
+
CODE_RECONNECT_EXHAUSTED = 13;
|
|
279
|
+
CODE_CREDENTIAL_UNRESOLVED = 14;
|
|
280
|
+
}
|
|
281
|
+
Code code = 1;
|
|
282
|
+
string message = 2;
|
|
283
|
+
string forward_id = 3;
|
|
284
|
+
string connection_id = 4;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// ---------------------------------------------------------------------------
|
|
289
|
+
// Optional control plane
|
|
290
|
+
// ---------------------------------------------------------------------------
|
|
291
|
+
//
|
|
292
|
+
// Implementing this service is NOT required to use the library in-process. It
|
|
293
|
+
// exists so that a non-Node runtime (for example a Python agent-flow runtime)
|
|
294
|
+
// can drive a Node tunnel manager over a loopback control API and resolve a
|
|
295
|
+
// named forward to a concrete local endpoint.
|
|
296
|
+
|
|
297
|
+
message OpenTunnelRequest { TunnelConfig config = 1; }
|
|
298
|
+
message OpenTunnelResponse { TunnelStatus status = 1; }
|
|
299
|
+
|
|
300
|
+
message CloseTunnelRequest { string tunnel_id = 1; }
|
|
301
|
+
message CloseTunnelResponse { TunnelStatus status = 1; }
|
|
302
|
+
|
|
303
|
+
message GetTunnelStatusRequest { string tunnel_id = 1; }
|
|
304
|
+
message GetTunnelStatusResponse { TunnelStatus status = 1; }
|
|
305
|
+
|
|
306
|
+
message OpenLocalForwardRequest {
|
|
307
|
+
string tunnel_id = 1;
|
|
308
|
+
LocalForward forward = 2;
|
|
309
|
+
}
|
|
310
|
+
message OpenRemoteForwardRequest {
|
|
311
|
+
string tunnel_id = 1;
|
|
312
|
+
RemoteForward forward = 2;
|
|
313
|
+
}
|
|
314
|
+
message OpenForwardResponse { ForwardStatus status = 1; }
|
|
315
|
+
|
|
316
|
+
message CloseForwardRequest {
|
|
317
|
+
string tunnel_id = 1;
|
|
318
|
+
string forward_id = 2;
|
|
319
|
+
}
|
|
320
|
+
message CloseForwardResponse { ForwardStatus status = 1; }
|
|
321
|
+
|
|
322
|
+
// Resolve a semantic forward id to the loopback endpoint a caller should use.
|
|
323
|
+
message ResolveEndpointRequest {
|
|
324
|
+
string tunnel_id = 1;
|
|
325
|
+
string forward_id = 2;
|
|
326
|
+
}
|
|
327
|
+
message ResolveEndpointResponse {
|
|
328
|
+
Endpoint endpoint = 1;
|
|
329
|
+
ForwardState state = 2;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
message WatchEventsRequest { string tunnel_id = 1; }
|
|
333
|
+
|
|
334
|
+
service TunnelControl {
|
|
335
|
+
rpc OpenTunnel(OpenTunnelRequest) returns (OpenTunnelResponse);
|
|
336
|
+
rpc CloseTunnel(CloseTunnelRequest) returns (CloseTunnelResponse);
|
|
337
|
+
rpc GetTunnelStatus(GetTunnelStatusRequest) returns (GetTunnelStatusResponse);
|
|
338
|
+
rpc OpenLocalForward(OpenLocalForwardRequest) returns (OpenForwardResponse);
|
|
339
|
+
rpc OpenRemoteForward(OpenRemoteForwardRequest) returns (OpenForwardResponse);
|
|
340
|
+
rpc CloseForward(CloseForwardRequest) returns (CloseForwardResponse);
|
|
341
|
+
rpc ResolveEndpoint(ResolveEndpointRequest) returns (ResolveEndpointResponse);
|
|
342
|
+
rpc WatchEvents(WatchEventsRequest) returns (stream TunnelEvent);
|
|
343
|
+
}
|
package/dist/TestMessage.d.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
export declare class TestMessage {
|
|
2
|
-
generateMessage(to: string, from: string, action: string, contents: string): {
|
|
3
|
-
envelope: {
|
|
4
|
-
to: string;
|
|
5
|
-
from: string;
|
|
6
|
-
timestamp: Date;
|
|
7
|
-
action: string;
|
|
8
|
-
};
|
|
9
|
-
contents: string;
|
|
10
|
-
};
|
|
11
|
-
signMessage(msg: string, key: any): any;
|
|
12
|
-
verifyMessage(msg: string, signature: any, key: any): any;
|
|
13
|
-
}
|
package/dist/TestMessage.js
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.TestMessage = void 0;
|
|
4
|
-
var crypto_1 = require("crypto");
|
|
5
|
-
var TestMessage = (function () {
|
|
6
|
-
function TestMessage() {
|
|
7
|
-
}
|
|
8
|
-
TestMessage.prototype.generateMessage = function (to, from, action, contents) {
|
|
9
|
-
return {
|
|
10
|
-
envelope: {
|
|
11
|
-
to: to,
|
|
12
|
-
from: from,
|
|
13
|
-
timestamp: new Date(),
|
|
14
|
-
action: action
|
|
15
|
-
},
|
|
16
|
-
contents: (contents) ? contents : (0, crypto_1.randomUUID)()
|
|
17
|
-
};
|
|
18
|
-
};
|
|
19
|
-
TestMessage.prototype.signMessage = function (msg, key) {
|
|
20
|
-
var s = key.createSign('sha1');
|
|
21
|
-
s.update(msg);
|
|
22
|
-
return s.sign();
|
|
23
|
-
};
|
|
24
|
-
TestMessage.prototype.verifyMessage = function (msg, signature, key) {
|
|
25
|
-
var v = key.createVerify('sha1');
|
|
26
|
-
v.update(msg);
|
|
27
|
-
return v.verify(signature);
|
|
28
|
-
};
|
|
29
|
-
return TestMessage;
|
|
30
|
-
}());
|
|
31
|
-
exports.TestMessage = TestMessage;
|
|
32
|
-
//# sourceMappingURL=TestMessage.js.map
|
package/dist/TestMessage.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"TestMessage.js","sourceRoot":"","sources":["../src/TestMessage.ts"],"names":[],"mappings":";;;AAAA,iCAAoC;AAEpC;IAAA;IAyBA,CAAC;IAvBU,qCAAe,GAAtB,UAAuB,EAAU,EAAE,IAAY,EAAE,MAAc,EAAE,QAAe;QAC5E,OAAO;YACH,QAAQ,EAAE;gBACN,EAAE,EAAE,EAAE;gBACN,IAAI,EAAE,IAAI;gBACV,SAAS,EAAE,IAAI,IAAI,EAAE;gBACrB,MAAM,EAAE,MAAM;aACjB;YACD,QAAQ,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAA,mBAAU,GAAE;SACjD,CAAA;IACL,CAAC;IAEM,iCAAW,GAAlB,UAAmB,GAAU,EAAE,GAAG;QAC9B,IAAM,CAAC,GAAG,GAAG,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;QACjC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACd,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC;IACpB,CAAC;IAEM,mCAAa,GAApB,UAAqB,GAAU,EAAE,SAAS,EAAE,GAAG;QAC3C,IAAM,CAAC,GAAG,GAAG,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;QACnC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACd,OAAO,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IAC/B,CAAC;IACL,kBAAC;AAAD,CAAC,AAzBD,IAyBC;AAzBY,kCAAW"}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
export declare class TestMessage {
|
|
2
|
-
generateMessage(to: string, from: string, action: string, contents: string): {
|
|
3
|
-
envelope: {
|
|
4
|
-
to: string;
|
|
5
|
-
from: string;
|
|
6
|
-
timestamp: Date;
|
|
7
|
-
action: string;
|
|
8
|
-
};
|
|
9
|
-
contents: string;
|
|
10
|
-
};
|
|
11
|
-
signMessage(msg: string, key: any): any;
|
|
12
|
-
verifyMessage(msg: string, signature: any, key: any): any;
|
|
13
|
-
}
|