mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
add query to check auditbeat indexes/indices/alias + indexfield query… (#26234)
* add query to check auditbeat indexes/indices/alias + indexfield query for KQL * Add api integration test for secops with esArchiver * restructure esArchiver folder for auditbeat * mock is not a page * add EmptyPage component + container to detect if audibeat indexes is set up or not + test * fix type issues * review re-working * remove any for ALiases * remove duplicate interface * resolve PR review * remove flicky transistion between data and no data
This commit is contained in:
parent
6bdcd88a63
commit
afe82e2054
59 changed files with 3125 additions and 117 deletions
|
@ -255,6 +255,7 @@
|
|||
"@types/bluebird": "^3.1.1",
|
||||
"@types/boom": "^7.2.0",
|
||||
"@types/chance": "^1.0.0",
|
||||
"@types/cheerio": "^0.22.10",
|
||||
"@types/classnames": "^2.2.3",
|
||||
"@types/d3": "^3.5.41",
|
||||
"@types/dedent": "^0.7.0",
|
||||
|
@ -381,9 +382,9 @@
|
|||
"sinon": "^5.0.7",
|
||||
"strip-ansi": "^3.0.1",
|
||||
"stylelint": "^9.7.1",
|
||||
"stylelint-processor-styled-components": "^1.5.0",
|
||||
"stylelint-config-styled-components": "^0.1.1",
|
||||
"stylelint-config-standard": "^18.2.0",
|
||||
"stylelint-config-styled-components": "^0.1.1",
|
||||
"stylelint-processor-styled-components": "^1.5.0",
|
||||
"supertest": "^3.1.0",
|
||||
"supertest-as-promised": "^4.0.2",
|
||||
"tree-kill": "^1.1.0",
|
||||
|
|
|
@ -97,6 +97,18 @@
|
|||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "status",
|
||||
"description": "The status of the source",
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": { "kind": "OBJECT", "name": "SourceStatus", "ofType": null }
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "getEvents",
|
||||
"description": "Gets Suricata events based on timerange and specified criteria, or all events in the timerange if no criteria is specified",
|
||||
|
@ -142,7 +154,7 @@
|
|||
"description": "A set of configuration options for a security data source",
|
||||
"fields": [
|
||||
{
|
||||
"name": "fileAlias",
|
||||
"name": "logAlias",
|
||||
"description": "The alias to read file data from",
|
||||
"args": [],
|
||||
"type": {
|
||||
|
@ -153,6 +165,18 @@
|
|||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "auditbeatAlias",
|
||||
"description": "The alias to read auditbeat data from",
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": { "kind": "SCALAR", "name": "String", "ofType": null }
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "fields",
|
||||
"description": "The field mapping to use for this source",
|
||||
|
@ -272,6 +296,176 @@
|
|||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "SourceStatus",
|
||||
"description": "The status of an infrastructure data source",
|
||||
"fields": [
|
||||
{
|
||||
"name": "auditbeatAliasExists",
|
||||
"description": "Whether the configured auditbeat alias exists",
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": { "kind": "SCALAR", "name": "Boolean", "ofType": null }
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "auditbeatIndicesExist",
|
||||
"description": "Whether the configured alias or wildcard pattern resolve to any auditbeat indices",
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": { "kind": "SCALAR", "name": "Boolean", "ofType": null }
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "auditbeatIndices",
|
||||
"description": "The list of indices in the auditbeat alias",
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "LIST",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": { "kind": "SCALAR", "name": "String", "ofType": null }
|
||||
}
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "indexFields",
|
||||
"description": "The list of fields defined in the index mappings",
|
||||
"args": [
|
||||
{
|
||||
"name": "indexType",
|
||||
"description": "",
|
||||
"type": { "kind": "ENUM", "name": "IndexType", "ofType": null },
|
||||
"defaultValue": "ANY"
|
||||
}
|
||||
],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "LIST",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": { "kind": "OBJECT", "name": "IndexField", "ofType": null }
|
||||
}
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"inputFields": null,
|
||||
"interfaces": [],
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "SCALAR",
|
||||
"name": "Boolean",
|
||||
"description": "The `Boolean` scalar type represents `true` or `false`.",
|
||||
"fields": null,
|
||||
"inputFields": null,
|
||||
"interfaces": null,
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "ENUM",
|
||||
"name": "IndexType",
|
||||
"description": "",
|
||||
"fields": null,
|
||||
"inputFields": null,
|
||||
"interfaces": null,
|
||||
"enumValues": [
|
||||
{ "name": "ANY", "description": "", "isDeprecated": false, "deprecationReason": null },
|
||||
{ "name": "LOGS", "description": "", "isDeprecated": false, "deprecationReason": null },
|
||||
{
|
||||
"name": "AUDITBEAT",
|
||||
"description": "",
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "IndexField",
|
||||
"description": "A descriptor of a field in an index",
|
||||
"fields": [
|
||||
{
|
||||
"name": "name",
|
||||
"description": "The name of the field",
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": { "kind": "SCALAR", "name": "String", "ofType": null }
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "type",
|
||||
"description": "The type of the field's values as recognized by Kibana",
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": { "kind": "SCALAR", "name": "String", "ofType": null }
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "searchable",
|
||||
"description": "Whether the field's values can be efficiently searched for",
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": { "kind": "SCALAR", "name": "Boolean", "ofType": null }
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "aggregatable",
|
||||
"description": "Whether the field's values can be aggregated",
|
||||
"args": [],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": { "kind": "SCALAR", "name": "Boolean", "ofType": null }
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"inputFields": null,
|
||||
"interfaces": [],
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "INPUT_OBJECT",
|
||||
"name": "TimerangeInput",
|
||||
|
@ -1026,16 +1220,6 @@
|
|||
],
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "SCALAR",
|
||||
"name": "Boolean",
|
||||
"description": "The `Boolean` scalar type represents `true` or `false`.",
|
||||
"fields": null,
|
||||
"inputFields": null,
|
||||
"interfaces": null,
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "__Field",
|
||||
|
|
7
x-pack/plugins/secops/common/graphql/shared/index.ts
Normal file
7
x-pack/plugins/secops/common/graphql/shared/index.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export * from './schema.gql';
|
15
x-pack/plugins/secops/common/graphql/shared/schema.gql.ts
Normal file
15
x-pack/plugins/secops/common/graphql/shared/schema.gql.ts
Normal file
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
* 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 gql from 'graphql-tag';
|
||||
|
||||
export const sharedSchema = gql`
|
||||
enum IndexType {
|
||||
ANY
|
||||
LOGS
|
||||
AUDITBEAT
|
||||
}
|
||||
`;
|
|
@ -37,12 +37,14 @@ export interface Query {
|
|||
export interface Source {
|
||||
id: string /** The id of the source */;
|
||||
configuration: SourceConfiguration /** The raw configuration of the source */;
|
||||
status: SourceStatus /** The status of the source */;
|
||||
getEvents?: EventsData | null /** Gets Suricata events based on timerange and specified criteria, or all events in the timerange if no criteria is specified */;
|
||||
whoAmI?: SayMyName | null /** Just a simple example to get the app name */;
|
||||
}
|
||||
/** A set of configuration options for a security data source */
|
||||
export interface SourceConfiguration {
|
||||
fileAlias: string /** The alias to read file data from */;
|
||||
logAlias: string /** The alias to read file data from */;
|
||||
auditbeatAlias: string /** The alias to read auditbeat data from */;
|
||||
fields: SourceFields /** The field mapping to use for this source */;
|
||||
}
|
||||
/** A mapping of semantic fields to their document counterparts */
|
||||
|
@ -54,6 +56,20 @@ export interface SourceFields {
|
|||
tiebreaker: string /** The field to use as a tiebreaker for log events that have identical timestamps */;
|
||||
timestamp: string /** The field to use as a timestamp for metrics and logs */;
|
||||
}
|
||||
/** The status of an infrastructure data source */
|
||||
export interface SourceStatus {
|
||||
auditbeatAliasExists: boolean /** Whether the configured auditbeat alias exists */;
|
||||
auditbeatIndicesExist: boolean /** Whether the configured alias or wildcard pattern resolve to any auditbeat indices */;
|
||||
auditbeatIndices: string[] /** The list of indices in the auditbeat alias */;
|
||||
indexFields: IndexField[] /** The list of fields defined in the index mappings */;
|
||||
}
|
||||
/** A descriptor of a field in an index */
|
||||
export interface IndexField {
|
||||
name: string /** The name of the field */;
|
||||
type: string /** The type of the field's values as recognized by Kibana */;
|
||||
searchable: boolean /** Whether the field's values can be efficiently searched for */;
|
||||
aggregatable: boolean /** Whether the field's values can be aggregated */;
|
||||
}
|
||||
|
||||
export interface EventsData {
|
||||
kpiEventType: KpiItem[];
|
||||
|
@ -135,6 +151,15 @@ export interface GetEventsSourceArgs {
|
|||
timerange: TimerangeInput;
|
||||
filterQuery?: string | null;
|
||||
}
|
||||
export interface IndexFieldsSourceStatusArgs {
|
||||
indexType?: IndexType | null;
|
||||
}
|
||||
|
||||
export enum IndexType {
|
||||
ANY = 'ANY',
|
||||
LOGS = 'LOGS',
|
||||
AUDITBEAT = 'AUDITBEAT',
|
||||
}
|
||||
|
||||
export namespace QueryResolvers {
|
||||
export interface Resolvers<Context = any> {
|
||||
|
@ -171,6 +196,7 @@ export namespace SourceResolvers {
|
|||
any,
|
||||
Context
|
||||
> /** The raw configuration of the source */;
|
||||
status?: StatusResolver<SourceStatus, any, Context> /** The status of the source */;
|
||||
getEvents?: GetEventsResolver<
|
||||
EventsData | null,
|
||||
any,
|
||||
|
@ -189,6 +215,11 @@ export namespace SourceResolvers {
|
|||
Parent = any,
|
||||
Context = any
|
||||
> = Resolver<R, Parent, Context>;
|
||||
export type StatusResolver<R = SourceStatus, Parent = any, Context = any> = Resolver<
|
||||
R,
|
||||
Parent,
|
||||
Context
|
||||
>;
|
||||
export type GetEventsResolver<R = EventsData | null, Parent = any, Context = any> = Resolver<
|
||||
R,
|
||||
Parent,
|
||||
|
@ -209,7 +240,12 @@ export namespace SourceResolvers {
|
|||
/** A set of configuration options for a security data source */
|
||||
export namespace SourceConfigurationResolvers {
|
||||
export interface Resolvers<Context = any> {
|
||||
fileAlias?: FileAliasResolver<string, any, Context> /** The alias to read file data from */;
|
||||
logAlias?: LogAliasResolver<string, any, Context> /** The alias to read file data from */;
|
||||
auditbeatAlias?: AuditbeatAliasResolver<
|
||||
string,
|
||||
any,
|
||||
Context
|
||||
> /** The alias to read auditbeat data from */;
|
||||
fields?: FieldsResolver<
|
||||
SourceFields,
|
||||
any,
|
||||
|
@ -217,7 +253,12 @@ export namespace SourceConfigurationResolvers {
|
|||
> /** The field mapping to use for this source */;
|
||||
}
|
||||
|
||||
export type FileAliasResolver<R = string, Parent = any, Context = any> = Resolver<
|
||||
export type LogAliasResolver<R = string, Parent = any, Context = any> = Resolver<
|
||||
R,
|
||||
Parent,
|
||||
Context
|
||||
>;
|
||||
export type AuditbeatAliasResolver<R = string, Parent = any, Context = any> = Resolver<
|
||||
R,
|
||||
Parent,
|
||||
Context
|
||||
|
@ -274,6 +315,90 @@ export namespace SourceFieldsResolvers {
|
|||
Context
|
||||
>;
|
||||
}
|
||||
/** The status of an infrastructure data source */
|
||||
export namespace SourceStatusResolvers {
|
||||
export interface Resolvers<Context = any> {
|
||||
auditbeatAliasExists?: AuditbeatAliasExistsResolver<
|
||||
boolean,
|
||||
any,
|
||||
Context
|
||||
> /** Whether the configured auditbeat alias exists */;
|
||||
auditbeatIndicesExist?: AuditbeatIndicesExistResolver<
|
||||
boolean,
|
||||
any,
|
||||
Context
|
||||
> /** Whether the configured alias or wildcard pattern resolve to any auditbeat indices */;
|
||||
auditbeatIndices?: AuditbeatIndicesResolver<
|
||||
string[],
|
||||
any,
|
||||
Context
|
||||
> /** The list of indices in the auditbeat alias */;
|
||||
indexFields?: IndexFieldsResolver<
|
||||
IndexField[],
|
||||
any,
|
||||
Context
|
||||
> /** The list of fields defined in the index mappings */;
|
||||
}
|
||||
|
||||
export type AuditbeatAliasExistsResolver<R = boolean, Parent = any, Context = any> = Resolver<
|
||||
R,
|
||||
Parent,
|
||||
Context
|
||||
>;
|
||||
export type AuditbeatIndicesExistResolver<R = boolean, Parent = any, Context = any> = Resolver<
|
||||
R,
|
||||
Parent,
|
||||
Context
|
||||
>;
|
||||
export type AuditbeatIndicesResolver<R = string[], Parent = any, Context = any> = Resolver<
|
||||
R,
|
||||
Parent,
|
||||
Context
|
||||
>;
|
||||
export type IndexFieldsResolver<R = IndexField[], Parent = any, Context = any> = Resolver<
|
||||
R,
|
||||
Parent,
|
||||
Context,
|
||||
IndexFieldsArgs
|
||||
>;
|
||||
export interface IndexFieldsArgs {
|
||||
indexType?: IndexType | null;
|
||||
}
|
||||
}
|
||||
/** A descriptor of a field in an index */
|
||||
export namespace IndexFieldResolvers {
|
||||
export interface Resolvers<Context = any> {
|
||||
name?: NameResolver<string, any, Context> /** The name of the field */;
|
||||
type?: TypeResolver<
|
||||
string,
|
||||
any,
|
||||
Context
|
||||
> /** The type of the field's values as recognized by Kibana */;
|
||||
searchable?: SearchableResolver<
|
||||
boolean,
|
||||
any,
|
||||
Context
|
||||
> /** Whether the field's values can be efficiently searched for */;
|
||||
aggregatable?: AggregatableResolver<
|
||||
boolean,
|
||||
any,
|
||||
Context
|
||||
> /** Whether the field's values can be aggregated */;
|
||||
}
|
||||
|
||||
export type NameResolver<R = string, Parent = any, Context = any> = Resolver<R, Parent, Context>;
|
||||
export type TypeResolver<R = string, Parent = any, Context = any> = Resolver<R, Parent, Context>;
|
||||
export type SearchableResolver<R = boolean, Parent = any, Context = any> = Resolver<
|
||||
R,
|
||||
Parent,
|
||||
Context
|
||||
>;
|
||||
export type AggregatableResolver<R = boolean, Parent = any, Context = any> = Resolver<
|
||||
R,
|
||||
Parent,
|
||||
Context
|
||||
>;
|
||||
}
|
||||
|
||||
export namespace EventsDataResolvers {
|
||||
export interface Resolvers<Context = any> {
|
||||
|
@ -624,6 +749,46 @@ export namespace GetEventsQuery {
|
|||
};
|
||||
}
|
||||
|
||||
export namespace SourceQuery {
|
||||
export type Variables = {
|
||||
sourceId?: string | null;
|
||||
};
|
||||
|
||||
export type Query = {
|
||||
__typename?: 'Query';
|
||||
source: Source;
|
||||
};
|
||||
|
||||
export type Source = {
|
||||
__typename?: 'Source';
|
||||
id: string;
|
||||
configuration: Configuration;
|
||||
status: Status;
|
||||
};
|
||||
|
||||
export type Configuration = {
|
||||
__typename?: 'SourceConfiguration';
|
||||
auditbeatAlias: string;
|
||||
logAlias: string;
|
||||
};
|
||||
|
||||
export type Status = {
|
||||
__typename?: 'SourceStatus';
|
||||
auditbeatIndicesExist: boolean;
|
||||
auditbeatAliasExists: boolean;
|
||||
auditbeatIndices: string[];
|
||||
indexFields: IndexFields[];
|
||||
};
|
||||
|
||||
export type IndexFields = {
|
||||
__typename?: 'IndexField';
|
||||
name: string;
|
||||
searchable: boolean;
|
||||
type: string;
|
||||
aggregatable: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export namespace WhoAmIQuery {
|
||||
export type Variables = {
|
||||
sourceId: string;
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders correctly 1`] = `
|
||||
<Component
|
||||
actionLabel="Do Something"
|
||||
actionUrl="my/url/from/nowwhere"
|
||||
message="My awesome message"
|
||||
title="My Super Title"
|
||||
/>
|
||||
`;
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* 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 { shallow } from 'enzyme';
|
||||
import toJson from 'enzyme-to-json';
|
||||
import React from 'react';
|
||||
import { EmptyPage } from './index';
|
||||
|
||||
it('renders correctly', () => {
|
||||
const EmptyComponent = shallow(
|
||||
<EmptyPage
|
||||
title="My Super Title"
|
||||
message="My awesome message"
|
||||
actionLabel="Do Something"
|
||||
actionUrl="my/url/from/nowwhere"
|
||||
/>
|
||||
);
|
||||
expect(toJson(EmptyComponent)).toMatchSnapshot();
|
||||
});
|
37
x-pack/plugins/secops/public/components/empty_page/index.tsx
Normal file
37
x-pack/plugins/secops/public/components/empty_page/index.tsx
Normal file
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* 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 { EuiButton, EuiEmptyPrompt } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
import { pure } from 'recompose';
|
||||
import styled from 'styled-components';
|
||||
|
||||
interface EmptyPageProps {
|
||||
message: string;
|
||||
title: string;
|
||||
actionLabel: string;
|
||||
actionUrl: string;
|
||||
'data-test-subj'?: string;
|
||||
}
|
||||
|
||||
export const EmptyPage = pure<EmptyPageProps>(
|
||||
({ actionLabel, actionUrl, message, title, ...rest }) => (
|
||||
<CenteredEmptyPrompt
|
||||
title={<h2>{title}</h2>}
|
||||
body={<p>{message}</p>}
|
||||
actions={
|
||||
<EuiButton href={actionUrl} color="primary" fill>
|
||||
{actionLabel}
|
||||
</EuiButton>
|
||||
}
|
||||
{...rest}
|
||||
/>
|
||||
)
|
||||
);
|
||||
|
||||
const CenteredEmptyPrompt = styled(EuiEmptyPrompt)`
|
||||
align-self: center;
|
||||
`;
|
|
@ -8,7 +8,7 @@ import { mount } from 'enzyme';
|
|||
import { noop } from 'lodash/fp';
|
||||
import * as React from 'react';
|
||||
import { Body } from '.';
|
||||
import { mockECSData } from '../../../pages/mock/mock_ecs';
|
||||
import { mockECSData } from '../../../mock/mock_ecs';
|
||||
import { headers } from './column_headers/headers';
|
||||
import { columnRenderers, rowRenderers } from './renderers';
|
||||
import { Sort } from './sort';
|
||||
|
|
|
@ -9,7 +9,7 @@ import { cloneDeep, omit } from 'lodash/fp';
|
|||
import React from 'react';
|
||||
|
||||
import { EMPTY_VALUE, emptyColumnRenderer } from '.';
|
||||
import { mockECSData } from '../../../../pages/mock/mock_ecs';
|
||||
import { mockECSData } from '../../../../mock/mock_ecs';
|
||||
import { ECS } from '../../ecs';
|
||||
|
||||
describe('empty_column_renderer', () => {
|
||||
|
|
|
@ -10,7 +10,7 @@ import React from 'react';
|
|||
|
||||
import { EMPTY_VALUE } from '.';
|
||||
import { columnRenderers } from '.';
|
||||
import { mockECSData } from '../../../../pages/mock/mock_ecs';
|
||||
import { mockECSData } from '../../../../mock/mock_ecs';
|
||||
import { ECS } from '../../ecs';
|
||||
import { getColumnRenderer } from './get_column_renderer';
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ import React from 'react';
|
|||
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { rowRenderers } from '.';
|
||||
import { mockECSData } from '../../../../pages/mock/mock_ecs';
|
||||
import { mockECSData } from '../../../../mock/mock_ecs';
|
||||
import { ECS } from '../../ecs';
|
||||
import { getRowRenderer } from './get_row_renderer';
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ import { cloneDeep, omit } from 'lodash/fp';
|
|||
import React from 'react';
|
||||
|
||||
import { EMPTY_VALUE, plainColumnRenderer } from '.';
|
||||
import { mockECSData } from '../../../../pages/mock/mock_ecs';
|
||||
import { mockECSData } from '../../../../mock/mock_ecs';
|
||||
import { ECS } from '../../ecs';
|
||||
|
||||
describe('plain_column_renderer', () => {
|
||||
|
|
|
@ -9,7 +9,7 @@ import React from 'react';
|
|||
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { plainRowRenderer } from '.';
|
||||
import { mockECSData } from '../../../../pages/mock/mock_ecs';
|
||||
import { mockECSData } from '../../../../mock/mock_ecs';
|
||||
import { ECS } from '../../ecs';
|
||||
|
||||
describe('plain_row_renderer', () => {
|
||||
|
|
|
@ -9,7 +9,7 @@ import { cloneDeep, omit, set } from 'lodash/fp';
|
|||
import React from 'react';
|
||||
|
||||
import { EMPTY_VALUE, suricataColumnRenderer } from '.';
|
||||
import { mockECSData } from '../../../../pages/mock/mock_ecs';
|
||||
import { mockECSData } from '../../../../mock/mock_ecs';
|
||||
import { ECS } from '../../ecs';
|
||||
|
||||
describe('suricata_column_renderer', () => {
|
||||
|
|
|
@ -9,7 +9,7 @@ import { cloneDeep, omit } from 'lodash/fp';
|
|||
import React from 'react';
|
||||
|
||||
import { suricataRowRenderer } from '.';
|
||||
import { mockECSData } from '../../../../pages/mock/mock_ecs';
|
||||
import { mockECSData } from '../../../../mock/mock_ecs';
|
||||
import { ECS } from '../../ecs';
|
||||
|
||||
describe('plain_row_renderer', () => {
|
||||
|
|
|
@ -9,7 +9,7 @@ import { cloneDeep } from 'lodash';
|
|||
import React from 'react';
|
||||
|
||||
import { EMPTY_VALUE } from '.';
|
||||
import { mockECSData } from '../../../../pages/mock/mock_ecs';
|
||||
import { mockECSData } from '../../../../mock/mock_ecs';
|
||||
import { ECS } from '../../ecs';
|
||||
import { unknownColumnRenderer } from './unknown_column_renderer';
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ import * as React from 'react';
|
|||
import { MockedProvider } from 'react-apollo/test-utils';
|
||||
|
||||
import { eventsQuery } from '../../containers/events/events.gql_query';
|
||||
import { mockECSData } from '../../pages/mock/mock_ecs';
|
||||
import { mockECSData } from '../../mock/mock_ecs';
|
||||
import { ColumnHeaderType } from './body/column_headers/column_header';
|
||||
import { headers } from './body/column_headers/headers';
|
||||
import { columnRenderers, rowRenderers } from './body/renderers';
|
||||
|
|
36
x-pack/plugins/secops/public/containers/source/index.tsx
Normal file
36
x-pack/plugins/secops/public/containers/source/index.tsx
Normal file
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* 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 { get } from 'lodash/fp';
|
||||
import React from 'react';
|
||||
import { Query } from 'react-apollo';
|
||||
|
||||
import { SourceQuery } from '../../../common/graphql/types';
|
||||
import { sourceQuery } from './source.gql_query';
|
||||
|
||||
interface WithSourceArgs {
|
||||
auditbeatIndicesExist: boolean;
|
||||
}
|
||||
|
||||
interface WithSourceProps {
|
||||
children: (args: WithSourceArgs) => React.ReactNode;
|
||||
sourceId: string;
|
||||
}
|
||||
|
||||
export const WithSource = ({ children, sourceId }: WithSourceProps) => (
|
||||
<Query<SourceQuery.Query, SourceQuery.Variables>
|
||||
query={sourceQuery}
|
||||
fetchPolicy="no-cache"
|
||||
notifyOnNetworkStatusChange
|
||||
variables={{ sourceId }}
|
||||
>
|
||||
{({ data }) =>
|
||||
children({
|
||||
auditbeatIndicesExist: get('source.status.auditbeatIndicesExist', data),
|
||||
})
|
||||
}
|
||||
</Query>
|
||||
);
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* 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 gql from 'graphql-tag';
|
||||
|
||||
export const sourceQuery = gql`
|
||||
query SourceQuery($sourceId: ID = "default") {
|
||||
source(id: $sourceId) {
|
||||
id
|
||||
configuration {
|
||||
auditbeatAlias
|
||||
logAlias
|
||||
}
|
||||
status {
|
||||
auditbeatIndicesExist
|
||||
auditbeatAliasExists
|
||||
auditbeatIndices
|
||||
indexFields {
|
||||
name
|
||||
searchable
|
||||
type
|
||||
aggregatable
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
|
@ -4,7 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { ECS } from '../../components/timeline/ecs';
|
||||
import { ECS } from '../components/timeline/ecs';
|
||||
|
||||
export const mockECSData: ECS[] = [
|
||||
{
|
|
@ -5,21 +5,26 @@
|
|||
*/
|
||||
|
||||
import { EuiBadge, EuiText } from '@elastic/eui';
|
||||
import { getOr } from 'lodash/fp';
|
||||
import { getOr, isUndefined } from 'lodash/fp';
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { pure } from 'recompose';
|
||||
import { Dispatch } from 'redux';
|
||||
import styled from 'styled-components';
|
||||
import chrome from 'ui/chrome';
|
||||
|
||||
import { EventItem, KpiItem } from '../../../common/graphql/types';
|
||||
import { BasicTable } from '../../components/basic_table';
|
||||
import { EmptyPage } from '../../components/empty_page';
|
||||
import { HorizontalBarChart, HorizontalBarChartData } from '../../components/horizontal_bar_chart';
|
||||
import { Pane1FlexContent } from '../../components/page';
|
||||
import { Placeholders, VisualizationPlaceholder } from '../../components/visualization_placeholder';
|
||||
import { EventsQuery } from '../../containers/events';
|
||||
import { WithSource } from '../../containers/source';
|
||||
import { timelineActions } from '../../store';
|
||||
|
||||
const basePath = chrome.getBasePath();
|
||||
|
||||
// start/end date to show good alert in the timeline
|
||||
const startDate = 1521830963132;
|
||||
const endDate = 1521862432253;
|
||||
|
@ -34,36 +39,49 @@ interface Props {
|
|||
|
||||
export const Hosts = connect()(
|
||||
pure<Props>(({ dispatch }) => (
|
||||
<EventsQuery sourceId="default" startDate={startDate} endDate={endDate}>
|
||||
{({ events, kpiEventType, loading }) => (
|
||||
<Pane1FlexContent data-test-subj="pane1FlexContent">
|
||||
<VisualizationPlaceholder>
|
||||
<HorizontalBarChart
|
||||
loading={loading}
|
||||
title="KPI event types"
|
||||
width={490}
|
||||
height={279}
|
||||
barChartdata={
|
||||
kpiEventType.map((i: KpiItem) => ({
|
||||
x: i.count,
|
||||
y: i.value,
|
||||
})) as HorizontalBarChartData[]
|
||||
}
|
||||
/>
|
||||
</VisualizationPlaceholder>
|
||||
<VisualizationPlaceholder>
|
||||
<BasicTable
|
||||
columns={getEventsColumns(dispatch)}
|
||||
loading={loading}
|
||||
pageOfItems={events}
|
||||
sortField="host.hostname"
|
||||
title="Events"
|
||||
/>
|
||||
</VisualizationPlaceholder>
|
||||
<Placeholders timelineId="pane2-timeline" count={8} myRoute="Hosts" />
|
||||
</Pane1FlexContent>
|
||||
)}
|
||||
</EventsQuery>
|
||||
<WithSource sourceId="default">
|
||||
{({ auditbeatIndicesExist }) =>
|
||||
auditbeatIndicesExist || isUndefined(auditbeatIndicesExist) ? (
|
||||
<EventsQuery sourceId="default" startDate={startDate} endDate={endDate}>
|
||||
{({ events, kpiEventType, loading }) => (
|
||||
<Pane1FlexContent data-test-subj="pane1FlexContent">
|
||||
<VisualizationPlaceholder>
|
||||
<HorizontalBarChart
|
||||
loading={loading}
|
||||
title="KPI event types"
|
||||
width={490}
|
||||
height={279}
|
||||
barChartdata={
|
||||
kpiEventType.map((i: KpiItem) => ({
|
||||
x: i.count,
|
||||
y: i.value,
|
||||
})) as HorizontalBarChartData[]
|
||||
}
|
||||
/>
|
||||
</VisualizationPlaceholder>
|
||||
<VisualizationPlaceholder>
|
||||
<BasicTable
|
||||
columns={getEventsColumns(dispatch)}
|
||||
loading={loading}
|
||||
pageOfItems={events}
|
||||
sortField="host.hostname"
|
||||
title="Events"
|
||||
/>
|
||||
</VisualizationPlaceholder>
|
||||
<Placeholders timelineId="pane2-timeline" count={8} myRoute="Hosts" />
|
||||
</Pane1FlexContent>
|
||||
)}
|
||||
</EventsQuery>
|
||||
) : (
|
||||
<EmptyPage
|
||||
title="Looks like you don't have any auditbeat indices."
|
||||
message="Let's add some!"
|
||||
actionLabel="Setup Instructions"
|
||||
actionUrl={`${basePath}/app/kibana#/home/tutorial_directory/security`}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</WithSource>
|
||||
))
|
||||
);
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ import { Range } from '../../../components/timeline/body/column_headers/range_pi
|
|||
import { Sort } from '../../../components/timeline/body/sort';
|
||||
import { DataProvider } from '../../../components/timeline/data_providers/data_provider';
|
||||
import { ECS } from '../../../components/timeline/ecs';
|
||||
import { mockECSData } from '../../../pages/mock/mock_ecs';
|
||||
import { mockECSData } from '../../../mock/mock_ecs';
|
||||
|
||||
export interface TimelineModel {
|
||||
id: string;
|
||||
|
|
|
@ -4,15 +4,24 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { rootSchema } from '../../common/graphql/root/schema.gql';
|
||||
import { rootSchema } from '../../common/graphql/root';
|
||||
import { sharedSchema } from '../../common/graphql/shared';
|
||||
import { getSourceQueryMock } from '../graphql/sources/source.mock';
|
||||
import { getAllSourcesQueryMock } from '../graphql/sources/sources.mock';
|
||||
import { Logger } from '../utils/logger';
|
||||
import { eventsSchema } from './events/schema.gql';
|
||||
import { sourceStatusSchema } from './source_status/schema.gql';
|
||||
import { sourcesSchema } from './sources/schema.gql';
|
||||
import { whoAmISchema } from './who_am_i/schema.gql';
|
||||
|
||||
export const schemas = [rootSchema, sourcesSchema, eventsSchema, whoAmISchema];
|
||||
export const schemas = [
|
||||
eventsSchema,
|
||||
rootSchema,
|
||||
sourcesSchema,
|
||||
sourceStatusSchema,
|
||||
sharedSchema,
|
||||
whoAmISchema,
|
||||
];
|
||||
|
||||
// The types from graphql-tools/src/mock.ts 'any' based. I add slightly
|
||||
// stricter types here, but these should go away when graphql-tools using something
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export { createSourceStatusResolvers } from './resolvers';
|
||||
export { sourceStatusSchema } from './schema.gql';
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* 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 { IndexType, SourceStatusResolvers } from '../../../common/graphql/types';
|
||||
import { AppResolvedResult, AppResolverOf } from '../../lib/framework';
|
||||
import { IndexFields } from '../../lib/index_fields';
|
||||
import { SourceStatus } from '../../lib/source_status';
|
||||
import { Context } from '../../lib/types';
|
||||
import { QuerySourceResolver } from '../sources/resolvers';
|
||||
|
||||
export type SourceStatusAuditbeatAliasExistsResolver = AppResolverOf<
|
||||
SourceStatusResolvers.AuditbeatAliasExistsResolver,
|
||||
AppResolvedResult<QuerySourceResolver>,
|
||||
Context
|
||||
>;
|
||||
|
||||
export type SourceStatusAuditbeatIndicesExistResolver = AppResolverOf<
|
||||
SourceStatusResolvers.AuditbeatIndicesExistResolver,
|
||||
AppResolvedResult<QuerySourceResolver>,
|
||||
Context
|
||||
>;
|
||||
|
||||
export type SourceStatusAuditbeatIndicesResolver = AppResolverOf<
|
||||
SourceStatusResolvers.AuditbeatIndicesResolver,
|
||||
AppResolvedResult<QuerySourceResolver>,
|
||||
Context
|
||||
>;
|
||||
|
||||
export type SourceStatusIndexFieldsResolver = AppResolverOf<
|
||||
SourceStatusResolvers.IndexFieldsResolver,
|
||||
AppResolvedResult<QuerySourceResolver>,
|
||||
Context
|
||||
>;
|
||||
|
||||
export const createSourceStatusResolvers = (libs: {
|
||||
sourceStatus: SourceStatus;
|
||||
fields: IndexFields;
|
||||
}): {
|
||||
SourceStatus: {
|
||||
auditbeatAliasExists: SourceStatusAuditbeatAliasExistsResolver;
|
||||
auditbeatIndicesExist: SourceStatusAuditbeatIndicesExistResolver;
|
||||
auditbeatIndices: SourceStatusAuditbeatIndicesResolver;
|
||||
indexFields: SourceStatusIndexFieldsResolver;
|
||||
};
|
||||
} => ({
|
||||
SourceStatus: {
|
||||
async auditbeatAliasExists(source, args, { req }) {
|
||||
return await libs.sourceStatus.hasAlias(req, source.id, 'auditbeatAlias');
|
||||
},
|
||||
async auditbeatIndicesExist(source, args, { req }) {
|
||||
return await libs.sourceStatus.hasIndices(req, source.id, 'auditbeatAlias');
|
||||
},
|
||||
async auditbeatIndices(source, args, { req }) {
|
||||
return await libs.sourceStatus.getIndexNames(req, source.id, 'auditbeatAlias');
|
||||
},
|
||||
async indexFields(source, args, { req }) {
|
||||
return await libs.fields.getFields(req, source.id, args.indexType || IndexType.ANY);
|
||||
},
|
||||
},
|
||||
});
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* 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 gql from 'graphql-tag';
|
||||
|
||||
export const sourceStatusSchema = gql`
|
||||
"A descriptor of a field in an index"
|
||||
type IndexField {
|
||||
"The name of the field"
|
||||
name: String!
|
||||
"The type of the field's values as recognized by Kibana"
|
||||
type: String!
|
||||
"Whether the field's values can be efficiently searched for"
|
||||
searchable: Boolean!
|
||||
"Whether the field's values can be aggregated"
|
||||
aggregatable: Boolean!
|
||||
}
|
||||
|
||||
extend type SourceStatus {
|
||||
"Whether the configured auditbeat alias exists"
|
||||
auditbeatAliasExists: Boolean!
|
||||
"Whether the configured alias or wildcard pattern resolve to any auditbeat indices"
|
||||
auditbeatIndicesExist: Boolean!
|
||||
"The list of indices in the auditbeat alias"
|
||||
auditbeatIndices: [String!]!
|
||||
"The list of fields defined in the index mappings"
|
||||
indexFields(indexType: IndexType = ANY): [IndexField!]!
|
||||
}
|
||||
`;
|
|
@ -4,7 +4,9 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import { GraphQLResolveInfo } from 'graphql';
|
||||
import { omit } from 'lodash/fp';
|
||||
import { FrameworkRequest, internalFrameworkRequest } from '../../lib/framework';
|
||||
import { SourceStatus, SourceStatusAdapter } from '../../lib/source_status';
|
||||
import { Sources, SourcesAdapter } from '../../lib/sources';
|
||||
import { createSourcesResolvers, SourcesResolversDeps } from './resolvers';
|
||||
import { mockSourceData } from './source.mock';
|
||||
|
@ -18,8 +20,22 @@ mockGetAll.mockResolvedValue({
|
|||
const mockSourcesAdapter: SourcesAdapter = {
|
||||
getAll: mockGetAll,
|
||||
};
|
||||
|
||||
const mockGetIndexNames = jest.fn();
|
||||
mockGetIndexNames.mockResolvedValue([]);
|
||||
const mockHasAlias = jest.fn();
|
||||
mockHasAlias.mockResolvedValue(false);
|
||||
const mockHasIndices = jest.fn();
|
||||
mockHasIndices.mockResolvedValue(false);
|
||||
const mockSourceStatusAdapter: SourceStatusAdapter = {
|
||||
getIndexNames: mockGetIndexNames,
|
||||
hasAlias: mockHasAlias,
|
||||
hasIndices: mockHasIndices,
|
||||
};
|
||||
|
||||
const mockLibs: SourcesResolversDeps = {
|
||||
sources: new Sources(mockSourcesAdapter),
|
||||
sourceStatus: new SourceStatus(mockSourceStatusAdapter, new Sources(mockSourcesAdapter)),
|
||||
};
|
||||
|
||||
const req: FrameworkRequest = {
|
||||
|
@ -40,7 +56,7 @@ const req: FrameworkRequest = {
|
|||
const context = { req };
|
||||
|
||||
describe('Test Source Resolvers', () => {
|
||||
test(`Make sure that getConfiguration have been called`, async () => {
|
||||
test('Make sure that getConfiguration have been called', async () => {
|
||||
const data = await createSourcesResolvers(mockLibs).Query.source(
|
||||
null,
|
||||
{ id: 'default' },
|
||||
|
@ -48,6 +64,6 @@ describe('Test Source Resolvers', () => {
|
|||
{} as GraphQLResolveInfo
|
||||
);
|
||||
expect(mockSourcesAdapter.getAll).toHaveBeenCalled();
|
||||
expect(data).toEqual(mockSourceData);
|
||||
expect(data).toEqual(omit('status', mockSourceData));
|
||||
});
|
||||
});
|
||||
|
|
|
@ -4,8 +4,9 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { QueryResolvers } from '../../../common/graphql/types';
|
||||
import { AppResolverWithFields } from '../../lib/framework';
|
||||
import { QueryResolvers, SourceResolvers } from '../../../common/graphql/types';
|
||||
import { AppResolvedResult, AppResolverWithFields } from '../../lib/framework';
|
||||
import { SourceStatus } from '../../lib/source_status';
|
||||
import { Sources } from '../../lib/sources';
|
||||
import { Context } from '../../lib/types';
|
||||
|
||||
|
@ -23,8 +24,16 @@ export type QueryAllSourcesResolver = AppResolverWithFields<
|
|||
'id' | 'configuration'
|
||||
>;
|
||||
|
||||
export type SourceStatusResolver = AppResolverWithFields<
|
||||
SourceResolvers.StatusResolver,
|
||||
AppResolvedResult<QuerySourceResolver>,
|
||||
Context,
|
||||
never
|
||||
>;
|
||||
|
||||
export interface SourcesResolversDeps {
|
||||
sources: Sources;
|
||||
sourceStatus: SourceStatus;
|
||||
}
|
||||
|
||||
export const createSourcesResolvers = (
|
||||
|
@ -34,6 +43,9 @@ export const createSourcesResolvers = (
|
|||
source: QuerySourceResolver;
|
||||
allSources: QueryAllSourcesResolver;
|
||||
};
|
||||
Source: {
|
||||
status: SourceStatusResolver;
|
||||
};
|
||||
} => ({
|
||||
Query: {
|
||||
async source(root, args) {
|
||||
|
@ -53,4 +65,9 @@ export const createSourcesResolvers = (
|
|||
}));
|
||||
},
|
||||
},
|
||||
Source: {
|
||||
async status(source) {
|
||||
return source;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
@ -19,12 +19,19 @@ export const sourcesSchema = gql`
|
|||
id: ID!
|
||||
"The raw configuration of the source"
|
||||
configuration: SourceConfiguration!
|
||||
"The status of the source"
|
||||
status: SourceStatus!
|
||||
}
|
||||
|
||||
"The status of an infrastructure data source"
|
||||
type SourceStatus
|
||||
|
||||
"A set of configuration options for a security data source"
|
||||
type SourceConfiguration {
|
||||
"The alias to read file data from"
|
||||
fileAlias: String!
|
||||
logAlias: String!
|
||||
"The alias to read auditbeat data from"
|
||||
auditbeatAlias: String!
|
||||
"The field mapping to use for this source"
|
||||
fields: SourceFields!
|
||||
}
|
||||
|
|
|
@ -8,7 +8,9 @@ import { graphql } from 'graphql';
|
|||
import { addMockFunctionsToSchema, makeExecutableSchema } from 'graphql-tools';
|
||||
|
||||
import { rootSchema } from '../../../common/graphql/root/schema.gql';
|
||||
import { sharedSchema } from '../../../common/graphql/shared';
|
||||
import { Logger } from '../../utils/logger';
|
||||
import { sourceStatusSchema } from '../source_status/schema.gql';
|
||||
import { sourcesSchema } from './schema.gql';
|
||||
import { getSourceQueryMock, mockSourceData } from './source.mock';
|
||||
|
||||
|
@ -22,7 +24,18 @@ const testCaseSource = {
|
|||
fields {
|
||||
host
|
||||
}
|
||||
}
|
||||
}
|
||||
status {
|
||||
auditbeatIndicesExist
|
||||
auditbeatAliasExists
|
||||
auditbeatIndices
|
||||
indexFields {
|
||||
name
|
||||
searchable
|
||||
type
|
||||
aggregatable
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
|
@ -46,7 +59,7 @@ const testCaseSource = {
|
|||
describe('Test Source Schema', () => {
|
||||
// Array of case types
|
||||
const cases = [testCaseSource];
|
||||
const typeDefs = [rootSchema, sourcesSchema];
|
||||
const typeDefs = [rootSchema, sharedSchema, sourcesSchema, sourceStatusSchema];
|
||||
const mockSchema = makeExecutableSchema({ typeDefs });
|
||||
|
||||
// Here we specify the return payloads of mocked types
|
||||
|
|
|
@ -15,6 +15,28 @@ export const mockSourceData = {
|
|||
host: 'beat.hostname',
|
||||
},
|
||||
},
|
||||
status: {
|
||||
auditbeatIndicesExist: true,
|
||||
auditbeatAliasExists: true,
|
||||
auditbeatIndices: [
|
||||
"auditbeat-7.0.0-alpha1-2018.10.03",
|
||||
"auditbeat-7.0.0-alpha1-2018.10.04",
|
||||
],
|
||||
indexFields: [
|
||||
{
|
||||
name: "@timestamp",
|
||||
searchable: true,
|
||||
type: "date",
|
||||
aggregatable: true,
|
||||
},
|
||||
{
|
||||
name: "apache2.access.agent",
|
||||
searchable: true,
|
||||
type: "string",
|
||||
aggregatable: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
/* tslint:enable */
|
||||
|
||||
|
|
|
@ -6,23 +6,12 @@
|
|||
|
||||
import { Logger } from '../../utils/logger';
|
||||
import { Context } from '../index';
|
||||
import { mockSourceData } from './source.mock';
|
||||
|
||||
/* tslint:disable */
|
||||
export const sourcesDataMock =
|
||||
[
|
||||
{
|
||||
"id": "default",
|
||||
"configuration": {
|
||||
"fields": {
|
||||
"container": "docker.container.name",
|
||||
"host": "beat.hostname",
|
||||
"message": [
|
||||
"message",
|
||||
"@message"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
mockSourceData,
|
||||
];
|
||||
/* tslint:enable */
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ import { addMockFunctionsToSchema, IResolvers, makeExecutableSchema } from 'grap
|
|||
|
||||
import { createMocks, schemas } from './graphql';
|
||||
import { createEventsResolvers } from './graphql/events';
|
||||
import { createSourceStatusResolvers } from './graphql/source_status';
|
||||
import { createSourcesResolvers } from './graphql/sources';
|
||||
import { createWhoAmIResolvers } from './graphql/who_am_i';
|
||||
import { AppBackendLibs } from './lib/types';
|
||||
|
@ -22,6 +23,7 @@ export const initServer = (libs: AppBackendLibs, config: Config) => {
|
|||
const { logger, mocking } = config;
|
||||
const schema = makeExecutableSchema({
|
||||
resolvers: [
|
||||
createSourceStatusResolvers(libs) as IResolvers,
|
||||
createSourcesResolvers(libs) as IResolvers,
|
||||
createEventsResolvers(libs) as IResolvers,
|
||||
createWhoAmIResolvers() as IResolvers,
|
||||
|
|
|
@ -9,22 +9,26 @@ import { Server } from 'hapi';
|
|||
import { KibanaConfigurationAdapter } from '../configuration/kibana_configuration_adapter';
|
||||
import { ElasticsearchEventsAdapter, Events } from '../events';
|
||||
import { KibanaBackendFrameworkAdapter } from '../framework/kibana_framework_adapter';
|
||||
import { Sources } from '../sources';
|
||||
import { ConfigurationSourcesAdapter } from '../sources/configuration_sources_adapter';
|
||||
import { ElasticsearchIndexFieldAdapter, IndexFields } from '../index_fields';
|
||||
import { ElasticsearchSourceStatusAdapter, SourceStatus } from '../source_status';
|
||||
import { ConfigurationSourcesAdapter, Sources } from '../sources';
|
||||
import { AppBackendLibs, AppDomainLibs, Configuration } from '../types';
|
||||
|
||||
export function compose(server: Server): AppBackendLibs {
|
||||
const configuration = new KibanaConfigurationAdapter<Configuration>(server);
|
||||
const framework = new KibanaBackendFrameworkAdapter(server);
|
||||
const sources = new Sources(new ConfigurationSourcesAdapter(configuration));
|
||||
const sourceStatus = new SourceStatus(new ElasticsearchSourceStatusAdapter(framework), sources);
|
||||
|
||||
const domainLibs: AppDomainLibs = {
|
||||
events: new Events(new ElasticsearchEventsAdapter(framework)),
|
||||
fields: new IndexFields(new ElasticsearchIndexFieldAdapter(framework), sources),
|
||||
};
|
||||
|
||||
const libs: AppBackendLibs = {
|
||||
configuration,
|
||||
framework,
|
||||
sourceStatus,
|
||||
sources,
|
||||
...domainLibs,
|
||||
};
|
||||
|
|
|
@ -90,7 +90,7 @@ export class ElasticsearchEventsAdapter implements EventsAdapter {
|
|||
|
||||
const query = {
|
||||
allowNoIndices: true,
|
||||
index: options.sourceConfiguration.fileAlias,
|
||||
index: options.sourceConfiguration.logAlias,
|
||||
ignoreUnavailable: true,
|
||||
body: {
|
||||
aggregations: agg,
|
||||
|
|
|
@ -4,4 +4,4 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export * from './adapter_types';
|
||||
export * from './types';
|
||||
|
|
|
@ -7,18 +7,19 @@
|
|||
import { GraphQLSchema } from 'graphql';
|
||||
import { Request, Server } from 'hapi';
|
||||
|
||||
import {
|
||||
FrameworkAdapter,
|
||||
FrameworkRequest,
|
||||
internalFrameworkRequest,
|
||||
WrappableRequest,
|
||||
} from './adapter_types';
|
||||
import {
|
||||
graphiqlHapi,
|
||||
graphqlHapi,
|
||||
HapiGraphiQLPluginOptions,
|
||||
HapiGraphQLPluginOptions,
|
||||
} from './apollo_server_hapi';
|
||||
import {
|
||||
FrameworkAdapter,
|
||||
FrameworkIndexPatternsService,
|
||||
FrameworkRequest,
|
||||
internalFrameworkRequest,
|
||||
WrappableRequest,
|
||||
} from './types';
|
||||
|
||||
declare module 'hapi' {
|
||||
interface PluginProperties {
|
||||
|
@ -82,6 +83,26 @@ export class KibanaBackendFrameworkAdapter implements FrameworkAdapter {
|
|||
plugin: graphiqlHapi,
|
||||
});
|
||||
}
|
||||
|
||||
public getIndexPatternsService(
|
||||
request: FrameworkRequest<Request>
|
||||
): FrameworkIndexPatternsService {
|
||||
if (!isServerWithIndexPatternsServiceFactory(this.server)) {
|
||||
throw new Error('Failed to access indexPatternsService for the request');
|
||||
}
|
||||
return this.server.indexPatternsServiceFactory({
|
||||
// tslint:disable-next-line:no-any
|
||||
callCluster: async (method: string, args: [object], ...rest: any[]) => {
|
||||
const fieldCaps = await this.callWithRequest(
|
||||
request,
|
||||
method,
|
||||
{ ...args, allowNoIndices: true },
|
||||
...rest
|
||||
);
|
||||
return fieldCaps;
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function wrapRequest<InternalRequest extends WrappableRequest>(
|
||||
|
@ -96,3 +117,16 @@ export function wrapRequest<InternalRequest extends WrappableRequest>(
|
|||
query,
|
||||
};
|
||||
}
|
||||
|
||||
interface ServerWithIndexPatternsServiceFactory extends Server {
|
||||
indexPatternsServiceFactory(options: {
|
||||
// tslint:disable-next-line:no-any
|
||||
callCluster: (...args: any[]) => any;
|
||||
}): FrameworkIndexPatternsService;
|
||||
}
|
||||
|
||||
const isServerWithIndexPatternsServiceFactory = (
|
||||
server: Server
|
||||
): server is ServerWithIndexPatternsServiceFactory =>
|
||||
// tslint:disable-next-line:no-any
|
||||
typeof (server as any).indexPatternsServiceFactory === 'function';
|
||||
|
|
|
@ -23,7 +23,19 @@ export interface FrameworkAdapter {
|
|||
req: FrameworkRequest,
|
||||
method: 'msearch',
|
||||
options?: object
|
||||
): Promise<InfraDatabaseMultiResponse<Hit, Aggregation>>;
|
||||
): Promise<DatabaseMultiResponse<Hit, Aggregation>>;
|
||||
callWithRequest(
|
||||
req: FrameworkRequest,
|
||||
method: 'indices.existsAlias',
|
||||
options?: object
|
||||
): Promise<boolean>;
|
||||
callWithRequest(
|
||||
req: FrameworkRequest,
|
||||
method: 'indices.getAlias' | 'indices.get',
|
||||
options?: object
|
||||
): Promise<DatabaseGetIndicesResponse>;
|
||||
// tslint:disable-next-line:no-any
|
||||
getIndexPatternsService(req: FrameworkRequest<any>): FrameworkIndexPatternsService;
|
||||
}
|
||||
|
||||
export interface FrameworkRequest<InternalRequest extends WrappableRequest = WrappableRequest> {
|
||||
|
@ -54,6 +66,36 @@ export interface DatabaseSearchResponse<Hit = {}, Aggregations = undefined>
|
|||
};
|
||||
}
|
||||
|
||||
export interface InfraDatabaseMultiResponse<Hit, Aggregation> extends DatabaseResponse {
|
||||
export interface DatabaseMultiResponse<Hit, Aggregation> extends DatabaseResponse {
|
||||
responses: Array<DatabaseSearchResponse<Hit, Aggregation>>;
|
||||
}
|
||||
|
||||
interface FrameworkIndexFieldDescriptor {
|
||||
name: string;
|
||||
type: string;
|
||||
searchable: boolean;
|
||||
aggregatable: boolean;
|
||||
readFromDocValues: boolean;
|
||||
}
|
||||
|
||||
export interface FrameworkIndexPatternsService {
|
||||
getFieldsForWildcard(options: {
|
||||
pattern: string | string[];
|
||||
}): Promise<FrameworkIndexFieldDescriptor[]>;
|
||||
}
|
||||
|
||||
interface Alias {
|
||||
settings: {
|
||||
index: {
|
||||
uuid: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export interface DatabaseGetIndicesResponse {
|
||||
[indexName: string]: {
|
||||
aliases: {
|
||||
[aliasName: string]: Alias;
|
||||
};
|
||||
};
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* 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 { IndexField } from '../../../common/graphql/types';
|
||||
import { FrameworkAdapter, FrameworkRequest } from '../framework';
|
||||
import { FieldsAdapter } from './types';
|
||||
|
||||
export class ElasticsearchIndexFieldAdapter implements FieldsAdapter {
|
||||
constructor(private readonly framework: FrameworkAdapter) {}
|
||||
|
||||
public async getIndexFields(request: FrameworkRequest, indices: string[]): Promise<IndexField[]> {
|
||||
const indexPatternsService = this.framework.getIndexPatternsService(request);
|
||||
return await indexPatternsService.getFieldsForWildcard({
|
||||
pattern: indices,
|
||||
});
|
||||
}
|
||||
}
|
45
x-pack/plugins/secops/server/lib/index_fields/index.ts
Normal file
45
x-pack/plugins/secops/server/lib/index_fields/index.ts
Normal file
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* 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 { IndexField, IndexType } from '../../../common/graphql/types';
|
||||
import { FrameworkRequest } from '../framework';
|
||||
import { Sources } from '../sources';
|
||||
import { FieldsAdapter } from './types';
|
||||
export { ElasticsearchIndexFieldAdapter } from './elasticsearch_adapter';
|
||||
|
||||
export class IndexFields implements FieldsAdapter {
|
||||
private adapter: FieldsAdapter;
|
||||
private sources: Sources;
|
||||
|
||||
constructor(adapter: FieldsAdapter, sources: Sources) {
|
||||
this.adapter = adapter;
|
||||
this.sources = sources;
|
||||
}
|
||||
|
||||
public async getFields(
|
||||
request: FrameworkRequest,
|
||||
sourceId: string,
|
||||
indexType: IndexType
|
||||
): Promise<IndexField[]> {
|
||||
const sourceConfiguration = await this.sources.getConfiguration(sourceId);
|
||||
const includeAuditBeatIndices = [IndexType.ANY, IndexType.AUDITBEAT].includes(indexType);
|
||||
const includeLogIndices = [IndexType.ANY, IndexType.LOGS].includes(indexType);
|
||||
|
||||
const indices = [
|
||||
...(includeAuditBeatIndices ? [sourceConfiguration.auditbeatAlias] : []),
|
||||
...(includeLogIndices ? [sourceConfiguration.logAlias] : []),
|
||||
];
|
||||
|
||||
return this.getIndexFields(request, indices);
|
||||
}
|
||||
|
||||
public async getIndexFields(
|
||||
request: FrameworkRequest,
|
||||
indices: string[] = []
|
||||
): Promise<IndexField[]> {
|
||||
return await this.adapter.getIndexFields(request, indices as string[]);
|
||||
}
|
||||
}
|
20
x-pack/plugins/secops/server/lib/index_fields/types.ts
Normal file
20
x-pack/plugins/secops/server/lib/index_fields/types.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* 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 { IndexField, IndexType } from '../../../common/graphql/types';
|
||||
import { FrameworkRequest } from '../framework';
|
||||
|
||||
export interface FieldsAdapter {
|
||||
getFields?(req: FrameworkRequest, sourceId: string, indexType: IndexType): Promise<IndexField[]>;
|
||||
getIndexFields(req: FrameworkRequest, indices: string[]): Promise<IndexField[]>;
|
||||
}
|
||||
|
||||
export interface IndexFieldDescriptor {
|
||||
name: string;
|
||||
type: string;
|
||||
searchable: boolean;
|
||||
aggregatable: boolean;
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* 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 { DatabaseGetIndicesResponse, FrameworkAdapter, FrameworkRequest } from '../framework';
|
||||
import { SourceStatusAdapter } from './index';
|
||||
|
||||
export class ElasticsearchSourceStatusAdapter implements SourceStatusAdapter {
|
||||
constructor(private readonly framework: FrameworkAdapter) {}
|
||||
|
||||
public async getIndexNames(request: FrameworkRequest, aliasName: string) {
|
||||
const indexMaps = await Promise.all([
|
||||
this.framework
|
||||
.callWithRequest(request, 'indices.getAlias', {
|
||||
name: aliasName,
|
||||
filterPath: '*.settings.index.uuid', // to keep the response size as small as possible
|
||||
})
|
||||
.catch(withDefaultIfNotFound<DatabaseGetIndicesResponse>({})),
|
||||
this.framework
|
||||
.callWithRequest(request, 'indices.get', {
|
||||
index: aliasName,
|
||||
filterPath: '*.settings.index.uuid', // to keep the response size as small as possible
|
||||
})
|
||||
.catch(withDefaultIfNotFound<DatabaseGetIndicesResponse>({})),
|
||||
]);
|
||||
return indexMaps.reduce(
|
||||
(indexNames, indexMap) => [...indexNames, ...Object.keys(indexMap)],
|
||||
[] as string[]
|
||||
);
|
||||
}
|
||||
|
||||
public async hasAlias(request: FrameworkRequest, aliasName: string) {
|
||||
return await this.framework.callWithRequest(request, 'indices.existsAlias', {
|
||||
name: aliasName,
|
||||
});
|
||||
}
|
||||
|
||||
public async hasIndices(request: FrameworkRequest, indexNames: string) {
|
||||
return (await this.getIndexNames(request, indexNames)).length > 0;
|
||||
}
|
||||
}
|
||||
|
||||
const withDefaultIfNotFound = <DefaultValue>(defaultValue: DefaultValue) => (
|
||||
// tslint:disable-next-line:no-any
|
||||
error: any
|
||||
): DefaultValue => {
|
||||
if (error && error.status === 404) {
|
||||
return defaultValue;
|
||||
}
|
||||
throw error;
|
||||
};
|
50
x-pack/plugins/secops/server/lib/source_status/index.ts
Normal file
50
x-pack/plugins/secops/server/lib/source_status/index.ts
Normal file
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* 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 { FrameworkRequest } from '../framework';
|
||||
import { AliasConfiguration, SourceConfiguration, Sources } from '../sources';
|
||||
export { ElasticsearchSourceStatusAdapter } from './elasticsearch_adapter';
|
||||
|
||||
export class SourceStatus {
|
||||
constructor(private readonly adapter: SourceStatusAdapter, private readonly sources: Sources) {}
|
||||
|
||||
public async getIndexNames(
|
||||
request: FrameworkRequest,
|
||||
sourceId: string,
|
||||
aliasName: keyof AliasConfiguration
|
||||
): Promise<string[]> {
|
||||
return await this.adapter.getIndexNames(request, await this.getAliasName(sourceId, aliasName));
|
||||
}
|
||||
public async hasAlias(
|
||||
request: FrameworkRequest,
|
||||
sourceId: string,
|
||||
aliasName: keyof AliasConfiguration
|
||||
): Promise<boolean> {
|
||||
return await this.adapter.hasAlias(request, await this.getAliasName(sourceId, aliasName));
|
||||
}
|
||||
public async hasIndices(
|
||||
request: FrameworkRequest,
|
||||
sourceId: string,
|
||||
aliasName: keyof AliasConfiguration
|
||||
): Promise<boolean> {
|
||||
return await this.adapter.hasIndices(request, await this.getAliasName(sourceId, aliasName));
|
||||
}
|
||||
|
||||
private getAliasName = async (sourceId: string, aliasName: keyof AliasConfiguration) => {
|
||||
const sourceConfiguration: SourceConfiguration = await this.sources.getConfiguration(sourceId);
|
||||
return prop(sourceConfiguration, aliasName);
|
||||
};
|
||||
}
|
||||
|
||||
export interface SourceStatusAdapter {
|
||||
getIndexNames(request: FrameworkRequest, aliasName: string): Promise<string[]>;
|
||||
hasAlias(request: FrameworkRequest, aliasName: string): Promise<boolean>;
|
||||
hasIndices(request: FrameworkRequest, indexNames: string): Promise<boolean>;
|
||||
}
|
||||
|
||||
function prop<T, K extends keyof SourceConfiguration>(obj: SourceConfiguration, key: K) {
|
||||
return obj[key];
|
||||
}
|
|
@ -5,8 +5,8 @@
|
|||
*/
|
||||
|
||||
import { InmemoryConfigurationAdapter } from '../configuration/inmemory_configuration_adapter';
|
||||
import { PartialSourceConfiguration } from './adapter_types';
|
||||
import { ConfigurationSourcesAdapter } from './configuration_sources_adapter';
|
||||
import { ConfigurationSourcesAdapter } from './configuration';
|
||||
import { PartialSourceConfiguration } from './types';
|
||||
|
||||
describe('the ConfigurationSourcesAdapter', () => {
|
||||
test('adds the default source when no sources are configured', async () => {
|
||||
|
@ -17,7 +17,8 @@ describe('the ConfigurationSourcesAdapter', () => {
|
|||
expect(await sourcesAdapter.getAll()).toMatchObject({
|
||||
default: {
|
||||
metricAlias: expect.any(String),
|
||||
fileAlias: expect.any(String),
|
||||
logAlias: expect.any(String),
|
||||
auditbeatAlias: expect.any(String),
|
||||
fields: {
|
||||
container: expect.any(String),
|
||||
host: expect.any(String),
|
||||
|
@ -42,7 +43,7 @@ describe('the ConfigurationSourcesAdapter', () => {
|
|||
expect(await sourcesAdapter.getAll()).toMatchObject({
|
||||
default: {
|
||||
metricAlias: expect.any(String),
|
||||
fileAlias: expect.any(String),
|
||||
logAlias: expect.any(String),
|
||||
},
|
||||
});
|
||||
});
|
||||
|
@ -53,7 +54,8 @@ describe('the ConfigurationSourcesAdapter', () => {
|
|||
sources: {
|
||||
default: {
|
||||
metricAlias: 'METRIC_ALIAS',
|
||||
fileAlias: 'FILE_ALIAS',
|
||||
logAlias: 'LOG_ALIAS',
|
||||
auditbeatAlias: 'AUDITBEAT_ALIAS',
|
||||
fields: {
|
||||
container: 'DIFFERENT_CONTAINER_FIELD',
|
||||
},
|
||||
|
@ -65,7 +67,8 @@ describe('the ConfigurationSourcesAdapter', () => {
|
|||
expect(await sourcesAdapter.getAll()).toMatchObject({
|
||||
default: {
|
||||
metricAlias: 'METRIC_ALIAS',
|
||||
fileAlias: 'FILE_ALIAS',
|
||||
logAlias: 'LOG_ALIAS',
|
||||
auditbeatAlias: 'AUDITBEAT_ALIAS',
|
||||
fields: {
|
||||
container: 'DIFFERENT_CONTAINER_FIELD',
|
||||
host: expect.any(String),
|
||||
|
@ -84,7 +87,8 @@ describe('the ConfigurationSourcesAdapter', () => {
|
|||
sources: {
|
||||
sourceOne: {
|
||||
metricAlias: 'METRIC_ALIAS',
|
||||
fileAlias: 'FILE_ALIAS',
|
||||
logAlias: 'LOG_ALIAS',
|
||||
auditbeatAlias: 'AUDITBEAT_ALIAS',
|
||||
fields: {
|
||||
container: 'DIFFERENT_CONTAINER_FIELD',
|
||||
},
|
||||
|
@ -96,7 +100,8 @@ describe('the ConfigurationSourcesAdapter', () => {
|
|||
expect(await sourcesAdapter.getAll()).toMatchObject({
|
||||
sourceOne: {
|
||||
metricAlias: 'METRIC_ALIAS',
|
||||
fileAlias: 'FILE_ALIAS',
|
||||
logAlias: 'LOG_ALIAS',
|
||||
auditbeatAlias: 'AUDITBEAT_ALIAS',
|
||||
fields: {
|
||||
container: 'DIFFERENT_CONTAINER_FIELD',
|
||||
host: expect.any(String),
|
|
@ -4,8 +4,8 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import { ConfigurationAdapter } from '../configuration';
|
||||
import { PartialSourceConfigurations } from './adapter_types';
|
||||
import { SourceConfigurations, SourcesAdapter } from './index';
|
||||
import { PartialSourceConfigurations } from './types';
|
||||
|
||||
interface ConfigurationWithSources {
|
||||
sources?: PartialSourceConfigurations;
|
||||
|
@ -58,6 +58,7 @@ const DEFAULT_FIELDS = {
|
|||
|
||||
const DEFAULT_SOURCE = {
|
||||
metricAlias: 'metricbeat-*',
|
||||
fileAlias: 'filebeat-*',
|
||||
logAlias: 'filebeat-*',
|
||||
auditbeatAlias: 'auditbeat-*',
|
||||
fields: DEFAULT_FIELDS,
|
||||
};
|
|
@ -4,15 +4,14 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export { ConfigurationSourcesAdapter } from './configuration_sources_adapter';
|
||||
export { ConfigurationSourcesAdapter } from './configuration';
|
||||
|
||||
export class Sources {
|
||||
constructor(private readonly adapter: SourcesAdapter) {}
|
||||
|
||||
public async getConfiguration(sourceId: string) {
|
||||
public async getConfiguration(sourceId: string): Promise<SourceConfiguration> {
|
||||
const sourceConfigurations = await this.getAllConfigurations();
|
||||
const requestedSourceConfiguration = sourceConfigurations[sourceId];
|
||||
|
||||
if (!requestedSourceConfiguration) {
|
||||
throw new Error(`Failed to find source '${sourceId}'`);
|
||||
}
|
||||
|
@ -33,8 +32,13 @@ export interface SourceConfigurations {
|
|||
[sourceId: string]: SourceConfiguration;
|
||||
}
|
||||
|
||||
export interface SourceConfiguration {
|
||||
fileAlias: string;
|
||||
export interface AliasConfiguration {
|
||||
metricAlias: string;
|
||||
logAlias: string;
|
||||
auditbeatAlias: string;
|
||||
}
|
||||
|
||||
export interface SourceConfiguration extends AliasConfiguration {
|
||||
fields: {
|
||||
container: string;
|
||||
host: string;
|
||||
|
|
|
@ -7,16 +7,20 @@
|
|||
import { ConfigurationAdapter } from './configuration';
|
||||
import { Events } from './events';
|
||||
import { FrameworkAdapter, FrameworkRequest } from './framework';
|
||||
import { IndexFields } from './index_fields';
|
||||
import { SourceStatus } from './source_status';
|
||||
import { SourceConfigurations, Sources } from './sources';
|
||||
|
||||
export interface AppDomainLibs {
|
||||
events: Events;
|
||||
fields: IndexFields;
|
||||
}
|
||||
|
||||
export interface AppBackendLibs extends AppDomainLibs {
|
||||
configuration: ConfigurationAdapter<Configuration>;
|
||||
framework: FrameworkAdapter;
|
||||
sources: Sources;
|
||||
sourceStatus: SourceStatus;
|
||||
}
|
||||
|
||||
export interface Configuration {
|
||||
|
|
|
@ -17,13 +17,8 @@ export default function ({ loadTestFile }) {
|
|||
// loadTestFile(require.resolve('./logstash'));
|
||||
// loadTestFile(require.resolve('./kibana'));
|
||||
|
||||
// TODO: I am only running infra at the moment
|
||||
// but in reality I should not be running infra and
|
||||
// should instead be running secops which still needs
|
||||
// to be built. I kept this api integration test running for right now
|
||||
// as an example. -- Frank H.
|
||||
// See completion of issue: https://github.com/elastic/ingest-dev/issues/56
|
||||
loadTestFile(require.resolve('./infra'));
|
||||
//Only running our secops test for now since we are working in our own branch
|
||||
loadTestFile(require.resolve('./secops'));
|
||||
|
||||
// loadTestFile(require.resolve('./beats'));
|
||||
});
|
||||
|
|
12
x-pack/test/api_integration/apis/secops/index.js
Normal file
12
x-pack/test/api_integration/apis/secops/index.js
Normal file
|
@ -0,0 +1,12 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
|
||||
export default function ({ loadTestFile }) {
|
||||
describe('SecOps GraphQL Endpoints', () => {
|
||||
loadTestFile(require.resolve('./sources'));
|
||||
});
|
||||
}
|
48
x-pack/test/api_integration/apis/secops/sources.ts
Normal file
48
x-pack/test/api_integration/apis/secops/sources.ts
Normal file
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* 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 expect from 'expect.js';
|
||||
import { SourceQuery } from '../../../../plugins/secops/common/graphql/types';
|
||||
import { sourceQuery } from '../../../../plugins/secops/public/containers/source/source.gql_query';
|
||||
|
||||
import { KbnTestProvider } from './types';
|
||||
|
||||
const sourcesTests: KbnTestProvider = ({ getService }) => {
|
||||
const esArchiver = getService('esArchiver');
|
||||
const client = getService('secOpsGraphQLClient');
|
||||
|
||||
describe('sources', () => {
|
||||
before(() => esArchiver.load('auditbeat/default'));
|
||||
after(() => esArchiver.unload('auditbeat/default'));
|
||||
|
||||
it('Make sure that we get source information when auditbeat indices is there', () => {
|
||||
return client
|
||||
.query<SourceQuery.Query>({
|
||||
query: sourceQuery,
|
||||
variables: {
|
||||
sourceId: 'default',
|
||||
},
|
||||
})
|
||||
.then(resp => {
|
||||
const sourceConfiguration = resp.data.source.configuration;
|
||||
const sourceStatus = resp.data.source.status;
|
||||
|
||||
// shipped default values
|
||||
expect(sourceConfiguration.auditbeatAlias).to.be('auditbeat-*');
|
||||
expect(sourceConfiguration.logAlias).to.be('filebeat-*');
|
||||
|
||||
// test data in x-pack/test/functional/es_archives/auditbeat_test_data/data.json.gz
|
||||
expect(sourceStatus.indexFields.length).to.be(345);
|
||||
expect(sourceStatus.auditbeatIndices.length).to.be(1);
|
||||
expect(sourceStatus.auditbeatIndicesExist).to.be(true);
|
||||
expect(sourceStatus.auditbeatAliasExists).to.be(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// tslint:disable-next-line no-default-export
|
||||
export default sourcesTests;
|
21
x-pack/test/api_integration/apis/secops/types.ts
Normal file
21
x-pack/test/api_integration/apis/secops/types.ts
Normal file
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* 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 { InMemoryCache } from 'apollo-cache-inmemory';
|
||||
import { ApolloClient } from 'apollo-client';
|
||||
|
||||
export interface EsArchiver {
|
||||
load(name: string): void;
|
||||
unload(name: string): void;
|
||||
}
|
||||
|
||||
export interface KbnTestProviderOptions {
|
||||
getService(name: string): any;
|
||||
getService(name: 'esArchiver'): EsArchiver;
|
||||
getService(name: 'secOpsGraphQLClient'): ApolloClient<InMemoryCache>;
|
||||
}
|
||||
|
||||
export type KbnTestProvider = (options: KbnTestProviderOptions) => void;
|
|
@ -9,7 +9,8 @@ import {
|
|||
EsSupertestWithoutAuthProvider,
|
||||
SupertestWithoutAuthProvider,
|
||||
UsageAPIProvider,
|
||||
InfraOpsGraphQLProvider
|
||||
InfraOpsGraphQLProvider,
|
||||
SecOpsGraphQLProvider,
|
||||
} from './services';
|
||||
|
||||
export default async function ({ readConfigFile }) {
|
||||
|
@ -27,6 +28,7 @@ export default async function ({ readConfigFile }) {
|
|||
supertestWithoutAuth: SupertestWithoutAuthProvider,
|
||||
esSupertestWithoutAuth: EsSupertestWithoutAuthProvider,
|
||||
infraOpsGraphQLClient: InfraOpsGraphQLProvider,
|
||||
secOpsGraphQLClient: SecOpsGraphQLProvider,
|
||||
es: EsProvider,
|
||||
esArchiver: kibanaCommonConfig.get('services.esArchiver'),
|
||||
usageAPI: UsageAPIProvider,
|
||||
|
|
|
@ -8,4 +8,5 @@ export { EsProvider } from './es';
|
|||
export { EsSupertestWithoutAuthProvider } from './es_supertest_without_auth';
|
||||
export { SupertestWithoutAuthProvider } from './supertest_without_auth';
|
||||
export { UsageAPIProvider } from './usage_api';
|
||||
export { InfraOpsGraphQLProvider } from './infraops_graphql_client';
|
||||
export { InfraOpsGraphQLProvider } from './infraops_graphql_client';
|
||||
export { SecOpsGraphQLProvider } from './secops_graphql_client';
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* 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 { format as formatUrl } from 'url';
|
||||
import fetch from 'node-fetch';
|
||||
import { InMemoryCache, IntrospectionFragmentMatcher } from 'apollo-cache-inmemory';
|
||||
import { ApolloClient } from 'apollo-client';
|
||||
import { HttpLink } from 'apollo-link-http';
|
||||
|
||||
import introspectionQueryResultData from '../../../plugins/secops/common/graphql/introspection.json';
|
||||
|
||||
export function SecOpsGraphQLProvider({ getService }) {
|
||||
const config = getService('config');
|
||||
const kbnURL = formatUrl(config.get('servers.kibana'));
|
||||
|
||||
return new ApolloClient({
|
||||
cache: new InMemoryCache({
|
||||
fragmentMatcher: new IntrospectionFragmentMatcher({
|
||||
introspectionQueryResultData,
|
||||
}),
|
||||
}),
|
||||
link: new HttpLink({
|
||||
credentials: 'same-origin',
|
||||
fetch,
|
||||
headers: {
|
||||
'kbn-xsrf': 'xxx',
|
||||
},
|
||||
uri: `${kbnURL}/api/secops/graphql`,
|
||||
}),
|
||||
});
|
||||
}
|
Binary file not shown.
1905
x-pack/test/functional/es_archives/auditbeat/default/mappings.json
Normal file
1905
x-pack/test/functional/es_archives/auditbeat/default/mappings.json
Normal file
File diff suppressed because it is too large
Load diff
|
@ -1074,6 +1074,11 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/cheerio/-/cheerio-0.22.8.tgz#5702f74f78b73e13f1eb1bd435c2c9de61a250d4"
|
||||
integrity sha512-LzF540VOFabhS2TR2yYFz2Mu/fTfkA+5AwYddtJbOJGwnYrr2e7fHadT7/Z3jNGJJdCRlO3ySxmW26NgRdwhNA==
|
||||
|
||||
"@types/cheerio@^0.22.10":
|
||||
version "0.22.10"
|
||||
resolved "https://registry.yarnpkg.com/@types/cheerio/-/cheerio-0.22.10.tgz#780d552467824be4a241b29510a7873a7432c4a6"
|
||||
integrity sha512-fOM/Jhv51iyugY7KOBZz2ThfT1gwvsGCfWxpLpZDgkGjpEO4Le9cld07OdskikLjDUQJ43dzDaVRSFwQlpdqVg==
|
||||
|
||||
"@types/classnames@^2.2.3":
|
||||
version "2.2.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/classnames/-/classnames-2.2.3.tgz#3f0ff6873da793870e20a260cada55982f38a9e5"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue