Skip to content

Commit

Permalink
Merge branch '2.x' into backport/backport-6238-to-2.x
Browse files Browse the repository at this point in the history
  • Loading branch information
xinruiba authored Apr 3, 2024
2 parents a7b399d + 31d880e commit 4c65830
Show file tree
Hide file tree
Showing 51 changed files with 2,496 additions and 304 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)

### 📈 Features/Enhancements

- [Multiple Datasource] Add TLS configuration for multiple data sources ([#6171](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6171))

### 🐛 Bug Fixes

### 🚞 Infrastructure
Expand Down
8 changes: 8 additions & 0 deletions config/opensearch_dashboards.yml
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,14 @@
# 'ff00::/8',
# ]

# Optional setting that enables you to specify a path to PEM files for the certificate
# authority for your connected datasources.
#data_source.ssl.certificateAuthorities: [ "/path/to/your/CA.pem" ]

# To disregard the validity of SSL certificates for connected data sources, change this setting's value to 'none'.
# Possible values include full, certificate and none
#data_source.ssl.verificationMode: full

# Set enabled false to hide authentication method in OpenSearch Dashboards.
# If this setting is commented then all 3 options will be available in OpenSearch Dashboards.
# Default value will be considered to True.
Expand Down
2 changes: 2 additions & 0 deletions src/core/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,8 @@ export {
exportSavedObjectsToStream,
importSavedObjectsFromStream,
resolveSavedObjectsImportErrors,
updateDataSourceNameInVegaSpec,
extractVegaSpecFromSavedObject,
} from './saved_objects';

export {
Expand Down
1 change: 1 addition & 0 deletions src/core/server/saved_objects/import/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,4 @@ export {
SavedObjectsResolveImportErrorsOptions,
SavedObjectsImportRetry,
} from './types';
export { updateDataSourceNameInVegaSpec, extractVegaSpecFromSavedObject } from './utils';
12 changes: 11 additions & 1 deletion src/core/server/saved_objects/import/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,24 @@
import { parse, stringify } from 'hjson';
import { SavedObject, SavedObjectsClientContract } from '../types';

/**
* Given a Vega spec, the new datasource (by name), and spacing, update the Vega spec to add the new datasource name to each local cluster query
*
* @param {string} spec - the stringified Vega spec (HJSON or JSON)
* @param {string} newDataSourceName - the datasource name to append
* @param {number} [spacing=2] - how large the indenting should be after updating the spec (should be set to > 0 for a readable spec)
*/
export interface UpdateDataSourceNameInVegaSpecProps {
spec: string;
newDataSourceName: string;
spacing?: number;
}

export const updateDataSourceNameInVegaSpec = (
props: UpdateDataSourceNameInVegaSpecProps
): string => {
const { spec } = props;
const { spec, spacing } = props;
const stringifiedSpacing = spacing || 2;

let parsedSpec = parseJSONSpec(spec);
const isJSONString = !!parsedSpec;
Expand All @@ -39,6 +48,7 @@ export const updateDataSourceNameInVegaSpec = (
: stringify(parsedSpec, {
bracesSameLine: true,
keepWsc: true,
space: stringifiedSpacing,
});
};

Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions src/plugins/data_source/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,15 @@ export const configSchema = schema.object({
defaultValue: new Array(32).fill(0),
}),
}),
ssl: schema.object({
verificationMode: schema.oneOf(
[schema.literal('none'), schema.literal('certificate'), schema.literal('full')],
{ defaultValue: 'full' }
),
certificateAuthorities: schema.maybe(
schema.oneOf([schema.string(), schema.arrayOf(schema.string(), { minSize: 1 })])
),
}),
clientPool: schema.object({
size: schema.number({ defaultValue: 5 }),
}),
Expand Down
124 changes: 117 additions & 7 deletions src/plugins/data_source/server/client/client_config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,133 @@
import { DataSourcePluginConfigType } from '../../config';
import { parseClientOptions } from './client_config';

const TEST_DATA_SOURCE_ENDPOINT = 'http://test.com/';
jest.mock('fs');
const mockReadFileSync: jest.Mock = jest.requireMock('fs').readFileSync;

const config = {
enabled: true,
clientPool: {
size: 5,
},
} as DataSourcePluginConfigType;
const TEST_DATA_SOURCE_ENDPOINT = 'http://test.com/';

describe('parseClientOptions', () => {
test('include the ssl client configs as defaults', () => {
const config = {
enabled: true,
clientPool: {
size: 5,
},
} as DataSourcePluginConfigType;

expect(parseClientOptions(config, TEST_DATA_SOURCE_ENDPOINT)).toEqual(
expect.objectContaining({
node: TEST_DATA_SOURCE_ENDPOINT,
ssl: {
requestCert: true,
rejectUnauthorized: true,
},
})
);
});

test('test ssl config with verification mode set to none', () => {
const config = {
enabled: true,
ssl: {
verificationMode: 'none',
},
clientPool: {
size: 5,
},
} as DataSourcePluginConfigType;
expect(parseClientOptions(config, TEST_DATA_SOURCE_ENDPOINT)).toEqual(
expect.objectContaining({
node: TEST_DATA_SOURCE_ENDPOINT,
ssl: {
requestCert: true,
rejectUnauthorized: false,
ca: undefined,
},
})
);
});

test('test ssl config with verification mode set to certificate', () => {
const config = {
enabled: true,
ssl: {
verificationMode: 'certificate',
certificateAuthorities: ['some-path'],
},
clientPool: {
size: 5,
},
} as DataSourcePluginConfigType;
mockReadFileSync.mockReset();
mockReadFileSync.mockImplementation((path: string) => `content-of-${path}`);
const parsedConfig = parseClientOptions(config, TEST_DATA_SOURCE_ENDPOINT);
expect(mockReadFileSync).toHaveBeenCalledTimes(1);
mockReadFileSync.mockClear();
expect(parsedConfig).toEqual(
expect.objectContaining({
node: TEST_DATA_SOURCE_ENDPOINT,
ssl: {
requestCert: true,
rejectUnauthorized: true,
checkServerIdentity: expect.any(Function),
ca: ['content-of-some-path'],
},
})
);
expect(parsedConfig.ssl?.checkServerIdentity()).toBeUndefined();
});

test('test ssl config with verification mode set to full', () => {
const config = {
enabled: true,
ssl: {
verificationMode: 'full',
certificateAuthorities: ['some-path'],
},
clientPool: {
size: 5,
},
} as DataSourcePluginConfigType;
mockReadFileSync.mockReset();
mockReadFileSync.mockImplementation((path: string) => `content-of-${path}`);
const parsedConfig = parseClientOptions(config, TEST_DATA_SOURCE_ENDPOINT);
expect(mockReadFileSync).toHaveBeenCalledTimes(1);
mockReadFileSync.mockClear();
expect(parsedConfig).toEqual(
expect.objectContaining({
node: TEST_DATA_SOURCE_ENDPOINT,
ssl: {
requestCert: true,
rejectUnauthorized: true,
ca: ['content-of-some-path'],
},
})
);
});

test('test ssl config with verification mode set to full with no ca list', () => {
const config = {
enabled: true,
ssl: {
verificationMode: 'full',
},
clientPool: {
size: 5,
},
} as DataSourcePluginConfigType;
mockReadFileSync.mockReset();
mockReadFileSync.mockImplementation((path: string) => `content-of-${path}`);
const parsedConfig = parseClientOptions(config, TEST_DATA_SOURCE_ENDPOINT);
expect(mockReadFileSync).toHaveBeenCalledTimes(0);
mockReadFileSync.mockClear();
expect(parsedConfig).toEqual(
expect.objectContaining({
node: TEST_DATA_SOURCE_ENDPOINT,
ssl: {
requestCert: true,
rejectUnauthorized: true,
ca: undefined,
},
})
);
Expand Down
46 changes: 42 additions & 4 deletions src/plugins/data_source/server/client/client_config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,17 @@
*/

import { ClientOptions } from '@opensearch-project/opensearch-next';
import { checkServerIdentity } from 'tls';
import { DataSourcePluginConfigType } from '../../config';
import { readCertificateAuthorities } from '../util/tls_settings_provider';

/** @internal */
type DataSourceSSLConfigOptions = Partial<{
requestCert: boolean;
rejectUnauthorized: boolean;
checkServerIdentity: typeof checkServerIdentity;
ca: string[];
}>;

/**
* Parse the client options from given data source config and endpoint
Expand All @@ -18,12 +28,40 @@ export function parseClientOptions(
endpoint: string,
registeredSchema: any[]
): ClientOptions {
const sslConfig: DataSourceSSLConfigOptions = {
requestCert: true,
rejectUnauthorized: true,
};

if (config.ssl) {
const verificationMode = config.ssl.verificationMode;
switch (verificationMode) {
case 'none':
sslConfig.rejectUnauthorized = false;
break;
case 'certificate':
sslConfig.rejectUnauthorized = true;

// by default, NodeJS is checking the server identify
sslConfig.checkServerIdentity = () => undefined;
break;
case 'full':
sslConfig.rejectUnauthorized = true;
break;
default:
throw new Error(`Unknown ssl verificationMode: ${verificationMode}`);
}

const { certificateAuthorities } = readCertificateAuthorities(
config.ssl?.certificateAuthorities
);

sslConfig.ca = certificateAuthorities;
}

const clientOptions: ClientOptions = {
node: endpoint,
ssl: {
requestCert: true,
rejectUnauthorized: true,
},
ssl: sslConfig,
plugins: registeredSchema,
};

Expand Down
Loading

0 comments on commit 4c65830

Please sign in to comment.