Skip to content

Commit 3201efe

Browse files
authored
[Ingest Manager] Add namespace validation (#75381)
* Add namespace validation on APIs and UI * Add test coverage * Fix imports * Fix schema * Rename to policy * Fix typo
1 parent 5308cc7 commit 3201efe

10 files changed

Lines changed: 113 additions & 6 deletions

File tree

x-pack/plugins/ingest_manager/common/services/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ export { storedPackagePoliciesToAgentInputs } from './package_policies_to_agent_
1010
export { fullAgentPolicyToYaml } from './full_agent_policy_to_yaml';
1111
export { isPackageLimited, doesAgentPolicyAlreadyIncludePackage } from './limited_package';
1212
export { decodeCloudId } from './decode_cloud_id';
13+
export { isValidNamespace } from './is_valid_namespace';
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
import { isValidNamespace } from './is_valid_namespace';
7+
8+
describe('Ingest Manager - isValidNamespace', () => {
9+
it('returns true for valid namespaces', () => {
10+
expect(isValidNamespace('default')).toBe(true);
11+
expect(isValidNamespace('namespace-with-dash')).toBe(true);
12+
expect(isValidNamespace('123')).toBe(true);
13+
});
14+
15+
it('returns false for invalid namespaces', () => {
16+
expect(isValidNamespace('Default')).toBe(false);
17+
expect(isValidNamespace('namespace with spaces')).toBe(false);
18+
expect(isValidNamespace('foo/bar')).toBe(false);
19+
expect(isValidNamespace('foo\\bar')).toBe(false);
20+
expect(isValidNamespace('foo*bar')).toBe(false);
21+
expect(isValidNamespace('foo?bar')).toBe(false);
22+
expect(isValidNamespace('foo"bar')).toBe(false);
23+
expect(isValidNamespace('foo<bar')).toBe(false);
24+
expect(isValidNamespace('foo|bar')).toBe(false);
25+
expect(isValidNamespace('foo,bar')).toBe(false);
26+
expect(isValidNamespace('foo#bar')).toBe(false);
27+
});
28+
});
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
// Namespace string eventually becomes part of an index name. This method partially implements index name rules from
8+
// https://github.com/elastic/elasticsearch/blob/master/docs/reference/indices/create-index.asciidoc
9+
export function isValidNamespace(namespace: string) {
10+
return (
11+
typeof namespace === 'string' &&
12+
// Lowercase only
13+
namespace === namespace.toLowerCase() &&
14+
// Cannot include \, /, *, ?, ", <, >, |, space character, comma, #, :
15+
/^[^\*\\/\?"<>|\s,#:]+$/.test(namespace)
16+
);
17+
}

x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/components/agent_policy_form.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import { FormattedMessage } from '@kbn/i18n/react';
2424
import { i18n } from '@kbn/i18n';
2525
import styled from 'styled-components';
2626
import { NewAgentPolicy, AgentPolicy } from '../../../types';
27+
import { isValidNamespace } from '../../../services';
2728
import { AgentPolicyDeleteProvider } from './agent_policy_delete_provider';
2829

2930
interface ValidationResults {
@@ -57,6 +58,13 @@ export const agentPolicyFormValidation = (
5758
defaultMessage="A namespace is required"
5859
/>,
5960
];
61+
} else if (!isValidNamespace(agentPolicy.namespace)) {
62+
errors.namespace = [
63+
<FormattedMessage
64+
id="xpack.ingestManager.agentPolicyForm.namespaceInvalidErrorMessage"
65+
defaultMessage="Namespace contains invalid characters"
66+
/>,
67+
];
6068
}
6169

6270
return errors;

x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_policy/create_package_policy_page/services/validate_package_policy.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
*/
66
import { i18n } from '@kbn/i18n';
77
import { safeLoad } from 'js-yaml';
8-
import { getFlattenedObject } from '../../../../services';
8+
import { getFlattenedObject, isValidNamespace } from '../../../../services';
99
import {
1010
NewPackagePolicy,
1111
PackagePolicyInput,
@@ -65,6 +65,12 @@ export const validatePackagePolicy = (
6565
defaultMessage: 'Namespace is required',
6666
}),
6767
];
68+
} else if (!isValidNamespace(packagePolicy.namespace)) {
69+
validationResults.namespace = [
70+
i18n.translate('xpack.ingestManager.packagePolicyValidation.namespaceInvalidErrorMessage', {
71+
defaultMessage: 'Namespace contains invalid characters',
72+
}),
73+
];
6874
}
6975

