Skip to content

Commit f79602b

Browse files
authored
Merge pull request #1577 from RedisInsight/feature/RI-3266_ssh-tunnel
Feature/ri 3266 ssh tunnel
2 parents 33480b8 + 0b08864 commit f79602b

File tree

99 files changed

+4172
-1117
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

99 files changed

+4172
-1117
lines changed

.circleci/config.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ aliases:
8888
- mods-preview # OSS Standalone and all preview modules
8989
- oss-st-6-tls # OSS Standalone v6 with TLS enabled
9090
- oss-st-6-tls-auth # OSS Standalone v6 with TLS auth required
91+
- oss-st-6-tls-auth-ssh # OSS Standalone v6 with TLS auth required through ssh
9192
- oss-clu # OSS Cluster
9293
- oss-clu-tls # OSS Cluster with TLS enabled
9394
- oss-sent # OSS Sentinel

redisinsight/api/config/default.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ export default {
5555
staticContent: !!process.env.SERVER_STATIC_CONTENT || false,
5656
buildType: process.env.BUILD_TYPE || 'ELECTRON',
5757
appVersion: process.env.APP_VERSION || '2.0.0',
58-
requestTimeout: parseInt(process.env.REQUEST_TIMEOUT, 10) || 10000,
58+
requestTimeout: parseInt(process.env.REQUEST_TIMEOUT, 10) || 25000,
5959
excludeRoutes: [],
6060
excludeAuthRoutes: [],
6161
},

redisinsight/api/config/ormconfig.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { SettingsEntity } from 'src/modules/settings/entities/settings.entity';
1010
import { CaCertificateEntity } from 'src/modules/certificate/entities/ca-certificate.entity';
1111
import { ClientCertificateEntity } from 'src/modules/certificate/entities/client-certificate.entity';
1212
import { DatabaseEntity } from 'src/modules/database/entities/database.entity';
13+
import { SshOptionsEntity } from 'src/modules/ssh/entities/ssh-options.entity';
1314
import migrations from '../migration';
1415
import * as config from '../src/utils/config';
1516

