Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions DOCKER_README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@ These commands will build the image and then start the container. Redis Insight

Redis Insight supports several configuration values that can be supplied via container environment variables. The following may be provided:

| Variable | Purpose | Default | Additional Info |
| ---------|---------|-----------------|---------|
| RI_APP_PORT | The port the app listens on | 5000 | See [Express Documentation](https://expressjs.com/en/api.html#app.listen) |
| RI_APP_HOST | The host the app listens on | 0.0.0.0 | See [Express Documentation](https://expressjs.com/en/api.html#app.listen) |
| RI_SERVER_TLS_KEY | Private key for HTTPS | | Private key in [PEM format](https://www.ssl.com/guide/pem-der-crt-and-cer-x-509-encodings-and-conversions/#ftoc-heading-3). May be a path to a file or a string in PEM format. |
| RI_SERVER_TLS_CERT | Certificate for supplied private key | | Public certificate in [PEM format](https://www.ssl.com/guide/pem-der-crt-and-cer-x-509-encodings-and-conversions/#ftoc-heading-3) |
| Variable | Purpose | Default | Additional Info |
| ---------|---------|-----------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| RI_APP_PORT | The port the app listens on | 5000 | See [Express Documentation](https://expressjs.com/en/api.html#app.listen) |
| RI_APP_HOST | The host the app listens on | 0.0.0.0 | See [Express Documentation](https://expressjs.com/en/api.html#app.listen) |
| RI_SERVER_TLS_KEY | Private key for HTTPS | | Private key in [PEM format](https://www.ssl.com/guide/pem-der-crt-and-cer-x-509-encodings-and-conversions/#ftoc-heading-3). May be a path to a file or a string in PEM format. |
| RI_SERVER_TLS_CERT | Certificate for supplied private key | | Public certificate in [PEM format](https://www.ssl.com/guide/pem-der-crt-and-cer-x-509-encodings-and-conversions/#ftoc-heading-3) |
| RI_ENCRYPTION_KEY | Key to encrypt data with | | Redisinsight stores some data such as connection details locally (using [sqlite3](https://github.com/TryGhost/node-sqlite3)). It might be usefull to store sensitive data such as passwords, or private keys encrypted. For this case RedisInsight supports encryption with provided key.<br />Note: The Key must be the same for the same RedisInsight instance to be able to decrypt exising data. If for some reason the key was changed, you will have to enter the credentials again to connect to the Redis database. |
1 change: 1 addition & 0 deletions redisinsight/api/config/default.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export default {
pluginsAssetsUri: '/static/resources/plugins',
base: process.env.RI_BASE || '/',
secretStoragePassword: process.env.SECRET_STORAGE_PASSWORD,
encryptionKey: process.env.RI_ENCRYPTION_KEY,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a configuration value that we're planning on exposing to the user in the first phase? If so, we should go ahead and add this to the DOCKER_README.md while we're in here.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1:1 :)

tlsCert: process.env.RI_SERVER_TLS_CERT,
tlsKey: process.env.RI_SERVER_TLS_KEY,
staticContent: !!process.env.SERVER_STATIC_CONTENT || false,
Expand Down
14 changes: 14 additions & 0 deletions redisinsight/api/src/__mocks__/encryption.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { EncryptionStrategy } from 'src/modules/encryption/models';

export const mockDataToEncrypt = 'stringtoencrypt';
export const mockKeytarPassword = 'somepassword';
export const mockEncryptionKey = 'somepassword';

export const mockEncryptionStrategy = EncryptionStrategy.KEYTAR;

Expand All @@ -10,6 +11,13 @@ export const mockEncryptResult = {
encryption: mockEncryptionStrategy,
};

export const mockKeyEncryptionStrategy = EncryptionStrategy.KEY;

export const mockKeyEncryptResult = {
data: '4a558dfef5c1abbdf745232614194ee9',
encryption: mockKeyEncryptionStrategy,
};

export const mockEncryptionService = jest.fn(() => ({
getAvailableEncryptionStrategies: jest.fn(),
encrypt: jest.fn(),
Expand All @@ -22,6 +30,12 @@ export const mockEncryptionStrategyInstance = jest.fn(() => ({
decrypt: jest.fn(),
}));

export const mockKeyEncryptionStrategyInstance = jest.fn(() => ({
isAvailable: jest.fn(),
encrypt: jest.fn(),
decrypt: jest.fn(),
}));

export const mockKeytarModule = {
getPassword: jest.fn(),
setPassword: jest.fn(),
Expand Down
2 changes: 2 additions & 0 deletions redisinsight/api/src/modules/encryption/encryption.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Module } from '@nestjs/common';
import { PlainEncryptionStrategy } from 'src/modules/encryption/strategies/plain-encryption.strategy';
import { KeytarEncryptionStrategy } from 'src/modules/encryption/strategies/keytar-encryption.strategy';
import { EncryptionService } from 'src/modules/encryption/encryption.service';
import { KeyEncryptionStrategy } from 'src/modules/encryption/strategies/key-encryption.strategy';

@Module({})
export class EncryptionModule {
Expand All @@ -11,6 +12,7 @@ export class EncryptionModule {
providers: [
PlainEncryptionStrategy,
KeytarEncryptionStrategy,
KeyEncryptionStrategy,
EncryptionService,
],
exports: [
Expand Down
67 changes: 62 additions & 5 deletions redisinsight/api/src/modules/encryption/encryption.service.spec.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import { Test, TestingModule } from '@nestjs/testing';
import {
mockAppSettings, mockAppSettingsInitial, mockAppSettingsWithoutPermissions,
mockAppSettings,
mockAppSettingsInitial,
mockAppSettingsWithoutPermissions,
mockEncryptionStrategyInstance,
mockEncryptResult,
mockKeyEncryptionStrategyInstance,
mockKeyEncryptResult,
mockSettingsService,
MockType,
} from 'src/__mocks__';
Expand All @@ -12,11 +16,13 @@ import { KeytarEncryptionStrategy } from 'src/modules/encryption/strategies/keyt
import { EncryptionStrategy } from 'src/modules/encryption/models';
import { UnsupportedEncryptionStrategyException } from 'src/modules/encryption/exceptions';
import { SettingsService } from 'src/modules/settings/settings.service';
import { KeyEncryptionStrategy } from 'src/modules/encryption/strategies/key-encryption.strategy';

describe('EncryptionService', () => {
let service: EncryptionService;
let plainEncryptionStrategy: MockType<PlainEncryptionStrategy>;
let keytarEncryptionStrategy: MockType<KeytarEncryptionStrategy>;
let keyEncryptionStrategy: MockType<KeyEncryptionStrategy>;
let settingsService: MockType<SettingsService>;

beforeEach(async () => {
Expand All @@ -33,6 +39,10 @@ describe('EncryptionService', () => {
provide: KeytarEncryptionStrategy,
useFactory: mockEncryptionStrategyInstance,
},
{
provide: KeyEncryptionStrategy,
useFactory: mockKeyEncryptionStrategyInstance,
},
{
provide: SettingsService,
useFactory: mockSettingsService,
Expand All @@ -43,22 +53,43 @@ describe('EncryptionService', () => {
service = module.get(EncryptionService);
plainEncryptionStrategy = module.get(PlainEncryptionStrategy);
keytarEncryptionStrategy = module.get(KeytarEncryptionStrategy);
keyEncryptionStrategy = module.get(KeyEncryptionStrategy);
settingsService = module.get(SettingsService);

settingsService.getAppSettings.mockResolvedValue(mockAppSettings);
});

describe('getAvailableEncryptionStrategies', () => {
it('Should return list 2 strategies available', async () => {
it('Should return list 2 strategies available (KEYTAR and PLAIN)', async () => {
keytarEncryptionStrategy.isAvailable.mockResolvedValueOnce(true);
keyEncryptionStrategy.isAvailable.mockResolvedValueOnce(false);

expect(await service.getAvailableEncryptionStrategies()).toEqual([
EncryptionStrategy.PLAIN,
EncryptionStrategy.KEYTAR,
]);
});
it('Should return list 2 strategies available (KEY and PLAIN)', async () => {
keytarEncryptionStrategy.isAvailable.mockResolvedValueOnce(false);
keyEncryptionStrategy.isAvailable.mockResolvedValueOnce(true);

expect(await service.getAvailableEncryptionStrategies()).toEqual([
EncryptionStrategy.PLAIN,
EncryptionStrategy.KEY,
]);
});
it('Should return list 2 strategies available (KEY and PLAIN) even when KEYTAR available', async () => {
keytarEncryptionStrategy.isAvailable.mockResolvedValueOnce(true);
keyEncryptionStrategy.isAvailable.mockResolvedValueOnce(true);

expect(await service.getAvailableEncryptionStrategies()).toEqual([
EncryptionStrategy.PLAIN,
EncryptionStrategy.KEY,
]);
});
it('Should return list with one strategy available', async () => {
keytarEncryptionStrategy.isAvailable.mockResolvedValueOnce(false);
keyEncryptionStrategy.isAvailable.mockResolvedValueOnce(false);

expect(await service.getAvailableEncryptionStrategies()).toEqual([
EncryptionStrategy.PLAIN,
Expand All @@ -70,7 +101,15 @@ describe('EncryptionService', () => {
it('Should return KEYTAR strategy based on app agreements', async () => {
expect(await service.getEncryptionStrategy()).toEqual(keytarEncryptionStrategy);
});
it('Should return PLAIN strategy based on app agreements', async () => {
it('Should return KEY strategy based on app agreements even when KEYTAR available', async () => {
keytarEncryptionStrategy.isAvailable.mockResolvedValueOnce(true);
keyEncryptionStrategy.isAvailable.mockResolvedValueOnce(true);

expect(await service.getEncryptionStrategy()).toEqual(keyEncryptionStrategy);
});
it('Should return PLAIN strategy based on app agreements even when KEY available', async () => {
keyEncryptionStrategy.isAvailable.mockResolvedValueOnce(true);

settingsService.getAppSettings.mockResolvedValueOnce(mockAppSettingsWithoutPermissions);

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

describe('encrypt', () => {
it('Should encrypt data and return proper response', async () => {
it('Should encrypt data and return proper response (KEYTAR)', async () => {
keyEncryptionStrategy.isAvailable.mockResolvedValueOnce(false);

keytarEncryptionStrategy.encrypt.mockResolvedValueOnce(mockEncryptResult);

expect(await service.encrypt('string')).toEqual(mockEncryptResult);
});
it('Should encrypt data and return proper response (KEY)', async () => {
keyEncryptionStrategy.isAvailable.mockResolvedValueOnce(true);

keyEncryptionStrategy.encrypt.mockResolvedValueOnce(mockKeyEncryptResult);

expect(await service.encrypt('string')).toEqual(mockKeyEncryptResult);
});
});

describe('decrypt', () => {
it('Should return decrypted string', async () => {
it('Should return decrypted string (KEYTAR)', async () => {
keyEncryptionStrategy.isAvailable.mockResolvedValueOnce(false);

keytarEncryptionStrategy.decrypt.mockResolvedValueOnce(mockEncryptResult.data);

expect(await service.decrypt('string', EncryptionStrategy.KEYTAR)).toEqual(mockEncryptResult.data);
});
it('Should return decrypted string (KEY)', async () => {
keyEncryptionStrategy.isAvailable.mockResolvedValueOnce(true);

keyEncryptionStrategy.decrypt.mockResolvedValueOnce(mockKeyEncryptResult.data);

expect(await service.decrypt('string', EncryptionStrategy.KEY)).toEqual(mockKeyEncryptResult.data);
});
it('Should return null when no data passed', async () => {
expect(await service.decrypt(null, EncryptionStrategy.KEYTAR)).toEqual(null);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ import {
UnsupportedEncryptionStrategyException,
} from 'src/modules/encryption/exceptions';
import { SettingsService } from 'src/modules/settings/settings.service';
import { KeyEncryptionStrategy } from 'src/modules/encryption/strategies/key-encryption.strategy';

@Injectable()
export class EncryptionService {
constructor(
private readonly settingsService: SettingsService,
private readonly keytarEncryptionStrategy: KeytarEncryptionStrategy,
private readonly plainEncryptionStrategy: PlainEncryptionStrategy,
private readonly keyEncryptionStrategy: KeyEncryptionStrategy,
) {}

/**
Expand All @@ -25,7 +27,9 @@ export class EncryptionService {
EncryptionStrategy.PLAIN,
];

if (await this.keytarEncryptionStrategy.isAvailable()) {
if (await this.keyEncryptionStrategy.isAvailable()) {
strategies.push(EncryptionStrategy.KEY);
} else if (await this.keytarEncryptionStrategy.isAvailable()) {
strategies.push(EncryptionStrategy.KEYTAR);
}

Expand All @@ -43,6 +47,9 @@ export class EncryptionService {
const settings = await this.settingsService.getAppSettings('1');
switch (settings.agreements?.encryption) {
case true:
if (await this.keyEncryptionStrategy.isAvailable()) {
return this.keyEncryptionStrategy;
}
return this.keytarEncryptionStrategy;
case false:
return this.plainEncryptionStrategy;
Expand Down
3 changes: 3 additions & 0 deletions redisinsight/api/src/modules/encryption/exceptions/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
export * from './encryption-service-error.exception';
export * from './key-decryption-error.exception';
export * from './key-encryption-error.exception';
export * from './key-unavailable.exception';
export * from './keytar-decryption-error.exception';
export * from './keytar-encryption-error.exception';
export * from './keytar-unavailable.exception';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import {
EncryptionServiceErrorException,
} from 'src/modules/encryption/exceptions/encryption-service-error.exception';

export class KeyDecryptionErrorException extends EncryptionServiceErrorException {
constructor(message = 'Unable to decrypt data') {
super({
message,
name: 'KeyDecryptionError',
statusCode: 500,
}, 500);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import {
EncryptionServiceErrorException,
} from 'src/modules/encryption/exceptions/encryption-service-error.exception';

export class KeyEncryptionErrorException extends EncryptionServiceErrorException {
constructor(message = 'Unable to encrypt data') {
super({
message,
name: 'KeyEncryptionError',
statusCode: 500,
}, 500);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import {
EncryptionServiceErrorException,
} from 'src/modules/encryption/exceptions/encryption-service-error.exception';

export class KeyUnavailableException extends EncryptionServiceErrorException {
constructor(message = 'Encryption key unavailable') {
super({
message,
name: 'KeyUnavailable',
statusCode: 503,
}, 503);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export enum EncryptionStrategy {
PLAIN = 'PLAIN',
KEYTAR = 'KEYTAR',
KEY = 'KEY',
}

export class EncryptionResult {
Expand Down
Loading