Skip to content

Commit

Permalink
[EDR Workflows][API] Gate Agent Tamper Protection setting on Agent Po…
Browse files Browse the repository at this point in the history
…licy Settings (elastic#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:

elastic#174278
elastic#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 <[email protected]>
  • Loading branch information
2 people authored and fkanout committed Feb 7, 2024
1 parent 836e7e4 commit b631a2a
Show file tree
Hide file tree
Showing 18 changed files with 742 additions and 36 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/fleet/server/mocks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ export const createMockAgentPolicyService = (): jest.Mocked<AgentPolicyServiceIn
list: jest.fn(),
getFullAgentPolicy: jest.fn(),
getByIds: jest.fn(),
bumpRevision: jest.fn(),
};
};

Expand Down
51 changes: 25 additions & 26 deletions x-pack/plugins/fleet/server/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
SavedObjectsClientContract,
SavedObjectsServiceStart,
HttpServiceSetup,
KibanaRequest,
ServiceStatus,
ElasticsearchClient,
SavedObjectsClientContract,
} 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,
Expand All @@ -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 {
agentPolicyService,
AgentServiceImpl,
appContextService,
licenseService,
ESIndexPatternSavedObjectService,
agentPolicyService,
FleetUsageSender,
licenseService,
packagePolicyService,
AgentServiceImpl,
PackageServiceImpl,
} from './services';
import {
registerFleetUsageCollector,
fetchAgentsUsage,
fetchFleetUsage,
registerFleetUsageCollector,
} from './collectors/register';
import { FleetArtifactsClient } from './services/artifacts';
import type { FleetRouter } from './types/request_context';
Expand Down Expand Up @@ -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]) => {
Expand Down
12 changes: 12 additions & 0 deletions x-pack/plugins/fleet/server/routes/agent_policy/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 });
}
};
Expand Down Expand Up @@ -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 });
}
};
Expand Down
51 changes: 49 additions & 2 deletions x-pack/plugins/fleet/server/services/agent_policy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ import type {
FullAgentPolicy,
ListWithKuery,
NewPackagePolicy,
PostAgentPolicyCreateCallback,
PostAgentPolicyUpdateCallback,
ExternalCallback,
} from '../types';
import {
getAllowedOutputTypeForPolicy,
Expand Down Expand Up @@ -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,
Expand All @@ -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();
Expand Down Expand Up @@ -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);

Expand Down
14 changes: 12 additions & 2 deletions x-pack/plugins/fleet/server/services/app_context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ import type {
PostPackagePolicyPostDeleteCallback,
PostPackagePolicyPostCreateCallback,
PutPackagePolicyUpdateCallback,
PostAgentPolicyCreateCallback,
PostAgentPolicyUpdateCallback,
} from '../types';
import type { FleetAppContext } from '../plugin';
import type { TelemetryEventsSender } from '../telemetry/sender';
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/fleet/server/services/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,7 @@ jest.mock('./app_context', () => ({
getUninstallTokenService: () => ({
generateTokenForPolicyId: jest.fn(),
}),
getExternalCallbacks: jest.fn(),
},
}));

Expand Down
23 changes: 22 additions & 1 deletion x-pack/plugins/fleet/server/types/extensions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import type {
UpdatePackagePolicy,
PackagePolicy,
DeletePackagePoliciesResponse,
NewAgentPolicy,
AgentPolicy,
} from '../../common/types';

export type PostPackagePolicyDeleteCallback = (
Expand Down Expand Up @@ -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',
Expand All @@ -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
*/
Expand All @@ -79,6 +98,8 @@ export type ExternalCallback =
| ExternalCallbackPostCreate
| ExternalCallbackDelete
| ExternalCallbackPostDelete
| ExternalCallbackUpdate;
| ExternalCallbackUpdate
| ExternalCallbackAgentPolicyCreate
| ExternalCallbackAgentPolicyUpdate;

export type ExternalCallbacksStorage = Map<ExternalCallback[0], Set<ExternalCallback[1]>>;
Loading

0 comments on commit b631a2a

Please sign in to comment.