Skip to content

Commit d397dc4

Browse files
Support connections over SSH / drivers (#1470)
* Add SSH tunneling support to MSSQL, MySQL, MySQLX, and PostgreSQL drivers * Fix build errors related to ssh2 module * Add SSH configuration options to MSSQL, MySQL, and PostgreSQL drivers * Use @sqltools/[email protected] * Use @sqltools/[email protected] * Use @sqltools/[email protected] * Make driver.sqlite use latest @sqltools/base-driver --------- Co-authored-by: John Murray <[email protected]>
1 parent 0adebf9 commit d397dc4

File tree

14 files changed

+367
-70
lines changed

14 files changed

+367
-70
lines changed

packages/driver.mssql/connection.schema.json

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,12 @@
8484
]
8585
}
8686
}
87+
},
88+
"ssh": {
89+
"title": "Over SSH",
90+
"type": "string",
91+
"enum": ["Enabled", "Disabled"],
92+
"default": "Disabled"
8793
}
8894
},
8995
"dependencies": {
@@ -219,9 +225,57 @@
219225
}
220226
}
221227
]
228+
},
229+
"ssh": {
230+
"oneOf": [
231+
{
232+
"properties": {
233+
"ssh": {
234+
"enum": ["Disabled"]
235+
}
236+
}
237+
},
238+
{
239+
"properties": {
240+
"ssh": {
241+
"enum": ["Enabled"]
242+
},
243+
"sshOptions": {
244+
"type": "object",
245+
"title": "SSH Connection Options",
246+
"properties": {
247+
"host": {
248+
"type": "string",
249+
"title": "Server Address",
250+
"minLength": 1
251+
},
252+
"port": {
253+
"type": "integer",
254+
"title": "Port",
255+
"default": 22,
256+
"minimum": 1
257+
},
258+
"username": {
259+
"type": "string",
260+
"title": "Username",
261+
"minLength": 1
262+
},
263+
"password": {
264+
"type": "string",
265+
"title": "Password"
266+
},
267+
"privateKeyPath": {
268+
"type": "string",
269+
"title": "Private Key File Path"
270+
}
271+
},
272+
"required": ["host", "username"]
273+
}
274+
},
275+
"required": ["sshOptions"]
276+
}
277+
]
222278
}
223279
},
224-
"required": [
225-
"connectionMethod"
226-
]
280+
"required": ["connectionMethod"]
227281
}

packages/driver.mssql/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@
5252
"build": "cross-env NODE_ENV=production concurrently \"npm:build:*\"",
5353
"build:ext": "yarn run compile:ext --define:process.env.NODE_ENV=\"'production'\" --minify",
5454
"build:ls": "yarn run compile:ls --define:process.env.NODE_ENV=\"'production'\" --minify",
55-
"esbuild": "esbuild --platform=node --tsconfig=./tsconfig.json --external:vscode --color=true --format=cjs",
55+
"esbuild": "esbuild --platform=node --tsconfig=./tsconfig.json --external:vscode --color=true --format=cjs --loader:.node=file",
5656
"prepackage": "yarn run build",
5757
"package": "vsce package --yarn -o .",
5858
"compile:ext": "yarn run esbuild --bundle ./src/extension.ts --outfile=./out/extension.js --target=es2017 --define:process.env.PRODUCT=\"'ext'\"",
@@ -66,7 +66,7 @@
6666
},
6767
"devDependencies": {
6868
"@sqltools/base-driver": "latest",
69-
"@sqltools/types": "^0.1.7",
69+
"@sqltools/types": "^0.2.0",
7070
"@types/lodash": "^4.14.123",
7171
"@types/mssql": "^4.0.12",
7272
"@types/vscode": "^1.72.0",

packages/driver.mssql/src/ls/driver.ts

Lines changed: 42 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -29,22 +29,49 @@ export default class MSSQL extends AbstractDriver<MSSQLLib.ConnectionPool, any>
2929
this.credentials.password = null;
3030
}
3131

32+
let pool: MSSQLLib.ConnectionPool;
3233

