mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[EDR Workflows][API] Gate Agent Tamper Protection setting on Agent Policy Settings (#174400)
This PR is part of an effort to limit EDR Workflow features to the Endpoint Complete tier on serverless and focuses on server skde part of gating Agent Tamper Protection. Related PRs: https://github.com/elastic/kibana/pull/174278 https://github.com/elastic/kibana/pull/175129 **We decided to stick with the existing Fleet privileges for this component, and no extra changes are needed RBAC wise (confirmed with @roxana-gheorghe).** **Plugin/Policy Watcher Changes**: To monitor agent policies for a downgrade in tier (from complete to essentials) and disable agent protections if enabled, the following steps have been taken: 1. A new app feature, `endpoint_agent_tamper_protection`, has been introduced and linked to the `endpoint:complete` tier. 2. An additional method, `bumpRevision`, has been exposed in the fleet's agent policy service. This method utilizes the service's internal update function and includes a `disable_protection` flag, allowing it to be used without further modifications. 3. The security solution side calls this method upon successful fleet plugin setup. If the `endpoint_agent_tamper_protection` app feature is not enabled, it retrieves all agent policies with `is_protected: true` and updates these policies with `is_protected: false`. **API Changes**: To respond to attempts to activate agent protection via the API by users on the Essentials tier, the following steps have been taken: 1. External callback functionality has been added to the agentPolicy service, following the implementation in packagePolicy. 2. Update and create agent policy callbacks have been registered in the security solution. These callbacks check for the enabled status of the `endpoint_agent_tamper_protection` app feature. If disabled, the callback throws an error. 3. External callback execution has been added to the update and create methods in agent policy route handlers. --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
7c45bfdc30
commit
8166d18a37
18 changed files with 744 additions and 38 deletions
|
@ -32,10 +32,10 @@ import {
|
|||
UpdatePackagePolicy,
|
||||
} from '@kbn/fleet-plugin/common';
|
||||
import {
|
||||
ExternalCallback,
|
||||
FleetStartContract,
|
||||
PostPackagePolicyPostDeleteCallback,
|
||||
PostPackagePolicyPostCreateCallback,
|
||||
ExternalCallback,
|
||||
} from '@kbn/fleet-plugin/server';
|
||||
import { CLOUD_SECURITY_POSTURE_PACKAGE_NAME } from '../common/constants';
|
||||
import Chance from 'chance';
|
||||
|
|
|
@ -157,6 +157,7 @@ export const createMockAgentPolicyService = (): jest.Mocked<AgentPolicyServiceIn
|
|||
list: jest.fn(),
|
||||
getFullAgentPolicy: jest.fn(),
|
||||
getByIds: jest.fn(),
|
||||
bumpRevision: jest.fn(),
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -8,33 +8,32 @@
|
|||
import { backOff } from 'exponential-backoff';
|
||||
import type { Observable } from 'rxjs';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { take, filter } from 'rxjs/operators';
|
||||
import { filter, take } from 'rxjs/operators';
|
||||
import { DEFAULT_SPACE_ID } from '@kbn/spaces-plugin/common';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import type {
|
||||
CoreSetup,
|
||||
CoreStart,
|
||||
ElasticsearchClient,
|
||||
ElasticsearchServiceStart,
|
||||
HttpServiceSetup,
|
||||
KibanaRequest,
|
||||
Logger,
|
||||
Plugin,
|
||||
PluginInitializerContext,
|
||||
SavedObjectsServiceStart,
|
||||
HttpServiceSetup,
|
||||
KibanaRequest,
|
||||
ServiceStatus,
|
||||
ElasticsearchClient,
|
||||
SavedObjectsClientContract,
|
||||
SavedObjectsServiceStart,
|
||||
ServiceStatus,
|
||||
} from '@kbn/core/server';
|
||||
import { DEFAULT_APP_CATEGORIES, SavedObjectsClient, ServiceStatusLevels } from '@kbn/core/server';
|
||||
import type { UsageCollectionSetup } from '@kbn/usage-collection-plugin/server';
|
||||
|
||||
import type { TelemetryPluginSetup, TelemetryPluginStart } from '@kbn/telemetry-plugin/server';
|
||||
|
||||
import { DEFAULT_APP_CATEGORIES, SavedObjectsClient, ServiceStatusLevels } from '@kbn/core/server';
|
||||
import type { PluginStart as DataPluginStart } from '@kbn/data-plugin/server';
|
||||
import type { LicensingPluginStart } from '@kbn/licensing-plugin/server';
|
||||
import type {
|
||||
EncryptedSavedObjectsPluginStart,
|
||||
EncryptedSavedObjectsPluginSetup,
|
||||
EncryptedSavedObjectsPluginStart,
|
||||
} from '@kbn/encrypted-saved-objects-plugin/server';
|
||||
import type {
|
||||
AuditLogger,
|
||||
|
@ -57,61 +56,60 @@ import { SECURITY_EXTENSION_ID } from '@kbn/core-saved-objects-server';
|
|||
|
||||
import type { FleetConfigType } from '../common/types';
|
||||
import type { FleetAuthz } from '../common';
|
||||
import type { ExperimentalFeatures } from '../common/experimental_features';
|
||||
|
||||
import {
|
||||
MESSAGE_SIGNING_KEYS_SAVED_OBJECT_TYPE,
|
||||
INTEGRATIONS_PLUGIN_ID,
|
||||
MESSAGE_SIGNING_KEYS_SAVED_OBJECT_TYPE,
|
||||
UNINSTALL_TOKENS_SAVED_OBJECT_TYPE,
|
||||
} from '../common';
|
||||
import type { ExperimentalFeatures } from '../common/experimental_features';
|
||||
import { parseExperimentalConfigValue } from '../common/experimental_features';
|
||||
|
||||
import { getFilesClientFactory } from './services/files/get_files_client_factory';
|
||||
|
||||
import type { MessageSigningServiceInterface } from './services/security';
|
||||
import {
|
||||
getRouteRequiredAuthz,
|
||||
makeRouterWithFleetAuthz,
|
||||
calculateRouteAuthz,
|
||||
getAuthzFromRequest,
|
||||
getRouteRequiredAuthz,
|
||||
makeRouterWithFleetAuthz,
|
||||
MessageSigningService,
|
||||
} from './services/security';
|
||||
|
||||
import {
|
||||
PLUGIN_ID,
|
||||
OUTPUT_SAVED_OBJECT_TYPE,
|
||||
AGENT_POLICY_SAVED_OBJECT_TYPE,
|
||||
PACKAGE_POLICY_SAVED_OBJECT_TYPE,
|
||||
PACKAGES_SAVED_OBJECT_TYPE,
|
||||
ASSETS_SAVED_OBJECT_TYPE,
|
||||
PRECONFIGURATION_DELETION_RECORD_SAVED_OBJECT_TYPE,
|
||||
DOWNLOAD_SOURCE_SAVED_OBJECT_TYPE,
|
||||
FLEET_SERVER_HOST_SAVED_OBJECT_TYPE,
|
||||
OUTPUT_SAVED_OBJECT_TYPE,
|
||||
PACKAGE_POLICY_SAVED_OBJECT_TYPE,
|
||||
PACKAGES_SAVED_OBJECT_TYPE,
|
||||
PLUGIN_ID,
|
||||
PRECONFIGURATION_DELETION_RECORD_SAVED_OBJECT_TYPE,
|
||||
} from './constants';
|
||||
import { registerSavedObjects, registerEncryptedSavedObjects } from './saved_objects';
|
||||
import { registerEncryptedSavedObjects, registerSavedObjects } from './saved_objects';
|
||||
import { registerRoutes } from './routes';
|
||||
|
||||
import type { ExternalCallback, FleetRequestHandlerContext } from './types';
|
||||
import type {
|
||||
ESIndexPatternService,
|
||||
AgentService,
|
||||
AgentPolicyServiceInterface,
|
||||
AgentService,
|
||||
ESIndexPatternService,
|
||||
PackageService,
|
||||
} from './services';
|
||||
import { FleetUsageSender } from './services';
|
||||
import {
|
||||
appContextService,
|
||||
licenseService,
|
||||
ESIndexPatternSavedObjectService,
|
||||
agentPolicyService,
|
||||
packagePolicyService,
|
||||
AgentServiceImpl,
|
||||
appContextService,
|
||||
ESIndexPatternSavedObjectService,
|
||||
FleetUsageSender,
|
||||
licenseService,
|
||||
packagePolicyService,
|
||||
PackageServiceImpl,
|
||||
} from './services';
|
||||
import {
|
||||
registerFleetUsageCollector,
|
||||
fetchAgentsUsage,
|
||||
fetchFleetUsage,
|
||||
registerFleetUsageCollector,
|
||||
} from './collectors/register';
|
||||
import { FleetArtifactsClient } from './services/artifacts';
|
||||
import type { FleetRouter } from './types/request_context';
|
||||
|
@ -627,6 +625,7 @@ export class FleetPlugin
|
|||
list: agentPolicyService.list,
|
||||
getFullAgentPolicy: agentPolicyService.getFullAgentPolicy,
|
||||
getByIds: agentPolicyService.getByIDs,
|
||||
bumpRevision: agentPolicyService.bumpRevision.bind(agentPolicyService),
|
||||
},
|
||||
packagePolicyService,
|
||||
registerExternalCallback: (type: ExternalCallback[0], callback: ExternalCallback[1]) => {
|
||||
|
|
|
@ -195,6 +195,12 @@ export const createAgentPolicyHandler: FleetRequestHandler<
|
|||
body,
|
||||
});
|
||||
} catch (error) {
|
||||
if (error.statusCode) {
|
||||
return response.customError({
|
||||
statusCode: error.statusCode,
|
||||
body: { message: error.message },
|
||||
});
|
||||
}
|
||||
return defaultFleetErrorHandler({ error, response });
|
||||
}
|
||||
};
|
||||
|
@ -229,6 +235,12 @@ export const updateAgentPolicyHandler: FleetRequestHandler<
|
|||
body,
|
||||
});
|
||||
} catch (error) {
|
||||
if (error.statusCode) {
|
||||
return response.customError({
|
||||
statusCode: error.statusCode,
|
||||
body: { message: error.message },
|
||||
});
|
||||
}
|
||||
return defaultFleetErrorHandler({ error, response });
|
||||
}
|
||||
};
|
||||
|
|
|
@ -44,6 +44,9 @@ import type {
|
|||
FullAgentPolicy,
|
||||
ListWithKuery,
|
||||
NewPackagePolicy,
|
||||
PostAgentPolicyCreateCallback,
|
||||
PostAgentPolicyUpdateCallback,
|
||||
ExternalCallback,
|
||||
} from '../types';
|
||||
import {
|
||||
getAllowedOutputTypeForPolicy,
|
||||
|
@ -234,6 +237,43 @@ class AgentPolicyService {
|
|||
return policyHasSyntheticsIntegration(agentPolicy);
|
||||
}
|
||||
|
||||
public async runExternalCallbacks(
|
||||
externalCallbackType: ExternalCallback[0],
|
||||
agentPolicy: NewAgentPolicy | Partial<AgentPolicy>
|
||||
): Promise<NewAgentPolicy | Partial<AgentPolicy>> {
|
||||
const logger = appContextService.getLogger();
|
||||
logger.debug(`Running external callbacks for ${externalCallbackType}`);
|
||||
try {
|
||||
const externalCallbacks = appContextService.getExternalCallbacks(externalCallbackType);
|
||||
let newAgentPolicy = agentPolicy;
|
||||
|
||||
if (externalCallbacks && externalCallbacks.size > 0) {
|
||||
let updatedNewAgentPolicy = newAgentPolicy;
|
||||
for (const callback of externalCallbacks) {
|
||||
let result;
|
||||
if (externalCallbackType === 'agentPolicyCreate') {
|
||||
result = await (callback as PostAgentPolicyCreateCallback)(
|
||||
newAgentPolicy as NewAgentPolicy
|
||||
);
|
||||
updatedNewAgentPolicy = result;
|
||||
}
|
||||
if (externalCallbackType === 'agentPolicyUpdate') {
|
||||
result = await (callback as PostAgentPolicyUpdateCallback)(
|
||||
newAgentPolicy as Partial<AgentPolicy>
|
||||
);
|
||||
updatedNewAgentPolicy = result;
|
||||
}
|
||||
}
|
||||
newAgentPolicy = updatedNewAgentPolicy;
|
||||
}
|
||||
return newAgentPolicy;
|
||||
} catch (error) {
|
||||
logger.error(`Error running external callbacks for ${externalCallbackType}`);
|
||||
logger.error(error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
public async create(
|
||||
soClient: SavedObjectsClientContract,
|
||||
esClient: ElasticsearchClient,
|
||||
|
@ -254,7 +294,7 @@ class AgentPolicyService {
|
|||
id: options.id,
|
||||
savedObjectType: AGENT_POLICY_SAVED_OBJECT_TYPE,
|
||||
});
|
||||
|
||||
await this.runExternalCallbacks('agentPolicyCreate', agentPolicy);
|
||||
this.checkTamperProtectionLicense(agentPolicy);
|
||||
|
||||
const logger = appContextService.getLogger();
|
||||
|
@ -519,7 +559,14 @@ class AgentPolicyService {
|
|||
if (!existingAgentPolicy) {
|
||||
throw new AgentPolicyNotFoundError('Agent policy not found');
|
||||
}
|
||||
|
||||
try {
|
||||
await this.runExternalCallbacks('agentPolicyUpdate', agentPolicy);
|
||||
} catch (error) {
|
||||
logger.error(`Error running external callbacks for agentPolicyUpdate`);
|
||||
if (error.apiPassThrough) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
this.checkTamperProtectionLicense(agentPolicy);
|
||||
await this.checkForValidUninstallToken(agentPolicy, id);
|
||||
|
||||
|
|
|
@ -42,6 +42,8 @@ import type {
|
|||
PostPackagePolicyPostDeleteCallback,
|
||||
PostPackagePolicyPostCreateCallback,
|
||||
PutPackagePolicyUpdateCallback,
|
||||
PostAgentPolicyCreateCallback,
|
||||
PostAgentPolicyUpdateCallback,
|
||||
} from '../types';
|
||||
import type { FleetAppContext } from '../plugin';
|
||||
import type { TelemetryEventsSender } from '../telemetry/sender';
|
||||
|
@ -245,7 +247,11 @@ class AppContextService {
|
|||
type: T
|
||||
):
|
||||
| Set<
|
||||
T extends 'packagePolicyCreate'
|
||||
T extends 'agentPolicyCreate'
|
||||
? PostAgentPolicyCreateCallback
|
||||
: T extends 'agentPolicyUpdate'
|
||||
? PostAgentPolicyUpdateCallback
|
||||
: T extends 'packagePolicyCreate'
|
||||
? PostPackagePolicyCreateCallback
|
||||
: T extends 'packagePolicyDelete'
|
||||
? PostPackagePolicyDeleteCallback
|
||||
|
@ -258,7 +264,11 @@ class AppContextService {
|
|||
| undefined {
|
||||
if (this.externalCallbacks) {
|
||||
return this.externalCallbacks.get(type) as Set<
|
||||
T extends 'packagePolicyCreate'
|
||||
T extends 'agentPolicyCreate'
|
||||
? PostAgentPolicyCreateCallback
|
||||
: T extends 'agentPolicyUpdate'
|
||||
? PostAgentPolicyUpdateCallback
|
||||
: T extends 'packagePolicyCreate'
|
||||
? PostPackagePolicyCreateCallback
|
||||
: T extends 'packagePolicyDelete'
|
||||
? PostPackagePolicyDeleteCallback
|
||||
|
|
|
@ -33,6 +33,7 @@ export interface AgentPolicyServiceInterface {
|
|||
list: typeof agentPolicyService['list'];
|
||||
getFullAgentPolicy: typeof agentPolicyService['getFullAgentPolicy'];
|
||||
getByIds: typeof agentPolicyService['getByIDs'];
|
||||
bumpRevision: typeof agentPolicyService['bumpRevision'];
|
||||
}
|
||||
|
||||
// Agent services
|
||||
|
|
|
@ -283,6 +283,7 @@ jest.mock('./app_context', () => ({
|
|||
getUninstallTokenService: () => ({
|
||||
generateTokenForPolicyId: jest.fn(),
|
||||
}),
|
||||
getExternalCallbacks: jest.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
|
|
|
@ -16,6 +16,8 @@ import type {
|
|||
UpdatePackagePolicy,
|
||||
PackagePolicy,
|
||||
DeletePackagePoliciesResponse,
|
||||
NewAgentPolicy,
|
||||
AgentPolicy,
|
||||
} from '../../common/types';
|
||||
|
||||
export type PostPackagePolicyDeleteCallback = (
|
||||
|
@ -58,6 +60,14 @@ export type PutPackagePolicyUpdateCallback = (
|
|||
request?: KibanaRequest
|
||||
) => Promise<UpdatePackagePolicy>;
|
||||
|
||||
export type PostAgentPolicyCreateCallback = (
|
||||
agentPolicy: NewAgentPolicy
|
||||
) => Promise<NewAgentPolicy>;
|
||||
|
||||
export type PostAgentPolicyUpdateCallback = (
|
||||
agentPolicy: Partial<AgentPolicy>
|
||||
) => Promise<Partial<AgentPolicy>>;
|
||||
|
||||
export type ExternalCallbackCreate = ['packagePolicyCreate', PostPackagePolicyCreateCallback];
|
||||
export type ExternalCallbackPostCreate = [
|
||||
'packagePolicyPostCreate',
|
||||
|
@ -71,6 +81,15 @@ export type ExternalCallbackPostDelete = [
|
|||
];
|
||||
export type ExternalCallbackUpdate = ['packagePolicyUpdate', PutPackagePolicyUpdateCallback];
|
||||
|
||||
export type ExternalCallbackAgentPolicyCreate = [
|
||||
'agentPolicyCreate',
|
||||
PostAgentPolicyCreateCallback
|
||||
];
|
||||
export type ExternalCallbackAgentPolicyUpdate = [
|
||||
'agentPolicyUpdate',
|
||||
PostAgentPolicyUpdateCallback
|
||||
];
|
||||
|
||||
/**
|
||||
* Callbacks supported by the Fleet plugin
|
||||
*/
|
||||
|
@ -79,6 +98,8 @@ export type ExternalCallback =
|
|||
| ExternalCallbackPostCreate
|
||||
| ExternalCallbackDelete
|
||||
| ExternalCallbackPostDelete
|
||||
| ExternalCallbackUpdate;
|
||||
| ExternalCallbackUpdate
|
||||
| ExternalCallbackAgentPolicyCreate
|
||||
| ExternalCallbackAgentPolicyUpdate;
|
||||
|
||||
export type ExternalCallbacksStorage = Map<ExternalCallback[0], Set<ExternalCallback[1]>>;
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* 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 type { IndexedFleetEndpointPolicyResponse } from '../../../../../../../common/endpoint/data_loaders/index_fleet_endpoint_policy';
|
||||
import {
|
||||
createAgentPolicyTask,
|
||||
createAgentPolicyWithAgentTamperProtectionsEnabled,
|
||||
enableAgentTamperProtectionFeatureFlagInPolicy,
|
||||
getEndpointIntegrationVersion,
|
||||
} from '../../../../tasks/fleet';
|
||||
import { login } from '../../../../tasks/login';
|
||||
|
||||
describe(
|
||||
'Agent policy settings API operations on Complete',
|
||||
{
|
||||
tags: ['@serverless'],
|
||||
env: {
|
||||
ftrConfig: {
|
||||
productTypes: [
|
||||
{ product_line: 'security', product_tier: 'complete' },
|
||||
{ product_line: 'endpoint', product_tier: 'complete' },
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
() => {
|
||||
let indexedPolicy: IndexedFleetEndpointPolicyResponse;
|
||||
// let policy: PolicyData;
|
||||
|
||||
beforeEach(() => {
|
||||
getEndpointIntegrationVersion().then((version) =>
|
||||
createAgentPolicyTask(version).then((data) => {
|
||||
indexedPolicy = data;
|
||||
})
|
||||
);
|
||||
login();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
if (indexedPolicy) {
|
||||
cy.task('deleteIndexedFleetEndpointPolicies', indexedPolicy);
|
||||
}
|
||||
});
|
||||
|
||||
describe('Agent tamper protections', () => {
|
||||
it('allow enabling the feature', () => {
|
||||
enableAgentTamperProtectionFeatureFlagInPolicy(indexedPolicy.agentPolicies[0].id).then(
|
||||
(response) => {
|
||||
expect(response.status).to.equal(200);
|
||||
expect(response.body.item.is_protected).to.equal(true);
|
||||
}
|
||||
);
|
||||
});
|
||||
it('throw error when trying to create agent policy', () => {
|
||||
createAgentPolicyWithAgentTamperProtectionsEnabled().then((response) => {
|
||||
expect(response.status).to.equal(200);
|
||||
expect(response.body.item.is_protected).to.equal(false); // We don't allow creating a policy with the feature enabled
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
|
@ -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 type { UpdateAgentPolicyResponse } from '@kbn/fleet-plugin/common/types';
|
||||
import type { IndexedFleetEndpointPolicyResponse } from '../../../../../../../common/endpoint/data_loaders/index_fleet_endpoint_policy';
|
||||
import {
|
||||
createAgentPolicyTask,
|
||||
createAgentPolicyWithAgentTamperProtectionsEnabled,
|
||||
enableAgentTamperProtectionFeatureFlagInPolicy,
|
||||
getEndpointIntegrationVersion,
|
||||
} from '../../../../tasks/fleet';
|
||||
import { login } from '../../../../tasks/login';
|
||||
|
||||
describe(
|
||||
'Agent policy settings API operations on Essentials',
|
||||
{
|
||||
tags: ['@serverless'],
|
||||
env: {
|
||||
ftrConfig: {
|
||||
productTypes: [
|
||||
{ product_line: 'security', product_tier: 'essentials' },
|
||||
{ product_line: 'endpoint', product_tier: 'essentials' },
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
() => {
|
||||
let indexedPolicy: IndexedFleetEndpointPolicyResponse;
|
||||
|
||||
beforeEach(() => {
|
||||
getEndpointIntegrationVersion().then((version) =>
|
||||
createAgentPolicyTask(version).then((data) => {
|
||||
indexedPolicy = data;
|
||||
})
|
||||
);
|
||||
login();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
if (indexedPolicy) {
|
||||
cy.task('deleteIndexedFleetEndpointPolicies', indexedPolicy);
|
||||
}
|
||||
});
|
||||
|
||||
describe('Agent tamper protections', () => {
|
||||
it('throw error when trying to update agent policy settings', () => {
|
||||
enableAgentTamperProtectionFeatureFlagInPolicy(indexedPolicy.agentPolicies[0].id, {
|
||||
failOnStatusCode: false,
|
||||
}).then((res) => {
|
||||
const response = res as Cypress.Response<UpdateAgentPolicyResponse & { message: string }>;
|
||||
expect(response.status).to.equal(403);
|
||||
expect(response.body.message).to.equal(
|
||||
'Agent Tamper Protection is not allowed in current environment'
|
||||
);
|
||||
});
|
||||
});
|
||||
it('throw error when trying to create agent policy', () => {
|
||||
createAgentPolicyWithAgentTamperProtectionsEnabled({ failOnStatusCode: false }).then(
|
||||
(res) => {
|
||||
const response = res as Cypress.Response<
|
||||
UpdateAgentPolicyResponse & { message: string }
|
||||
>;
|
||||
expect(response.status).to.equal(403);
|
||||
expect(response.body.message).to.equal(
|
||||
'Agent Tamper Protection is not allowed in current environment'
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
|
@ -11,6 +11,7 @@ import type {
|
|||
GetInfoResponse,
|
||||
GetPackagePoliciesResponse,
|
||||
GetOneAgentPolicyResponse,
|
||||
CreateAgentPolicyResponse,
|
||||
} from '@kbn/fleet-plugin/common';
|
||||
import {
|
||||
agentRouteService,
|
||||
|
@ -99,7 +100,30 @@ export const createAgentPolicyTask = (
|
|||
);
|
||||
};
|
||||
|
||||
export const enableAgentTamperProtectionFeatureFlagInPolicy = (agentPolicyId: string) => {
|
||||
export const createAgentPolicyWithAgentTamperProtectionsEnabled = (
|
||||
overwrite?: Record<string, unknown>
|
||||
) => {
|
||||
return request<CreateAgentPolicyResponse>({
|
||||
method: 'POST',
|
||||
url: agentPolicyRouteService.getCreatePath(),
|
||||
body: {
|
||||
name: `With agent tamper protection enabled ${Math.random().toString(36).substring(2, 7)}`,
|
||||
agent_features: [{ name: 'tamper_protection', enabled: true }],
|
||||
is_protected: true,
|
||||
description: 'test',
|
||||
namespace: 'default',
|
||||
monitoring_enabled: ['logs', 'metrics'],
|
||||
inactivity_timeout: 1209600,
|
||||
},
|
||||
headers: { 'Elastic-Api-Version': API_VERSIONS.public.v1 },
|
||||
...(overwrite ?? {}),
|
||||
});
|
||||
};
|
||||
|
||||
export const enableAgentTamperProtectionFeatureFlagInPolicy = (
|
||||
agentPolicyId: string,
|
||||
overwrite?: Record<string, unknown>
|
||||
) => {
|
||||
return request<UpdateAgentPolicyResponse>({
|
||||
method: 'PUT',
|
||||
url: agentPolicyRouteService.getUpdatePath(agentPolicyId),
|
||||
|
@ -113,6 +137,7 @@ export const enableAgentTamperProtectionFeatureFlagInPolicy = (agentPolicyId: st
|
|||
inactivity_timeout: 1209600,
|
||||
},
|
||||
headers: { 'Elastic-Api-Version': API_VERSIONS.public.v1 },
|
||||
...(overwrite ?? {}),
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -24,6 +24,8 @@ import type { PluginStartContract as AlertsPluginStartContract } from '@kbn/aler
|
|||
import type { CloudSetup } from '@kbn/cloud-plugin/server';
|
||||
import type { FleetActionsClientInterface } from '@kbn/fleet-plugin/server/services/actions/types';
|
||||
import {
|
||||
getAgentPolicyCreateCallback,
|
||||
getAgentPolicyUpdateCallback,
|
||||
getPackagePolicyCreateCallback,
|
||||
getPackagePolicyDeleteCallback,
|
||||
getPackagePolicyPostCreateCallback,
|
||||
|
@ -119,6 +121,15 @@ export class EndpointAppContextService {
|
|||
savedObjectsClient,
|
||||
} = dependencies;
|
||||
|
||||
registerIngestCallback(
|
||||
'agentPolicyCreate',
|
||||
getAgentPolicyCreateCallback(logger, appFeaturesService)
|
||||
);
|
||||
registerIngestCallback(
|
||||
'agentPolicyUpdate',
|
||||
getAgentPolicyUpdateCallback(logger, appFeaturesService)
|
||||
);
|
||||
|
||||
registerIngestCallback(
|
||||
'packagePolicyCreate',
|
||||
getPackagePolicyCreateCallback(
|
||||
|
|
|
@ -0,0 +1,167 @@
|
|||
/*
|
||||
* 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 { createMockEndpointAppContextServiceStartContract } from '../mocks';
|
||||
import type { Logger } from '@kbn/logging';
|
||||
import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server';
|
||||
import type { EndpointInternalFleetServicesInterface } from '../services/fleet';
|
||||
|
||||
import { ALL_APP_FEATURE_KEYS } from '@kbn/security-solution-features/keys';
|
||||
import type { AppFeaturesService } from '../../lib/app_features_service/app_features_service';
|
||||
import { createAppFeaturesServiceMock } from '../../lib/app_features_service/mocks';
|
||||
import { turnOffAgentPolicyFeatures } from './turn_off_agent_policy_features';
|
||||
import { FleetAgentPolicyGenerator } from '../../../common/endpoint/data_generators/fleet_agent_policy_generator';
|
||||
import type { AgentPolicy, GetAgentPoliciesResponseItem } from '@kbn/fleet-plugin/common';
|
||||
|
||||
describe('Turn Off Agent Policy Features Migration', () => {
|
||||
let esClient: ElasticsearchClient;
|
||||
let fleetServices: EndpointInternalFleetServicesInterface;
|
||||
let appFeatureService: AppFeaturesService;
|
||||
let logger: Logger;
|
||||
|
||||
const callTurnOffAgentPolicyFeatures = () =>
|
||||
turnOffAgentPolicyFeatures(esClient, fleetServices, appFeatureService, logger);
|
||||
|
||||
beforeEach(() => {
|
||||
const endpointContextStartContract = createMockEndpointAppContextServiceStartContract();
|
||||
|
||||
({ esClient, logger } = endpointContextStartContract);
|
||||
|
||||
appFeatureService = endpointContextStartContract.appFeaturesService;
|
||||
fleetServices = endpointContextStartContract.endpointFleetServicesFactory.asInternalUser();
|
||||
});
|
||||
|
||||
describe('and `agentTamperProtection` is enabled', () => {
|
||||
it('should do nothing', async () => {
|
||||
await callTurnOffAgentPolicyFeatures();
|
||||
|
||||
expect(fleetServices.agentPolicy.list as jest.Mock).not.toHaveBeenCalled();
|
||||
expect(logger.info).toHaveBeenLastCalledWith(
|
||||
'App feature [endpoint_agent_tamper_protection] is enabled. Nothing to do!'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('and `agentTamperProtection` is disabled', () => {
|
||||
let policyGenerator: FleetAgentPolicyGenerator;
|
||||
let page1Items: GetAgentPoliciesResponseItem[] = [];
|
||||
let page2Items: GetAgentPoliciesResponseItem[] = [];
|
||||
let page3Items: GetAgentPoliciesResponseItem[] = [];
|
||||
let bulkUpdateResponse: AgentPolicy[];
|
||||
|
||||
const generatePolicyMock = (): GetAgentPoliciesResponseItem => {
|
||||
return policyGenerator.generate({ is_protected: true });
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
policyGenerator = new FleetAgentPolicyGenerator('seed');
|
||||
const agentPolicyListSrv = fleetServices.agentPolicy.list as jest.Mock;
|
||||
|
||||
appFeatureService = createAppFeaturesServiceMock(
|
||||
ALL_APP_FEATURE_KEYS.filter((key) => key !== 'endpoint_agent_tamper_protection')
|
||||
);
|
||||
|
||||
page1Items = [generatePolicyMock(), generatePolicyMock()];
|
||||
page2Items = [generatePolicyMock(), generatePolicyMock()];
|
||||
page3Items = [generatePolicyMock()];
|
||||
|
||||
agentPolicyListSrv
|
||||
.mockImplementationOnce(async () => {
|
||||
return {
|
||||
total: 2500,
|
||||
page: 1,
|
||||
perPage: 1000,
|
||||
items: page1Items,
|
||||
};
|
||||
})
|
||||
.mockImplementationOnce(async () => {
|
||||
return {
|
||||
total: 2500,
|
||||
page: 2,
|
||||
perPage: 1000,
|
||||
items: page2Items,
|
||||
};
|
||||
})
|
||||
.mockImplementationOnce(async () => {
|
||||
return {
|
||||
total: 2500,
|
||||
page: 3,
|
||||
perPage: 1000,
|
||||
items: page3Items,
|
||||
};
|
||||
});
|
||||
|
||||
bulkUpdateResponse = [
|
||||
page1Items[0],
|
||||
page1Items[1],
|
||||
page2Items[0],
|
||||
page2Items[1],
|
||||
page3Items[0],
|
||||
];
|
||||
|
||||
(fleetServices.agentPolicy.bumpRevision as jest.Mock).mockImplementation(async () => {
|
||||
return bulkUpdateResponse;
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should update only policies that have protections turn on', async () => {
|
||||
await callTurnOffAgentPolicyFeatures();
|
||||
|
||||
expect(fleetServices.agentPolicy.list as jest.Mock).toHaveBeenCalledTimes(3);
|
||||
|
||||
const updates = Array.from({ length: 5 }, (_, i) => ({
|
||||
soClient: fleetServices.internalSoClient,
|
||||
esClient,
|
||||
id: bulkUpdateResponse![i].id,
|
||||
}));
|
||||
|
||||
expect(fleetServices.agentPolicy.bumpRevision as jest.Mock).toHaveBeenCalledTimes(5);
|
||||
updates.forEach((args, i) => {
|
||||
expect(fleetServices.agentPolicy.bumpRevision as jest.Mock).toHaveBeenNthCalledWith(
|
||||
i + 1,
|
||||
args.soClient,
|
||||
args.esClient,
|
||||
args.id,
|
||||
{ removeProtection: true, user: { username: 'elastic' } }
|
||||
);
|
||||
});
|
||||
|
||||
expect(logger.info).toHaveBeenCalledWith(
|
||||
'App feature [endpoint_agent_tamper_protection] is disabled. Checking fleet agent policies for compliance'
|
||||
);
|
||||
|
||||
expect(logger.info).toHaveBeenCalledWith(
|
||||
`Found 5 policies that need updates:\n${bulkUpdateResponse!
|
||||
.map(
|
||||
(policy) =>
|
||||
`Policy [${policy.id}][${policy.name}] updated to disable agent tamper protection.`
|
||||
)
|
||||
.join('\n')}`
|
||||
);
|
||||
expect(logger.info).toHaveBeenCalledWith('Done. All updates applied successfully');
|
||||
});
|
||||
|
||||
it('should log failures', async () => {
|
||||
(fleetServices.agentPolicy.bumpRevision as jest.Mock).mockImplementationOnce(async () => {
|
||||
throw new Error('oh noo');
|
||||
});
|
||||
await callTurnOffAgentPolicyFeatures();
|
||||
|
||||
expect(logger.error).toHaveBeenCalledWith(
|
||||
`Done - 1 out of 5 were successful. Errors encountered:\nPolicy [${
|
||||
bulkUpdateResponse![0].id
|
||||
}] failed to update due to error: Error: oh noo`
|
||||
);
|
||||
|
||||
expect(fleetServices.agentPolicy.bumpRevision as jest.Mock).toHaveBeenCalledTimes(5);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
* 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 type { Logger, ElasticsearchClient } from '@kbn/core/server';
|
||||
import type { AgentPolicy } from '@kbn/fleet-plugin/common';
|
||||
import type { AuthenticatedUser } from '@kbn/security-plugin/common';
|
||||
import { AppFeatureSecurityKey } from '@kbn/security-solution-features/keys';
|
||||
import pMap from 'p-map';
|
||||
import type { EndpointInternalFleetServicesInterface } from '../services/fleet';
|
||||
import type { AppFeaturesService } from '../../lib/app_features_service/app_features_service';
|
||||
|
||||
export const turnOffAgentPolicyFeatures = async (
|
||||
esClient: ElasticsearchClient,
|
||||
fleetServices: EndpointInternalFleetServicesInterface,
|
||||
appFeaturesService: AppFeaturesService,
|
||||
logger: Logger
|
||||
): Promise<void> => {
|
||||
const log = logger.get('endpoint', 'agentPolicyFeatures');
|
||||
|
||||
if (appFeaturesService.isEnabled(AppFeatureSecurityKey.endpointAgentTamperProtection)) {
|
||||
log.info(
|
||||
`App feature [${AppFeatureSecurityKey.endpointAgentTamperProtection}] is enabled. Nothing to do!`
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
log.info(
|
||||
`App feature [${AppFeatureSecurityKey.endpointAgentTamperProtection}] is disabled. Checking fleet agent policies for compliance`
|
||||
);
|
||||
|
||||
const { agentPolicy: agentPolicyService, internalSoClient } = fleetServices;
|
||||
const updates: AgentPolicy[] = [];
|
||||
const messages: string[] = [];
|
||||
const perPage = 1000;
|
||||
let hasMoreData = true;
|
||||
let total = 0;
|
||||
let page = 1;
|
||||
|
||||
do {
|
||||
const currentPage = page++;
|
||||
const { items, total: totalPolicies } = await agentPolicyService.list(internalSoClient, {
|
||||
page: currentPage,
|
||||
kuery: 'ingest-agent-policies.is_protected: true',
|
||||
perPage,
|
||||
});
|
||||
|
||||
total = totalPolicies;
|
||||
hasMoreData = currentPage * perPage < total;
|
||||
|
||||
for (const item of items) {
|
||||
messages.push(
|
||||
`Policy [${item.id}][${item.name}] updated to disable agent tamper protection.`
|
||||
);
|
||||
|
||||
updates.push({ ...item, is_protected: false });
|
||||
}
|
||||
} while (hasMoreData);
|
||||
|
||||
if (updates.length > 0) {
|
||||
logger.info(`Found ${updates.length} policies that need updates:\n${messages.join('\n')}`);
|
||||
const policyUpdateErrors: Array<{ id: string; error: Error }> = [];
|
||||
await pMap(updates, async (update) => {
|
||||
try {
|
||||
return await agentPolicyService.bumpRevision(internalSoClient, esClient, update.id, {
|
||||
user: { username: 'elastic' } as AuthenticatedUser,
|
||||
removeProtection: true,
|
||||
});
|
||||
} catch (error) {
|
||||
policyUpdateErrors.push({ error, id: update.id });
|
||||
}
|
||||
});
|
||||
|
||||
if (policyUpdateErrors.length > 0) {
|
||||
logger.error(
|
||||
`Done - ${policyUpdateErrors.length} out of ${
|
||||
updates.length
|
||||
} were successful. Errors encountered:\n${policyUpdateErrors
|
||||
.map((e) => `Policy [${e.id}] failed to update due to error: ${e.error}`)
|
||||
.join('\n')}`
|
||||
);
|
||||
} else {
|
||||
logger.info(`Done. All updates applied successfully`);
|
||||
}
|
||||
} else {
|
||||
logger.info(`Done. Checked ${total} policies and no updates needed`);
|
||||
}
|
||||
};
|
|
@ -24,13 +24,15 @@ import {
|
|||
} from '../../common/endpoint/models/policy_config';
|
||||
import { buildManifestManagerMock } from '../endpoint/services/artifacts/manifest_manager/manifest_manager.mock';
|
||||
import {
|
||||
getAgentPolicyCreateCallback,
|
||||
getAgentPolicyUpdateCallback,
|
||||
getPackagePolicyCreateCallback,
|
||||
getPackagePolicyDeleteCallback,
|
||||
getPackagePolicyPostCreateCallback,
|
||||
getPackagePolicyUpdateCallback,
|
||||
} from './fleet_integration';
|
||||
import type { KibanaRequest } from '@kbn/core/server';
|
||||
import { ALL_APP_FEATURE_KEYS } from '@kbn/security-solution-features/keys';
|
||||
import type { KibanaRequest, Logger } from '@kbn/core/server';
|
||||
import { ALL_APP_FEATURE_KEYS, AppFeatureSecurityKey } from '@kbn/security-solution-features/keys';
|
||||
import { requestContextMock } from '../lib/detection_engine/routes/__mocks__';
|
||||
import { requestContextFactoryMock } from '../request_context_factory.mock';
|
||||
import type { EndpointAppContextServiceStartContract } from '../endpoint/endpoint_app_context_services';
|
||||
|
@ -50,7 +52,10 @@ import { getMockArtifacts, toArtifactRecords } from '../endpoint/lib/artifacts/m
|
|||
import { Manifest } from '../endpoint/lib/artifacts';
|
||||
import type { NewPackagePolicy, PackagePolicy } from '@kbn/fleet-plugin/common/types/models';
|
||||
import type { ManifestSchema } from '../../common/endpoint/schema/manifest';
|
||||
import type { PostDeletePackagePoliciesResponse } from '@kbn/fleet-plugin/common';
|
||||
import type {
|
||||
GetAgentPoliciesResponseItem,
|
||||
PostDeletePackagePoliciesResponse,
|
||||
} from '@kbn/fleet-plugin/common';
|
||||
import { createMockPolicyData } from '../endpoint/services/feature_usage/mocks';
|
||||
import { ALL_ENDPOINT_ARTIFACT_LIST_IDS } from '../../common/endpoint/service/artifacts/constants';
|
||||
import { ENDPOINT_EVENT_FILTERS_LIST_ID } from '@kbn/securitysolution-list-constants';
|
||||
|
@ -58,6 +63,7 @@ import { disableProtections } from '../../common/endpoint/models/policy_config_h
|
|||
import type { AppFeaturesService } from '../lib/app_features_service/app_features_service';
|
||||
import { createAppFeaturesServiceMock } from '../lib/app_features_service/mocks';
|
||||
import * as moment from 'moment';
|
||||
import type { PostAgentPolicyCreateCallback } from '@kbn/fleet-plugin/server/types';
|
||||
|
||||
jest.mock('uuid', () => ({
|
||||
v4: (): string => 'NEW_UUID',
|
||||
|
@ -382,6 +388,115 @@ describe('ingest_integration tests ', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('agent policy update callback', () => {
|
||||
it('AppFeature disabled - returns an error if higher tier features are turned on in the policy', async () => {
|
||||
const logger = loggingSystemMock.create().get('ingest_integration.test');
|
||||
|
||||
appFeaturesService = createAppFeaturesServiceMock(
|
||||
ALL_APP_FEATURE_KEYS.filter(
|
||||
(key) => key !== AppFeatureSecurityKey.endpointAgentTamperProtection
|
||||
)
|
||||
);
|
||||
const callback = getAgentPolicyUpdateCallback(logger, appFeaturesService);
|
||||
|
||||
const policyConfig = generator.generateAgentPolicy();
|
||||
policyConfig.is_protected = true;
|
||||
|
||||
await expect(() => callback(policyConfig)).rejects.toThrow(
|
||||
'Agent Tamper Protection is not allowed in current environment'
|
||||
);
|
||||
});
|
||||
it('AppFeature disabled - returns agent policy if higher tier features are turned off in the policy', async () => {
|
||||
const logger = loggingSystemMock.create().get('ingest_integration.test');
|
||||
|
||||
appFeaturesService = createAppFeaturesServiceMock(
|
||||
ALL_APP_FEATURE_KEYS.filter(
|
||||
(key) => key !== AppFeatureSecurityKey.endpointAgentTamperProtection
|
||||
)
|
||||
);
|
||||
const callback = getAgentPolicyUpdateCallback(logger, appFeaturesService);
|
||||
|
||||
const policyConfig = generator.generateAgentPolicy();
|
||||
|
||||
const updatedPolicyConfig = await callback(policyConfig);
|
||||
|
||||
expect(updatedPolicyConfig).toEqual(policyConfig);
|
||||
});
|
||||
it('AppFeature enabled - returns agent policy if higher tier features are turned on in the policy', async () => {
|
||||
const logger = loggingSystemMock.create().get('ingest_integration.test');
|
||||
|
||||
const callback = getAgentPolicyUpdateCallback(logger, appFeaturesService);
|
||||
|
||||
const policyConfig = generator.generateAgentPolicy();
|
||||
policyConfig.is_protected = true;
|
||||
|
||||
const updatedPolicyConfig = await callback(policyConfig);
|
||||
|
||||
expect(updatedPolicyConfig).toEqual(policyConfig);
|
||||
});
|
||||
it('AppFeature enabled - returns agent policy if higher tier features are turned off in the policy', async () => {
|
||||
const logger = loggingSystemMock.create().get('ingest_integration.test');
|
||||
|
||||
const callback = getAgentPolicyUpdateCallback(logger, appFeaturesService);
|
||||
const policyConfig = generator.generateAgentPolicy();
|
||||
|
||||
const updatedPolicyConfig = await callback(policyConfig);
|
||||
|
||||
expect(updatedPolicyConfig).toEqual(policyConfig);
|
||||
});
|
||||
});
|
||||
|
||||
describe('agent policy create callback', () => {
|
||||
let logger: Logger;
|
||||
let callback: PostAgentPolicyCreateCallback;
|
||||
let policyConfig: GetAgentPoliciesResponseItem;
|
||||
|
||||
beforeEach(() => {
|
||||
logger = loggingSystemMock.create().get('ingest_integration.test');
|
||||
callback = getAgentPolicyCreateCallback(logger, appFeaturesService);
|
||||
policyConfig = generator.generateAgentPolicy();
|
||||
});
|
||||
|
||||
it('AppFeature disabled - returns an error if higher tier features are turned on in the policy', async () => {
|
||||
appFeaturesService = createAppFeaturesServiceMock(
|
||||
ALL_APP_FEATURE_KEYS.filter(
|
||||
(key) => key !== AppFeatureSecurityKey.endpointAgentTamperProtection
|
||||
)
|
||||
);
|
||||
callback = getAgentPolicyCreateCallback(logger, appFeaturesService);
|
||||
policyConfig.is_protected = true;
|
||||
|
||||
await expect(() => callback(policyConfig)).rejects.toThrow(
|
||||
'Agent Tamper Protection is not allowed in current environment'
|
||||
);
|
||||
});
|
||||
|
||||
it('AppFeature disabled - returns agent policy if higher tier features are turned off in the policy', async () => {
|
||||
appFeaturesService = createAppFeaturesServiceMock(
|
||||
ALL_APP_FEATURE_KEYS.filter(
|
||||
(key) => key !== AppFeatureSecurityKey.endpointAgentTamperProtection
|
||||
)
|
||||
);
|
||||
callback = getAgentPolicyCreateCallback(logger, appFeaturesService);
|
||||
const updatedPolicyConfig = await callback(policyConfig);
|
||||
|
||||
expect(updatedPolicyConfig).toEqual(policyConfig);
|
||||
});
|
||||
|
||||
it('AppFeature enabled - returns agent policy if higher tier features are turned on in the policy', async () => {
|
||||
policyConfig.is_protected = true;
|
||||
const updatedPolicyConfig = await callback(policyConfig);
|
||||
|
||||
expect(updatedPolicyConfig).toEqual(policyConfig);
|
||||
});
|
||||
|
||||
it('AppFeature enabled - returns agent policy if higher tier features are turned off in the policy', async () => {
|
||||
const updatedPolicyConfig = await callback(policyConfig);
|
||||
|
||||
expect(updatedPolicyConfig).toEqual(policyConfig);
|
||||
});
|
||||
});
|
||||
|
||||
describe('package policy update callback (when the license is below platinum)', () => {
|
||||
const soClient = savedObjectsClientMock.create();
|
||||
const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
|
||||
|
|
|
@ -16,6 +16,8 @@ import type {
|
|||
} from '@kbn/fleet-plugin/server';
|
||||
|
||||
import type {
|
||||
AgentPolicy,
|
||||
NewAgentPolicy,
|
||||
NewPackagePolicy,
|
||||
PackagePolicy,
|
||||
UpdatePackagePolicy,
|
||||
|
@ -23,6 +25,10 @@ import type {
|
|||
import type { CloudSetup } from '@kbn/cloud-plugin/server';
|
||||
import type { InfoResponse } from '@elastic/elasticsearch/lib/api/types';
|
||||
import { AppFeatureSecurityKey } from '@kbn/security-solution-features/keys';
|
||||
import type {
|
||||
PostAgentPolicyCreateCallback,
|
||||
PostAgentPolicyUpdateCallback,
|
||||
} from '@kbn/fleet-plugin/server/types';
|
||||
import { validatePolicyAgainstAppFeatures } from './handlers/validate_policy_against_app_features';
|
||||
import { validateEndpointPackagePolicy } from './handlers/validate_endpoint_package_policy';
|
||||
import {
|
||||
|
@ -291,6 +297,54 @@ export const getPackagePolicyPostCreateCallback = (
|
|||
};
|
||||
};
|
||||
|
||||
const throwAgentTamperProtectionUnavailableError = (
|
||||
logger: Logger,
|
||||
policyName?: string,
|
||||
policyId?: string
|
||||
): void => {
|
||||
const agentTamperProtectionUnavailableError: Error & {
|
||||
statusCode?: number;
|
||||
apiPassThrough?: boolean;
|
||||
} = new Error('Agent Tamper Protection is not allowed in current environment');
|
||||
// Agent Policy Service will check for apiPassThrough and rethrow. Route handler will check for statusCode and overwrite.
|
||||
agentTamperProtectionUnavailableError.statusCode = 403;
|
||||
agentTamperProtectionUnavailableError.apiPassThrough = true;
|
||||
logger.error(
|
||||
`Policy [${policyName}:${policyId}] error: Agent Tamper Protection requires Complete Endpoint Security tier`
|
||||
);
|
||||
throw agentTamperProtectionUnavailableError;
|
||||
};
|
||||
|
||||
export const getAgentPolicyCreateCallback = (
|
||||
logger: Logger,
|
||||
appFeatures: AppFeaturesService
|
||||
): PostAgentPolicyCreateCallback => {
|
||||
return async (agentPolicy: NewAgentPolicy): Promise<NewAgentPolicy> => {
|
||||
if (
|
||||
agentPolicy.is_protected &&
|
||||
!appFeatures.isEnabled(AppFeatureSecurityKey.endpointAgentTamperProtection)
|
||||
) {
|
||||
throwAgentTamperProtectionUnavailableError(logger, agentPolicy.name, agentPolicy.id);
|
||||
}
|
||||
return agentPolicy;
|
||||
};
|
||||
};
|
||||
|
||||
export const getAgentPolicyUpdateCallback = (
|
||||
logger: Logger,
|
||||
appFeatures: AppFeaturesService
|
||||
): PostAgentPolicyUpdateCallback => {
|
||||
return async (agentPolicy: Partial<AgentPolicy>): Promise<Partial<AgentPolicy>> => {
|
||||
if (
|
||||
agentPolicy.is_protected &&
|
||||
!appFeatures.isEnabled(AppFeatureSecurityKey.endpointAgentTamperProtection)
|
||||
) {
|
||||
throwAgentTamperProtectionUnavailableError(logger, agentPolicy.name, agentPolicy.id);
|
||||
}
|
||||
return agentPolicy;
|
||||
};
|
||||
};
|
||||
|
||||
export const getPackagePolicyDeleteCallback = (
|
||||
exceptionsClient: ExceptionListClient | undefined,
|
||||
savedObjectsClient: SavedObjectsClientContract | undefined
|
||||
|
|
|
@ -115,6 +115,7 @@ import {
|
|||
} from '../common/entity_analytics/risk_engine';
|
||||
import { isEndpointPackageV2 } from '../common/endpoint/utils/package_v2';
|
||||
import { getAssistantTools } from './assistant/tools';
|
||||
import { turnOffAgentPolicyFeatures } from './endpoint/migrations/turn_off_agent_policy_features';
|
||||
|
||||
export type { SetupPlugins, StartPlugins, PluginSetup, PluginStart } from './plugin_contract';
|
||||
|
||||
|
@ -555,6 +556,13 @@ export class Plugin implements ISecuritySolutionPlugin {
|
|||
appFeaturesService,
|
||||
logger
|
||||
);
|
||||
|
||||
turnOffAgentPolicyFeatures(
|
||||
core.elasticsearch.client.asInternalUser,
|
||||
endpointFleetServicesFactory.asInternalUser(),
|
||||
appFeaturesService,
|
||||
logger
|
||||
);
|
||||
});
|
||||
|
||||
// License related start
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue