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

[Security Solution] Give notice when endpoint policy is out of date #83469

Merged
merged 14 commits into from
Nov 20, 2020
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 @@ -9,6 +9,7 @@ import { AgentStatus, Agent, EsAssetReference } from '../types';
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 @@ -59,6 +60,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;
pzl marked this conversation as resolved.
Show resolved Hide resolved
id: string;
}>;

export type HostInfo = Immutable<{
metadata: HostMetadata;
host_status: HostStatus;
policy_info?: {
pzl marked this conversation as resolved.
Show resolved Hide resolved
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;
pzl marked this conversation as resolved.
Show resolved Hide resolved
version: number;
};
};
};
Expand Down Expand Up @@ -1068,7 +1093,8 @@ export interface HostPolicyResponse {
Endpoint: {
policy: {
applied: {
version: string;
version: number;
pzl marked this conversation as resolved.
Show resolved Hide resolved
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 = (
pzl marked this conversation as resolved.
Show resolved Hide resolved
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