Skip to content

Commit

Permalink
[Cloud Posture] status api (#134356)
Browse files Browse the repository at this point in the history
  • Loading branch information
CohenIdo authored Jul 18, 2022
1 parent f9e2ed4 commit ea711d2
Show file tree
Hide file tree
Showing 14 changed files with 763 additions and 242 deletions.
2 changes: 1 addition & 1 deletion x-pack/plugins/cloud_security_posture/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* 2.0.
*/

export const INFO_ROUTE_PATH = '/internal/cloud_security_posture/setup_status';
export const STATUS_ROUTE_PATH = '/internal/cloud_security_posture/status';
export const STATS_ROUTE_PATH = '/internal/cloud_security_posture/stats';
export const BENCHMARKS_ROUTE_PATH = '/internal/cloud_security_posture/benchmarks';
export const UPDATE_RULES_CONFIG_ROUTE_PATH =
Expand Down
26 changes: 24 additions & 2 deletions x-pack/plugins/cloud_security_posture/common/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,32 @@ export interface ComplianceDashboardData {
trend: PostureTrend[];
}

export interface CspSetupStatus {
latestFindingsIndexStatus: 'applicable' | 'inapplicable';
export type Status =
| 'indexed' // latest findings index exists and has results
| 'indexing' // index timeout was not surpassed since installation, assumes data is being indexed
| 'index-timeout' // index timeout was surpassed since installation
| 'not-deployed' // no healthy agents were deployed
| 'not-installed'; // number of installed csp integrations is 0;

interface BaseCspSetupStatus {
latestPackageVersion: string;
installedIntegrations: number;
healthyAgents: number;
}

interface CspSetupNotInstalledStatus extends BaseCspSetupStatus {
status: Extract<Status, 'not-installed'>;
}

interface CspSetupInstalledStatus extends BaseCspSetupStatus {
status: Exclude<Status, 'not-installed'>;
// if installedPackageVersion == undefined but status != 'not-installed' it means the integration was installed in the past and findings were found
// status can be `indexed` but return with undefined package information in this case
installedPackageVersion: string | undefined;
}

export type CspSetupStatus = CspSetupInstalledStatus | CspSetupNotInstalledStatus;

export interface CspRulesStatus {
all: number;
enabled: number;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@
import { useQuery } from 'react-query';
import { useKibana } from '../hooks/use_kibana';
import { CspSetupStatus } from '../../../common/types';
import { INFO_ROUTE_PATH } from '../../../common/constants';
import { STATUS_ROUTE_PATH } from '../../../common/constants';

const getCspSetupStatusQueryKey = 'csp_info_key';
const getCspSetupStatusQueryKey = 'csp_status_key';

export const useCspSetupStatusApi = () => {
const { http } = useKibana().services;
return useQuery([getCspSetupStatusQueryKey], () => http.get<CspSetupStatus>(INFO_ROUTE_PATH));
return useQuery([getCspSetupStatusQueryKey], () => http.get<CspSetupStatus>(STATUS_ROUTE_PATH));
};
Original file line number Diff line number Diff line change
Expand Up @@ -196,9 +196,9 @@ describe('<ComplianceDashboard />', () => {
);
};

it('shows noDataConfig when latestFindingsIndexStatus is inapplicable', () => {
it('shows noDataConfig when status is not deployed', () => {
(useCspSetupStatusApi as jest.Mock).mockImplementation(() =>
createReactQueryResponse({ status: 'success', data: 'inapplicable' })
createReactQueryResponse({ status: 'success', data: 'not-deployed' })
);
(useComplianceDashboardDataApi as jest.Mock).mockImplementation(() =>
createReactQueryResponse({ status: 'success', data: undefined })
Expand All @@ -210,11 +210,11 @@ describe('<ComplianceDashboard />', () => {
expect(screen.queryByTestId(DASHBOARD_CONTAINER)).not.toBeInTheDocument();
});

it('shows dashboard when latestFindingsIndexStatus is applicable', () => {
it('shows dashboard when there are findings in latest findings index', () => {
(useCspSetupStatusApi as jest.Mock).mockImplementation(() => ({
isLoading: false,
isSuccess: true,
data: { latestFindingsIndexStatus: 'applicable' },
data: { status: 'indexed' },
}));

(useComplianceDashboardDataApi as jest.Mock).mockImplementation(() => ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ const NoData = ({ onClick }: { onClick: () => void }) => (

export const ComplianceDashboard = () => {
const getInfo = useCspSetupStatusApi();
const isFindingsIndexApplicable = getInfo.data?.latestFindingsIndexStatus === 'applicable';
const isFindingsIndexApplicable = getInfo.data?.status === 'indexed';
const getDashboardData = useComplianceDashboardDataApi({
enabled: isFindingsIndexApplicable,
});
Expand Down
76 changes: 76 additions & 0 deletions x-pack/plugins/cloud_security_posture/server/lib/fleet_util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { uniq, map } from 'lodash';
import type { SavedObjectsClientContract } from '@kbn/core/server';
import type {
PackagePolicyServiceInterface,
AgentPolicyServiceInterface,
AgentService,
} from '@kbn/fleet-plugin/server';
import type {
GetAgentPoliciesResponseItem,
PackagePolicy,
AgentPolicy,
ListResult,
} from '@kbn/fleet-plugin/common';
import {
BENCHMARK_PACKAGE_POLICY_PREFIX,
BenchmarksQueryParams,
} from '../../common/schemas/benchmark';

export const PACKAGE_POLICY_SAVED_OBJECT_TYPE = 'ingest-package-policies';

const getPackageNameQuery = (packageName: string, benchmarkFilter?: string): string => {
const integrationNameQuery = `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name:${packageName}`;
const kquery = benchmarkFilter
? `${integrationNameQuery} AND ${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.name: *${benchmarkFilter}*`
: integrationNameQuery;

return kquery;
};

export const addRunningAgentToAgentPolicy = async (
agentService: AgentService,
agentPolicies: AgentPolicy[]
): Promise<GetAgentPoliciesResponseItem[]> => {
if (!agentPolicies.length) return [];

return Promise.all(
agentPolicies.map((agentPolicy) =>
agentService.asInternalUser
.getAgentStatusForAgentPolicy(agentPolicy.id)
.then((agentStatus) => ({
...agentPolicy,
agents: agentStatus.total,
}))
)
);
};

export const getCspAgentPolicies = async (
soClient: SavedObjectsClientContract,
packagePolicies: PackagePolicy[],
agentPolicyService: AgentPolicyServiceInterface
): Promise<AgentPolicy[]> =>
agentPolicyService.getByIds(soClient, uniq(map(packagePolicies, 'policy_id')));

export const getCspPackagePolicies = (
soClient: SavedObjectsClientContract,
packagePolicyService: PackagePolicyServiceInterface,
packageName: string,
queryParams: Partial<BenchmarksQueryParams>
): Promise<ListResult<PackagePolicy>> => {
const sortField = queryParams.sort_field?.replaceAll(BENCHMARK_PACKAGE_POLICY_PREFIX, '');

return packagePolicyService.list(soClient, {
kuery: getPackageNameQuery(packageName, queryParams.benchmark_name),
page: queryParams.page,
perPage: queryParams.per_page,
sortField,
sortOrder: queryParams.sort_order,
});
};
Original file line number Diff line number Diff line change
@@ -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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { ElasticsearchClient } from '@kbn/core/server';
import { CspAppContext } from '../plugin';
import { LATEST_FINDINGS_INDEX_DEFAULT_NS } from '../../common/constants';

export const isLatestFindingsIndexExists = async (
esClient: ElasticsearchClient,
logger: CspAppContext['logger']
): Promise<boolean> => {
try {
const queryResult = await esClient.search({
index: LATEST_FINDINGS_INDEX_DEFAULT_NS,
query: {
match_all: {},
},
size: 1,
});

return !!queryResult.hits.hits.length;
} catch (e) {
logger.error(e.message);
return false;
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,12 @@ import {
DEFAULT_BENCHMARKS_PER_PAGE,
} from '../../../common/schemas/benchmark';
import {
defineGetBenchmarksRoute,
PACKAGE_POLICY_SAVED_OBJECT_TYPE,
getCspPackagePolicies,
getAgentPolicies,
getCspAgentPolicies,
} from '../../lib/fleet_util';
import {
defineGetBenchmarksRoute,
createBenchmarkEntry,
addPackagePolicyCspRules,
} from './benchmarks';
Expand Down Expand Up @@ -307,7 +309,7 @@ describe('benchmarks API', () => {
const agentPolicyService = createMockAgentPolicyService();
const packagePolicies = [createPackagePolicyMock(), createPackagePolicyMock()];

await getAgentPolicies(mockSoClient, packagePolicies, agentPolicyService);
await getCspAgentPolicies(mockSoClient, packagePolicies, agentPolicyService);

expect(agentPolicyService.getByIds.mock.calls[0][1]).toHaveLength(1);
});
Expand All @@ -320,7 +322,7 @@ describe('benchmarks API', () => {
packagePolicy2.policy_id = 'AnotherId';
const packagePolicies = [packagePolicy1, packagePolicy2];

await getAgentPolicies(mockSoClient, packagePolicies, agentPolicyService);
await getCspAgentPolicies(mockSoClient, packagePolicies, agentPolicyService);

expect(agentPolicyService.getByIds.mock.calls[0][1]).toHaveLength(2);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,15 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { uniq, map } from 'lodash';
import type { SavedObjectsClientContract, SavedObjectsFindResponse } from '@kbn/core/server';
import { transformError } from '@kbn/securitysolution-es-utils';
import type {
PackagePolicyServiceInterface,
AgentPolicyServiceInterface,
AgentService,
} from '@kbn/fleet-plugin/server';
import type {
GetAgentPoliciesResponseItem,
PackagePolicy,
AgentPolicy,
ListResult,
} from '@kbn/fleet-plugin/common';
import type { GetAgentPoliciesResponseItem, PackagePolicy } from '@kbn/fleet-plugin/common';
import {
BENCHMARKS_ROUTE_PATH,
CLOUD_SECURITY_POSTURE_PACKAGE_NAME,
CSP_RULE_SAVED_OBJECT_TYPE,
} from '../../../common/constants';
import {
BENCHMARK_PACKAGE_POLICY_PREFIX,
benchmarksQueryParamsSchema,
BenchmarksQueryParams,
} from '../../../common/schemas/benchmark';
import { benchmarksQueryParamsSchema } from '../../../common/schemas/benchmark';
import { CspAppContext } from '../../plugin';
import type { Benchmark, CspRulesStatus } from '../../../common/types';
import type { CspRule } from '../../../common/schemas';
Expand All @@ -37,73 +21,20 @@ import {
isNonNullable,
} from '../../../common/utils/helpers';
import { CspRouter } from '../../types';
import {
addRunningAgentToAgentPolicy,
getCspAgentPolicies,
getCspPackagePolicies,
} from '../../lib/fleet_util';

export const PACKAGE_POLICY_SAVED_OBJECT_TYPE = 'ingest-package-policies';

const getPackageNameQuery = (packageName: string, benchmarkFilter?: string): string => {
const integrationNameQuery = `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name:${packageName}`;
const kquery = benchmarkFilter
? `${integrationNameQuery} AND ${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.name: *${benchmarkFilter}*`
: integrationNameQuery;

return kquery;
};

export const getCspPackagePolicies = (
soClient: SavedObjectsClientContract,
packagePolicyService: PackagePolicyServiceInterface,
packageName: string,
queryParams: Partial<BenchmarksQueryParams>
): Promise<ListResult<PackagePolicy>> => {
if (!packagePolicyService) {
throw new Error('packagePolicyService is undefined');
}

const sortField = queryParams.sort_field?.startsWith(BENCHMARK_PACKAGE_POLICY_PREFIX)
? queryParams.sort_field.substring(BENCHMARK_PACKAGE_POLICY_PREFIX.length)
: queryParams.sort_field;

return packagePolicyService?.list(soClient, {
kuery: getPackageNameQuery(packageName, queryParams.benchmark_name),
page: queryParams.page,
perPage: queryParams.per_page,
sortField,
sortOrder: queryParams.sort_order,
});
};

export const getAgentPolicies = async (
soClient: SavedObjectsClientContract,
packagePolicies: PackagePolicy[],
agentPolicyService: AgentPolicyServiceInterface
): Promise<AgentPolicy[]> => {
const agentPolicyIds = uniq(map(packagePolicies, 'policy_id'));
const agentPolicies = await agentPolicyService.getByIds(soClient, agentPolicyIds);

return agentPolicies;
};

const addRunningAgentToAgentPolicy = async (
agentService: AgentService,
agentPolicies: AgentPolicy[]
): Promise<GetAgentPoliciesResponseItem[]> => {
if (!agentPolicies?.length) return [];
return Promise.all(
agentPolicies.map((agentPolicy) =>
agentService.asInternalUser
.getAgentStatusForAgentPolicy(agentPolicy.id)
.then((agentStatus) => ({
...agentPolicy,
agents: agentStatus.total,
}))
)
);
};
export interface RulesStatusAggregation {
enabled_status: {
doc_count: number;
};
}

export const getCspRulesStatus = (
soClient: SavedObjectsClientContract,
packagePolicy: PackagePolicy
Expand All @@ -125,6 +56,7 @@ export const getCspRulesStatus = (
},
perPage: 0,
});

return cspRules;
};

Expand All @@ -138,6 +70,7 @@ export const addPackagePolicyCspRules = async (
enabled: rules.aggregations?.enabled_status.doc_count || 0,
disabled: rules.total - (rules.aggregations?.enabled_status.doc_count || 0),
};

return packagePolicyRules;
};

Expand Down Expand Up @@ -191,6 +124,7 @@ const createBenchmarks = (
const benchmark = createBenchmarkEntry(agentPolicy, cspPackage, cspRulesStatus);
return benchmark;
});

return benchmarks;
})
);
Expand Down Expand Up @@ -229,7 +163,7 @@ export const defineGetBenchmarksRoute = (router: CspRouter, cspContext: CspAppCo
query
);

const agentPolicies = await getAgentPolicies(
const agentPolicies = await getCspAgentPolicies(
soClient,
cspPackagePolicies.items,
agentPolicyService
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import { defineGetComplianceDashboardRoute } from './compliance_dashboard/compliance_dashboard';
import { defineGetBenchmarksRoute } from './benchmarks/benchmarks';
import { defineUpdateRulesConfigRoute } from './configuration/update_rules_configuration';
import { defineGetCspSetupStatusRoute } from './setup_status/setup_status';
import { defineGetCspSetupStatusRoute } from './status/status';
import { defineEsPitRoute } from './es_pit/es_pit';
import { CspAppContext } from '../plugin';
import { CspRouter } from '../types';
Expand Down
Loading

0 comments on commit ea711d2

Please sign in to comment.