Skip to content

Commit

Permalink
Update stats api types (elastic#102)
Browse files Browse the repository at this point in the history
  • Loading branch information
orouz authored Jan 19, 2022
1 parent 2cdcccd commit 2dbd6d2
Show file tree
Hide file tree
Showing 8 changed files with 82 additions and 47 deletions.
3 changes: 3 additions & 0 deletions x-pack/plugins/cloud_security_posture/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,6 @@ export const RULE_FAILED = `failed`;
export const INTERNAL_FEATURE_FLAGS = {
benchmarks: false,
} as const;

/** This Kibana Advanced Setting enables the `Cloud Security Posture` beta feature */
export const ENABLE_CSP = 'securitySolution:enableCloudSecurityPosture';
8 changes: 4 additions & 4 deletions x-pack/plugins/cloud_security_posture/common/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ export type Evaluation = 'passed' | 'failed' | 'NA';
export type Score = number;

export interface Stats {
postureScore?: Score;
totalFindings?: number;
totalPassed?: number;
totalFailed?: number;
postureScore: Score;
totalFindings: number;
totalPassed: number;
totalFailed: number;
}

export interface BenchmarkStats extends Stats {
Expand Down
2 changes: 1 addition & 1 deletion x-pack/plugins/cloud_security_posture/public/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import type {
} from './types';
import { PLUGIN_NAME, PLUGIN_ID } from '../common';
import { DEFAULT_APP_CATEGORIES } from '../../../../src/core/public';
import { ENABLE_CSP } from '../server/constants';
import { ENABLE_CSP } from '../common/constants';

export class CspPlugin
implements
Expand Down
3 changes: 0 additions & 3 deletions x-pack/plugins/cloud_security_posture/server/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,3 @@
export const RULE_PASSED = `passed`;
export const RULE_FAILED = `failed`;
export const SECURITY_SOLUTION_APP_ID = 'securitySolution';

/** This Kibana Advanced Setting enables the `Cloud Security Posture` beta feature */
export const ENABLE_CSP = 'securitySolution:enableCloudSecurityPosture' as const;
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import { elasticsearchClientMock } from 'src/core/server/elasticsearch/client/mocks';

import { getLatestCycleIds } from './get_latest_cycle_ids';

const mockEsClient = elasticsearchClientMock.createClusterClient().asScoped().asInternalUser;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,13 @@ describe('general cloud posture score', () => {
});
});

it('mocking without finding index - expect to undefined from getAllFindingsStats ', async () => {
const generalScore = await getAllFindingsStats(mockEsClient, 'randomCycleId');
expect(generalScore).toEqual({
name: 'general',
postureScore: undefined,
totalFailed: undefined,
totalFindings: undefined,
totalPassed: undefined,
});
it("getAllFindingsStats throws when cycleId doesn't exists", async () => {
try {
await getAllFindingsStats(mockEsClient, 'randomCycleId');
} catch (e) {
expect(e).toBeInstanceOf(Error);
expect(e.message).toEqual('missing stats');
}
});
});

Expand Down
91 changes: 64 additions & 27 deletions x-pack/plugins/cloud_security_posture/server/routes/stats/stats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,8 @@
*/

