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

[MD] Credentials security redesign #2253

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
11 changes: 0 additions & 11 deletions src/plugins/data_source/common/credentials/index.ts

This file was deleted.

29 changes: 0 additions & 29 deletions src/plugins/data_source/common/credentials/types.ts

This file was deleted.

16 changes: 15 additions & 1 deletion src/plugins/data_source/common/data_sources/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,20 @@ import { SavedObjectAttributes } from 'src/core/types';

export interface DataSourceAttributes extends SavedObjectAttributes {
title: string;
description?: string;
endpoint: string;
noAuth: boolean;
auth: {
type: AuthType;
credentials: UsernamePasswordTypedContent | undefined;
};
}

export interface UsernamePasswordTypedContent extends SavedObjectAttributes {
username: string;
password: string;
}

export enum AuthType {
NoAuth = 'no_auth',
UsernamePasswordType = 'username_password',
noCharger marked this conversation as resolved.
Show resolved Hide resolved
}
8 changes: 0 additions & 8 deletions src/plugins/data_source/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,3 @@
export const PLUGIN_ID = 'dataSource';
export const PLUGIN_NAME = 'data_source';
export const DATA_SOURCE_SAVED_OBJECT_TYPE = 'data-source';
export const CREDENTIAL_SAVED_OBJECT_TYPE = 'credential';

export {
CredentialMaterialsType,
CredentialSavedObjectAttributes,
CredentialMaterials,
UsernamePasswordTypedContent,
} from './credentials';
8 changes: 0 additions & 8 deletions src/plugins/data_source/public/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,3 @@ export function plugin() {
}

export { DataSourcePublicPluginSetup, DataSourcePublicPluginStart } from './types';

export {
CredentialMaterialsType,
CredentialSavedObjectAttributes,
CredentialMaterials,
UsernamePasswordTypedContent,
CREDENTIAL_SAVED_OBJECT_TYPE,
} from '../common';
60 changes: 24 additions & 36 deletions src/plugins/data_source/server/client/configure_client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,8 @@

import { SavedObjectsClientContract } from '../../../../core/server';
import { loggingSystemMock, savedObjectsClientMock } from '../../../../core/server/mocks';
import { DATA_SOURCE_SAVED_OBJECT_TYPE, CREDENTIAL_SAVED_OBJECT_TYPE } from '../../common';
import {
CredentialMaterialsType,
CredentialSavedObjectAttributes,
} from '../../common/credentials/types';
import { DataSourceAttributes } from '../../common/data_sources';
import { DATA_SOURCE_SAVED_OBJECT_TYPE } from '../../common';
import { DataSourceAttributes, AuthType } from '../../common/data_sources/types';
import { DataSourcePluginConfigType } from '../../config';
import { ClientMock, parseClientOptionsMock } from './configure_client.test.mocks';
import { OpenSearchClientPoolSetup } from './client_pool';
Expand All @@ -21,7 +17,6 @@ import { opensearchClientMock } from '../../../../core/server/opensearch/client/
import { CryptographyClient } from '../cryptography';

const DATA_SOURCE_ID = 'a54b76ec86771ee865a0f74a305dfff8';
const CREDENETIAL_ID = 'a54dsaadasfasfwe22d23d23d2453df3';
const cryptoClient = new CryptographyClient('test', 'test', new Array(32).fill(0));

