Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

[7.x] [Security Solution] Give notice when endpoint policy is out of date (#83469) #83979

Merged
merged 1 commit into from
Nov 20, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion x-pack/plugins/fleet/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,13 @@ import {
} from '../common';

export { default as apm } from 'elastic-apm-node';
export { AgentService, ESIndexPatternService, getRegistryUrl, PackageService } from './services';
export {
AgentService,
ESIndexPatternService,
getRegistryUrl,
PackageService,
AgentPolicyServiceInterface,
} from './services';
export { FleetSetupContract, FleetSetupDeps, FleetStartContract, ExternalCallback } from './plugin';

export const config: PluginConfigDescriptor = {
Expand Down
26 changes: 26 additions & 0 deletions x-pack/plugins/fleet/server/mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { FleetAppContext } from './plugin';
import { encryptedSavedObjectsMock } from '../../encrypted_saved_objects/server/mocks';
import { securityMock } from '../../security/server/mocks';
import { PackagePolicyServiceInterface } from './services/package_policy';
import { AgentPolicyServiceInterface, AgentService } from './services';

export const createAppContextStartContractMock = (): FleetAppContext => {
return {
Expand All @@ -35,3 +36,28 @@ export const createPackagePolicyServiceMock = () => {
update: jest.fn(),
} as jest.Mocked<PackagePolicyServiceInterface>;
};

/**
* Create mock AgentPolicyService
*/

export const createMockAgentPolicyService = (): jest.Mocked<AgentPolicyServiceInterface> => {
return {
get: jest.fn(),
list: jest.fn(),
getDefaultAgentPolicyId: jest.fn(),
getFullAgentPolicy: jest.fn(),
};
};

/**
* Creates a mock AgentService
*/
export const createMockAgentService = (): jest.Mocked<AgentService> => {
return {
getAgentStatusById: jest.fn(),
authenticateAgentWithAccessToken: jest.fn(),
getAgent: jest.fn(),
listAgents: jest.fn(),
};
};
9 changes: 9 additions & 0 deletions x-pack/plugins/fleet/server/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ import {
ESIndexPatternSavedObjectService,
ESIndexPatternService,
AgentService,
AgentPolicyServiceInterface,
agentPolicyService,
packagePolicyService,
PackageService,
} from './services';
Expand Down Expand Up @@ -134,6 +136,7 @@ export interface FleetStartContract {
* Services for Fleet's package policies
*/
packagePolicyService: typeof packagePolicyService;
agentPolicyService: AgentPolicyServiceInterface;
/**
* Register callbacks for inclusion in fleet API processing
* @param args
Expand Down Expand Up @@ -292,6 +295,12 @@ export class FleetPlugin
getAgentStatusById,
authenticateAgentWithAccessToken,
},
agentPolicyService: {
get: agentPolicyService.get,
list: agentPolicyService.list,
getDefaultAgentPolicyId: agentPolicyService.getDefaultAgentPolicyId,
getFullAgentPolicy: agentPolicyService.getFullAgentPolicy,
},
packagePolicyService,
registerExternalCallback: (...args: ExternalCallback) => {
return appContextService.addExternalCallback(...args);
Expand Down
8 changes: 8 additions & 0 deletions x-pack/plugins/fleet/server/services/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import * as settingsService from './settings';
import { getAgent, listAgents } from './agents';

export { ESIndexPatternSavedObjectService } from './es_index_pattern';
import { agentPolicyService } from './agent_policy';

export { getRegistryUrl } from './epm/registry/registry_url';

Expand Down Expand Up @@ -60,6 +61,13 @@ export interface AgentService {
listAgents: typeof listAgents;
}

export interface AgentPolicyServiceInterface {
get: typeof agentPolicyService['get'];
list: typeof agentPolicyService['list'];
getDefaultAgentPolicyId: typeof agentPolicyService['getDefaultAgentPolicyId'];
getFullAgentPolicy: typeof agentPolicyService['getFullAgentPolicy'];
}

// Saved object services
export { agentPolicyService } from './agent_policy';
export { packagePolicyService } from './package_policy';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,21 +118,29 @@ const APPLIED_POLICIES: Array<{
name: string;
id: string;
status: HostPolicyResponseActionStatus;
endpoint_policy_version: number;
version: number;
}> = [
{
name: 'Default',
id: '00000000-0000-0000-0000-000000000000',
status: HostPolicyResponseActionStatus.success,
endpoint_policy_version: 1,
version: 3,
},
{
name: 'With Eventing',
id: 'C2A9093E-E289-4C0A-AA44-8C32A414FA7A',
status: HostPolicyResponseActionStatus.success,
endpoint_policy_version: 3,
version: 5,
},
{
name: 'Detect Malware Only',
id: '47d7965d-6869-478b-bd9c-fb0d2bb3959f',
status: HostPolicyResponseActionStatus.success,
endpoint_policy_version: 4,
version: 9,
},
];

Expand Down Expand Up @@ -251,6 +259,8 @@ interface HostInfo {
id: string;
status: HostPolicyResponseActionStatus;
name: string;
endpoint_policy_version: number;
version: number;
};
};
};
Expand Down Expand Up @@ -1332,7 +1342,7 @@ export class EndpointDocGenerator {
allStatus?: HostPolicyResponseActionStatus;
policyDataStream?: DataStream;
} = {}): HostPolicyResponse {
const policyVersion = this.seededUUIDv4();
const policyVersion = this.randomN(10);
const status = () => {
return allStatus || this.randomHostPolicyResponseActionStatus();
};
Expand Down Expand Up @@ -1501,6 +1511,8 @@ export class EndpointDocGenerator {
status: this.commonInfo.Endpoint.policy.applied.status,
version: policyVersion,
name: this.commonInfo.Endpoint.policy.applied.name,
endpoint_policy_version: this.commonInfo.Endpoint.policy.applied
.endpoint_policy_version,
},
},
},
Expand Down
28 changes: 27 additions & 1 deletion x-pack/plugins/security_solution/common/endpoint/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,8 @@ export interface HostResultList {
request_page_index: number;
/* the version of the query strategy */
query_strategy_version: MetadataQueryStrategyVersions;
/* policy IDs and versions */
policy_info?: HostInfo['policy_info'];
}

/**
Expand Down Expand Up @@ -520,9 +522,30 @@ export enum MetadataQueryStrategyVersions {
VERSION_2 = 'v2',
}

export type PolicyInfo = Immutable<{
revision: number;
id: string;
}>;

export type HostInfo = Immutable<{
metadata: HostMetadata;
host_status: HostStatus;
policy_info?: {
agent: {
/**
* As set in Kibana
*/
configured: PolicyInfo;
/**
* Last reported running in agent (may lag behind configured)
*/
applied: PolicyInfo;
};
/**
* Current intended 'endpoint' package policy
*/
endpoint: PolicyInfo;
};
/* the version of the query strategy */
query_strategy_version: MetadataQueryStrategyVersions;
}>;
Expand Down Expand Up @@ -558,6 +581,8 @@ export type HostMetadata = Immutable<{
id: string;
status: HostPolicyResponseActionStatus;
name: string;
endpoint_policy_version: number;
version: number;
};
};
};
Expand Down Expand Up @@ -1068,7 +1093,8 @@ export interface HostPolicyResponse {
Endpoint: {
policy: {
applied: {
version: string;
version: number;
endpoint_policy_version: number;
id: string;
name: string;
status: HostPolicyResponseActionStatus;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ describe('EndpointList store concerns', () => {
agentsWithEndpointsTotalError: undefined,
endpointsTotalError: undefined,
queryStrategyVersion: undefined,
policyVersionInfo: undefined,
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export const initialEndpointListState: Immutable<EndpointState> = {
endpointsTotal: 0,
endpointsTotalError: undefined,
queryStrategyVersion: undefined,
policyVersionInfo: undefined,
};

/* eslint-disable-next-line complexity */
Expand All @@ -55,6 +56,7 @@ export const endpointListReducer: ImmutableReducer<EndpointState, AppAction> = (
request_page_size: pageSize,
request_page_index: pageIndex,
query_strategy_version: queryStrategyVersion,
policy_info: policyVersionInfo,
} = action.payload;
return {
...state,
Expand All @@ -63,6 +65,7 @@ export const endpointListReducer: ImmutableReducer<EndpointState, AppAction> = (
pageSize,
pageIndex,
queryStrategyVersion,
policyVersionInfo,
loading: false,
error: undefined,
};
Expand Down Expand Up @@ -104,6 +107,7 @@ export const endpointListReducer: ImmutableReducer<EndpointState, AppAction> = (
return {
...state,
details: action.payload.metadata,
policyVersionInfo: action.payload.policy_info,
detailsLoading: false,
detailsError: undefined,
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ export const isAutoRefreshEnabled = (state: Immutable<EndpointState>) => state.i

export const autoRefreshInterval = (state: Immutable<EndpointState>) => state.autoRefreshInterval;

export const policyVersionInfo = (state: Immutable<EndpointState>) => state.policyVersionInfo;

export const areEndpointsEnrolling = (state: Immutable<EndpointState>) => {
return state.agentsWithEndpointsTotal > state.endpointsTotal;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ export interface EndpointState {
endpointsTotalError?: ServerApiError;
/** The query strategy version that informs whether the transform for KQL is enabled or not */
queryStrategyVersion?: MetadataQueryStrategyVersions;
/** The policy IDs and revision number of the corresponding agent, and endpoint. May be more recent than what's running */
policyVersionInfo?: HostInfo['policy_info'];
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* 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 { HostInfo, HostMetadata } from '../../../../common/endpoint/types';

export const isPolicyOutOfDate = (
reported: HostMetadata['Endpoint']['policy']['applied'],
current: HostInfo['policy_info']
): boolean => {
if (current === undefined || current === null) {
return false; // we don't know, can't declare it out-of-date
}
return !(
reported.id === current.endpoint.id && // endpoint package policy not reassigned
current.agent.configured.id === current.agent.applied.id && // agent policy wasn't reassigned and not-yet-applied
// all revisions match up
reported.version >= current.agent.applied.revision &&
reported.version >= current.agent.configured.revision &&
reported.endpoint_policy_version >= current.endpoint.revision
);
};
Original file line number Diff line number Diff line change
@@ -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 React from 'react';
import { EuiText, EuiIcon } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';

export const OutOfDate = React.memo<{ style?: React.CSSProperties }>(({ style, ...otherProps }) => {
return (
<EuiText color="subdued" size="xs" className="eui-textNoWrap" style={style} {...otherProps}>
<EuiIcon size="m" type="alert" color="warning" />
<FormattedMessage id="xpack.securitySolution.outOfDateLabel" defaultMessage="Out-of-date" />
</EuiText>
);
});

OutOfDate.displayName = 'OutOfDate';
Loading