[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
This commit is contained in:
Jen Huang 2020-08-20 15:29:56 -07:00 committed by GitHub
parent 5308cc7100
commit 3201efe797
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 113 additions and 6 deletions

View file

@ -10,3 +10,4 @@ export { storedPackagePoliciesToAgentInputs } from './package_policies_to_agent_
export { fullAgentPolicyToYaml } from './full_agent_policy_to_yaml';
export { isPackageLimited, doesAgentPolicyAlreadyIncludePackage } from './limited_package';
export { decodeCloudId } from './decode_cloud_id';
export { isValidNamespace } from './is_valid_namespace';

View file

@ -0,0 +1,28 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { isValidNamespace } from './is_valid_namespace';
describe('Ingest Manager - isValidNamespace', () => {
it('returns true for valid namespaces', () => {
expect(isValidNamespace('default')).toBe(true);
expect(isValidNamespace('namespace-with-dash')).toBe(true);
expect(isValidNamespace('123')).toBe(true);
});
it('returns false for invalid namespaces', () => {
expect(isValidNamespace('Default')).toBe(false);
expect(isValidNamespace('namespace with spaces')).toBe(false);
expect(isValidNamespace('foo/bar')).toBe(false);
expect(isValidNamespace('foo\\bar')).toBe(false);
expect(isValidNamespace('foo*bar')).toBe(false);
expect(isValidNamespace('foo?bar')).toBe(false);
expect(isValidNamespace('foo"bar')).toBe(false);
expect(isValidNamespace('foo<bar')).toBe(false);
expect(isValidNamespace('foo|bar')).toBe(false);
expect(isValidNamespace('foo,bar')).toBe(false);
expect(isValidNamespace('foo#bar')).toBe(false);
});
});

View file

@ -0,0 +1,17 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
// Namespace string eventually becomes part of an index name. This method partially implements index name rules from
// https://github.com/elastic/elasticsearch/blob/master/docs/reference/indices/create-index.asciidoc
export function isValidNamespace(namespace: string) {
return (
typeof namespace === 'string' &&
// Lowercase only
namespace === namespace.toLowerCase() &&
// Cannot include \, /, *, ?, ", <, >, |, space character, comma, #, :
/^[^\*\\/\?"<>|\s,#:]+$/.test(namespace)
);
}

View file

@ -24,6 +24,7 @@ import { FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
import styled from 'styled-components';
import { NewAgentPolicy, AgentPolicy } from '../../../types';
import { isValidNamespace } from '../../../services';
import { AgentPolicyDeleteProvider } from './agent_policy_delete_provider';
interface ValidationResults {
@ -57,6 +58,13 @@ export const agentPolicyFormValidation = (
defaultMessage="A namespace is required"
/>,
];
} else if (!isValidNamespace(agentPolicy.namespace)) {
errors.namespace = [
<FormattedMessage
id="xpack.ingestManager.agentPolicyForm.namespaceInvalidErrorMessage"
defaultMessage="Namespace contains invalid characters"
/>,
];
}
return errors;

View file

@ -5,7 +5,7 @@
*/
import { i18n } from '@kbn/i18n';
import { safeLoad } from 'js-yaml';
import { getFlattenedObject } from '../../../../services';
import { getFlattenedObject, isValidNamespace } from '../../../../services';
import {
NewPackagePolicy,
PackagePolicyInput,
@ -65,6 +65,12 @@ export const validatePackagePolicy = (
defaultMessage: 'Namespace is required',
}),
];
} else if (!isValidNamespace(packagePolicy.namespace)) {
validationResults.namespace = [
i18n.translate('xpack.ingestManager.packagePolicyValidation.namespaceInvalidErrorMessage', {
defaultMessage: 'Namespace contains invalid characters',
}),
];
}
if (

View file

@ -24,4 +24,5 @@ export {
fullAgentPolicyToYaml,
isPackageLimited,
doesAgentPolicyAlreadyIncludePackage,
isValidNamespace,
} from '../../../../common';

View file

@ -4,12 +4,12 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { schema } from '@kbn/config-schema';
import { PackagePolicySchema } from './package_policy';
import { PackagePolicySchema, NamespaceSchema } from './package_policy';
import { AgentPolicyStatus } from '../../../common';
const AgentPolicyBaseSchema = {
name: schema.string({ minLength: 1 }),
namespace: schema.string({ minLength: 1 }),
namespace: NamespaceSchema,
description: schema.maybe(schema.string()),
monitoring_enabled: schema.maybe(
schema.arrayOf(schema.oneOf([schema.literal('logs'), schema.literal('metrics')]))

View file

@ -4,6 +4,16 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { schema } from '@kbn/config-schema';
import { isValidNamespace } from '../../../common';
export const NamespaceSchema = schema.string({
minLength: 1,
validate: (value) => {
if (!isValidNamespace(value)) {
return 'Namespace contains invalid characters';
}
},
});
const ConfigRecordSchema = schema.recordOf(
schema.string(),
@ -16,7 +26,7 @@ const ConfigRecordSchema = schema.recordOf(
const PackagePolicyBaseSchema = {
name: schema.string(),
description: schema.maybe(schema.string()),
namespace: schema.string({ minLength: 1 }),
namespace: NamespaceSchema,
policy_id: schema.string(),
enabled: schema.boolean(),
package: schema.maybe(

View file

@ -26,7 +26,7 @@ export default function ({ getService }: FtrProviderContext) {
expect(apiResponse.success).to.be(true);
});
it('should return a 400 with an invalid namespace', async () => {
it('should return a 400 with an empty namespace', async () => {
await supertest
.post(`/api/ingest_manager/agent_policies`)
.set('kbn-xsrf', 'xxxx')
@ -36,6 +36,17 @@ export default function ({ getService }: FtrProviderContext) {
})
.expect(400);
});
it('should return a 400 with an invalid namespace', async () => {
await supertest
.post(`/api/ingest_manager/agent_policies`)
.set('kbn-xsrf', 'xxxx')
.send({
name: 'TEST',
namespace: 'InvalidNamespace',
})
.expect(400);
});
});
describe('POST /api/ingest_manager/agent_policies/{agentPolicyId}/copy', () => {

View file

@ -59,7 +59,7 @@ export default function ({ getService }: FtrProviderContext) {
}
});
it('should return a 400 with an invalid namespace', async function () {
it('should return a 400 with an empty namespace', async function () {
if (server.enabled) {
await supertest
.post(`/api/ingest_manager/package_policies`)
@ -84,6 +84,31 @@ export default function ({ getService }: FtrProviderContext) {
}
});
it('should return a 400 with an invalid namespace', async function () {
if (server.enabled) {
await supertest
.post(`/api/ingest_manager/package_policies`)
.set('kbn-xsrf', 'xxxx')
.send({
name: 'filetest-1',
description: '',
namespace: 'InvalidNamespace',
policy_id: agentPolicyId,
enabled: true,
output_id: '',
inputs: [],
package: {
name: 'filetest',
title: 'For File Tests',
version: '0.1.0',
},
})
.expect(400);
} else {
warnAndSkipTest(this, log);
}
});
it('should not allow multiple limited packages on the same agent policy', async function () {
if (server.enabled) {
await supertest