Skip to content

Commit

Permalink
[Security Solution] Give notice when endpoint policy is out of date (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
pzl authored Nov 20, 2020
1 parent a11f70f commit 2cd2528
Show file tree
Hide file tree
Showing 24 changed files with 715 additions and 219 deletions.
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;
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

0 comments on commit 2cd2528

Please sign in to comment.