Skip to content

Commit

Permalink
feat: SSH Tunnel functionality (#81)
Browse files Browse the repository at this point in the history
* added ssh-tunnel-functionality for mysql-connections

* remove autoformat-stuff

* added identity for using ssh-key

* added identity to mysqlclient to use sshkey

* removed debug console.log

* added ssh-tunnel-functionality for postgresqlclient

* changed naming to sshKey for sshKey-input

* refactoring code

* fix lint

* set dbConfig.ssl to null initially
  • Loading branch information
digitalgopnik committed Jul 5, 2021
1 parent 0db5ebd commit 1801bef
Show file tree
Hide file tree
Showing 13 changed files with 308 additions and 23 deletions.
1 change: 1 addition & 0 deletions package.json
Expand Up @@ -102,6 +102,7 @@
"source-map-support": "^0.5.16",
"spectre.css": "^0.5.9",
"sql-formatter": "^4.0.2",
"ssh2-promise": "^0.1.7",
"v-mask": "^2.2.4",
"vue-i18n": "^8.24.4",
"vuedraggable": "^2.24.3",
Expand Down
28 changes: 24 additions & 4 deletions src/main/ipc-handlers/connection.js
Expand Up @@ -24,13 +24,23 @@ export default connections => {
};
}

const connection = ClientsFactory.getConnection({
client: conn.client,
params
});
if (conn.ssh) {
params.ssh = {
host: conn.sshHost,
username: conn.sshUser,
password: conn.sshPass,
port: conn.sshPort ? conn.sshPort : 22,
identity: conn.sshKey
};
}

try {
const connection = await ClientsFactory.getConnection({
client: conn.client,
params
});
await connection.connect();

await connection.select('1+1').run();
connection.destroy();

Expand Down Expand Up @@ -66,6 +76,16 @@ export default connections => {
};
}

if (conn.ssh) {
params.ssh = {
host: conn.sshHost,
username: conn.sshUser,
password: conn.sshPass,
port: conn.sshPort ? conn.sshPort : 22,
identity: conn.sshKey
};
}