import type { ElasticsearchClient, IRouter } from 'src/core/server';
import type {
AggregationsTermsAggregate,
DictionaryResponseBase,
AggregationsKeyedBucketKeys,
} from '@elastic/elasticsearch/lib/api/types';
import type { AggregationsMultiBucketAggregateBase } from '@elastic/elasticsearch/lib/api/types';
import { number, UnknownRecord } from 'io-ts';
import type {
CloudPostureStats,
BenchmarkStats,
Expand All @@ -25,6 +22,20 @@ import {
} from './stats_queries';
import { STATS_ROUTE_PATH } from '../../../common/constants';
import { RULE_PASSED, RULE_FAILED } from '../../constants';

// TODO: use a schema decoder
function assertBenchmarkStats(v: unknown): asserts v is BenchmarkStats {
if (
!UnknownRecord.is(v) ||
!number.is(v.totalFindings) ||
!number.is(v.totalPassed) ||
!number.is(v.totalFailed) ||
!number.is(v.postureScore)
) {
throw new Error('missing stats');
}
}

interface LastCycle {
run_id: string;
}
Expand All @@ -33,8 +44,12 @@ interface GroupFilename {
// TODO find the 'key', 'doc_count' interface
key: string;
doc_count: number;
group_docs: AggregationsTermsAggregate<AggregationsKeyedBucketKeys>;
}

interface ResourcesEvaluationEsAgg {
group: AggregationsMultiBucketAggregateBase<GroupFilename>;
}

const numOfResource = 5;

/**
Expand All @@ -52,10 +67,15 @@ const getLatestCycleId = async (esClient: ElasticsearchClient) => {
};

export const getBenchmarks = async (esClient: ElasticsearchClient) => {
const queryResult = await esClient.search(getBenchmarksQuery());
const benchmarksBuckets = queryResult.body.aggregations?.benchmarks as AggregationsTermsAggregate<
DictionaryResponseBase<string, string>
>;
const queryResult = await esClient.search<
{},
{ benchmarks: AggregationsMultiBucketAggregateBase<Pick<GroupFilename, 'key'>> }
>(getBenchmarksQuery());
const benchmarksBuckets = queryResult.body.aggregations?.benchmarks;
if (!benchmarksBuckets || !Array.isArray(benchmarksBuckets?.buckets)) {
throw new Error('missing buckets');
}

return benchmarksBuckets.buckets.map((e) => e.key);
};

Expand All @@ -72,14 +92,18 @@ export const getAllFindingsStats = async (
const totalFindings = findings.body.count;
const totalPassed = passedFindings.body.count;
const totalFailed = failedFindings.body.count;

return {
const postureScore = calculatePostureScore(totalFindings, totalPassed, totalFailed);
const stats = {
name: 'general',
postureScore: calculatePostureScore(totalFindings, totalPassed, totalFailed),
postureScore,
totalFindings,
totalPassed,
totalFailed,
};

assertBenchmarkStats(stats);

return stats;
};

export const getBenchmarksStats = async (
Expand All @@ -88,7 +112,7 @@ export const getBenchmarksStats = async (
benchmarks: string[]
): Promise<BenchmarkStats[]> => {
const benchmarkPromises = benchmarks.map((benchmark) => {
const benchmarkFindings = esClient.count(getFindingsEsQuery(benchmark, cycleId));
const benchmarkFindings = esClient.count(getFindingsEsQuery(cycleId, undefined, benchmark));
const benchmarkPassedFindings = esClient.count(
getFindingsEsQuery(cycleId, RULE_PASSED, benchmark)
);
Expand All @@ -101,29 +125,37 @@ export const getBenchmarksStats = async (
const totalFindings = benchmarkFindingsResult.body.count;
const totalPassed = benchmarkPassedFindingsResult.body.count;
const totalFailed = benchmarkFailedFindingsResult.body.count;
return {
const postureScore = calculatePostureScore(totalFindings, totalPassed, totalFailed);
const stats = {
name: benchmark,
postureScore: calculatePostureScore(totalFindings, totalPassed, totalFailed),
postureScore,
totalFindings,
totalPassed,
totalFailed,
};

assertBenchmarkStats(stats);
return stats;
}
);
});

return Promise.all(benchmarkPromises);
};

export const getResourcesEvaluation = async (
esClient: ElasticsearchClient,
cycleId: string
): Promise<EvaluationResult[]> => {
const failedEvaluationsPerResourceResult = await esClient.search(
getResourcesEvaluationEsQuery(cycleId, RULE_FAILED, numOfResource)
);

const failedResourcesGroup = failedEvaluationsPerResourceResult.body.aggregations
?.group as AggregationsTermsAggregate<GroupFilename>;
const failedEvaluationsPerResourceResult = await esClient.search<
GroupFilename,
ResourcesEvaluationEsAgg
>(getResourcesEvaluationEsQuery(cycleId, RULE_FAILED, numOfResource));

const failedResourcesGroup = failedEvaluationsPerResourceResult.body.aggregations?.group!;
if (!Array.isArray(failedResourcesGroup.buckets)) {
throw new Error('missing buckets array');
}
const topFailedResources = failedResourcesGroup.buckets.map((e) => e.key);
const failedEvaluationPerResource = failedResourcesGroup.buckets.map((e) => {
return {
Expand All @@ -133,11 +165,16 @@ export const getResourcesEvaluation = async (
} as const;
});

const passedEvaluationsPerResourceResult = await esClient.search(
getResourcesEvaluationEsQuery(cycleId, RULE_PASSED, 5, topFailedResources)
);
const passedResourcesGroup = passedEvaluationsPerResourceResult.body.aggregations
?.group as AggregationsTermsAggregate<GroupFilename>;
const passedEvaluationsPerResourceResult = await esClient.search<
GroupFilename,
ResourcesEvaluationEsAgg
>(getResourcesEvaluationEsQuery(cycleId, RULE_PASSED, 5, topFailedResources));
const passedResourcesGroup = passedEvaluationsPerResourceResult.body.aggregations?.group!;

if (!Array.isArray(passedResourcesGroup.buckets)) {
throw new Error('missing buckets array');
}

const passedEvaluationPerResources = passedResourcesGroup.buckets.map((e) => {
return {
resource: e.key,
Expand Down
3 changes: 2 additions & 1 deletion x-pack/plugins/cloud_security_posture/server/uiSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
import { i18n } from '@kbn/i18n';
import { schema } from '@kbn/config-schema';
import { CoreSetup } from '../../../../src/core/server';
import { SECURITY_SOLUTION_APP_ID, ENABLE_CSP } from './constants';
import { SECURITY_SOLUTION_APP_ID } from './constants';
import { ENABLE_CSP } from '../common/constants';

export const initUiSettings = (uiSettings: CoreSetup['uiSettings']) => {
uiSettings.register({
Expand Down

0 comments on commit 2dbd6d2

Please sign in to comment.