Skip to content

Commit

Permalink
[RAM] Bug api find/index alerts (#131338)
Browse files Browse the repository at this point in the history
* fix bug

* fix unit test

* bing back tests alive after a long CPR

* fix test and bring back recursive aggs

* I need to do an intersectiona and not union

* fix last integration test
  • Loading branch information
XavierM authored May 4, 2022
1 parent 5f06375 commit 956612d
Show file tree
Hide file tree
Showing 17 changed files with 305 additions and 263 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export enum ReadOperations {
GetAlertSummary = 'getAlertSummary',
GetExecutionLog = 'getExecutionLog',
Find = 'find',
GetAuthorizedAlertsIndices = 'getAuthorizedAlertsIndices',
}

export enum WriteOperations {
Expand Down
270 changes: 146 additions & 124 deletions x-pack/plugins/rule_registry/common/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,8 @@ interface BucketAggsSchemas {
| { [x: string]: SortOrderSchema }
| Array<{ [x: string]: SortOrderSchema }>;
};
aggs?: {
[x: string]: BucketAggsSchemas;
};
aggs?: BucketAggsSchemas;
aggregations?: BucketAggsSchemas;
}

/**
Expand Down Expand Up @@ -114,78 +113,83 @@ interface BucketAggsSchemas {
* - significant_text
* - variable_width_histogram
*/
export const BucketAggsSchemas: t.Type<BucketAggsSchemas> = t.recursion('BucketAggsSchemas', () =>
t.exact(
t.partial({
filter: t.exact(
t.partial({
term: t.record(t.string, t.union([t.string, t.boolean, t.number])),
})
),
date_histogram: t.exact(
t.partial({
field: t.string,
fixed_interval: t.string,
min_doc_count: t.number,
extended_bounds: t.type({
min: t.string,
max: t.string,
}),
})
),
histogram: t.exact(
t.partial({
field: t.string,
interval: t.number,
min_doc_count: t.number,
extended_bounds: t.exact(
t.type({
min: t.number,
max: t.number,
})
),
hard_bounds: t.exact(
t.type({
min: t.number,
max: t.number,
})
),
missing: t.number,
keyed: t.boolean,
order: t.exact(
t.type({
_count: t.string,
_key: t.string,
})
),
})
),
nested: t.type({
path: t.string,
}),
terms: t.exact(
t.partial({
field: t.string,
collect_mode: t.string,
exclude: t.union([t.string, t.array(t.string)]),
include: t.union([t.string, t.array(t.string)]),
execution_hint: t.string,
missing: t.union([t.number, t.string]),
min_doc_count: t.number,
size: t.number,
show_term_doc_count_error: t.boolean,
order: t.union([
sortOrderSchema,
t.record(t.string, sortOrderSchema),
t.array(t.record(t.string, sortOrderSchema)),
]),
})
),
aggs: t.record(t.string, BucketAggsSchemas),
})
)
const bucketAggsTempsSchemas: t.Type<BucketAggsSchemas> = t.exact(
t.partial({
filter: t.exact(
t.partial({
term: t.record(t.string, t.union([t.string, t.boolean, t.number])),
})
),
date_histogram: t.exact(
t.partial({
field: t.string,
fixed_interval: t.string,
min_doc_count: t.number,
extended_bounds: t.type({
min: t.string,
max: t.string,
}),
})
),
histogram: t.exact(
t.partial({
field: t.string,
interval: t.number,
min_doc_count: t.number,
extended_bounds: t.exact(
t.type({
min: t.number,
max: t.number,
})
),
hard_bounds: t.exact(
t.type({
min: t.number,
max: t.number,
})
),
missing: t.number,
keyed: t.boolean,
order: t.exact(
t.type({
_count: t.string,
_key: t.string,
})
),
})
),
nested: t.type({
path: t.string,
}),
terms: t.exact(
t.partial({
field: t.string,
collect_mode: t.string,
exclude: t.union([t.string, t.array(t.string)]),
include: t.union([t.string, t.array(t.string)]),
execution_hint: t.string,
missing: t.union([t.number, t.string]),
min_doc_count: t.number,
size: t.number,
show_term_doc_count_error: t.boolean,
order: t.union([
sortOrderSchema,
t.record(t.string, sortOrderSchema),
t.array(t.record(t.string, sortOrderSchema)),
]),
})
),
})
);

export const bucketAggsSchemas = t.intersection([
bucketAggsTempsSchemas,
t.partial({
aggs: t.union([t.record(t.string, bucketAggsTempsSchemas), t.undefined]),
aggregations: t.union([t.record(t.string, bucketAggsTempsSchemas), t.undefined]),
}),
]);

/**
* Schemas for the metrics Aggregations
*
Expand Down Expand Up @@ -215,57 +219,75 @@ export const BucketAggsSchemas: t.Type<BucketAggsSchemas> = t.recursion('BucketA
* - t_test
* - value_count
*/
export const metricsAggsSchemas = t.partial({
avg: t.partial({
field: t.string,
missing: t.union([t.string, t.number, t.boolean]),
}),
cardinality: t.partial({
field: t.string,
precision_threshold: t.number,
rehash: t.boolean,
missing: t.union([t.string, t.number, t.boolean]),
}),
min: t.partial({
field: t.string,
missing: t.union([t.string, t.number, t.boolean]),
format: t.string,
}),
max: t.partial({
field: t.string,
missing: t.union([t.string, t.number, t.boolean]),
format: t.string,
}),
sum: t.partial({
field: t.string,
missing: t.union([t.string, t.number, t.boolean]),
}),
top_hits: t.partial({
explain: t.boolean,
docvalue_fields: t.union([t.string, t.array(t.string)]),
stored_fields: t.union([t.string, t.array(t.string)]),
from: t.number,
size: t.number,
sort: sortSchema,
seq_no_primary_term: t.boolean,
version: t.boolean,
track_scores: t.boolean,
highlight: t.any,
_source: t.union([t.boolean, t.string, t.array(t.string)]),
}),
weighted_avg: t.partial({
format: t.string,
value_type: t.string,
value: t.partial({
field: t.string,
missing: t.number,
}),
weight: t.partial({
field: t.string,
missing: t.number,
}),
}),
});
export const metricsAggsSchemas = t.exact(
t.partial({
avg: t.exact(
t.partial({
field: t.string,
missing: t.union([t.string, t.number, t.boolean]),
})
),
cardinality: t.exact(
t.partial({
field: t.string,
precision_threshold: t.number,
rehash: t.boolean,
missing: t.union([t.string, t.number, t.boolean]),
})
),
min: t.exact(
t.partial({
field: t.string,
missing: t.union([t.string, t.number, t.boolean]),
format: t.string,
})
),
max: t.exact(
t.partial({
field: t.string,
missing: t.union([t.string, t.number, t.boolean]),
format: t.string,
})
),
sum: t.exact(
t.partial({
field: t.string,
missing: t.union([t.string, t.number, t.boolean]),
})
),
top_hits: t.exact(
t.partial({
explain: t.boolean,
docvalue_fields: t.union([t.string, t.array(t.string)]),
stored_fields: t.union([t.string, t.array(t.string)]),
from: t.number,
size: t.number,
sort: sortSchema,
seq_no_primary_term: t.boolean,
version: t.boolean,
track_scores: t.boolean,
highlight: t.any,
_source: t.union([t.boolean, t.string, t.array(t.string)]),
})
),
weighted_avg: t.exact(
t.partial({
format: t.string,
value_type: t.string,
value: t.partial({
field: t.string,
missing: t.number,
}),
weight: t.partial({
field: t.string,
missing: t.number,
}),
})
),
aggs: t.undefined,
aggregations: t.undefined,
})
);

export type PutIndexTemplateRequest = estypes.IndicesPutIndexTemplateRequest & {
body?: { composed_of?: string[] };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -647,27 +647,23 @@ export class AlertsClient {
[ReadOperations.Find, ReadOperations.Get, WriteOperations.Update],
AlertingAuthorizationEntity.Alert
);

// As long as the user can read a minimum of one type of rule type produced by the provided feature,
// the user should be provided that features' alerts index.
// Limiting which alerts that user can read on that index will be done via the findAuthorizationFilter
const authorizedFeatures = new Set<string>();
for (const ruleType of augmentedRuleTypes.authorizedRuleTypes) {
authorizedFeatures.add(ruleType.producer);
}

const validAuthorizedFeatures = Array.from(authorizedFeatures).filter(
(feature): feature is ValidFeatureId =>
featureIds.includes(feature) && isValidFeatureId(feature)
);

const toReturn = validAuthorizedFeatures.flatMap((feature) => {
const indices = this.ruleDataService.findIndicesByFeature(feature, Dataset.alerts);
if (feature === 'siem') {
return indices.map((i) => `${i.baseName}-${this.spaceId}`);
} else {
return indices.map((i) => i.baseName);
const toReturn = validAuthorizedFeatures.map((feature) => {
const index = this.ruleDataService.findIndexByFeature(feature, Dataset.alerts);
if (index == null) {
throw new Error(`This feature id ${feature} should be associated to an alert index`);
}
return index?.getPrimaryAlias(this.spaceId ?? '*') ?? '';
});

return toReturn;
Expand Down
8 changes: 6 additions & 2 deletions x-pack/plugins/rule_registry/server/routes/find.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { PositiveInteger } from '@kbn/securitysolution-io-ts-types';
import { RacRequestHandlerContext } from '../types';
import { BASE_RAC_ALERTS_API_PATH } from '../../common/constants';
import { buildRouteValidation } from './utils/route_validation';
import { BucketAggsSchemas } from '../../common/types';
import { bucketAggsSchemas, metricsAggsSchemas } from '../../common/types';

export const findAlertsByQueryRoute = (router: IRouter<RacRequestHandlerContext>) => {
router.post(
Expand All @@ -26,7 +26,11 @@ export const findAlertsByQueryRoute = (router: IRouter<RacRequestHandlerContext>
t.partial({
index: t.string,
query: t.object,
aggs: t.union([t.record(t.string, BucketAggsSchemas), t.undefined]),
aggs: t.union([
t.record(t.string, bucketAggsSchemas),
t.record(t.string, metricsAggsSchemas),
t.undefined,
]),
size: t.union([PositiveInteger, t.undefined]),
track_total_hits: t.union([t.boolean, t.undefined]),
_source: t.union([t.array(t.string), t.undefined]),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export const ruleDataServiceMock = {
initializeService: jest.fn(),
initializeIndex: jest.fn(),
findIndexByName: jest.fn(),
findIndicesByFeature: jest.fn(),
findIndexByFeature: jest.fn(),
}),
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ export interface IRuleDataService {
* Looks up the index information associated with the given Kibana "feature".
* Note: features are used in RBAC.
*/
findIndicesByFeature(featureId: ValidFeatureId, dataset?: Dataset): IndexInfo[];
findIndexByFeature(featureId: ValidFeatureId, dataset: Dataset): IndexInfo | null;
}

// TODO: This is a leftover. Remove its usage from the "observability" plugin and delete it.
Expand Down Expand Up @@ -214,8 +214,13 @@ export class RuleDataService implements IRuleDataService {
return this.indicesByBaseName.get(baseName) ?? null;
}

public findIndicesByFeature(featureId: ValidFeatureId, dataset?: Dataset): IndexInfo[] {
public findIndexByFeature(featureId: ValidFeatureId, dataset: Dataset): IndexInfo | null {
const foundIndices = this.indicesByFeatureId.get(featureId) ?? [];
return dataset ? foundIndices.filter((i) => i.indexOptions.dataset === dataset) : foundIndices;
if (dataset && foundIndices.length > 0) {
return foundIndices.filter((i) => i.indexOptions.dataset === dataset)[0];
} else if (foundIndices.length > 0) {
return foundIndices[0];
}
return null;
}
}
Loading

0 comments on commit 956612d

Please sign in to comment.