// TODO: improve UT
Expand Down Expand Up @@ -54,38 +49,26 @@ describe('configureClient', () => {
dataSourceAttr = {
title: 'title',
endpoint: 'http://localhost',
noAuth: false,
auth: {
type: AuthType.UsernamePasswordType,
credentials: {
username: 'username',
password: 'password',
},
},
} as DataSourceAttributes;

clientPoolSetup = {
getClientFromPool: jest.fn(),
addClientToPool: jest.fn(),
};

const crendentialAttr = {
title: 'cred',
credentialMaterials: {
credentialMaterialsType: CredentialMaterialsType.UsernamePasswordType,
credentialMaterialsContent: {
username: 'username',
password: 'password',
},
},
} as CredentialSavedObjectAttributes;

savedObjectsMock.get
.mockResolvedValueOnce({
id: DATA_SOURCE_ID,
type: DATA_SOURCE_SAVED_OBJECT_TYPE,
attributes: dataSourceAttr,
references: [{ name: 'user', type: CREDENTIAL_SAVED_OBJECT_TYPE, id: CREDENETIAL_ID }],
})
.mockResolvedValueOnce({
id: CREDENETIAL_ID,
type: CREDENTIAL_SAVED_OBJECT_TYPE,
attributes: crendentialAttr,
references: [],
});
savedObjectsMock.get.mockResolvedValueOnce({
id: DATA_SOURCE_ID,
type: DATA_SOURCE_SAVED_OBJECT_TYPE,
attributes: dataSourceAttr,
references: [],
});

ClientMock.mockImplementation(() => {
return dsClient;
Expand All @@ -96,11 +79,16 @@ describe('configureClient', () => {
ClientMock.mockReset();
});

test('configure client with noAuth == true, will call new Client() to create client', async () => {
test('configure client with auth.type == no_auth, will call new Client() to create client', async () => {
savedObjectsMock.get.mockReset().mockResolvedValueOnce({
id: DATA_SOURCE_ID,
type: DATA_SOURCE_SAVED_OBJECT_TYPE,
attributes: { ...dataSourceAttr, noAuth: true },
attributes: {
...dataSourceAttr,
auth: {
type: AuthType.NoAuth,
},
},
references: [],
});

Expand All @@ -122,7 +110,7 @@ describe('configureClient', () => {
expect(client).toBe(dsClient.child.mock.results[0].value);
});

test('configure client with noAuth == false, will first call decrypt()', async () => {
test('configure client with auth.type == username_password, will first call decrypt()', async () => {
const spy = jest.spyOn(cryptoClient, 'decodeAndDecrypt').mockResolvedValue('password');

const client = await configureClient(
Expand All @@ -135,7 +123,7 @@ describe('configureClient', () => {
);

expect(ClientMock).toHaveBeenCalledTimes(1);
expect(savedObjectsMock.get).toHaveBeenCalledTimes(2);
expect(savedObjectsMock.get).toHaveBeenCalledTimes(1);
expect(spy).toHaveBeenCalledTimes(1);
expect(client).toBe(dsClient.child.mock.results[0].value);
});
Expand Down
35 changes: 12 additions & 23 deletions src/plugins/data_source/server/client/configure_client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@ import {
SavedObjectsClientContract,
SavedObjectsErrorHelpers,
} from '../../../../../src/core/server';
import { DATA_SOURCE_SAVED_OBJECT_TYPE, CREDENTIAL_SAVED_OBJECT_TYPE } from '../../common';
import { DATA_SOURCE_SAVED_OBJECT_TYPE } from '../../common';

import {
CredentialSavedObjectAttributes,
AuthType,
DataSourceAttributes,
UsernamePasswordTypedContent,
} from '../../common/credentials/types';
import { DataSourceAttributes } from '../../common/data_sources';
} from '../../common/data_sources';
import { DataSourcePluginConfigType } from '../../config';
import { CryptographyClient } from '../cryptography';
import { parseClientOptions } from './client_config';
Expand All @@ -32,7 +33,7 @@ export const configureClient = async (
const dataSource = await getDataSource(dataSourceId, savedObjects);
const rootClient = getRootClient(dataSource.attributes, config, openSearchClientPoolSetup);

return getQueryClient(rootClient, dataSource, savedObjects, cryptographyClient);
return getQueryClient(rootClient, dataSource, cryptographyClient);
};

export const getDataSource = async (
Expand All @@ -52,19 +53,11 @@ export const getDataSource = async (
};

export const getCredential = async (
credentialId: string,
savedObjects: SavedObjectsClientContract,
dataSource: SavedObject<DataSourceAttributes>,
cryptographyClient: CryptographyClient
): Promise<UsernamePasswordTypedContent> => {
try {
const credentialSavedObject = await savedObjects.get<CredentialSavedObjectAttributes>(
CREDENTIAL_SAVED_OBJECT_TYPE,
credentialId
);
const {
username,
password,
} = credentialSavedObject.attributes.credentialMaterials.credentialMaterialsContent;
const { username, password } = dataSource.attributes.auth.credentials!;
const decodedPassword = await cryptographyClient.decodeAndDecrypt(password);
const credential = {
username,
Expand All @@ -83,23 +76,19 @@ export const getCredential = async (
*
* @param rootClient root client for the connection with given data source endpoint.
* @param dataSource data source saved object
* @param savedObjects scoped saved object client
* @param cryptographyClient cryptography client for password encryption / decrpytion
* @returns child client.
*/
const getQueryClient = async (
rootClient: Client,
dataSource: SavedObject<DataSourceAttributes>,
savedObjects: SavedObjectsClientContract,
cryptographyClient: CryptographyClient
): Promise<Client> => {
if (dataSource.attributes.noAuth) {
if (AuthType.NoAuth === dataSource.attributes.auth.type) {
return rootClient.child();
} else {
const credential = await getCredential(
dataSource.references[0].id,
savedObjects,
cryptographyClient
);
const credential = await getCredential(dataSource, cryptographyClient);

return getBasicAuthClient(rootClient, credential);
}
};
Expand Down
17 changes: 8 additions & 9 deletions src/plugins/data_source/server/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@ import { DataSourcePluginConfigType } from '../config';
import { LoggingAuditor } from './audit/logging_auditor';
import { CryptographyClient } from './cryptography';
import { DataSourceService, DataSourceServiceSetup } from './data_source_service';
import { credential, CredentialSavedObjectsClientWrapper, dataSource } from './saved_objects';
import { DataSourceSavedObjectsClientWrapper, dataSource } from './saved_objects';
import { DataSourcePluginSetup, DataSourcePluginStart } from './types';
import { DATA_SOURCE_SAVED_OBJECT_TYPE } from '../common';

// eslint-disable-next-line @osd/eslint/no-restricted-paths
import { ensureRawRequest } from '../../../../src/core/server/http/router';
export class DataSourcePlugin implements Plugin<DataSourcePluginSetup, DataSourcePluginStart> {
Expand All @@ -41,9 +43,6 @@ export class DataSourcePlugin implements Plugin<DataSourcePluginSetup, DataSourc
public async setup(core: CoreSetup) {
this.logger.debug('data_source: Setup');

// Register credential saved object type
core.savedObjects.registerType(credential);

// Register data source saved object type
core.savedObjects.registerType(dataSource);

Expand All @@ -52,21 +51,21 @@ export class DataSourcePlugin implements Plugin<DataSourcePluginSetup, DataSourc
// Fetch configs used to create credential saved objects client wrapper
const { wrappingKeyName, wrappingKeyNamespace, wrappingKey } = config.encryption;

// Create credential saved objects client wrapper
// Create data source saved objects client wrapper
const cryptographyClient = new CryptographyClient(
wrappingKeyName,
wrappingKeyNamespace,
wrappingKey
);
const credentialSavedObjectsClientWrapper = new CredentialSavedObjectsClientWrapper(
const dataSourceSavedObjectsClientWrapper = new DataSourceSavedObjectsClientWrapper(
cryptographyClient
);

// Add credential saved objects client wrapper factory
// Add data source saved objects client wrapper factory
core.savedObjects.addClientWrapper(
1,
'credential',
credentialSavedObjectsClientWrapper.wrapperFactory
DATA_SOURCE_SAVED_OBJECT_TYPE,
dataSourceSavedObjectsClientWrapper.wrapperFactory
);

const dataSourceService: DataSourceServiceSetup = await this.dataSourceService.setup(config);
Expand Down
Loading