Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Fleet] Support logstash as an output type in API and Kibana config #125990

Merged
merged 10 commits into from
Mar 1, 2022
1 change: 1 addition & 0 deletions x-pack/plugins/fleet/common/constants/output.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
17 changes: 17 additions & 0 deletions x-pack/plugins/fleet/common/openapi/bundled.json
Original file line number Diff line number Diff line change
Expand Up @@ -4260,6 +4260,23 @@
},
"config_yaml": {
"type": "string"
},
"ssl": {
"type": "object",
"properties": {
"certificate": {
"type": "string"
},
"certificate_authorities": {
"type": "array",
"items": {
"type": "string"
}
},
"key": {
"type": "string"
}
}
}
},
"required": [
Expand Down
11 changes: 11 additions & 0 deletions x-pack/plugins/fleet/common/openapi/bundled.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2685,6 +2685,17 @@ components:
type: object
config_yaml:
type: string
ssl:
type: object
properties:
certificate:
type: string
certificate_authorities:
type: array
items:
type: string
key:
type: string
required:
- id
- is_default
Expand Down
11 changes: 11 additions & 0 deletions x-pack/plugins/fleet/common/openapi/components/schemas/output.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,17 @@ properties:
type: object
config_yaml:
type: string
ssl:
type: object
properties:
certificate:
type: string
certificate_authorities:
type: array
items:
type: string
key:
type: string
required:
- id
- is_default
Expand Down
7 changes: 6 additions & 1 deletion x-pack/plugins/fleet/common/types/models/output.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<OutputType>;
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 & {
Expand Down
14 changes: 12 additions & 2 deletions x-pack/plugins/fleet/common/types/rest_spec/output.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,28 +28,38 @@ export interface PutOutputRequest {
outputId: string;
};
body: {
type?: 'elasticsearch';
type?: 'elasticsearch' | 'logstash';
name?: string;
hosts?: string[];
ca_sha256?: string;
ca_trusted_fingerprint?: string;
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;
ca_trusted_fingerprint?: string;
is_default?: boolean;
is_default_monitoring?: boolean;
config_yaml?: string;
ssl?: {
certificate_authorities?: string[];
certificate?: string;
key?: string;
};
};
}

Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/fleet/server/saved_objects/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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",
}
`);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -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}';
}
Expand Down
8 changes: 4 additions & 4 deletions x-pack/plugins/fleet/server/services/output.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -149,7 +149,7 @@ class OutputService {
}
}

if (data.hosts) {
if (data.type === outputType.Elasticsearch && data.hosts) {
data.hosts = data.hosts.map(normalizeHostsForAgents);
}

Expand Down Expand Up @@ -260,7 +260,7 @@ class OutputService {
);
}

const updateData = { ...data };
const updateData = { type: originalOutput.type, ...data };

// ensure only default output exists
if (data.is_default) {
Expand All @@ -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<OutputSOAttributes>(
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/fleet/server/services/preconfiguration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,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
Expand Down
26 changes: 26 additions & 0 deletions x-pack/plugins/fleet/server/types/models/output.test.ts
Original file line number Diff line number Diff line change
@@ -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)"`
);
});
});
});
68 changes: 63 additions & 5 deletions x-pack/plugins/fleet/server/types/models/output.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,74 @@ import { schema } from '@kbn/config-schema';

import { outputType } from '../../../common/constants';

export function validateLogstashHost(val: string) {
nchaulet marked this conversation as resolved.
Show resolved Hide resolved
if (val.match(/^http([s]){0,1}:\/\//)) {
return 'Invalid logstash host should not start with http(s)';
}

try {
const url = new URL(`http://${val}`);

if (url.host !== val) {
return 'Invalid host';
}
} catch (err) {
return '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.oneOf([
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({
Expand Down
Loading