@@ -31,6 +32,7 @@ const ormConfig = {
3132
PluginStateEntity,
3233
NotificationEntity,
3334
DatabaseAnalysisEntity,
35+
SshOptionsEntity,
3436
],
3537
migrations,
3638
};
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { MigrationInterface, QueryRunner } from "typeorm";
2+
3+
export class sshOptions1673035852335 implements MigrationInterface {
4+
name = 'sshOptions1673035852335'
5+
6+
public async up(queryRunner: QueryRunner): Promise<void> {
7+
await queryRunner.query(`CREATE TABLE "ssh_options" ("id" varchar PRIMARY KEY NOT NULL, "host" varchar NOT NULL, "port" integer NOT NULL, "encryption" varchar, "username" varchar, "password" varchar, "privateKey" varchar, "passphrase" varchar, "databaseId" varchar, CONSTRAINT "REL_fe3c3f8b1246e4824a3fb83047" UNIQUE ("databaseId"))`);
8+
await queryRunner.query(`CREATE TABLE "temporary_database_instance" ("id" varchar PRIMARY KEY NOT NULL, "host" varchar NOT NULL, "port" integer NOT NULL, "name" varchar NOT NULL, "username" varchar, "password" varchar, "tls" boolean, "verifyServerCert" boolean, "lastConnection" datetime, "caCertId" varchar, "clientCertId" varchar, "connectionType" varchar NOT NULL DEFAULT ('STANDALONE'), "nodes" varchar DEFAULT ('[]'), "nameFromProvider" varchar, "sentinelMasterName" varchar, "sentinelMasterUsername" varchar, "sentinelMasterPassword" varchar, "provider" varchar DEFAULT ('UNKNOWN'), "modules" varchar NOT NULL DEFAULT ('[]'), "db" integer, "encryption" varchar, "tlsServername" varchar, "new" boolean, "ssh" boolean, CONSTRAINT "FK_d1bc747b5938e22b4b708d8e9a5" FOREIGN KEY ("caCertId") REFERENCES "ca_certificate" ("id") ON DELETE SET NULL ON UPDATE NO ACTION, CONSTRAINT "FK_3b9b625266c00feb2d66a9f36e4" FOREIGN KEY ("clientCertId") REFERENCES "client_certificate" ("id") ON DELETE SET NULL ON UPDATE NO ACTION)`);
9+
await queryRunner.query(`INSERT INTO "temporary_database_instance"("id", "host", "port", "name", "username", "password", "tls", "verifyServerCert", "lastConnection", "caCertId", "clientCertId", "connectionType", "nodes", "nameFromProvider", "sentinelMasterName", "sentinelMasterUsername", "sentinelMasterPassword", "provider", "modules", "db", "encryption", "tlsServername", "new") SELECT "id", "host", "port", "name", "username", "password", "tls", "verifyServerCert", "lastConnection", "caCertId", "clientCertId", "connectionType", "nodes", "nameFromProvider", "sentinelMasterName", "sentinelMasterUsername", "sentinelMasterPassword", "provider", "modules", "db", "encryption", "tlsServername", "new" FROM "database_instance"`);
10+
await queryRunner.query(`DROP TABLE "database_instance"`);
11+
await queryRunner.query(`ALTER TABLE "temporary_database_instance" RENAME TO "database_instance"`);
12+
await queryRunner.query(`CREATE TABLE "temporary_ssh_options" ("id" varchar PRIMARY KEY NOT NULL, "host" varchar NOT NULL, "port" integer NOT NULL, "encryption" varchar, "username" varchar, "password" varchar, "privateKey" varchar, "passphrase" varchar, "databaseId" varchar, CONSTRAINT "REL_fe3c3f8b1246e4824a3fb83047" UNIQUE ("databaseId"), CONSTRAINT "FK_fe3c3f8b1246e4824a3fb83047d" FOREIGN KEY ("databaseId") REFERENCES "database_instance" ("id") ON DELETE CASCADE ON UPDATE NO ACTION)`);
13+
await queryRunner.query(`INSERT INTO "temporary_ssh_options"("id", "host", "port", "encryption", "username", "password", "privateKey", "passphrase", "databaseId") SELECT "id", "host", "port", "encryption", "username", "password", "privateKey", "passphrase", "databaseId" FROM "ssh_options"`);
14+
await queryRunner.query(`DROP TABLE "ssh_options"`);
15+
await queryRunner.query(`ALTER TABLE "temporary_ssh_options" RENAME TO "ssh_options"`);
16+
}
17+
18+
public async down(queryRunner: QueryRunner): Promise<void> {
19+
await queryRunner.query(`ALTER TABLE "ssh_options" RENAME TO "temporary_ssh_options"`);
20+
await queryRunner.query(`CREATE TABLE "ssh_options" ("id" varchar PRIMARY KEY NOT NULL, "host" varchar NOT NULL, "port" integer NOT NULL, "encryption" varchar, "username" varchar, "password" varchar, "privateKey" varchar, "passphrase" varchar, "databaseId" varchar, CONSTRAINT "REL_fe3c3f8b1246e4824a3fb83047" UNIQUE ("databaseId"))`);
21+
await queryRunner.query(`INSERT INTO "ssh_options"("id", "host", "port", "encryption", "username", "password", "privateKey", "passphrase", "databaseId") SELECT "id", "host", "port", "encryption", "username", "password", "privateKey", "passphrase", "databaseId" FROM "temporary_ssh_options"`);
22+
await queryRunner.query(`DROP TABLE "temporary_ssh_options"`);
23+
await queryRunner.query(`ALTER TABLE "database_instance" RENAME TO "temporary_database_instance"`);
24+
await queryRunner.query(`CREATE TABLE "database_instance" ("id" varchar PRIMARY KEY NOT NULL, "host" varchar NOT NULL, "port" integer NOT NULL, "name" varchar NOT NULL, "username" varchar, "password" varchar, "tls" boolean, "verifyServerCert" boolean, "lastConnection" datetime, "caCertId" varchar, "clientCertId" varchar, "connectionType" varchar NOT NULL DEFAULT ('STANDALONE'), "nodes" varchar DEFAULT ('[]'), "nameFromProvider" varchar, "sentinelMasterName" varchar, "sentinelMasterUsername" varchar, "sentinelMasterPassword" varchar, "provider" varchar DEFAULT ('UNKNOWN'), "modules" varchar NOT NULL DEFAULT ('[]'), "db" integer, "encryption" varchar, "tlsServername" varchar, "new" boolean, CONSTRAINT "FK_d1bc747b5938e22b4b708d8e9a5" FOREIGN KEY ("caCertId") REFERENCES "ca_certificate" ("id") ON DELETE SET NULL ON UPDATE NO ACTION, CONSTRAINT "FK_3b9b625266c00feb2d66a9f36e4" FOREIGN KEY ("clientCertId") REFERENCES "client_certificate" ("id") ON DELETE SET NULL ON UPDATE NO ACTION)`);
25+
await queryRunner.query(`INSERT INTO "database_instance"("id", "host", "port", "name", "username", "password", "tls", "verifyServerCert", "lastConnection", "caCertId", "clientCertId", "connectionType", "nodes", "nameFromProvider", "sentinelMasterName", "sentinelMasterUsername", "sentinelMasterPassword", "provider", "modules", "db", "encryption", "tlsServername", "new") SELECT "id", "host", "port", "name", "username", "password", "tls", "verifyServerCert", "lastConnection", "caCertId", "clientCertId", "connectionType", "nodes", "nameFromProvider", "sentinelMasterName", "sentinelMasterUsername", "sentinelMasterPassword", "provider", "modules", "db", "encryption", "tlsServername", "new" FROM "temporary_database_instance"`);
26+
await queryRunner.query(`DROP TABLE "temporary_database_instance"`);
27+
await queryRunner.query(`DROP TABLE "ssh_options"`);
28+
}
29+
30+
}

