[Synthetics] quote monitor name to prevent invalid yaml (#156210)

## Summary

Appropriately quotes monitor names for synthetics integration policies,
to ensure that customers can use monitor names that would otherwise
break yaml.

### Testing
1. Create a private location
2. Save a monitor with name `[Synthetics] test` to that private location
3. Navigate to the agent policy for that location. Confirm that the
integration policy was added to the agent policy, and that the name is
correct

---------

Co-authored-by: shahzad31 <shahzad31comp@gmail.com>
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Dominique Clarke 2023-05-08 13:13:19 -04:00 committed by GitHub
parent 6185b4033c
commit a22561a524
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 102 additions and 78 deletions

View file

@ -38,9 +38,9 @@ export const browserFormatters: BrowserFormatMap = {
[ConfigKey.SCREENSHOTS]: null,
[ConfigKey.IGNORE_HTTPS_ERRORS]: null,
[ConfigKey.PLAYWRIGHT_OPTIONS]: null,
[ConfigKey.TEXT_ASSERTION]: null,
[ConfigKey.PORT]: null,
[ConfigKey.URLS]: null,
[ConfigKey.TEXT_ASSERTION]: stringToJsonFormatter,
[ConfigKey.PORT]: stringToJsonFormatter,
[ConfigKey.URLS]: stringToJsonFormatter,
[ConfigKey.METADATA]: objectToJsonFormatter,
[ConfigKey.SOURCE_INLINE]: stringToJsonFormatter,
[ConfigKey.SYNTHETICS_ARGS]: arrayToJsonFormatter,

View file

@ -6,30 +6,30 @@
*/
import { CommonFields, ConfigKey, SourceType } from '../../runtime_types/monitor_management';
import { arrayToJsonFormatter, FormatterFn } from '../formatting_utils';
import { arrayToJsonFormatter, stringToJsonFormatter, FormatterFn } from '../formatting_utils';
export type Formatter = null | FormatterFn;
export type CommonFormatMap = Record<keyof CommonFields | ConfigKey.NAME, Formatter>;
export const commonFormatters: CommonFormatMap = {
[ConfigKey.APM_SERVICE_NAME]: null,
[ConfigKey.NAME]: null,
[ConfigKey.APM_SERVICE_NAME]: stringToJsonFormatter,
[ConfigKey.NAME]: stringToJsonFormatter,
[ConfigKey.LOCATIONS]: null,
[ConfigKey.MONITOR_TYPE]: null,
[ConfigKey.ENABLED]: null,
[ConfigKey.ALERT_CONFIG]: null,
[ConfigKey.CONFIG_ID]: null,
[ConfigKey.NAMESPACE]: null,
[ConfigKey.NAMESPACE]: stringToJsonFormatter,
[ConfigKey.REVISION]: null,
[ConfigKey.MONITOR_SOURCE_TYPE]: null,
[ConfigKey.FORM_MONITOR_TYPE]: null,
[ConfigKey.JOURNEY_ID]: null,
[ConfigKey.PROJECT_ID]: null,
[ConfigKey.CUSTOM_HEARTBEAT_ID]: null,
[ConfigKey.ORIGINAL_SPACE]: null,
[ConfigKey.JOURNEY_ID]: stringToJsonFormatter,
[ConfigKey.PROJECT_ID]: stringToJsonFormatter,
[ConfigKey.CUSTOM_HEARTBEAT_ID]: stringToJsonFormatter,
[ConfigKey.ORIGINAL_SPACE]: stringToJsonFormatter,
[ConfigKey.CONFIG_HASH]: null,
[ConfigKey.MONITOR_QUERY_ID]: null,
[ConfigKey.MONITOR_QUERY_ID]: stringToJsonFormatter,
[ConfigKey.SCHEDULE]: (fields) =>
JSON.stringify(
`@every ${fields[ConfigKey.SCHEDULE]?.number}${fields[ConfigKey.SCHEDULE]?.unit}`

View file

@ -354,7 +354,7 @@ describe('formatSyntheticsPolicy', () => {
},
id: {
type: 'text',
value: '00bb3ceb-a242-4c7a-8405-8da963661374',
value: '"00bb3ceb-a242-4c7a-8405-8da963661374"',
},
ignore_https_errors: {
type: 'bool',
@ -372,7 +372,7 @@ describe('formatSyntheticsPolicy', () => {
},
name: {
type: 'text',
value: 'Test HTTP Monitor 03',
value: '"Test HTTP Monitor 03"',
},
origin: {
type: 'text',
@ -401,7 +401,7 @@ describe('formatSyntheticsPolicy', () => {
},
'service.name': {
type: 'text',
value: '',
value: '"Local Service"',
},
'source.inline.script': {
type: 'yaml',
@ -532,7 +532,7 @@ describe('formatSyntheticsPolicy', () => {
},
id: {
type: 'text',
value: '51ccd9d9-fc3f-4718-ba9d-b6ef80e73fc5',
value: '"51ccd9d9-fc3f-4718-ba9d-b6ef80e73fc5"',
},
location_name: {
type: 'text',
@ -550,7 +550,7 @@ describe('formatSyntheticsPolicy', () => {
},
name: {
type: 'text',
value: 'Test Monitor',
value: '"Test Monitor"',
},
origin: {
type: 'text',
@ -558,11 +558,11 @@ describe('formatSyntheticsPolicy', () => {
},
password: {
type: 'password',
value: 'changeme',
value: '"changeme"',
},
proxy_url: {
type: 'text',
value: 'https://proxy.com',
value: '"https://proxy.com"',
},
'response.include_body': {
type: 'text',
@ -582,7 +582,7 @@ describe('formatSyntheticsPolicy', () => {
},
'service.name': {
type: 'text',
value: 'LocalService',
value: '"LocalService"',
},
'ssl.certificate': {
type: 'yaml',
@ -622,11 +622,11 @@ describe('formatSyntheticsPolicy', () => {
},
urls: {
type: 'text',
value: 'https://www.google.com',
value: '"https://www.google.com"',
},
username: {
type: 'text',
value: '',
value: '"admin"',
},
},
},
@ -1110,7 +1110,7 @@ const browserConfig: any = {
enabled: true,
alert: { status: { enabled: true } },
schedule: { number: '3', unit: 'm' },
'service.name': '',
'service.name': 'Local Service',
config_id: '00bb3ceb-a242-4c7a-8405-8da963661374',
tags: ['cookie-test', 'browser'],
timeout: '16',
@ -1198,7 +1198,7 @@ const httpPolicy: any = {
'check.request.body': { type: 'text', value: '' },
'check.request.headers': {},
'check.request.method': 'GET',
username: '',
username: 'admin',
'ssl.certificate_authorities': '',
'ssl.certificate': '',
'ssl.key': '',

View file

@ -61,6 +61,15 @@ export const stringToJsonFormatter: FormatterFn = (fields, key) => {
return value ? JSON.stringify(value) : null;
};
export const stringifyString = (value?: string) => {
if (!value) return value;
try {
return JSON.stringify(value);
} catch (e) {
return value;
}
};
export const replaceStringWithParams = (
value: string | boolean | {} | [],
params: Record<string, string>,

View file

@ -9,7 +9,11 @@ import { tlsFormatters } from '../tls/formatters';
import { HTTPFields, ConfigKey } from '../../runtime_types/monitor_management';
import { Formatter, commonFormatters } from '../common/formatters';
import { arrayToJsonFormatter, objectToJsonFormatter } from '../formatting_utils';
import {
stringToJsonFormatter,
arrayToJsonFormatter,
objectToJsonFormatter,
} from '../formatting_utils';
export type HTTPFormatMap = Record<keyof HTTPFields, Formatter>;
@ -19,12 +23,12 @@ export const httpFormatters: HTTPFormatMap = {
[ConfigKey.RESPONSE_BODY_INDEX]: null,
[ConfigKey.RESPONSE_HEADERS_INDEX]: null,
[ConfigKey.METADATA]: objectToJsonFormatter,
[ConfigKey.URLS]: null,
[ConfigKey.USERNAME]: null,
[ConfigKey.PASSWORD]: null,
[ConfigKey.PROXY_URL]: null,
[ConfigKey.URLS]: stringToJsonFormatter,
[ConfigKey.USERNAME]: stringToJsonFormatter,
[ConfigKey.PASSWORD]: stringToJsonFormatter,
[ConfigKey.PROXY_URL]: stringToJsonFormatter,
[ConfigKey.PROXY_HEADERS]: objectToJsonFormatter,
[ConfigKey.PORT]: null,
[ConfigKey.PORT]: stringToJsonFormatter,
[ConfigKey.RESPONSE_BODY_CHECK_NEGATIVE]: arrayToJsonFormatter,
[ConfigKey.RESPONSE_BODY_CHECK_POSITIVE]: arrayToJsonFormatter,
[ConfigKey.RESPONSE_JSON_CHECK]: arrayToJsonFormatter,

View file

@ -9,11 +9,12 @@ import { secondsToCronFormatter } from '../formatting_utils';
import { ICMPFields, ConfigKey } from '../../runtime_types/monitor_management';
import { Formatter, commonFormatters } from '../common/formatters';
import { stringToJsonFormatter } from '../formatting_utils';
export type ICMPFormatMap = Record<keyof ICMPFields, Formatter>;
export const icmpFormatters: ICMPFormatMap = {
[ConfigKey.HOSTS]: null,
[ConfigKey.HOSTS]: stringToJsonFormatter,
[ConfigKey.WAIT]: secondsToCronFormatter,
[ConfigKey.MODE]: null,
[ConfigKey.IPV4]: null,

View file

@ -10,19 +10,19 @@ import { TCPFields, ConfigKey } from '../../runtime_types/monitor_management';
import { Formatter, commonFormatters } from '../common/formatters';
import { objectToJsonFormatter } from '../formatting_utils';
import { tlsFormatters } from '../tls/formatters';
import { stringToJsonFormatter } from '../formatting_utils';
export type TCPFormatMap = Record<keyof TCPFields, Formatter>;
export const tcpFormatters: TCPFormatMap = {
[ConfigKey.METADATA]: objectToJsonFormatter,
[ConfigKey.HOSTS]: null,
[ConfigKey.HOSTS]: stringToJsonFormatter,
[ConfigKey.PROXY_USE_LOCAL_RESOLVER]: null,
[ConfigKey.RESPONSE_RECEIVE_CHECK]: null,
[ConfigKey.REQUEST_SEND_CHECK]: null,
[ConfigKey.PROXY_URL]: null,
[ConfigKey.PROXY_URL]: null,
[ConfigKey.PORT]: null,
[ConfigKey.URLS]: null,
[ConfigKey.RESPONSE_RECEIVE_CHECK]: stringToJsonFormatter,
[ConfigKey.REQUEST_SEND_CHECK]: stringToJsonFormatter,
[ConfigKey.PROXY_URL]: stringToJsonFormatter,
[ConfigKey.PORT]: stringToJsonFormatter,
[ConfigKey.URLS]: stringToJsonFormatter,
[ConfigKey.MODE]: null,
[ConfigKey.IPV4]: null,
[ConfigKey.IPV6]: null,

View file

@ -119,15 +119,15 @@ describe('formatMonitorConfig', () => {
enabled: true,
locations: [],
max_redirects: '0',
name: 'Test',
password: '3z9SBOQWW5F0UrdqLVFqlF6z',
name: '"Test"',
password: '"3z9SBOQWW5F0UrdqLVFqlF6z"',
'response.include_body': 'on_error',
'response.include_headers': true,
schedule: '@every 3m',
timeout: '16s',
type: 'http',
urls: 'https://www.google.com',
proxy_url: 'https://www.google.com',
urls: '"https://www.google.com"',
proxy_url: '"https://www.google.com"',
});
});
@ -158,15 +158,15 @@ describe('formatMonitorConfig', () => {
enabled: true,
locations: [],
max_redirects: '0',
name: 'Test',
password: '3z9SBOQWW5F0UrdqLVFqlF6z',
proxy_url: 'https://www.google.com',
name: '"Test"',
password: '"3z9SBOQWW5F0UrdqLVFqlF6z"',
proxy_url: '"https://www.google.com"',
'response.include_body': 'on_error',
'response.include_headers': true,
schedule: '@every 3m',
timeout: '16s',
type: 'http',
urls: 'https://www.google.com',
urls: '"https://www.google.com"',
...(isTLSEnabled ? { 'ssl.verification_mode': 'none' } : {}),
});
}
@ -183,7 +183,7 @@ describe('browser fields', () => {
enabled: true,
'filter_journeys.tags': ['dev'],
ignore_https_errors: false,
name: 'Test',
name: '"Test"',
locations: [],
schedule: '@every 3m',
screenshots: 'on',

View file

@ -31,7 +31,7 @@ describe('SyntheticsPrivateLocation', () => {
type: 'http',
enabled: true,
schedule: '@every 3m',
'service.name': '',
'service.name': 'test service',
locations: [mockPrivateLocation],
tags: [],
timeout: '16',
@ -226,7 +226,7 @@ describe('SyntheticsPrivateLocation', () => {
},
name: {
type: 'text',
value: 'Browser monitor',
value: '"Browser monitor"',
},
params: {
type: 'yaml',
@ -246,7 +246,7 @@ describe('SyntheticsPrivateLocation', () => {
},
'service.name': {
type: 'text',
value: '',
value: '"test service"',
},
'source.inline.script': {
type: 'yaml',
@ -286,7 +286,7 @@ const dummyBrowserConfig: Partial<MonitorFields> & {
type: DataStream.BROWSER,
enabled: true,
schedule: { unit: ScheduleUnit.MINUTES, number: '10' },
'service.name': '',
'service.name': 'test service',
tags: [],
timeout: null,
name: 'Browser monitor',

View file

@ -9,6 +9,7 @@ import { NewPackagePolicy } from '@kbn/fleet-plugin/common';
import { NewPackagePolicyWithId } from '@kbn/fleet-plugin/server/services/package_policy';
import { cloneDeep } from 'lodash';
import { SavedObjectError } from '@kbn/core-saved-objects-common';
import { stringifyString } from '../../../common/formatters/formatting_utils';
import { formatSyntheticsPolicy } from '../../../common/formatters/format_synthetics_policy';
import {
ConfigKey,
@ -86,9 +87,9 @@ export class SyntheticsPrivateLocation {
{
...(config as Partial<MonitorFields>),
config_id: config.fields?.config_id,
location_name: privateLocation.label,
'monitor.project.id': config.fields?.['monitor.project.name'],
'monitor.project.name': config.fields?.['monitor.project.name'],
location_name: stringifyString(privateLocation.label),
'monitor.project.id': stringifyString(config.fields?.['monitor.project.name']),
'monitor.project.name': stringifyString(config.fields?.['monitor.project.name']),
},
globalParams
);

View file

@ -90,7 +90,7 @@ export default function ({ getService }: FtrProviderContext) {
it('does not add a monitor if there is an error in creating integration', async () => {
const newMonitor = { ...httpMonitorJson };
const invalidName = '[] - invalid name';
const invalidName = '!@#$%^&*()_++[\\-\\]- wow';
newMonitor.locations.push({
id: testFleetPolicyID,
@ -109,7 +109,7 @@ export default function ({ getService }: FtrProviderContext) {
expect(apiResponse.body).eql({
statusCode: 500,
message:
'YAMLException: end of the stream or a document separator is expected at line 3, column 10:\n name: [] - invalid name\n ^',
'YAMLException: unknown escape sequence at line 3, column 34:\n name: "!@#$,%,^,&,*,(,),_,+,+,[,\\,\\,-,\\,\\,],-, ,w,o,w,"\n ^',
error: 'Internal Server Error',
});

View file

@ -64,13 +64,13 @@ export default function ({ getService }: FtrProviderContext) {
};
const testMonitors = [
projectMonitors.monitors[0],
{ ...secondMonitor, name: '[] - invalid name' },
{ ...secondMonitor, name: '!@#$%^&*()_++[\\-\\]- wow name' },
];
try {
const body = await monitorTestService.addProjectMonitors(project, testMonitors);
expect(body.createdMonitors.length).eql(1);
expect(body.failedMonitors[0].reason).eql(
'end of the stream or a document separator is expected at line 3, column 10:\n name: [] - invalid name\n ^'
'unknown escape sequence at line 3, column 34:\n name: "!@#$,%,^,&,*,(,),_,+,+,[,\\,\\,-,\\,\\,],-, ,w,o,w, ,n,a,m,e,"\n ^'
);
} finally {
await Promise.all([
@ -97,7 +97,7 @@ export default function ({ getService }: FtrProviderContext) {
expect(editedBody.createdMonitors.length).eql(0);
expect(editedBody.updatedMonitors.length).eql(2);
testMonitors[1].name = '[] - invalid name';
testMonitors[1].name = '!@#$%^&*()_++[\\-\\]- wow name';
const editedBodyError = await monitorTestService.addProjectMonitors(project, testMonitors);
expect(editedBodyError.createdMonitors.length).eql(0);
@ -107,7 +107,7 @@ export default function ({ getService }: FtrProviderContext) {
'Failed to update journey: test-id-2'
);
expect(editedBodyError.failedMonitors[0].reason).eql(
'end of the stream or a document separator is expected at line 3, column 10:\n name: [] - invalid name\n ^'
'unknown escape sequence at line 3, column 34:\n name: "!@#$,%,^,&,*,(,),_,+,+,[,\\,\\,-,\\,\\,],-, ,w,o,w, ,n,a,m,e,"\n ^'
);
} finally {
await Promise.all([

View file

@ -177,9 +177,9 @@ export const getTestBrowserSyntheticsPolicy = ({
},
enabled: { value: true, type: 'bool' },
type: { value: 'browser', type: 'text' },
name: { value: 'Test HTTP Monitor 03', type: 'text' },
name: { value: '"Test HTTP Monitor 03"', type: 'text' },
schedule: { value: '"@every 3m"', type: 'text' },
'service.name': { value: '', type: 'text' },
'service.name': { value: null, type: 'text' },
timeout: { value: '16s', type: 'text' },
tags: { value: '["cookie-test","browser"]', type: 'yaml' },
'source.zip_url.url': { type: 'text' },
@ -210,8 +210,8 @@ export const getTestBrowserSyntheticsPolicy = ({
'source.zip_url.ssl.verification_mode': { type: 'text' },
'source.zip_url.ssl.supported_protocols': { type: 'yaml' },
'source.zip_url.proxy_url': { type: 'text' },
location_name: { value: 'Test private location 0', type: 'text' },
id: { value: id, type: 'text' },
location_name: { value: JSON.stringify('Test private location 0'), type: 'text' },
id: { value: JSON.stringify(id), type: 'text' },
config_id: { value: id, type: 'text' },
run_once: { value: false, type: 'bool' },
origin: { value: 'ui', type: 'text' },

View file

@ -48,17 +48,23 @@ export const getTestSyntheticsPolicy = (
},
enabled: { value: true, type: 'bool' },
type: { value: 'http', type: 'text' },
name: { value: name, type: 'text' },
name: { value: `"${name}"`, type: 'text' },
schedule: { value: '"@every 5m"', type: 'text' },
urls: { value: 'https://nextjs-test-synthetics.vercel.app/api/users', type: 'text' },
'service.name': { value: '', type: 'text' },
urls: {
value: JSON.stringify('https://nextjs-test-synthetics.vercel.app/api/users'),
type: 'text',
},
'service.name': { value: null, type: 'text' },
timeout: { value: '3ms', type: 'text' },
max_redirects: { value: '3', type: 'integer' },
proxy_url: { value: proxyUrl ?? 'http://proxy.com', type: 'text' },
proxy_url: {
value: JSON.stringify(proxyUrl) ?? JSON.stringify('http://proxy.com'),
type: 'text',
},
proxy_headers: { value: null, type: 'yaml' },
tags: { value: '["tag1","tag2"]', type: 'yaml' },
username: { value: 'test-username', type: 'text' },
password: { value: 'test', type: 'password' },
username: { value: '"test-username"', type: 'text' },
password: { value: '"test"', type: 'password' },
'response.include_headers': { value: true, type: 'bool' },
'response.include_body': { value: 'never', type: 'text' },
'response.include_body_max_bytes': { value: '1024', type: 'text' },
@ -85,8 +91,11 @@ export const getTestSyntheticsPolicy = (
value: isTLSEnabled ? '["TLSv1.1","TLSv1.2"]' : null,
type: 'yaml',
},
location_name: { value: locationName ?? 'Test private location 0', type: 'text' },
id: { value: id, type: 'text' },
location_name: {
value: JSON.stringify(locationName) ?? JSON.stringify('Test private location 0'),
type: 'text',
},
id: { value: JSON.stringify(id), type: 'text' },
config_id: { value: id, type: 'text' },
run_once: { value: false, type: 'bool' },
origin: { value: 'ui', type: 'text' },

View file

@ -202,9 +202,9 @@ export const getTestProjectSyntheticsPolicy = (
},
enabled: { value: true, type: 'bool' },
type: { value: 'browser', type: 'text' },
name: { value: 'check if title is present', type: 'text' },
name: { value: '"check if title is present"', type: 'text' },
schedule: { value: '"@every 10m"', type: 'text' },
'service.name': { value: '', type: 'text' },
'service.name': { value: null, type: 'text' },
timeout: { value: null, type: 'text' },
tags: { value: null, type: 'yaml' },
'source.zip_url.url': { type: 'text' },
@ -238,13 +238,13 @@ export const getTestProjectSyntheticsPolicy = (
'source.zip_url.ssl.verification_mode': { type: 'text' },
'source.zip_url.ssl.supported_protocols': { type: 'yaml' },
'source.zip_url.proxy_url': { type: 'text' },
location_name: { value: 'Test private location 0', type: 'text' },
id: { value: id, type: 'text' },
location_name: { value: '"Test private location 0"', type: 'text' },
id: { value: `"${id}"`, type: 'text' },
config_id: { value: configId, type: 'text' },
run_once: { value: false, type: 'bool' },
origin: { value: 'project', type: 'text' },
'monitor.project.id': { value: projectId, type: 'text' },
'monitor.project.name': { value: projectId, type: 'text' },
'monitor.project.id': { value: JSON.stringify(projectId), type: 'text' },
'monitor.project.name': { value: JSON.stringify(projectId), type: 'text' },
...inputs,
},
id: `synthetics/browser-browser-4b6abc6c-118b-4d93-a489-1135500d09f1-${projectId}-default-d70a46e0-22ea-11ed-8c6b-09a2d21dfbc3`,