try {
const connection = ClientsFactory.getConnection({
client: conn.client,
Expand Down
4 changes: 4 additions & 0 deletions src/main/libs/ClientsFactory.js
Expand Up @@ -12,6 +12,10 @@ export class ClientsFactory {
* @param {String} args.params.host
* @param {Number} args.params.port
* @param {String} args.params.password
* @param {String} args.params.ssh.host
* @param {String} args.params.ssh.username
* @param {String} args.params.ssh.password
* @param {Number} args.params.ssh.port
* @param {Number=} args.poolSize
* @returns Database Connection
* @memberof ClientsFactory
Expand Down
29 changes: 26 additions & 3 deletions src/main/libs/clients/MySQLClient.js
Expand Up @@ -2,6 +2,7 @@
import mysql from 'mysql2/promise';
import { AntaresCore } from '../AntaresCore';
import dataTypes from 'common/data-types/mysql';
import * as SSH2Promise from 'ssh2-promise';

export class MySQLClient extends AntaresCore {
constructor (args) {
Expand Down Expand Up @@ -104,11 +105,32 @@ export class MySQLClient extends AntaresCore {
async connect () {
delete this._params.application_name;

if (!this._poolSize)
this._connection = await mysql.createConnection(this._params);
const dbConfig = {
host: this._params.host,
port: this._params.port,
user: this._params.user,
password: this._params.password,
ssl: null
};

if (this._params.database?.length) dbConfig.database = this._params.database;

if (this._params.ssl) dbConfig.ssl = { ...this._params.ssl };

if (this._params.ssh) {
this._ssh = new SSH2Promise({ ...this._params.ssh });

this._tunnel = await this._ssh.addTunnel({
remoteAddr: this._params.host,
remotePort: this._params.port
});
dbConfig.port = this._tunnel.localPort;
}

if (!this._poolSize) this._connection = await mysql.createConnection(dbConfig);
else {
this._connection = mysql.createPool({
...this._params,
...dbConfig,
connectionLimit: this._poolSize,
typeCast: (field, next) => {
if (field.type === 'DATETIME')
Expand All @@ -125,6 +147,7 @@ export class MySQLClient extends AntaresCore {
*/
destroy () {
this._connection.end();
if (this._ssh) this._ssh.close();
}

/**
Expand Down
28 changes: 26 additions & 2 deletions src/main/libs/clients/PostgreSQLClient.js
Expand Up @@ -3,6 +3,7 @@ import { Pool, Client, types } from 'pg';
import { parse } from 'pgsql-ast-parser';
import { AntaresCore } from '../AntaresCore';
import dataTypes from 'common/data-types/postgresql';
import * as SSH2Promise from 'ssh2-promise';

function pgToString (value) {
return value.toString();
Expand Down Expand Up @@ -51,13 +52,35 @@ export class PostgreSQLClient extends AntaresCore {
* @memberof PostgreSQLClient
*/
async connect () {
const dbConfig = {
host: this._params.host,
port: this._params.port,
user: this._params.user,
password: this._params.password,
ssl: null
};

if (this._params.database?.length) dbConfig.database = this._params.database;

if (this._params.ssl) dbConfig.ssl = { ...this._params.ssl };

if (this._params.ssh) {
this._ssh = new SSH2Promise({ ...this._params.ssh });

this._tunnel = await this._ssh.addTunnel({
remoteAddr: this._params.host,
remotePort: this._params.port
});
dbConfig.port = this._tunnel.localPort;
}

if (!this._poolSize) {
const client = new Client(this._params);
const client = new Client(dbConfig);
await client.connect();
this._connection = client;
}
else {
const pool = new Pool({ ...this._params, max: this._poolSize });
const pool = new Pool({ ...dbConfig, max: this._poolSize });
this._connection = pool;
}
}
Expand All @@ -67,6 +90,7 @@ export class PostgreSQLClient extends AntaresCore {
*/
destroy () {
this._connection.end();
if (this._ssh) this._ssh.close();
}

/**
Expand Down
100 changes: 99 additions & 1 deletion src/renderer/components/ModalEditConnection.vue
Expand Up @@ -28,6 +28,13 @@
>
<a class="tab-link">{{ $t('word.ssl') }}</a>
</li>
<li
class="tab-item"
:class="{'active': selectedTab === 'ssh'}"
@click="selectTab('ssh')"
>
<a class="c-hand">{{ $t('word.sshTunnel') }}</a>
</li>
</ul>
</div>
<div v-if="selectedTab === 'general'" class="panel-body py-0">
Expand Down Expand Up @@ -208,7 +215,6 @@
/>
</div>
</div>

<div class="form-group">
<div class="col-4 col-sm-12">
<label class="form-label">{{ $t('word.ciphers') }}</label>
Expand All @@ -231,6 +237,95 @@
:status="toast.status"
/>
</div>
<div v-if="selectedTab === 'ssh'" class="panel-body py-0">
<div class="container">
<form class="form-horizontal">
<div class="form-group">
<div class="col-4 col-sm-12">
<label class="form-label">
{{ $t('message.enableSsh') }}
</label>
</div>
<div class="col-8 col-sm-12">
<label class="form-switch d-inline-block" @click.prevent="toggleSsh">
<input type="checkbox" :checked="localConnection.ssh">
<i class="form-icon" />
</label>
</div>
</div>
<fieldset class="m-0" :disabled="isTesting || !localConnection.ssh">
<div class="form-group">
<div class="col-4 col-sm-12">
<label class="form-label">{{ $t('word.hostName') }}/IP</label>
</div>
<div class="col-8 col-sm-12">
<input
v-model="localConnection.sshHost"
class="form-input"
type="text"
>
</div>
</div>
<div class="form-group">
<div class="col-4 col-sm-12">
<label class="form-label">{{ $t('word.user') }}</label>
</div>
<div class="col-8 col-sm-12">
<input
v-model="localConnection.sshUser"
class="form-input"
type="text"
>
</div>
</div>
<div class="form-group">
<div class="col-4 col-sm-12">
<label class="form-label">{{ $t('word.password') }}</label>
</div>
<div class="col-8 col-sm-12">
<input
v-model="localConnection.sshPass"
class="form-input"
type="password"
>
</div>
</div>
<div class="form-group">
<div class="col-4 col-sm-12">
<label class="form-label">{{ $t('word.port') }}</label>
</div>
<div class="col-8 col-sm-12">
<input
v-model="localConnection.sshPort"
class="form-input"
type="number"
min="1"
max="65535"
>
</div>
</div>
<div class="form-group">
<div class="col-4 col-sm-12">
<label class="form-label">{{ $t('word.privateKey') }}</label>
</div>
<div class="col-8 col-sm-12">
<BaseUploadInput
:value="localConnection.sshKey"
:message="$t('word.browse')"
@clear="pathClear('sshKey')"
@change="pathSelection($event, 'sshKey')"
/>
</div>
</div>
</fieldset>
</form>
</div>
<BaseToast
class="mb-2"
:message="toast.message"
:status="toast.status"
/>
</div>
<div class="modal-footer text-light">
<button
class="btn btn-gray mr-2"
Expand Down Expand Up @@ -369,6 +464,9 @@ export default {
toggleSsl () {
this.localConnection.ssl = !this.localConnection.ssl;
},
toggleSsh () {
this.localConnection.ssh = !this.localConnection.ssh;
},
pathSelection (event, name) {
const { files } = event.target;
if (!files.length) return;
Expand Down

0 comments on commit 1801bef

Please sign in to comment.