33-
const pool = new MSSQLLib.ConnectionPool(this.credentials.connectString || {
34-
database: this.credentials.database,
35-
connectionTimeout: this.credentials.connectionTimeout * 1000,
36-
server: this.credentials.server,
37-
user: this.credentials.username,
38-
password: this.credentials.password,
39-
domain: this.credentials.domain || undefined,
40-
port: this.credentials.port,
41-
...mssqlOptions,
42-
options: {
43-
...((mssqlOptions || {}).options || {}),
44-
encrypt: encryptAttempt,
45-
trustServerCertificate: trustServerCertificate,
46-
},
47-
});
34+
if (this.credentials.connectString) {
35+
pool = new MSSQLLib.ConnectionPool(this.credentials.connectString);
36+
} else {
37+
const poolConfig = {
38+
database: this.credentials.database,
39+
connectionTimeout: this.credentials.connectionTimeout * 1000,
40+
server: this.credentials.server,
41+
user: this.credentials.username,
42+
password: this.credentials.password,
43+
domain: this.credentials.domain || undefined,
44+
port: this.credentials.port,
45+
...mssqlOptions,
46+
options: {
47+
...((mssqlOptions || {}).options || {}),
48+
encrypt: encryptAttempt,
49+
trustServerCertificate: trustServerCertificate,
50+
},
51+
};
52+
53+
if (this.credentials.ssh === 'Enabled' && this.credentials.sshOptions) {
54+
const { port: localPort } = await this.createSshTunnel(
55+
{
56+
host: this.credentials.sshOptions.host,
57+
port: this.credentials.sshOptions.port,
58+
username: this.credentials.sshOptions.username,
59+
password: this.credentials.sshOptions.password,
60+
privateKeyPath: this.credentials.sshOptions.privateKeyPath,
61+
},
62+
{
63+
host: this.credentials.server,
64+
port: this.credentials.port,
65+
}
66+
);
67+
Object.assign(poolConfig, {
68+
server: 'localhost',
69+
port: localPort,
70+
});
71+
}
72+
73+
pool = new MSSQLLib.ConnectionPool(poolConfig);
74+
}
4875

4976
await new Promise((resolve, reject) => {
5077
pool.on('error', reject);

packages/driver.mssql/ui.schema.json

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,18 @@
11
{
2-
"ui:order": ["connectionMethod", "server", "port", "socketPath", "connectString", "database", "username", "usePassword","password", "mssqlOptions"],
2+
"ui:order": [
3+
"connectionMethod",
4+
"server",
5+
"port",
6+
"socketPath",
7+
"connectString",
8+
"database",
9+
"username",
10+
"usePassword",
11+
"password",
12+
"mssqlOptions",
13+
"ssh",
14+
"sshOptions"
15+
],
316
"password": { "ui:widget": "password" },
417
"askForPassword": { "ui:widget": "hidden" },
518
"socketPath": { "ui:widget": "file" },
@@ -23,4 +36,4 @@
2336
"ui:help": "The version of TDS to use (default: 7_4, available: 7_1, 7_2, 7_3_A, 7_3_B, 7_4)"
2437
}
2538
}
26-
}
39+
}

packages/driver.mysql/connection.schema.json

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,12 @@
106106
"title": "Connection Timeout",
107107
"type": "integer",
108108
"minimum": 0
109+
},
110+
"ssh": {
111+
"title": "Over SSH",
112+
"type": "string",
113+
"enum": ["Enabled", "Disabled"],
114+
"default": "Disabled"
109115
}
110116
},
111117
"dependencies": {
@@ -239,9 +245,57 @@
239245
}
240246
}
241247
]
248+
},
249+
"ssh": {
250+
"oneOf": [
251+
{
252+
"properties": {
253+
"ssh": {
254+
"enum": ["Disabled"]
255+
}
256+
}
257+
},
258+
{
259+
"properties": {
260+
"ssh": {
261+
"enum": ["Enabled"]
262+
},
263+
"sshOptions": {
264+
"type": "object",
265+
"title": "SSH Connection Options",
266+
"properties": {
267+
"host": {
268+
"type": "string",
269+
"title": "Server Address",
270+
"minLength": 1
271+
},
272+
"port": {
273+
"type": "integer",
274+
"title": "Port",
275+
"default": 22,
276+
"minimum": 1
277+
},
278+
"username": {
279+
"type": "string",
280+
"title": "Username",
281+
"minLength": 1
282+
},
283+
"password": {
284+
"type": "string",
285+
"title": "Password"
286+
},
287+
"privateKeyPath": {
288+
"type": "string",
289+
"title": "Private Key File Path"
290+
}
291+
},
292+
"required": ["host", "username"]
293+
}
294+
},
295+
"required": ["sshOptions"]
296+
}
297+
]
242298
}
243299
},
244-
"required": [
245-
"connectionMethod"
246-
]
300+
"required": ["connectionMethod"]
247301
}

packages/driver.mysql/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@
5252
"build": "cross-env NODE_ENV=production concurrently \"npm:build:*\"",
5353
"build:ext": "yarn run compile:ext --define:process.env.NODE_ENV=\"'production'\" --minify-whitespace",
5454
"build:ls": "yarn run compile:ls --define:process.env.NODE_ENV=\"'production'\" --minify-whitespace",
55-
"esbuild": "esbuild --platform=node --tsconfig=./tsconfig.json --external:vscode --color=true --format=cjs",
55+
"esbuild": "esbuild --platform=node --tsconfig=./tsconfig.json --external:vscode --color=true --format=cjs --loader:.node=file",
5656
"prepackage": "yarn run build",
5757
"package": "vsce package --yarn -o .",
5858
"compile:ext": "yarn run esbuild --bundle ./src/extension.ts --outfile=./out/extension.js --target=es2017 --define:process.env.PRODUCT=\"'ext'\"",
@@ -67,7 +67,7 @@
6767
"devDependencies": {
6868
"@mysql/xdevapi": "^8.0.20",
6969
"@sqltools/base-driver": "latest",
70-
"@sqltools/types": "^0.1.7",
70+
"@sqltools/types": "^0.2.0",
7171
"@types/lodash": "^4.14.123",
7272
"@types/mysql": "^2.15.12",
7373
"compare-versions": "3.6.0",

packages/driver.mysql/src/ls/default.ts

Lines changed: 43 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import generateId from '@sqltools/util/internal-id';
99

1010
export default class MySQLDefault extends AbstractDriver<MySQLLib.Pool, MySQLLib.PoolOptions> implements IConnectionDriver {
1111
queries = Queries;
12-
public open() {
12+
public async open() {
1313
if (this.connection) {
1414
return this.connection;
1515
}
@@ -22,20 +22,48 @@ export default class MySQLDefault extends AbstractDriver<MySQLLib.Pool, MySQLLib
2222
});
2323
}
2424

25-
const pool = MySQLLib.createPool(this.credentials.connectString || {
26-
connectTimeout: this.credentials.connectionTimeout * 1000,
27-
database: this.credentials.database,
28-
socketPath: this.credentials.socketPath,
29-
host: this.credentials.server,
30-
port: this.credentials.port,
31-
password: this.credentials.password,
32-
user: this.credentials.username,
33-
multipleStatements: true,
34-
dateStrings: true,
35-
bigNumberStrings: true,
36-
supportBigNumbers: true,
37-
...mysqlOptions
38-
});
25+
let pool: MySQLLib.Pool;
26+
27+
if (this.credentials.connectString) {
28+
pool = MySQLLib.createPool(this.credentials.connectString);
29+
} else {
30+
const poolConfig = {
31+
host: this.credentials.server,
32+
port: this.credentials.port,
33+
connectTimeout: this.credentials.connectionTimeout * 1000,
34+
database: this.credentials.database,
35+
socketPath: this.credentials.socketPath,
36+
password: this.credentials.password,
37+
user: this.credentials.username,
38+
multipleStatements: true,
39+
dateStrings: true,
40+
bigNumberStrings: true,
41+
supportBigNumbers: true,
42+
...mysqlOptions,
43+
};
44+
45+
if (this.credentials.ssh === 'Enabled' && this.credentials.sshOptions) {
46+
const { port: localPort } = await this.createSshTunnel(
47+
{
48+
host: this.credentials.sshOptions.host,
49+
port: this.credentials.sshOptions.port,
50+
username: this.credentials.sshOptions.username,
51+
password: this.credentials.sshOptions.password,
52+
privateKeyPath: this.credentials.sshOptions.privateKeyPath,
53+
},
54+
{
55+
host: this.credentials.server,
56+
port: this.credentials.port,
57+
}
58+
);
59+
Object.assign(poolConfig, {
60+
host: 'localhost',
61+
port: localPort,
62+
});
63+
}
64+
65+
pool = MySQLLib.createPool(poolConfig);
66+
}
3967

4068
return new Promise<MySQLLib.Pool>((resolve, reject) => {
4169
pool.getConnection((err, conn) => {

0 commit comments

Comments
 (0)