diff --git a/src/plugins/data_source/server/client/configure_client.test.ts b/src/plugins/data_source/server/client/configure_client.test.ts index 70dd078d4fdb..11523f649685 100644 --- a/src/plugins/data_source/server/client/configure_client.test.ts +++ b/src/plugins/data_source/server/client/configure_client.test.ts @@ -18,10 +18,13 @@ import { configureClient } from './configure_client'; import { ClientOptions } from '@opensearch-project/opensearch'; // eslint-disable-next-line @osd/eslint/no-restricted-paths import { opensearchClientMock } from '../../../../core/server/opensearch/client/mocks'; +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 describe('configureClient', () => { let logger: ReturnType; let config: DataSourcePluginConfigType; @@ -35,7 +38,6 @@ describe('configureClient', () => { dsClient = opensearchClientMock.createInternalClient(); logger = loggingSystemMock.createLogger(); savedObjectsMock = savedObjectsClientMock.create(); - config = { enabled: true, clientPool: { @@ -93,7 +95,7 @@ describe('configureClient', () => { afterEach(() => { ClientMock.mockReset(); }); - // TODO: mark as skip until we fix the issue of mocking "@opensearch-project/opensearch" + test('configure client with noAuth == true, will call new Client() to create client', async () => { savedObjectsMock.get.mockReset().mockResolvedValueOnce({ id: DATA_SOURCE_ID, @@ -107,6 +109,7 @@ describe('configureClient', () => { const client = await configureClient( DATA_SOURCE_ID, savedObjectsMock, + cryptoClient, clientPoolSetup, config, logger @@ -119,10 +122,13 @@ describe('configureClient', () => { expect(client).toBe(dsClient.child.mock.results[0].value); }); - test('configure client with noAuth == false, will first call new Client()', async () => { + test('configure client with noAuth == false, will first call decrypt()', async () => { + const spy = jest.spyOn(cryptoClient, 'decodeAndDecrypt').mockResolvedValue('password'); + const client = await configureClient( DATA_SOURCE_ID, savedObjectsMock, + cryptoClient, clientPoolSetup, config, logger @@ -130,6 +136,7 @@ describe('configureClient', () => { expect(ClientMock).toHaveBeenCalledTimes(1); expect(savedObjectsMock.get).toHaveBeenCalledTimes(2); + expect(spy).toHaveBeenCalledTimes(1); expect(client).toBe(dsClient.child.mock.results[0].value); }); }); diff --git a/src/plugins/data_source/server/client/configure_client.ts b/src/plugins/data_source/server/client/configure_client.ts index 15a824dabae2..05163970a869 100644 --- a/src/plugins/data_source/server/client/configure_client.ts +++ b/src/plugins/data_source/server/client/configure_client.ts @@ -11,15 +11,20 @@ import { SavedObjectsErrorHelpers, } from '../../../../../src/core/server'; import { DATA_SOURCE_SAVED_OBJECT_TYPE, CREDENTIAL_SAVED_OBJECT_TYPE } from '../../common'; -import { CredentialSavedObjectAttributes } from '../../common/credentials/types'; +import { + CredentialSavedObjectAttributes, + UsernamePasswordTypedContent, +} from '../../common/credentials/types'; import { DataSourceAttributes } from '../../common/data_sources'; import { DataSourcePluginConfigType } from '../../config'; +import { CryptographyClient } from '../cryptography'; import { parseClientOptions } from './client_config'; import { OpenSearchClientPoolSetup } from './client_pool'; export const configureClient = async ( dataSourceId: string, savedObjects: SavedObjectsClientContract, + cryptographyClient: CryptographyClient, openSearchClientPoolSetup: OpenSearchClientPoolSetup, config: DataSourcePluginConfigType, logger: Logger @@ -27,7 +32,7 @@ export const configureClient = async ( const dataSource = await getDataSource(dataSourceId, savedObjects); const rootClient = getRootClient(dataSource.attributes, config, openSearchClientPoolSetup); - return getQueryClient(rootClient, dataSource, savedObjects); + return getQueryClient(rootClient, dataSource, savedObjects, cryptographyClient); }; export const getDataSource = async ( @@ -48,13 +53,24 @@ export const getDataSource = async ( export const getCredential = async ( credentialId: string, - savedObjects: SavedObjectsClientContract -): Promise> => { + savedObjects: SavedObjectsClientContract, + cryptographyClient: CryptographyClient +): Promise => { try { - const credential = await savedObjects.get( + const credentialSavedObject = await savedObjects.get( CREDENTIAL_SAVED_OBJECT_TYPE, credentialId ); + const { + username, + password, + } = credentialSavedObject.attributes.credentialMaterials.credentialMaterialsContent; + const decodedPassword = await cryptographyClient.decodeAndDecrypt(password); + const credential = { + username, + password: decodedPassword, + }; + return credential; } catch (error: any) { // it will cause 500 error when failed to get saved objects, need to handle such error gracefully @@ -73,13 +89,18 @@ export const getCredential = async ( const getQueryClient = async ( rootClient: Client, dataSource: SavedObject, - savedObjects: SavedObjectsClientContract + savedObjects: SavedObjectsClientContract, + cryptographyClient: CryptographyClient ): Promise => { if (dataSource.attributes.noAuth) { return rootClient.child(); } else { - const credential = await getCredential(dataSource.references[0].id, savedObjects); - return getBasicAuthClient(rootClient, credential.attributes); + const credential = await getCredential( + dataSource.references[0].id, + savedObjects, + cryptographyClient + ); + return getBasicAuthClient(rootClient, credential); } }; @@ -112,9 +133,9 @@ const getRootClient = ( const getBasicAuthClient = ( rootClient: Client, - credentialAttr: CredentialSavedObjectAttributes + credential: UsernamePasswordTypedContent ): Client => { - const { username, password } = credentialAttr.credentialMaterials.credentialMaterialsContent; + const { username, password } = credential; return rootClient.child({ auth: { username, diff --git a/src/plugins/data_source/server/data_source_service.ts b/src/plugins/data_source/server/data_source_service.ts index 5bb25492f596..b7071962f5ab 100644 --- a/src/plugins/data_source/server/data_source_service.ts +++ b/src/plugins/data_source/server/data_source_service.ts @@ -6,11 +6,13 @@ import { Logger, OpenSearchClient, SavedObjectsClientContract } from '../../../../src/core/server'; import { DataSourcePluginConfigType } from '../config'; import { OpenSearchClientPool, configureClient } from './client'; +import { CryptographyClient } from './cryptography'; export interface DataSourceServiceSetup { getDataSourceClient: ( dataSourceId: string, // this saved objects client is used to fetch data source on behalf of users, caller should pass scoped saved objects client - savedObjects: SavedObjectsClientContract + savedObjects: SavedObjectsClientContract, + cryptographyClient: CryptographyClient ) => Promise; } export class DataSourceService { @@ -25,11 +27,13 @@ export class DataSourceService { const getDataSourceClient = async ( dataSourceId: string, - savedObjects: SavedObjectsClientContract + savedObjects: SavedObjectsClientContract, + cryptographyClient: CryptographyClient ): Promise => { return configureClient( dataSourceId, savedObjects, + cryptographyClient, openSearchClientPoolSetup, config, this.logger diff --git a/src/plugins/data_source/server/plugin.ts b/src/plugins/data_source/server/plugin.ts index 82a9ae65e6a9..49d5418c86bc 100644 --- a/src/plugins/data_source/server/plugin.ts +++ b/src/plugins/data_source/server/plugin.ts @@ -4,6 +4,7 @@ */ import { first } from 'rxjs/operators'; +import { OpenSearchClientError } from '@opensearch-project/opensearch/lib/errors'; import { dataSource, credential, CredentialSavedObjectsClientWrapper } from './saved_objects'; import { DataSourcePluginConfigType } from '../config'; import { @@ -24,8 +25,8 @@ export class DataSourcePlugin implements Plugin) { - this.logger = this.initializerContext.logger.get('dataSource'); - this.dataSourceService = new DataSourceService(this.logger); + this.logger = this.initializerContext.logger.get(); + this.dataSourceService = new DataSourceService(this.logger.get('data-source-service')); } public async setup(core: CoreSetup) { @@ -44,8 +45,13 @@ export class DataSourcePlugin implements Plugin, 'dataSource'> => { return (context, req) => { @@ -86,13 +93,14 @@ export class DataSourcePlugin implements Plugin