Skip to content

Commit 7299a0d

Browse files
authored
Merge pull request #1605 from RedisInsight/feature/RI-3932-import_ssh
#RI-3932 import ssh
2 parents 0e31c20 + 6d865c5 commit 7299a0d

29 files changed

+1160
-75
lines changed

redisinsight/api/src/__mocks__/database-import.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { mockCaCertificate, mockClientCertificate } from 'src/__mocks__/certific
66
import {
77
InvalidCaCertificateBodyException, InvalidCertificateNameException,
88
} from 'src/modules/database-import/exceptions';
9+
import { mockSshOptionsPrivateKey } from 'src/__mocks__/ssh';
910

1011
export const mockDatabasesToImportArray = new Array(10).fill(mockSentinelDatabaseWithTlsAuth);
1112

@@ -87,3 +88,10 @@ export const mockCertificateImportService = jest.fn(() => ({
8788
processCaCertificate: jest.fn().mockResolvedValue(mockCaCertificate),
8889
processClientCertificate: jest.fn().mockResolvedValue(mockClientCertificate),
8990
}));
91+
92+
export const mockSshImportService = jest.fn(() => ({
93+
processSshOptions: jest.fn().mockResolvedValue({
94+
...mockSshOptionsPrivateKey,
95+
id: undefined,
96+
}),
97+
}));

redisinsight/api/src/common/utils/certificate-import.util.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,6 @@ import { readFileSync } from 'fs';
33

44
export const isValidPemCertificate = (cert: string): boolean => cert.startsWith('-----BEGIN CERTIFICATE-----');
55
export const isValidPemPrivateKey = (cert: string): boolean => cert.startsWith('-----BEGIN PRIVATE KEY-----');
6+
export const isValidSshPrivateKey = (cert: string): boolean => cert.startsWith('-----BEGIN OPENSSH PRIVATE KEY-----');
67
export const getPemBodyFromFileSync = (path: string): string => readFileSync(path).toString('utf8');
78
export const getCertNameFromFilename = (path: string): string => parse(path).name;

redisinsight/api/src/constants/error-messages.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ export default {
1818

1919
CA_CERT_EXIST: 'This ca certificate name is already in use.',
2020
INVALID_CA_BODY: 'Invalid CA body',
21+
INVALID_SSH_PRIVATE_KEY_BODY: 'Invalid SSH private key body',
22+
SSH_AGENTS_ARE_NOT_SUPPORTED: 'SSH Agents are not supported',
23+
INVALID_SSH_BODY: 'Invalid SSH body',
2124
INVALID_CERTIFICATE_BODY: 'Invalid certificate body',
2225
INVALID_PRIVATE_KEY: 'Invalid private key',
2326
CERTIFICATE_NAME_IS_NOT_DEFINED: 'Certificate name is not defined',

redisinsight/api/src/modules/database-import/database-import.module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@ import { DatabaseImportController } from 'src/modules/database-import/database-i
33
import { DatabaseImportService } from 'src/modules/database-import/database-import.service';
44
import { DatabaseImportAnalytics } from 'src/modules/database-import/database-import.analytics';
55
import { CertificateImportService } from 'src/modules/database-import/certificate-import.service';
6+
import { SshImportService } from 'src/modules/database-import/ssh-import.service';
67

78
@Module({
89
controllers: [DatabaseImportController],
910
providers: [
1011
DatabaseImportService,
1112
CertificateImportService,
13+
SshImportService,
1214
DatabaseImportAnalytics,
1315
],
1416
})

redisinsight/api/src/modules/database-import/database-import.service.spec.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
mockDatabaseImportAnalytics,
77
mockDatabaseImportFile,
88
mockDatabaseImportResponse,
9+
mockSshImportService,
910
MockType,
1011
} from 'src/__mocks__';
1112
import { DatabaseRepository } from 'src/modules/database/repositories/database.repository';
@@ -18,9 +19,10 @@ import {
1819
InvalidCaCertificateBodyException, InvalidCertificateNameException, InvalidClientCertificateBodyException,
1920
NoDatabaseImportFileProvidedException,
2021
SizeLimitExceededDatabaseImportFileException,
21-
UnableToParseDatabaseImportFileException
22+
UnableToParseDatabaseImportFileException,
2223
} from 'src/modules/database-import/exceptions';
2324
import { CertificateImportService } from 'src/modules/database-import/certificate-import.service';
25+
import { SshImportService } from 'src/modules/database-import/ssh-import.service';
2426