7076
if (

x-pack/plugins/ingest_manager/public/applications/ingest_manager/services/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,5 @@ export {
2424
fullAgentPolicyToYaml,
2525
isPackageLimited,
2626
doesAgentPolicyAlreadyIncludePackage,
27+
isValidNamespace,
2728
} from '../../../../common';

x-pack/plugins/ingest_manager/server/types/models/agent_policy.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@
44
* you may not use this file except in compliance with the Elastic License.
55
*/
66
import { schema } from '@kbn/config-schema';
7-
import { PackagePolicySchema } from './package_policy';
7+
import { PackagePolicySchema, NamespaceSchema } from './package_policy';
88
import { AgentPolicyStatus } from '../../../common';
99

1010
const AgentPolicyBaseSchema = {
1111
name: schema.string({ minLength: 1 }),
12-
namespace: schema.string({ minLength: 1 }),
12+
namespace: NamespaceSchema,
1313
description: schema.maybe(schema.string()),
1414
monitoring_enabled: schema.maybe(
1515
schema.arrayOf(schema.oneOf([schema.literal('logs'), schema.literal('metrics')]))

x-pack/plugins/ingest_manager/server/types/models/package_policy.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,16 @@
44
* you may not use this file except in compliance with the Elastic License.
55
*/
66
import { schema } from '@kbn/config-schema';
7+
import { isValidNamespace } from '../../../common';
8+
9+
export const NamespaceSchema = schema.string({
10+
minLength: 1,
11+
validate: (value) => {
12+
if (!isValidNamespace(value)) {
13+
return 'Namespace contains invalid characters';
14+
}
15+
},
16+
});
717

818
const ConfigRecordSchema = schema.recordOf(
919
schema.string(),
@@ -16,7 +26,7 @@ const ConfigRecordSchema = schema.recordOf(
1626
const PackagePolicyBaseSchema = {
1727
name: schema.string(),
1828
description: schema.maybe(schema.string()),
19-
namespace: schema.string({ minLength: 1 }),
29+
namespace: NamespaceSchema,
2030
policy_id: schema.string(),
2131
enabled: schema.boolean(),
2232
package: schema.maybe(

x-pack/test/ingest_manager_api_integration/apis/agent_policy/agent_policy.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export default function ({ getService }: FtrProviderContext) {
2626
expect(apiResponse.success).to.be(true);
2727
});
2828

29-
it('should return a 400 with an invalid namespace', async () => {
29+
it('should return a 400 with an empty namespace', async () => {
3030
await supertest
3131
.post(`/api/ingest_manager/agent_policies`)
3232
.set('kbn-xsrf', 'xxxx')
@@ -36,6 +36,17 @@ export default function ({ getService }: FtrProviderContext) {
3636
})
3737
.expect(400);
3838
});
39+
40+
it('should return a 400 with an invalid namespace', async () => {
41+
await supertest
42+
.post(`/api/ingest_manager/agent_policies`)
43+
.set('kbn-xsrf', 'xxxx')
44+
.send({
45+
name: 'TEST',
46+
namespace: 'InvalidNamespace',
47+
})
48+
.expect(400);
49+
});
3950
});
4051

4152
describe('POST /api/ingest_manager/agent_policies/{agentPolicyId}/copy', () => {

x-pack/test/ingest_manager_api_integration/apis/package_policy/create.ts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ export default function ({ getService }: FtrProviderContext) {
5959
}
6060
});
6161

62-
it('should return a 400 with an invalid namespace', async function () {
62+
it('should return a 400 with an empty namespace', async function () {
6363
if (server.enabled) {
6464
await supertest
6565
.post(`/api/ingest_manager/package_policies`)
@@ -84,6 +84,31 @@ export default function ({ getService }: FtrProviderContext) {
8484
}
8585
});
8686

87+
it('should return a 400 with an invalid namespace', async function () {
88+
if (server.enabled) {
89+
await supertest
90+
.post(`/api/ingest_manager/package_policies`)
91+
.set('kbn-xsrf', 'xxxx')
92+
.send({
93+
name: 'filetest-1',
94+
description: '',
95+
namespace: 'InvalidNamespace',
96+
policy_id: agentPolicyId,
97+
enabled: true,
98+
output_id: '',
99+
inputs: [],
100+
package: {
101+
name: 'filetest',
102+
title: 'For File Tests',
103+
version: '0.1.0',
104+
},
105+
})
106+
.expect(400);
107+
} else {
108+
warnAndSkipTest(this, log);
109+
}
110+
});
111+
87112
it('should not allow multiple limited packages on the same agent policy', async function () {
88113
if (server.enabled) {
89114
await supertest

0 commit comments

Comments
 (0)