redisinsight/api/migration/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import { databaseAnalysisExpirationGroups1664886479051 } from './1664886479051-d
2222
import { workbenchExecutionTime1667368983699 } from './1667368983699-workbench-execution-time';
2323
import { database1667477693934 } from './1667477693934-database';
2424
import { databaseNew1670252337342 } from './1670252337342-database-new';
25+
import { sshOptions1673035852335 } from './1673035852335-ssh-options';
2526

2627
export default [
2728
initialMigration1614164490968,
@@ -48,4 +49,5 @@ export default [
4849
workbenchExecutionTime1667368983699,
4950
database1667477693934,
5051
databaseNew1670252337342,
52+
sshOptions1673035852335,
5153
];

redisinsight/api/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
"body-parser": "^1.19.0",
5454
"class-transformer": "^0.2.3",
5555
"class-validator": "^0.12.2",
56+
"detect-port": "^1.5.1",
5657
"dotenv": "^16.0.0",
5758
"express": "^4.17.1",
5859
"fs-extra": "^10.0.0",
@@ -67,6 +68,7 @@
6768
"socket.io": "^4.4.0",
6869
"source-map-support": "^0.5.19",
6970
"sqlite3": "^5.0.11",
71+
"ssh2": "^1.11.0",
7072
"swagger-ui-express": "^4.1.4",
7173
"typeorm": "^0.3.9",
7274
"uuid": "^8.3.2",
@@ -84,6 +86,7 @@
8486
"@types/lodash": "^4.14.167",
8587
"@types/node": "14.14.10",
8688
"@types/socket.io": "^3.0.2",
89+
"@types/ssh2": "^1.11.6",
8790
"@types/supertest": "^2.0.8",
8891
"@typescript-eslint/eslint-plugin": "^4.8.1",
8992
"@typescript-eslint/parser": "^4.8.1",

redisinsight/api/src/__mocks__/common.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ export const mockRepository = jest.fn(() => ({
5454
findOneBy: jest.fn(),
5555
find: jest.fn(),
5656
findByIds: jest.fn(),
57+
merge: jest.fn(),
5758
create: jest.fn(),
5859
save: jest.fn(),
5960
insert: jest.fn(),

redisinsight/api/src/__mocks__/databases.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,12 @@ import { pick } from 'lodash';
99
import { RedisDatabaseInfoResponse } from 'src/modules/database/dto/redis-info.dto';
1010
import { DatabaseOverview } from 'src/modules/database/models/database-overview';
1111
import { ClientContext, ClientMetadata } from 'src/common/models';
12+
import {
13+
mockSshOptionsBasic,
14+
mockSshOptionsBasicEntity,
15+
mockSshOptionsPrivateKey,
16+
mockSshOptionsPrivateKeyEntity,
17+
} from 'src/__mocks__/ssh';
1218

1319
export const mockDatabaseId = 'a77b23c1-7816-4ea4-b61f-d37795a0f805-db-id';
1420

@@ -34,6 +40,29 @@ export const mockDatabaseEntity = Object.assign(new DatabaseEntity(), {
3440
encryption: null,
3541
});
3642

43+
export const mockDatabaseWithSshBasic = Object.assign(new Database(), {
44+
...mockDatabase,
45+
ssh: true,
46+
sshOptions: mockSshOptionsBasic,
47+
});
48+
49+
export const mockDatabaseWithSshBasicEntity = Object.assign(new DatabaseEntity(), {
50+
...mockDatabaseWithSshBasic,
51+
encryption: null,
52+
sshOptions: mockSshOptionsBasicEntity,
53+
});
54+
55+
export const mockDatabaseWithSshPrivateKey = Object.assign(new Database(), {
56+
...mockDatabase,
57+
ssh: true,
58+
sshOptions: mockSshOptionsPrivateKey,
59+
});
60+
61+
export const mockDatabaseWithSshPrivateKeyEntity = Object.assign(new DatabaseEntity(), {
62+
...mockDatabaseWithSshPrivateKey,
63+
sshOptions: mockSshOptionsPrivateKeyEntity,
64+
});
65+
3766
export const mockDatabaseWithAuth = Object.assign(new Database(), {
3867
...mockDatabase,
3968
username: 'some username',
@@ -181,6 +210,7 @@ export const mockDatabaseService = jest.fn(() => ({
181210

182211
export const mockDatabaseConnectionService = jest.fn(() => ({
183212
getOrCreateClient: jest.fn().mockResolvedValue(mockIORedisClient),
213+
createClient: jest.fn().mockResolvedValue(mockIORedisClient),
184214
}));
185215

186216
export const mockDatabaseInfoProvider = jest.fn(() => ({

redisinsight/api/src/__mocks__/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,4 @@ export * from './redis-enterprise';
1818
export * from './redis-sentinel';
1919
export * from './database-import';
2020
export * from './redis-client';
21+
export * from './ssh';
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { EncryptionStrategy } from 'src/modules/encryption/models';
2+
import { SshOptions } from 'src/modules/ssh/models/ssh-options';
3+
import { SshOptionsEntity } from 'src/modules/ssh/entities/ssh-options.entity';
4+
5+
export const mockSshOptionsId = 'a77b23c1-7816-4ea4-b61f-d37795a0f805-ssh-id';
6+
7+
export const mockSshOptionsUsernamePlain = 'ssh-username';
8+
export const mockSshOptionsUsernameEncrypted = 'ssh.username.ENCRYPTED';
9+
export const mockSshOptionsPasswordPlain = 'ssh-password';
10+
export const mockSshOptionsPasswordEncrypted = 'ssh.password.ENCRYPTED';
11+
export const mockSshOptionsPrivateKeyPlain = '-----BEGIN OPENSSH PRIVATE KEY-----\nssh-private-key';
12+
export const mockSshOptionsPrivateKeyEncrypted = 'ssh.privateKey.ENCRYPTED';
13+
export const mockSshOptionsPassphrasePlain = 'ssh-passphrase';
14+
export const mockSshOptionsPassphraseEncrypted = 'ssh.passphrase.ENCRYPTED';
15+
16+
export const mockSshOptionsBasic = Object.assign(new SshOptions(), {
17+
id: mockSshOptionsId,
18+
host: 'ssh.host.test',
19+
port: 22,
20+
username: mockSshOptionsUsernamePlain,
21+
password: mockSshOptionsPasswordPlain,
22+
privateKey: null,
23+
passphrase: null,
24+
});
25+
26+
export const mockSshOptionsBasicEntity = Object.assign(new SshOptionsEntity(), {
27+
...mockSshOptionsBasic,
28+
username: mockSshOptionsUsernameEncrypted,
29+
password: mockSshOptionsPasswordEncrypted,
30+
encryption: EncryptionStrategy.KEYTAR,
31+
});
32+
33+
export const mockSshOptionsPrivateKey = Object.assign(new SshOptions(), {
34+
...mockSshOptionsBasic,
35+
password: null,
36+
privateKey: mockSshOptionsPrivateKeyPlain,
37+
passphrase: mockSshOptionsPassphrasePlain,
38+
});
39+
40+
export const mockSshOptionsPrivateKeyEntity = Object.assign(new SshOptionsEntity(), {
41+
...mockSshOptionsBasicEntity,
42+
password: null,
43+
privateKey: mockSshOptionsPrivateKeyEncrypted,
44+
passphrase: mockSshOptionsPassphraseEncrypted,
45+
});
46+
47+
export const mockSshTunnelProvider = jest.fn(() => {
48+
49+
});

0 commit comments

Comments
 (0)