2527
describe('DatabaseImportService', () => {
2628
let service: DatabaseImportService;
@@ -45,6 +47,10 @@ describe('DatabaseImportService', () => {
4547
provide: CertificateImportService,
4648
useFactory: mockCertificateImportService,
4749
},
50+
{
51+
provide: SshImportService,
52+
useFactory: mockSshImportService,
53+
},
4854
{
4955
provide: DatabaseImportAnalytics,
5056
useFactory: mockDatabaseImportAnalytics,

redisinsight/api/src/modules/database-import/database-import.service.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
} from 'src/modules/database-import/exceptions';
2323
import { ValidationException } from 'src/common/exceptions';
2424
import { CertificateImportService } from 'src/modules/database-import/certificate-import.service';
25+
import { SshImportService } from 'src/modules/database-import/ssh-import.service';
2526

2627
@Injectable()
2728
export class DatabaseImportService {
@@ -51,10 +52,18 @@ export class DatabaseImportService {
5152
['sentinelMasterPassword', [
5253
'sentinelMaster.password', 'sentinelOptions.nodePassword', 'sentinelOptions.sentinelPassword',
5354
]],
55+
['sshHost', ['sshOptions.host', 'ssh_host', 'sshHost']],
56+
['sshPort', ['sshOptions.port', 'ssh_port', 'sshPort']],
57+
['sshUsername', ['sshOptions.username', 'ssh_user', 'sshUser']],
58+
['sshPassword', ['sshOptions.password', 'ssh_password', 'sshPassword']],
59+
['sshPrivateKey', ['sshOptions.privateKey', 'sshOptions.privatekey', 'ssh_private_key_path', 'sshKeyFile']],
60+
['sshPassphrase', ['sshOptions.passphrase', 'sshKeyPassphrase']],
61+
['sshAgentPath', ['ssh_agent_path']],
5462
];
5563

5664
constructor(
5765
private readonly certificateImportService: CertificateImportService,
66+
private readonly sshImportService: SshImportService,
5867
private readonly databaseRepository: DatabaseRepository,
5968
private readonly analytics: DatabaseImportAnalytics,
6069
) {}
@@ -159,7 +168,7 @@ export class DatabaseImportService {
159168
if (data?.sentinelMasterName) {
160169
data.sentinelMaster = {
161170
name: data.sentinelMasterName,
162-
username: data.sentinelMasterUsername,
171+
username: data.sentinelMasterUsername || undefined,
163172
password: data.sentinelMasterPassword,
164173
};
165174
data.nodes = [{
@@ -168,6 +177,17 @@ export class DatabaseImportService {
168177
}];
169178
}
170179

180+
if (data?.sshHost || data?.sshAgentPath) {
181+
data.ssh = true;
182+
try {
183+
data.sshOptions = await this.sshImportService.processSshOptions(data);
184+
} catch (e) {
185+
status = DatabaseImportStatus.Partial;
186+
data.ssh = false;
187+
errors.push(e);
188+
}
189+
}
190+
171191
if (data?.tlsCaCert) {
172192
try {
173193
data.tls = true;

redisinsight/api/src/modules/database-import/dto/import.database.dto.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { UseClientCertificateDto } from 'src/modules/certificate/dto/use.client-
1414
export class ImportDatabaseDto extends PickType(Database, [
1515
'host', 'port', 'name', 'db', 'username', 'password',
1616
'connectionType', 'tls', 'verifyServerCert', 'sentinelMaster', 'nodes',
17-
'new',
17+
'new', 'ssh', 'sshOptions',
1818
] as const) {
1919
@Expose()
2020
@IsNotEmpty()

redisinsight/api/src/modules/database-import/exceptions/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ export * from './invalid-ca-certificate-body.exception';
22
export * from './invalid-client-certificate-body.exception';
33
export * from './invalid-client-private-key.exception';
44
export * from './invalid-certificate-name.exception';
5+
export * from './invalid-ssh-body.exception';
6+
export * from './invalid-ssh-private-key-body.exception';
57
export * from './size-limit-exceeded-database-import-file.exception';
68
export * from './no-database-import-file-provided.exception';
79
export * from './unable-to-parse-database-import-file.exception';
10+
export * from './ssh-agents-are-not-supported.exception';
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { HttpException } from '@nestjs/common';
2+
import ERROR_MESSAGES from 'src/constants/error-messages';
3+
4+
export class InvalidSshBodyException extends HttpException {
5+
constructor(message: string = ERROR_MESSAGES.INVALID_SSH_BODY) {
6+
const response = {
7+
message,
8+
statusCode: 400,
9+
error: 'Invalid SSH body',
10+
};
11+
12+
super(response, 400);
13+
}
14+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { HttpException } from '@nestjs/common';
2+
import ERROR_MESSAGES from 'src/constants/error-messages';
3+
4+
export class InvalidSshPrivateKeyBodyException extends HttpException {
5+
constructor(message: string = ERROR_MESSAGES.INVALID_SSH_PRIVATE_KEY_BODY) {
6+
const response = {
7+
message,
8+
statusCode: 400,
9+
error: 'Invalid SSH Private Key Body',
10+
};
11+
12+
super(response, 400);
13+
}
14+
}

0 commit comments

Comments
 (0)