From c7db63b569b2406336dd50bc18d89d0d20d1ac43 Mon Sep 17 00:00:00 2001 From: Nicolas Chaulet Date: Wed, 16 Feb 2022 09:50:42 -0500 Subject: [PATCH 1/6] [Fleet] Support logstash as an output type --- .../plugins/fleet/common/constants/output.ts | 1 + .../openapi/components/schemas/output.yaml | 9 +++ .../fleet/common/types/models/output.ts | 7 +- .../fleet/server/saved_objects/index.ts | 1 + .../agent_policies/full_agent_policy.test.ts | 50 +++++++++++++ .../agent_policies/full_agent_policy.ts | 5 +- .../plugins/fleet/server/services/output.ts | 8 +-- .../fleet/server/services/preconfiguration.ts | 1 + .../fleet/server/types/models/output.ts | 70 +++++++++++++++++-- .../server/types/models/preconfiguration.ts | 12 +--- .../fleet/server/types/rest_spec/output.ts | 25 ++----- .../apis/outputs/crud.ts | 52 ++++++++++++++ 12 files changed, 199 insertions(+), 42 deletions(-) diff --git a/x-pack/plugins/fleet/common/constants/output.ts b/x-pack/plugins/fleet/common/constants/output.ts index 67c45fea37d78..e41e3c526951e 100644 --- a/x-pack/plugins/fleet/common/constants/output.ts +++ b/x-pack/plugins/fleet/common/constants/output.ts @@ -11,6 +11,7 @@ export const OUTPUT_SAVED_OBJECT_TYPE = 'ingest-outputs'; export const outputType = { Elasticsearch: 'elasticsearch', + Logstash: 'logstash', } as const; export const DEFAULT_OUTPUT_ID = 'fleet-default-output'; diff --git a/x-pack/plugins/fleet/common/openapi/components/schemas/output.yaml b/x-pack/plugins/fleet/common/openapi/components/schemas/output.yaml index 75823d02af865..1fff27f279b14 100644 --- a/x-pack/plugins/fleet/common/openapi/components/schemas/output.yaml +++ b/x-pack/plugins/fleet/common/openapi/components/schemas/output.yaml @@ -24,6 +24,15 @@ properties: type: object config_yaml: type: string + ssl: + type: object + properties: + certificate: + type: string + certificate_authorities: + type: string + key: + type: string required: - id - is_default diff --git a/x-pack/plugins/fleet/common/types/models/output.ts b/x-pack/plugins/fleet/common/types/models/output.ts index ba09d41d2874f..1e22c88f64cb2 100644 --- a/x-pack/plugins/fleet/common/types/models/output.ts +++ b/x-pack/plugins/fleet/common/types/models/output.ts @@ -13,13 +13,18 @@ export type OutputType = typeof outputType; export interface NewOutput { is_default: boolean; is_default_monitoring: boolean; + is_preconfigured?: boolean; name: string; type: ValueOf; hosts?: string[]; ca_sha256?: string; ca_trusted_fingerprint?: string; config_yaml?: string; - is_preconfigured?: boolean; + ssl?: { + certificate_authorities?: string[]; + certificate?: string; + key?: string; + }; } export type OutputSOAttributes = NewOutput & { diff --git a/x-pack/plugins/fleet/server/saved_objects/index.ts b/x-pack/plugins/fleet/server/saved_objects/index.ts index e91eaf0bb0569..6b06d28af7042 100644 --- a/x-pack/plugins/fleet/server/saved_objects/index.ts +++ b/x-pack/plugins/fleet/server/saved_objects/index.ts @@ -117,6 +117,7 @@ const getSavedObjectTypes = ( config: { type: 'flattened' }, config_yaml: { type: 'text' }, is_preconfigured: { type: 'boolean', index: false }, + ssl: { type: 'flattened', index: false }, }, }, migrations: { diff --git a/x-pack/plugins/fleet/server/services/agent_policies/full_agent_policy.test.ts b/x-pack/plugins/fleet/server/services/agent_policies/full_agent_policy.test.ts index 67be0b6914537..c17d67c593229 100644 --- a/x-pack/plugins/fleet/server/services/agent_policies/full_agent_policy.test.ts +++ b/x-pack/plugins/fleet/server/services/agent_policies/full_agent_policy.test.ts @@ -352,4 +352,54 @@ ssl.test: 123 } `); }); + + it('should return placeholder ES_USERNAME and ES_PASSWORD for elasticsearch output type in standalone ', () => { + const policyOutput = transformOutputToFullPolicyOutput( + { + id: 'id123', + hosts: ['http://host.fr'], + is_default: false, + is_default_monitoring: false, + name: 'test output', + type: 'elasticsearch', + }, + true + ); + + expect(policyOutput).toMatchInlineSnapshot(` + Object { + "ca_sha256": undefined, + "hosts": Array [ + "http://host.fr", + ], + "password": "{ES_PASSWORD}", + "type": "elasticsearch", + "username": "{ES_USERNAME}", + } + `); + }); + + it('should not return placeholder ES_USERNAME and ES_PASSWORD for logstash output type in standalone ', () => { + const policyOutput = transformOutputToFullPolicyOutput( + { + id: 'id123', + hosts: ['host.fr:3332'], + is_default: false, + is_default_monitoring: false, + name: 'test output', + type: 'logstash', + }, + true + ); + + expect(policyOutput).toMatchInlineSnapshot(` + Object { + "ca_sha256": undefined, + "hosts": Array [ + "host.fr:3332", + ], + "type": "logstash", + } + `); + }); }); diff --git a/x-pack/plugins/fleet/server/services/agent_policies/full_agent_policy.ts b/x-pack/plugins/fleet/server/services/agent_policies/full_agent_policy.ts index 9f522875544e1..5d11c91889e25 100644 --- a/x-pack/plugins/fleet/server/services/agent_policies/full_agent_policy.ts +++ b/x-pack/plugins/fleet/server/services/agent_policies/full_agent_policy.ts @@ -171,17 +171,18 @@ export function transformOutputToFullPolicyOutput( standalone = false ): FullAgentPolicyOutput { // eslint-disable-next-line @typescript-eslint/naming-convention - const { config_yaml, type, hosts, ca_sha256, ca_trusted_fingerprint } = output; + const { config_yaml, type, hosts, ca_sha256, ca_trusted_fingerprint, ssl } = output; const configJs = config_yaml ? safeLoad(config_yaml) : {}; const newOutput: FullAgentPolicyOutput = { ...configJs, type, hosts, ca_sha256, + ...(ssl ? { ssl } : {}), ...(ca_trusted_fingerprint ? { 'ssl.ca_trusted_fingerprint': ca_trusted_fingerprint } : {}), }; - if (standalone) { + if (output.type === outputType.Elasticsearch && standalone) { newOutput.username = '{ES_USERNAME}'; newOutput.password = '{ES_PASSWORD}'; } diff --git a/x-pack/plugins/fleet/server/services/output.ts b/x-pack/plugins/fleet/server/services/output.ts index 61592420e9116..832cb810f7505 100644 --- a/x-pack/plugins/fleet/server/services/output.ts +++ b/x-pack/plugins/fleet/server/services/output.ts @@ -10,7 +10,7 @@ import uuid from 'uuid/v5'; import type { NewOutput, Output, OutputSOAttributes } from '../types'; import { DEFAULT_OUTPUT, DEFAULT_OUTPUT_ID, OUTPUT_SAVED_OBJECT_TYPE } from '../constants'; -import { decodeCloudId, normalizeHostsForAgents, SO_SEARCH_LIMIT } from '../../common'; +import { decodeCloudId, normalizeHostsForAgents, SO_SEARCH_LIMIT, outputType } from '../../common'; import { OutputUnauthorizedError } from '../errors'; import { appContextService } from './app_context'; @@ -149,7 +149,7 @@ class OutputService { } } - if (data.hosts) { + if (data.type === outputType.Elasticsearch && data.hosts) { data.hosts = data.hosts.map(normalizeHostsForAgents); } @@ -260,7 +260,7 @@ class OutputService { ); } - const updateData = { ...data }; + const updateData = { type: originalOutput.type, ...data }; // ensure only default output exists if (data.is_default) { @@ -287,7 +287,7 @@ class OutputService { } } - if (updateData.hosts) { + if (updateData.type === outputType.Elasticsearch && updateData.hosts) { updateData.hosts = updateData.hosts.map(normalizeHostsForAgents); } const outputSO = await soClient.update( diff --git a/x-pack/plugins/fleet/server/services/preconfiguration.ts b/x-pack/plugins/fleet/server/services/preconfiguration.ts index e9c079d435e7e..9d37740d26fda 100644 --- a/x-pack/plugins/fleet/server/services/preconfiguration.ts +++ b/x-pack/plugins/fleet/server/services/preconfiguration.ts @@ -58,6 +58,7 @@ function isPreconfiguredOutputDifferentFromCurrent( existingOutput.hosts?.map(normalizeHostsForAgents), preconfiguredOutput.hosts.map(normalizeHostsForAgents) )) || + (preconfiguredOutput.ssl && !isEqual(preconfiguredOutput.ssl, existingOutput.ssl)) || existingOutput.ca_sha256 !== preconfiguredOutput.ca_sha256 || existingOutput.ca_trusted_fingerprint !== preconfiguredOutput.ca_trusted_fingerprint || existingOutput.config_yaml !== preconfiguredOutput.config_yaml diff --git a/x-pack/plugins/fleet/server/types/models/output.ts b/x-pack/plugins/fleet/server/types/models/output.ts index 7d9232862092e..f9438316b3ba6 100644 --- a/x-pack/plugins/fleet/server/types/models/output.ts +++ b/x-pack/plugins/fleet/server/types/models/output.ts @@ -9,16 +9,76 @@ import { schema } from '@kbn/config-schema'; import { outputType } from '../../../common/constants'; +export function validateLogstashHost(val: string) { + if (val.match(/^http([s]){0,1}:\/\//)) { + throw new Error('Invalid logstash host should not start with http(s)'); + } + + try { + const url = new URL(`http://${val}`); + + if (url.host !== val) { + throw new Error('Invalid host'); + } + } catch (err) { + throw new Error('Invalid logstash host'); + } +} + const OutputBaseSchema = { + id: schema.maybe(schema.string()), name: schema.string(), - type: schema.oneOf([schema.literal(outputType.Elasticsearch)]), - hosts: schema.maybe(schema.arrayOf(schema.string())), - config: schema.maybe(schema.recordOf(schema.string(), schema.any())), + type: schema.oneOf([ + schema.literal(outputType.Elasticsearch), + schema.literal(outputType.Logstash), + ]), + hosts: schema.conditional( + schema.siblingRef('type'), + schema.literal(outputType.Elasticsearch), + schema.arrayOf(schema.uri({ scheme: ['http', 'https'] })), + schema.arrayOf(schema.string({ validate: validateLogstashHost })) + ), + is_default: schema.boolean({ defaultValue: false }), + is_default_monitoring: schema.boolean({ defaultValue: false }), + ca_sha256: schema.maybe(schema.string()), + ca_trusted_fingerprint: schema.maybe(schema.string()), config_yaml: schema.maybe(schema.string()), + ssl: schema.maybe( + schema.object({ + certificate_authorities: schema.maybe(schema.arrayOf(schema.string())), + certificate: schema.maybe(schema.string()), + key: schema.maybe(schema.string()), + }) + ), }; -export const NewOutputSchema = schema.object({ - ...OutputBaseSchema, +export const NewOutputSchema = schema.object({ ...OutputBaseSchema }); + +export const UpdateOutputSchema = schema.object({ + name: schema.maybe(schema.string()), + type: schema.maybe( + schema.oneOf([schema.literal(outputType.Elasticsearch), schema.literal(outputType.Logstash)]) + ), + hosts: schema.maybe( + schema.conditional( + schema.siblingRef('type'), + schema.literal(outputType.Elasticsearch), + schema.arrayOf(schema.uri({ scheme: ['http', 'https'] })), + schema.arrayOf(schema.string({ validate: validateLogstashHost })) + ) + ), + is_default: schema.maybe(schema.boolean()), + is_default_monitoring: schema.maybe(schema.boolean()), + ca_sha256: schema.maybe(schema.string()), + ca_trusted_fingerprint: schema.maybe(schema.string()), + config_yaml: schema.maybe(schema.string()), + ssl: schema.maybe( + schema.object({ + certificate_authorities: schema.maybe(schema.arrayOf(schema.string())), + certificate: schema.maybe(schema.string()), + key: schema.maybe(schema.string()), + }) + ), }); export const OutputSchema = schema.object({ diff --git a/x-pack/plugins/fleet/server/types/models/preconfiguration.ts b/x-pack/plugins/fleet/server/types/models/preconfiguration.ts index 960ecbe67d593..b025e6ff5c755 100644 --- a/x-pack/plugins/fleet/server/types/models/preconfiguration.ts +++ b/x-pack/plugins/fleet/server/types/models/preconfiguration.ts @@ -10,10 +10,10 @@ import semverValid from 'semver/functions/valid'; import { PRECONFIGURATION_LATEST_KEYWORD } from '../../constants'; import type { PreconfiguredOutput } from '../../../common'; -import { outputType } from '../../../common'; import { AgentPolicyBaseSchema } from './agent_policy'; import { NamespaceSchema } from './package_policy'; +import { NewOutputSchema } from './output'; const varsSchema = schema.maybe( schema.arrayOf( @@ -74,16 +74,10 @@ function validatePreconfiguredOutputs(outputs: PreconfiguredOutput[]) { } export const PreconfiguredOutputsSchema = schema.arrayOf( - schema.object({ + NewOutputSchema.extends({ id: schema.string(), - is_default: schema.boolean({ defaultValue: false }), - is_default_monitoring: schema.boolean({ defaultValue: false }), - name: schema.string(), - type: schema.oneOf([schema.literal(outputType.Elasticsearch)]), - hosts: schema.maybe(schema.arrayOf(schema.uri({ scheme: ['http', 'https'] }))), - ca_sha256: schema.maybe(schema.string()), - ca_trusted_fingerprint: schema.maybe(schema.string()), config: schema.maybe(schema.object({}, { unknowns: 'allow' })), + config_yaml: schema.never(), }), { defaultValue: [], diff --git a/x-pack/plugins/fleet/server/types/rest_spec/output.ts b/x-pack/plugins/fleet/server/types/rest_spec/output.ts index de2ddeb3a1bfd..21a0b19c0dd20 100644 --- a/x-pack/plugins/fleet/server/types/rest_spec/output.ts +++ b/x-pack/plugins/fleet/server/types/rest_spec/output.ts @@ -7,6 +7,8 @@ import { schema } from '@kbn/config-schema'; +import { NewOutputSchema, UpdateOutputSchema } from '../models'; + export const GetOneOutputRequestSchema = { params: schema.object({ outputId: schema.string(), @@ -22,31 +24,12 @@ export const DeleteOutputRequestSchema = { export const GetOutputsRequestSchema = {}; export const PostOutputRequestSchema = { - body: schema.object({ - id: schema.maybe(schema.string()), - name: schema.string(), - type: schema.oneOf([schema.literal('elasticsearch')]), - is_default: schema.boolean({ defaultValue: false }), - is_default_monitoring: schema.boolean({ defaultValue: false }), - hosts: schema.maybe(schema.arrayOf(schema.uri({ scheme: ['http', 'https'] }))), - ca_sha256: schema.maybe(schema.string()), - ca_trusted_fingerprint: schema.maybe(schema.string()), - config_yaml: schema.maybe(schema.string()), - }), + body: NewOutputSchema, }; export const PutOutputRequestSchema = { params: schema.object({ outputId: schema.string(), }), - body: schema.object({ - type: schema.maybe(schema.oneOf([schema.literal('elasticsearch')])), - name: schema.maybe(schema.string()), - is_default: schema.maybe(schema.boolean()), - is_default_monitoring: schema.maybe(schema.boolean()), - hosts: schema.maybe(schema.arrayOf(schema.uri({ scheme: ['http', 'https'] }))), - ca_sha256: schema.maybe(schema.string()), - ca_trusted_fingerprint: schema.maybe(schema.string()), - config_yaml: schema.maybe(schema.string()), - }), + body: UpdateOutputSchema, }; diff --git a/x-pack/test/fleet_api_integration/apis/outputs/crud.ts b/x-pack/test/fleet_api_integration/apis/outputs/crud.ts index 86ce4a8fdf617..9aa9e1a6973cf 100644 --- a/x-pack/test/fleet_api_integration/apis/outputs/crud.ts +++ b/x-pack/test/fleet_api_integration/apis/outputs/crud.ts @@ -18,6 +18,7 @@ export default function (providerContext: FtrProviderContext) { describe('fleet_output_crud', async function () { skipIfNoDockerRegistry(providerContext); before(async () => { + await esArchiver.load('x-pack/test/functional/es_archives/empty_kibana'); await esArchiver.load('x-pack/test/functional/es_archives/fleet/empty_fleet_server'); }); setupFleetAndAgents(providerContext); @@ -36,6 +37,7 @@ export default function (providerContext: FtrProviderContext) { }); after(async () => { + await esArchiver.unload('x-pack/test/functional/es_archives/empty_kibana'); await esArchiver.unload('x-pack/test/functional/es_archives/fleet/empty_fleet_server'); }); @@ -99,6 +101,56 @@ export default function (providerContext: FtrProviderContext) { }); }); + it('should allow to create a logstash output ', async function () { + const { body: postResponse } = await supertest + .post(`/api/fleet/outputs`) + .set('kbn-xsrf', 'xxxx') + .send({ + name: 'My Logstash Output', + type: 'logstash', + hosts: ['test.fr:443'], + ssl: { + certificate: 'CERTIFICATE', + key: 'KEY', + certificate_authorities: ['CA1', 'CA2'], + }, + }) + .expect(200); + + const { id: _, ...itemWithoutId } = postResponse.item; + expect(itemWithoutId).to.eql({ + name: 'My Logstash Output', + type: 'logstash', + hosts: ['test.fr:443'], + is_default: false, + is_default_monitoring: false, + ssl: { + certificate: 'CERTIFICATE', + key: 'KEY', + certificate_authorities: ['CA1', 'CA2'], + }, + }); + }); + + it('should not allow to create a logstash output with http hosts ', async function () { + const { body: postResponse } = await supertest + .post(`/api/fleet/outputs`) + .set('kbn-xsrf', 'xxxx') + .send({ + name: 'My Logstash Output', + type: 'logstash', + hosts: ['https://test.fr:443'], + ssl: { + certificate: 'CERTIFICATE', + key: 'KEY', + certificate_authorities: ['CA1', 'CA2'], + }, + }) + .expect(400); + + expect(postResponse.message).match(/Invalid logstash host/); + }); + it('should toggle default output when creating a new default output ', async function () { await supertest .post(`/api/fleet/outputs`) From e46dd21b8e714158f1ff9cab9e0e9b3e437d4782 Mon Sep 17 00:00:00 2001 From: Nicolas Chaulet Date: Mon, 21 Feb 2022 09:07:10 -0500 Subject: [PATCH 2/6] update openAPI bundled --- x-pack/plugins/fleet/common/openapi/bundled.json | 14 ++++++++++++++ x-pack/plugins/fleet/common/openapi/bundled.yaml | 9 +++++++++ 2 files changed, 23 insertions(+) diff --git a/x-pack/plugins/fleet/common/openapi/bundled.json b/x-pack/plugins/fleet/common/openapi/bundled.json index bc6fd8bff0da0..3b7c030e5f39c 100644 --- a/x-pack/plugins/fleet/common/openapi/bundled.json +++ b/x-pack/plugins/fleet/common/openapi/bundled.json @@ -4231,6 +4231,20 @@ }, "config_yaml": { "type": "string" + }, + "ssl": { + "type": "object", + "properties": { + "certificate": { + "type": "string" + }, + "certificate_authorities": { + "type": "string" + }, + "key": { + "type": "string" + } + } } }, "required": [ diff --git a/x-pack/plugins/fleet/common/openapi/bundled.yaml b/x-pack/plugins/fleet/common/openapi/bundled.yaml index 50a7463029f98..4706ee74a71e3 100644 --- a/x-pack/plugins/fleet/common/openapi/bundled.yaml +++ b/x-pack/plugins/fleet/common/openapi/bundled.yaml @@ -2668,6 +2668,15 @@ components: type: object config_yaml: type: string + ssl: + type: object + properties: + certificate: + type: string + certificate_authorities: + type: string + key: + type: string required: - id - is_default From ce6c0e19ae310fc173a560c3b3e82e721c265efc Mon Sep 17 00:00:00 2001 From: Nicolas Chaulet Date: Mon, 21 Feb 2022 10:05:02 -0500 Subject: [PATCH 3/6] Fix tests --- .../fleet/server/types/models/preconfiguration.test.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/x-pack/plugins/fleet/server/types/models/preconfiguration.test.ts b/x-pack/plugins/fleet/server/types/models/preconfiguration.test.ts index 9cf8626f5fed5..7b71070a504bb 100644 --- a/x-pack/plugins/fleet/server/types/models/preconfiguration.test.ts +++ b/x-pack/plugins/fleet/server/types/models/preconfiguration.test.ts @@ -17,12 +17,14 @@ describe('Test preconfiguration schema', () => { name: 'Output 1', type: 'elasticsearch', is_default: true, + hosts: ['http://test.fr:9200'], }, { id: 'output-2', name: 'Output 2', type: 'elasticsearch', is_default: true, + hosts: ['http://test.fr:9200'], }, ]); }).toThrowError('preconfigured outputs can only have one default output.'); @@ -35,12 +37,14 @@ describe('Test preconfiguration schema', () => { name: 'Output 1', type: 'elasticsearch', is_default_monitoring: true, + hosts: ['http://test.fr:9200'], }, { id: 'output-2', name: 'Output 2', type: 'elasticsearch', is_default_monitoring: true, + hosts: ['http://test.fr:9200'], }, ]); }).toThrowError('preconfigured outputs can only have one default monitoring output.'); @@ -52,11 +56,13 @@ describe('Test preconfiguration schema', () => { id: 'nonuniqueid', name: 'Output 1', type: 'elasticsearch', + hosts: ['http://test.fr:9200'], }, { id: 'nonuniqueid', name: 'Output 2', type: 'elasticsearch', + hosts: ['http://test.fr:9200'], }, ]); }).toThrowError('preconfigured outputs need to have unique ids.'); @@ -68,11 +74,13 @@ describe('Test preconfiguration schema', () => { id: 'output-1', name: 'nonuniquename', type: 'elasticsearch', + hosts: ['http://test.fr:9200'], }, { id: 'output-2', name: 'nonuniquename', type: 'elasticsearch', + hosts: ['http://test.fr:9200'], }, ]); }).toThrowError('preconfigured outputs need to have unique names.'); From ce7d90e4c2335104339f45bc87db885afb58cb89 Mon Sep 17 00:00:00 2001 From: Nicolas Chaulet Date: Mon, 21 Feb 2022 10:52:04 -0500 Subject: [PATCH 4/6] Fix rest spec --- .../plugins/fleet/common/types/rest_spec/output.ts | 14 ++++++++++++-- x-pack/plugins/fleet/server/types/models/output.ts | 8 +++----- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/x-pack/plugins/fleet/common/types/rest_spec/output.ts b/x-pack/plugins/fleet/common/types/rest_spec/output.ts index 9a5001a3af10b..f5fb227b2c3be 100644 --- a/x-pack/plugins/fleet/common/types/rest_spec/output.ts +++ b/x-pack/plugins/fleet/common/types/rest_spec/output.ts @@ -28,7 +28,7 @@ export interface PutOutputRequest { outputId: string; }; body: { - type?: 'elasticsearch'; + type?: 'elasticsearch' | 'logstash'; name?: string; hosts?: string[]; ca_sha256?: string; @@ -36,13 +36,18 @@ export interface PutOutputRequest { config_yaml?: string; is_default?: boolean; is_default_monitoring?: boolean; + ssl?: { + certificate_authorities?: string; + certificate?: string; + key?: string; + }; }; } export interface PostOutputRequest { body: { id?: string; - type: 'elasticsearch'; + type: 'elasticsearch' | 'logstash'; name: string; hosts?: string[]; ca_sha256?: string; @@ -50,6 +55,11 @@ export interface PostOutputRequest { is_default?: boolean; is_default_monitoring?: boolean; config_yaml?: string; + ssl?: { + certificate_authorities?: string; + certificate?: string; + key?: string; + }; }; } diff --git a/x-pack/plugins/fleet/server/types/models/output.ts b/x-pack/plugins/fleet/server/types/models/output.ts index f9438316b3ba6..56e14a140119b 100644 --- a/x-pack/plugins/fleet/server/types/models/output.ts +++ b/x-pack/plugins/fleet/server/types/models/output.ts @@ -60,12 +60,10 @@ export const UpdateOutputSchema = schema.object({ schema.oneOf([schema.literal(outputType.Elasticsearch), schema.literal(outputType.Logstash)]) ), hosts: schema.maybe( - schema.conditional( - schema.siblingRef('type'), - schema.literal(outputType.Elasticsearch), + schema.oneOf([ schema.arrayOf(schema.uri({ scheme: ['http', 'https'] })), - schema.arrayOf(schema.string({ validate: validateLogstashHost })) - ) + schema.arrayOf(schema.string({ validate: validateLogstashHost })), + ]) ), is_default: schema.maybe(schema.boolean()), is_default_monitoring: schema.maybe(schema.boolean()), From 2b972c4eb1266e02b93b332f163a854e23901985 Mon Sep 17 00:00:00 2001 From: Nicolas Chaulet Date: Tue, 22 Feb 2022 08:46:33 -0500 Subject: [PATCH 5/6] Update typo in types --- x-pack/plugins/fleet/common/openapi/bundled.json | 5 ++++- x-pack/plugins/fleet/common/openapi/bundled.yaml | 4 +++- .../fleet/common/openapi/components/schemas/output.yaml | 4 +++- x-pack/plugins/fleet/common/types/rest_spec/output.ts | 4 ++-- 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/fleet/common/openapi/bundled.json b/x-pack/plugins/fleet/common/openapi/bundled.json index 3b7c030e5f39c..f9345836e22b3 100644 --- a/x-pack/plugins/fleet/common/openapi/bundled.json +++ b/x-pack/plugins/fleet/common/openapi/bundled.json @@ -4239,7 +4239,10 @@ "type": "string" }, "certificate_authorities": { - "type": "string" + "type": "array", + "items": { + "type": "string" + } }, "key": { "type": "string" diff --git a/x-pack/plugins/fleet/common/openapi/bundled.yaml b/x-pack/plugins/fleet/common/openapi/bundled.yaml index 4706ee74a71e3..8fd679daa6998 100644 --- a/x-pack/plugins/fleet/common/openapi/bundled.yaml +++ b/x-pack/plugins/fleet/common/openapi/bundled.yaml @@ -2674,7 +2674,9 @@ components: certificate: type: string certificate_authorities: - type: string + type: array + items: + type: string key: type: string required: diff --git a/x-pack/plugins/fleet/common/openapi/components/schemas/output.yaml b/x-pack/plugins/fleet/common/openapi/components/schemas/output.yaml index 1fff27f279b14..45f564414e14e 100644 --- a/x-pack/plugins/fleet/common/openapi/components/schemas/output.yaml +++ b/x-pack/plugins/fleet/common/openapi/components/schemas/output.yaml @@ -30,7 +30,9 @@ properties: certificate: type: string certificate_authorities: - type: string + type: array + items: + type: string key: type: string required: diff --git a/x-pack/plugins/fleet/common/types/rest_spec/output.ts b/x-pack/plugins/fleet/common/types/rest_spec/output.ts index f5fb227b2c3be..2a3929c0f33f8 100644 --- a/x-pack/plugins/fleet/common/types/rest_spec/output.ts +++ b/x-pack/plugins/fleet/common/types/rest_spec/output.ts @@ -37,7 +37,7 @@ export interface PutOutputRequest { is_default?: boolean; is_default_monitoring?: boolean; ssl?: { - certificate_authorities?: string; + certificate_authorities?: string[]; certificate?: string; key?: string; }; @@ -56,7 +56,7 @@ export interface PostOutputRequest { is_default_monitoring?: boolean; config_yaml?: string; ssl?: { - certificate_authorities?: string; + certificate_authorities?: string[]; certificate?: string; key?: string; }; From efab4500b012c08aad7643c094d63e86311875e3 Mon Sep 17 00:00:00 2001 From: Nicolas Chaulet Date: Tue, 22 Feb 2022 09:04:03 -0500 Subject: [PATCH 6/6] Fix after review --- .../fleet/server/types/models/output.test.ts | 26 +++++++++++++++++++ .../fleet/server/types/models/output.ts | 6 ++--- .../apis/outputs/crud.ts | 2 +- 3 files changed, 30 insertions(+), 4 deletions(-) create mode 100644 x-pack/plugins/fleet/server/types/models/output.test.ts diff --git a/x-pack/plugins/fleet/server/types/models/output.test.ts b/x-pack/plugins/fleet/server/types/models/output.test.ts new file mode 100644 index 0000000000000..8ad2b14299c73 --- /dev/null +++ b/x-pack/plugins/fleet/server/types/models/output.test.ts @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { validateLogstashHost } from './output'; + +describe('Output model', () => { + describe('validateLogstashHost', () => { + it('should support valid host', () => { + expect(validateLogstashHost('test.fr:5044')).toBeUndefined(); + }); + + it('should return an error for an invalid host', () => { + expect(validateLogstashHost('!@#%&!#!@')).toMatchInlineSnapshot(`"Invalid logstash host"`); + }); + + it('should return an error for an invalid host with http scheme', () => { + expect(validateLogstashHost('https://test.fr:5044')).toMatchInlineSnapshot( + `"Invalid logstash host should not start with http(s)"` + ); + }); + }); +}); diff --git a/x-pack/plugins/fleet/server/types/models/output.ts b/x-pack/plugins/fleet/server/types/models/output.ts index 56e14a140119b..ee7854ade30a8 100644 --- a/x-pack/plugins/fleet/server/types/models/output.ts +++ b/x-pack/plugins/fleet/server/types/models/output.ts @@ -11,17 +11,17 @@ import { outputType } from '../../../common/constants'; export function validateLogstashHost(val: string) { if (val.match(/^http([s]){0,1}:\/\//)) { - throw new Error('Invalid logstash host should not start with http(s)'); + return 'Invalid logstash host should not start with http(s)'; } try { const url = new URL(`http://${val}`); if (url.host !== val) { - throw new Error('Invalid host'); + return 'Invalid host'; } } catch (err) { - throw new Error('Invalid logstash host'); + return 'Invalid logstash host'; } } diff --git a/x-pack/test/fleet_api_integration/apis/outputs/crud.ts b/x-pack/test/fleet_api_integration/apis/outputs/crud.ts index 9aa9e1a6973cf..14f2e53949cfc 100644 --- a/x-pack/test/fleet_api_integration/apis/outputs/crud.ts +++ b/x-pack/test/fleet_api_integration/apis/outputs/crud.ts @@ -148,7 +148,7 @@ export default function (providerContext: FtrProviderContext) { }) .expect(400); - expect(postResponse.message).match(/Invalid logstash host/); + expect(postResponse.message).match(/Invalid logstash host should not start with http\(s\)/); }); it('should toggle default output when creating a new default output ', async function () {