Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: SSH Tunnel functionality #81

Merged
merged 12 commits into from
Jul 5, 2021
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Loading