Skip to content

Commit fec4e82

Browse files
author
Artem
committed
#RI-5072 add env key encryption strategy
1 parent 0912568 commit fec4e82

12 files changed

+310
-6
lines changed

redisinsight/api/config/default.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ export default {
5555
pluginsAssetsUri: '/static/resources/plugins',
5656
base: process.env.RI_BASE || '/',
5757
secretStoragePassword: process.env.SECRET_STORAGE_PASSWORD,
58+
encryptionKey: process.env.RI_ENCRYPTION_KEY,
5859
tlsCert: process.env.RI_SERVER_TLS_CERT,
5960
tlsKey: process.env.RI_SERVER_TLS_KEY,
6061
staticContent: !!process.env.SERVER_STATIC_CONTENT || false,

redisinsight/api/src/__mocks__/encryption.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { EncryptionStrategy } from 'src/modules/encryption/models';
22

33
export const mockDataToEncrypt = 'stringtoencrypt';
44
export const mockKeytarPassword = 'somepassword';
5+
export const mockEncryptionKey = 'somepassword';
56

67
export const mockEncryptionStrategy = EncryptionStrategy.KEYTAR;
78

@@ -10,6 +11,13 @@ export const mockEncryptResult = {
1011
encryption: mockEncryptionStrategy,
1112
};
1213

14+
export const mockKeyEncryptionStrategy = EncryptionStrategy.KEY;
15+
16+
export const mockKeyEncryptResult = {
17+
data: '4a558dfef5c1abbdf745232614194ee9',
18+
encryption: mockKeyEncryptionStrategy,
19+
};
20+
1321
export const mockEncryptionService = jest.fn(() => ({
1422
getAvailableEncryptionStrategies: jest.fn(),
1523
encrypt: jest.fn(),
@@ -22,6 +30,12 @@ export const mockEncryptionStrategyInstance = jest.fn(() => ({
2230
decrypt: jest.fn(),
2331
}));
2432

33+
export const mockKeyEncryptionStrategyInstance = jest.fn(() => ({
34+
isAvailable: jest.fn(),
35+
encrypt: jest.fn(),
36+
decrypt: jest.fn(),
37+
}));
38+
2539
export const mockKeytarModule = {
2640
getPassword: jest.fn(),
2741
setPassword: jest.fn(),

redisinsight/api/src/modules/encryption/encryption.module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { Module } from '@nestjs/common';
22
import { PlainEncryptionStrategy } from 'src/modules/encryption/strategies/plain-encryption.strategy';
33
import { KeytarEncryptionStrategy } from 'src/modules/encryption/strategies/keytar-encryption.strategy';
44
import { EncryptionService } from 'src/modules/encryption/encryption.service';
5+
import { KeyEncryptionStrategy } from 'src/modules/encryption/strategies/key-encryption.strategy';
56

67
@Module({})
78
export class EncryptionModule {
@@ -11,6 +12,7 @@ export class EncryptionModule {
1112
providers: [
1213
PlainEncryptionStrategy,
1314
KeytarEncryptionStrategy,
15+
KeyEncryptionStrategy,
1416
EncryptionService,
1517
],
1618
exports: [

redisinsight/api/src/modules/encryption/encryption.service.spec.ts

Lines changed: 62 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
import { Test, TestingModule } from '@nestjs/testing';
22
import {
3-
mockAppSettings, mockAppSettingsInitial, mockAppSettingsWithoutPermissions,
3+
mockAppSettings,
4+
mockAppSettingsInitial,
5+
mockAppSettingsWithoutPermissions,
46
mockEncryptionStrategyInstance,
57
mockEncryptResult,
8+
mockKeyEncryptionStrategyInstance,
9+
mockKeyEncryptResult,
610
mockSettingsService,
711
MockType,
812
} from 'src/__mocks__';
@@ -12,11 +16,13 @@ import { KeytarEncryptionStrategy } from 'src/modules/encryption/strategies/keyt
1216
import { EncryptionStrategy } from 'src/modules/encryption/models';
1317
import { UnsupportedEncryptionStrategyException } from 'src/modules/encryption/exceptions';
1418
import { SettingsService } from 'src/modules/settings/settings.service';
19+
import { KeyEncryptionStrategy } from 'src/modules/encryption/strategies/key-encryption.strategy';
1520

1621
describe('EncryptionService', () => {
1722
let service: EncryptionService;
1823
let plainEncryptionStrategy: MockType<PlainEncryptionStrategy>;
1924
let keytarEncryptionStrategy: MockType<KeytarEncryptionStrategy>;
25+
let keyEncryptionStrategy: MockType<KeyEncryptionStrategy>;
2026
let settingsService: MockType<SettingsService>;
2127

2228
beforeEach(async () => {
@@ -33,6 +39,10 @@ describe('EncryptionService', () => {
3339
provide: KeytarEncryptionStrategy,
3440
useFactory: mockEncryptionStrategyInstance,
3541
},
42+
{
43+
provide: KeyEncryptionStrategy,
44+
useFactory: mockKeyEncryptionStrategyInstance,
45+
},
3646
{
3747
provide: SettingsService,
3848
useFactory: mockSettingsService,
@@ -43,22 +53,43 @@ describe('EncryptionService', () => {
4353
service = module.get(EncryptionService);
4454
plainEncryptionStrategy = module.get(PlainEncryptionStrategy);
4555
keytarEncryptionStrategy = module.get(KeytarEncryptionStrategy);
56+
keyEncryptionStrategy = module.get(KeyEncryptionStrategy);
4657
settingsService = module.get(SettingsService);
4758

4859
settingsService.getAppSettings.mockResolvedValue(mockAppSettings);
4960
});
5061

5162
describe('getAvailableEncryptionStrategies', () => {
52-
it('Should return list 2 strategies available', async () => {
63+
it('Should return list 2 strategies available (KEYTAR and PLAIN)', async () => {
5364
keytarEncryptionStrategy.isAvailable.mockResolvedValueOnce(true);
65+
keyEncryptionStrategy.isAvailable.mockResolvedValueOnce(false);
5466

5567
expect(await service.getAvailableEncryptionStrategies()).toEqual([
5668
EncryptionStrategy.PLAIN,
5769
EncryptionStrategy.KEYTAR,
5870
]);
5971
});
72+
it('Should return list 2 strategies available (KEY and PLAIN)', async () => {
73+
keytarEncryptionStrategy.isAvailable.mockResolvedValueOnce(false);
74+
keyEncryptionStrategy.isAvailable.mockResolvedValueOnce(true);
75+
76+
expect(await service.getAvailableEncryptionStrategies()).toEqual([
77+
EncryptionStrategy.PLAIN,
78+
EncryptionStrategy.KEY,
79+
]);
80+
});
81+
it('Should return list 2 strategies available (KEY and PLAIN) even when KEYTAR available', async () => {
82+
keytarEncryptionStrategy.isAvailable.mockResolvedValueOnce(true);
83+
keyEncryptionStrategy.isAvailable.mockResolvedValueOnce(true);
84+
85+
expect(await service.getAvailableEncryptionStrategies()).toEqual([
86+
EncryptionStrategy.PLAIN,
87+
EncryptionStrategy.KEY,
88+
]);
89+
});
6090
it('Should return list with one strategy available', async () => {
6191
keytarEncryptionStrategy.isAvailable.mockResolvedValueOnce(false);
92+
keyEncryptionStrategy.isAvailable.mockResolvedValueOnce(false);
6293

6394
expect(await service.getAvailableEncryptionStrategies()).toEqual([
6495
EncryptionStrategy.PLAIN,
@@ -70,7 +101,15 @@ describe('EncryptionService', () => {
70101
it('Should return KEYTAR strategy based on app agreements', async () => {
71102
expect(await service.getEncryptionStrategy()).toEqual(keytarEncryptionStrategy);
72103
});
73-
it('Should return PLAIN strategy based on app agreements', async () => {
104+
it('Should return KEY strategy based on app agreements even when KEYTAR available', async () => {
105+
keytarEncryptionStrategy.isAvailable.mockResolvedValueOnce(true);
106+
keyEncryptionStrategy.isAvailable.mockResolvedValueOnce(true);
107+
108+
expect(await service.getEncryptionStrategy()).toEqual(keyEncryptionStrategy);
109+
});
110+
it('Should return PLAIN strategy based on app agreements even when KEY available', async () => {
111+
keyEncryptionStrategy.isAvailable.mockResolvedValueOnce(true);
112+
74113
settingsService.getAppSettings.mockResolvedValueOnce(mockAppSettingsWithoutPermissions);
75114

76115
expect(await service.getEncryptionStrategy()).toEqual(plainEncryptionStrategy);
@@ -83,19 +122,37 @@ describe('EncryptionService', () => {
83122
});
84123

85124
describe('encrypt', () => {
86-
it('Should encrypt data and return proper response', async () => {
125+
it('Should encrypt data and return proper response (KEYTAR)', async () => {
126+
keyEncryptionStrategy.isAvailable.mockResolvedValueOnce(false);
127+
87128
keytarEncryptionStrategy.encrypt.mockResolvedValueOnce(mockEncryptResult);
88129

89130
expect(await service.encrypt('string')).toEqual(mockEncryptResult);
90131
});
132+
it('Should encrypt data and return proper response (KEY)', async () => {
133+
keyEncryptionStrategy.isAvailable.mockResolvedValueOnce(true);
134+
135+
keyEncryptionStrategy.encrypt.mockResolvedValueOnce(mockKeyEncryptResult);
136+
137+
expect(await service.encrypt('string')).toEqual(mockKeyEncryptResult);
138+
});
91139
});
92140

93141
describe('decrypt', () => {
94-
it('Should return decrypted string', async () => {
142+
it('Should return decrypted string (KEYTAR)', async () => {
143+
keyEncryptionStrategy.isAvailable.mockResolvedValueOnce(false);
144+
95145
keytarEncryptionStrategy.decrypt.mockResolvedValueOnce(mockEncryptResult.data);
96146

97147
expect(await service.decrypt('string', EncryptionStrategy.KEYTAR)).toEqual(mockEncryptResult.data);
98148
});
149+
it('Should return decrypted string (KEY)', async () => {
150+
keyEncryptionStrategy.isAvailable.mockResolvedValueOnce(true);
151+
152+
keyEncryptionStrategy.decrypt.mockResolvedValueOnce(mockKeyEncryptResult.data);
153+
154+
expect(await service.decrypt('string', EncryptionStrategy.KEY)).toEqual(mockKeyEncryptResult.data);
155+
});
99156
it('Should return null when no data passed', async () => {
100157
expect(await service.decrypt(null, EncryptionStrategy.KEYTAR)).toEqual(null);
101158
});

redisinsight/api/src/modules/encryption/encryption.service.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,15 @@ import {
77
UnsupportedEncryptionStrategyException,
88
} from 'src/modules/encryption/exceptions';
99
import { SettingsService } from 'src/modules/settings/settings.service';
10+
import { KeyEncryptionStrategy } from 'src/modules/encryption/strategies/key-encryption.strategy';
1011

1112
@Injectable()
1213
export class EncryptionService {
1314
constructor(
1415
private readonly settingsService: SettingsService,
1516
private readonly keytarEncryptionStrategy: KeytarEncryptionStrategy,
1617
private readonly plainEncryptionStrategy: PlainEncryptionStrategy,
18+
private readonly keyEncryptionStrategy: KeyEncryptionStrategy,
1719
) {}
1820

1921
/**
@@ -25,7 +27,9 @@ export class EncryptionService {
2527
EncryptionStrategy.PLAIN,
2628
];
2729

28-
if (await this.keytarEncryptionStrategy.isAvailable()) {
30+
if (await this.keyEncryptionStrategy.isAvailable()) {
31+
strategies.push(EncryptionStrategy.KEY);
32+
} else if (await this.keytarEncryptionStrategy.isAvailable()) {
2933
strategies.push(EncryptionStrategy.KEYTAR);
3034
}
3135

@@ -43,6 +47,9 @@ export class EncryptionService {
4347
const settings = await this.settingsService.getAppSettings('1');
4448
switch (settings.agreements?.encryption) {
4549
case true:
50+
if (await this.keyEncryptionStrategy.isAvailable()) {
51+
return this.keyEncryptionStrategy;
52+
}
4653
return this.keytarEncryptionStrategy;
4754
case false:
4855
return this.plainEncryptionStrategy;

redisinsight/api/src/modules/encryption/exceptions/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
export * from './encryption-service-error.exception';
2+
export * from './key-decryption-error.exception';
3+
export * from './key-encryption-error.exception';
4+
export * from './key-unavailable.exception';
25
export * from './keytar-decryption-error.exception';
36
export * from './keytar-encryption-error.exception';
47
export * from './keytar-unavailable.exception';
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import {
2+
EncryptionServiceErrorException,
3+
} from 'src/modules/encryption/exceptions/encryption-service-error.exception';
4+
5+
export class KeyDecryptionErrorException extends EncryptionServiceErrorException {
6+
constructor(message = 'Unable to decrypt data') {
7+
super({
8+
message,
9+
name: 'KeyDecryptionError',
10+
statusCode: 500,
11+
}, 500);
12+
}
13+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import {
2+
EncryptionServiceErrorException,
3+
} from 'src/modules/encryption/exceptions/encryption-service-error.exception';
4+
5+
export class KeyEncryptionErrorException extends EncryptionServiceErrorException {
6+
constructor(message = 'Unable to encrypt data') {
7+
super({
8+
message,
9+
name: 'KeyEncryptionError',
10+
statusCode: 500,
11+
}, 500);
12+
}
13+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import {
2+
EncryptionServiceErrorException,
3+
} from 'src/modules/encryption/exceptions/encryption-service-error.exception';
4+
5+
export class KeyUnavailableException extends EncryptionServiceErrorException {
6+
constructor(message = 'Encryption key unavailable') {
7+
super({
8+
message,
9+
name: 'KeyUnavailable',
10+
statusCode: 503,
11+
}, 503);
12+
}
13+
}

redisinsight/api/src/modules/encryption/models/encryption-result.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
export enum EncryptionStrategy {
22
PLAIN = 'PLAIN',
33
KEYTAR = 'KEYTAR',
4+
KEY = 'KEY',
45
}
56

67
export class EncryptionResult {

0 commit comments

Comments
 (0)