[Fleet] Use fleet server indices for enrollment keys and to list agents with a feature flag (#86179)
This commit is contained in:
parent
25f16db4d9
commit
3b728b73cf
|
@ -22,3 +22,5 @@ export const AGENT_UPDATE_ACTIONS_INTERVAL_MS = 5000;
|
|||
|
||||
export const AGENT_POLICY_ROLLOUT_RATE_LIMIT_INTERVAL_MS = 1000;
|
||||
export const AGENT_POLICY_ROLLOUT_RATE_LIMIT_REQUEST_PER_INTERVAL = 5;
|
||||
|
||||
export const AGENTS_INDEX = '.fleet-agents';
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
import { defaultPackages } from './epm';
|
||||
import { AgentPolicy } from '../types';
|
||||
export const AGENT_POLICY_SAVED_OBJECT_TYPE = 'ingest-agent-policies';
|
||||
|
||||
export const AGENT_POLICY_INDEX = '.fleet-policies';
|
||||
export const agentPolicyStatuses = {
|
||||
Active: 'active',
|
||||
Inactive: 'inactive',
|
||||
|
|
|
@ -5,3 +5,5 @@
|
|||
*/
|
||||
|
||||
export const ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE = 'fleet-enrollment-api-keys';
|
||||
|
||||
export const ENROLLMENT_API_KEYS_INDEX = '.fleet-enrollment-api-keys';
|
||||
|
|
|
@ -41,7 +41,7 @@ export function getAgentStatus(agent: Agent, now: number = Date.now()): AgentSta
|
|||
}
|
||||
|
||||
export function buildKueryForEnrollingAgents() {
|
||||
return `not ${AGENT_SAVED_OBJECT_TYPE}.last_checkin:*`;
|
||||
return `not (${AGENT_SAVED_OBJECT_TYPE}.last_checkin:*)`;
|
||||
}
|
||||
|
||||
export function buildKueryForUnenrollingAgents() {
|
||||
|
@ -53,7 +53,7 @@ export function buildKueryForOnlineAgents() {
|
|||
}
|
||||
|
||||
export function buildKueryForErrorAgents() {
|
||||
return `( ${AGENT_SAVED_OBJECT_TYPE}.last_checkin_status:error or ${AGENT_SAVED_OBJECT_TYPE}.last_checkin_status:degraded )`;
|
||||
return `${AGENT_SAVED_OBJECT_TYPE}.last_checkin_status:error or ${AGENT_SAVED_OBJECT_TYPE}.last_checkin_status:degraded`;
|
||||
}
|
||||
|
||||
export function buildKueryForOfflineAgents() {
|
||||
|
|
|
@ -11,6 +11,7 @@ export interface FleetConfigType {
|
|||
registryUrl?: string;
|
||||
registryProxyUrl?: string;
|
||||
agents: {
|
||||
fleetServerEnabled: boolean;
|
||||
enabled: boolean;
|
||||
tlsCheckDisabled: boolean;
|
||||
pollingRequestTimeout: number;
|
||||
|
|
|
@ -80,3 +80,37 @@ export interface FullAgentPolicyKibanaConfig {
|
|||
protocol: string;
|
||||
path?: string;
|
||||
}
|
||||
|
||||
// Generated from Fleet Server schema.json
|
||||
|
||||
/**
|
||||
* A policy that an Elastic Agent is attached to
|
||||
*/
|
||||
export interface FleetServerPolicy {
|
||||
/**
|
||||
* Date/time the policy revision was created
|
||||
*/
|
||||
'@timestamp'?: string;
|
||||
/**
|
||||
* The ID of the policy
|
||||
*/
|
||||
policy_id: string;
|
||||
/**
|
||||
* The revision index of the policy
|
||||
*/
|
||||
revision_idx: number;
|
||||
/**
|
||||
* The coordinator index of the policy
|
||||
*/
|
||||
coordinator_idx: number;
|
||||
/**
|
||||
* The opaque payload.
|
||||
*/
|
||||
data: {
|
||||
[k: string]: unknown;
|
||||
};
|
||||
/**
|
||||
* True when this policy is the default policy to start Fleet Server
|
||||
*/
|
||||
default_fleet_server: boolean;
|
||||
}
|
||||
|
|
|
@ -15,3 +15,31 @@ export interface EnrollmentAPIKey {
|
|||
}
|
||||
|
||||
export type EnrollmentAPIKeySOAttributes = Omit<EnrollmentAPIKey, 'id'>;
|
||||
|
||||
// Generated
|
||||
|
||||
/**
|
||||
* An Elastic Agent enrollment API key
|
||||
*/
|
||||
export interface FleetServerEnrollmentAPIKey {
|
||||
/**
|
||||
* True when the key is active
|
||||
*/
|
||||
active?: boolean;
|
||||
/**
|
||||
* The unique identifier for the enrollment key, currently xid
|
||||
*/
|
||||
api_key_id: string;
|
||||
/**
|
||||
* Api key
|
||||
*/
|
||||
api_key: string;
|
||||
/**
|
||||
* Enrollment key name
|
||||
*/
|
||||
name?: string;
|
||||
policy_id?: string;
|
||||
expire_at?: string;
|
||||
created_at?: string;
|
||||
updated_at?: string;
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ export const createConfigurationMock = (): FleetConfigType => {
|
|||
registryProxyUrl: '',
|
||||
agents: {
|
||||
enabled: true,
|
||||
fleetServerEnabled: false,
|
||||
tlsCheckDisabled: true,
|
||||
pollingRequestTimeout: 1000,
|
||||
maxConcurrentConnections: 100,
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { SavedObjectsClient } from 'kibana/server';
|
||||
import { ElasticsearchClient, SavedObjectsClient } from 'kibana/server';
|
||||
import * as AgentService from '../services/agents';
|
||||
export interface AgentUsage {
|
||||
total: number;
|
||||
|
@ -13,9 +13,12 @@ export interface AgentUsage {
|
|||
offline: number;
|
||||
}
|
||||
|
||||
export const getAgentUsage = async (soClient?: SavedObjectsClient): Promise<AgentUsage> => {
|
||||
export const getAgentUsage = async (
|
||||
soClient?: SavedObjectsClient,
|
||||
esClient?: ElasticsearchClient
|
||||
): Promise<AgentUsage> => {
|
||||
// TODO: unsure if this case is possible at all.
|
||||
if (!soClient) {
|
||||
if (!soClient || !esClient) {
|
||||
return {
|
||||
total: 0,
|
||||
online: 0,
|
||||
|
@ -24,7 +27,8 @@ export const getAgentUsage = async (soClient?: SavedObjectsClient): Promise<Agen
|
|||
};
|
||||
}
|
||||
const { total, online, error, offline } = await AgentService.getAgentStatusForAgentPolicy(
|
||||
soClient
|
||||
soClient,
|
||||
esClient
|
||||
);
|
||||
return {
|
||||
total,
|
||||
|
|
|
@ -5,11 +5,14 @@
|
|||
*/
|
||||
|
||||
import { CoreSetup } from 'kibana/server';
|
||||
import { SavedObjectsClient } from '../../../../../src/core/server';
|
||||
import { ElasticsearchClient, SavedObjectsClient } from '../../../../../src/core/server';
|
||||
|
||||
export async function getInternalSavedObjectsClient(core: CoreSetup) {
|
||||
export async function getInternalClients(
|
||||
core: CoreSetup
|
||||
): Promise<[SavedObjectsClient, ElasticsearchClient]> {
|
||||
return core.getStartServices().then(async ([coreStart]) => {
|
||||
const savedObjectsRepo = coreStart.savedObjects.createInternalRepository();
|
||||
return new SavedObjectsClient(savedObjectsRepo);
|
||||
const esClient = coreStart.elasticsearch.client.asInternalUser;
|
||||
return [new SavedObjectsClient(savedObjectsRepo), esClient];
|
||||
});
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ import { UsageCollectionSetup } from 'src/plugins/usage_collection/server';
|
|||
import { CoreSetup } from 'kibana/server';
|
||||
import { getIsAgentsEnabled } from './config_collectors';
|
||||
import { AgentUsage, getAgentUsage } from './agent_collectors';
|
||||
import { getInternalSavedObjectsClient } from './helpers';
|
||||
import { getInternalClients } from './helpers';
|
||||
import { PackageUsage, getPackageUsage } from './package_collectors';
|
||||
import { FleetConfigType } from '..';
|
||||
|
||||
|
@ -34,10 +34,10 @@ export function registerFleetUsageCollector(
|
|||
type: 'fleet',
|
||||
isReady: () => true,
|
||||
fetch: async () => {
|
||||
const soClient = await getInternalSavedObjectsClient(core);
|
||||
const [soClient, esClient] = await getInternalClients(core);
|
||||
return {
|
||||
agents_enabled: getIsAgentsEnabled(config),
|
||||
agents: await getAgentUsage(soClient),
|
||||
agents: await getAgentUsage(soClient, esClient),
|
||||
packages: await getPackageUsage(soClient),
|
||||
};
|
||||
},
|
||||
|
|
|
@ -47,4 +47,7 @@ export {
|
|||
// Defaults
|
||||
DEFAULT_AGENT_POLICY,
|
||||
DEFAULT_OUTPUT,
|
||||
// Fleet Server index
|
||||
ENROLLMENT_API_KEYS_INDEX,
|
||||
AGENTS_INDEX,
|
||||
} from '../../common';
|
||||
|
|
|
@ -37,6 +37,7 @@ export const config: PluginConfigDescriptor = {
|
|||
registryProxyUrl: schema.maybe(schema.uri({ scheme: ['http', 'https'] })),
|
||||
agents: schema.object({
|
||||
enabled: schema.boolean({ defaultValue: true }),
|
||||
fleetServerEnabled: schema.boolean({ defaultValue: false }),
|
||||
tlsCheckDisabled: schema.boolean({ defaultValue: false }),
|
||||
pollingRequestTimeout: schema.number({
|
||||
defaultValue: AGENT_POLLING_REQUEST_TIMEOUT_MS,
|
||||
|
|
|
@ -4,7 +4,11 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { loggingSystemMock, savedObjectsServiceMock } from 'src/core/server/mocks';
|
||||
import {
|
||||
elasticsearchServiceMock,
|
||||
loggingSystemMock,
|
||||
savedObjectsServiceMock,
|
||||
} from 'src/core/server/mocks';
|
||||
import { FleetAppContext } from './plugin';
|
||||
import { encryptedSavedObjectsMock } from '../../encrypted_saved_objects/server/mocks';
|
||||
import { securityMock } from '../../security/server/mocks';
|
||||
|
@ -13,6 +17,7 @@ import { AgentPolicyServiceInterface, AgentService } from './services';
|
|||
|
||||
export const createAppContextStartContractMock = (): FleetAppContext => {
|
||||
return {
|
||||
elasticsearch: elasticsearchServiceMock.createStart(),
|
||||
encryptedSavedObjectsStart: encryptedSavedObjectsMock.createStart(),
|
||||
savedObjects: savedObjectsServiceMock.createStartContract(),
|
||||
security: securityMock.createStart(),
|
||||
|
|
|
@ -8,6 +8,7 @@ import { first } from 'rxjs/operators';
|
|||
import {
|
||||
CoreSetup,
|
||||
CoreStart,
|
||||
ElasticsearchServiceStart,
|
||||
Logger,
|
||||
Plugin,
|
||||
PluginInitializerContext,
|
||||
|
@ -80,6 +81,7 @@ import { agentCheckinState } from './services/agents/checkin/state';
|
|||
import { registerFleetUsageCollector } from './collectors/register';
|
||||
import { getInstallation } from './services/epm/packages';
|
||||
import { makeRouterEnforcingSuperuser } from './routes/security';
|
||||
import { runFleetServerMigration } from './services/fleet_server_migration';
|
||||
|
||||
export interface FleetSetupDeps {
|
||||
licensing: LicensingPluginSetup;
|
||||
|
@ -96,6 +98,7 @@ export interface FleetStartDeps {
|
|||
}
|
||||
|
||||
export interface FleetAppContext {
|
||||
elasticsearch: ElasticsearchServiceStart;
|
||||
encryptedSavedObjectsStart?: EncryptedSavedObjectsPluginStart;
|
||||
encryptedSavedObjectsSetup?: EncryptedSavedObjectsPluginSetup;
|
||||
security?: SecurityPluginStart;
|
||||
|
@ -276,6 +279,7 @@ export class FleetPlugin
|
|||
|
||||
public async start(core: CoreStart, plugins: FleetStartDeps): Promise<FleetStartContract> {
|
||||
await appContextService.start({
|
||||
elasticsearch: core.elasticsearch,
|
||||
encryptedSavedObjectsStart: plugins.encryptedSavedObjects,
|
||||
encryptedSavedObjectsSetup: this.encryptedSavedObjectsSetup,
|
||||
security: plugins.security,
|
||||
|
@ -291,6 +295,13 @@ export class FleetPlugin
|
|||
licenseService.start(this.licensing$);
|
||||
agentCheckinState.start();
|
||||
|
||||
const fleetServerEnabled = appContextService.getConfig()?.agents?.fleetServerEnabled;
|
||||
if (fleetServerEnabled) {
|
||||
// We need licence to be initialized before using the SO service.
|
||||
await this.licensing$.pipe(first()).toPromise();
|
||||
await runFleetServerMigration();
|
||||
}
|
||||
|
||||
return {
|
||||
esIndexPatternService: new ESIndexPatternSavedObjectService(),
|
||||
packageService: {
|
||||
|
|
|
@ -6,11 +6,16 @@
|
|||
|
||||
import { postAgentAcksHandlerBuilder } from './acks_handlers';
|
||||
import {
|
||||
ElasticsearchClient,
|
||||
KibanaResponseFactory,
|
||||
RequestHandlerContext,
|
||||
SavedObjectsClientContract,
|
||||
} from 'kibana/server';
|
||||
import { httpServerMock, savedObjectsClientMock } from '../../../../../../src/core/server/mocks';
|
||||
import {
|
||||
elasticsearchServiceMock,
|
||||
httpServerMock,
|
||||
savedObjectsClientMock,
|
||||
} from '../../../../../../src/core/server/mocks';
|
||||
import { PostAgentAcksResponse } from '../../../common/types/rest_spec';
|
||||
import { AckEventSchema } from '../../types/models';
|
||||
import { AcksService } from '../../services/agents';
|
||||
|
@ -45,9 +50,11 @@ describe('test acks schema', () => {
|
|||
describe('test acks handlers', () => {
|
||||
let mockResponse: jest.Mocked<KibanaResponseFactory>;
|
||||
let mockSavedObjectsClient: jest.Mocked<SavedObjectsClientContract>;
|
||||
let mockElasticsearchClient: jest.Mocked<ElasticsearchClient>;
|
||||
|
||||
beforeEach(() => {
|
||||
mockSavedObjectsClient = savedObjectsClientMock.create();
|
||||
mockElasticsearchClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
|
||||
mockResponse = httpServerMock.createResponseFactory();
|
||||
});
|
||||
|
||||
|
@ -81,6 +88,7 @@ describe('test acks handlers', () => {
|
|||
id: 'agent',
|
||||
}),
|
||||
getSavedObjectsClientContract: jest.fn().mockReturnValueOnce(mockSavedObjectsClient),
|
||||
getElasticsearchClientContract: jest.fn().mockReturnValueOnce(mockElasticsearchClient),
|
||||
saveAgentEvents: jest.fn(),
|
||||
} as jest.Mocked<AcksService>;
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ export const postAgentAcksHandlerBuilder = function (
|
|||
return async (context, request, response) => {
|
||||
try {
|
||||
const soClient = ackService.getSavedObjectsClientContract(request);
|
||||
const esClient = ackService.getElasticsearchClientContract();
|
||||
const agent = await ackService.authenticateAgentWithAccessToken(soClient, request);
|
||||
const agentEvents = request.body.events as AgentEvent[];
|
||||
|
||||
|
@ -33,7 +34,12 @@ export const postAgentAcksHandlerBuilder = function (
|
|||
});
|
||||
}
|
||||
|
||||
const agentActions = await ackService.acknowledgeAgentActions(soClient, agent, agentEvents);
|
||||
const agentActions = await ackService.acknowledgeAgentActions(
|
||||
soClient,
|
||||
esClient,
|
||||
agent,
|
||||
agentEvents
|
||||
);
|
||||
|
||||
if (agentActions.length > 0) {
|
||||
await ackService.saveAgentEvents(soClient, agentEvents);
|
||||
|
|
|
@ -6,11 +6,16 @@
|
|||
|
||||
import { NewAgentActionSchema } from '../../types/models';
|
||||
import {
|
||||
ElasticsearchClient,
|
||||
KibanaResponseFactory,
|
||||
RequestHandlerContext,
|
||||
SavedObjectsClientContract,
|
||||
} from 'kibana/server';
|
||||
import { savedObjectsClientMock, httpServerMock } from 'src/core/server/mocks';
|
||||
import {
|
||||
elasticsearchServiceMock,
|
||||
savedObjectsClientMock,
|
||||
httpServerMock,
|
||||
} from 'src/core/server/mocks';
|
||||
import { ActionsService } from '../../services/agents';
|
||||
import { AgentAction } from '../../../common/types/models';
|
||||
import { postNewAgentActionHandlerBuilder } from './actions_handlers';
|
||||
|
@ -41,9 +46,11 @@ describe('test actions handlers schema', () => {
|
|||
describe('test actions handlers', () => {
|
||||
let mockResponse: jest.Mocked<KibanaResponseFactory>;
|
||||
let mockSavedObjectsClient: jest.Mocked<SavedObjectsClientContract>;
|
||||
let mockElasticsearchClient: jest.Mocked<ElasticsearchClient>;
|
||||
|
||||
beforeEach(() => {
|
||||
mockSavedObjectsClient = savedObjectsClientMock.create();
|
||||
mockElasticsearchClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
|
||||
mockResponse = httpServerMock.createResponseFactory();
|
||||
});
|
||||
|
||||
|
@ -84,6 +91,11 @@ describe('test actions handlers', () => {
|
|||
savedObjects: {
|
||||
client: mockSavedObjectsClient,
|
||||
},
|
||||
elasticsearch: {
|
||||
client: {
|
||||
asInternalUser: mockElasticsearchClient,
|
||||
},
|
||||
},
|
||||
},
|
||||
} as unknown) as RequestHandlerContext,
|
||||
mockRequest,
|
||||
|
|
|
@ -23,8 +23,9 @@ export const postNewAgentActionHandlerBuilder = function (
|
|||
return async (context, request, response) => {
|
||||
try {
|
||||
const soClient = context.core.savedObjects.client;
|
||||
const esClient = context.core.elasticsearch.client.asInternalUser;
|
||||
|
||||
const agent = await actionsService.getAgent(soClient, request.params.agentId);
|
||||
const agent = await actionsService.getAgent(soClient, esClient, request.params.agentId);
|
||||
|
||||
const newAgentAction = request.body.action;
|
||||
|
||||
|
|
|
@ -39,8 +39,10 @@ export const getAgentHandler: RequestHandler<
|
|||
TypeOf<typeof GetOneAgentRequestSchema.params>
|
||||
> = async (context, request, response) => {
|
||||
const soClient = context.core.savedObjects.client;
|
||||
const esClient = context.core.elasticsearch.client.asCurrentUser;
|
||||
|
||||
try {
|
||||
const agent = await AgentService.getAgent(soClient, request.params.agentId);
|
||||
const agent = await AgentService.getAgent(soClient, esClient, request.params.agentId);
|
||||
|
||||
const body: GetOneAgentResponse = {
|
||||
item: {
|
||||
|
@ -98,8 +100,10 @@ export const deleteAgentHandler: RequestHandler<
|
|||
TypeOf<typeof DeleteAgentRequestSchema.params>
|
||||
> = async (context, request, response) => {
|
||||
const soClient = context.core.savedObjects.client;
|
||||
const esClient = context.core.elasticsearch.client.asCurrentUser;
|
||||
|
||||
try {
|
||||
await AgentService.deleteAgent(soClient, request.params.agentId);
|
||||
await AgentService.deleteAgent(soClient, esClient, request.params.agentId);
|
||||
|
||||
const body = {
|
||||
action: 'deleted',
|
||||
|
@ -124,11 +128,13 @@ export const updateAgentHandler: RequestHandler<
|
|||
TypeOf<typeof UpdateAgentRequestSchema.body>
|
||||
> = async (context, request, response) => {
|
||||
const soClient = context.core.savedObjects.client;
|
||||
const esClient = context.core.elasticsearch.client.asCurrentUser;
|
||||
|
||||
try {
|
||||
await AgentService.updateAgent(soClient, request.params.agentId, {
|
||||
userProvidedMetatada: request.body.user_provided_metadata,
|
||||
});
|
||||
const agent = await AgentService.getAgent(soClient, request.params.agentId);
|
||||
const agent = await AgentService.getAgent(soClient, esClient, request.params.agentId);
|
||||
|
||||
const body = {
|
||||
item: {
|
||||
|
@ -156,6 +162,7 @@ export const postAgentCheckinHandler: RequestHandler<
|
|||
> = async (context, request, response) => {
|
||||
try {
|
||||
const soClient = appContextService.getInternalUserSOClient(request);
|
||||
const esClient = appContextService.getInternalUserESClient();
|
||||
const agent = await AgentService.authenticateAgentWithAccessToken(soClient, request);
|
||||
const abortController = new AbortController();
|
||||
request.events.aborted$.subscribe(() => {
|
||||
|
@ -164,6 +171,7 @@ export const postAgentCheckinHandler: RequestHandler<
|
|||
const signal = abortController.signal;
|
||||
const { actions } = await AgentService.agentCheckin(
|
||||
soClient,
|
||||
esClient,
|
||||
agent,
|
||||
{
|
||||
events: request.body.events || [],
|
||||
|
@ -234,8 +242,10 @@ export const getAgentsHandler: RequestHandler<
|
|||
TypeOf<typeof GetAgentsRequestSchema.query>
|
||||
> = async (context, request, response) => {
|
||||
const soClient = context.core.savedObjects.client;
|
||||
const esClient = context.core.elasticsearch.client.asCurrentUser;
|
||||
|
||||
try {
|
||||
const { agents, total, page, perPage } = await AgentService.listAgents(soClient, {
|
||||
const { agents, total, page, perPage } = await AgentService.listAgents(soClient, esClient, {
|
||||
page: request.query.page,
|
||||
perPage: request.query.perPage,
|
||||
showInactive: request.query.showInactive,
|
||||
|
@ -243,7 +253,7 @@ export const getAgentsHandler: RequestHandler<
|
|||
kuery: request.query.kuery,
|
||||
});
|
||||
const totalInactive = request.query.showInactive
|
||||
? await AgentService.countInactiveAgents(soClient, {
|
||||
? await AgentService.countInactiveAgents(soClient, esClient, {
|
||||
kuery: request.query.kuery,
|
||||
})
|
||||
: 0;
|
||||
|
@ -270,8 +280,14 @@ export const putAgentsReassignHandler: RequestHandler<
|
|||
TypeOf<typeof PutAgentReassignRequestSchema.body>
|
||||
> = async (context, request, response) => {
|
||||
const soClient = context.core.savedObjects.client;
|
||||
const esClient = context.core.elasticsearch.client.asInternalUser;
|
||||
try {
|
||||
await AgentService.reassignAgent(soClient, request.params.agentId, request.body.policy_id);
|
||||
await AgentService.reassignAgent(
|
||||
soClient,
|
||||
esClient,
|
||||
request.params.agentId,
|
||||
request.body.policy_id
|
||||
);
|
||||
|
||||
const body: PutAgentReassignResponse = {};
|
||||
return response.ok({ body });
|
||||
|
@ -293,16 +309,19 @@ export const postBulkAgentsReassignHandler: RequestHandler<
|
|||
}
|
||||
|
||||
const soClient = context.core.savedObjects.client;
|
||||
const esClient = context.core.elasticsearch.client.asInternalUser;
|
||||
try {
|
||||
// Reassign by array of IDs
|
||||
const result = Array.isArray(request.body.agents)
|
||||
? await AgentService.reassignAgents(
|
||||
soClient,
|
||||
esClient,
|
||||
{ agentIds: request.body.agents },
|
||||
request.body.policy_id
|
||||
)
|
||||
: await AgentService.reassignAgents(
|
||||
soClient,
|
||||
esClient,
|
||||
{ kuery: request.body.agents },
|
||||
request.body.policy_id
|
||||
);
|
||||
|
@ -326,10 +345,13 @@ export const getAgentStatusForAgentPolicyHandler: RequestHandler<
|
|||
TypeOf<typeof GetAgentStatusRequestSchema.query>
|
||||
> = async (context, request, response) => {
|
||||
const soClient = context.core.savedObjects.client;
|
||||
const esClient = context.core.elasticsearch.client.asCurrentUser;
|
||||
|
||||
try {
|
||||
// TODO change path
|
||||
const results = await AgentService.getAgentStatusForAgentPolicy(
|
||||
soClient,
|
||||
esClient,
|
||||
request.query.policyId,
|
||||
request.query.kuery
|
||||
);
|
||||
|
|
|
@ -294,6 +294,9 @@ export const registerElasticAgentRoutes = (router: IRouter, config: FleetConfigT
|
|||
getSavedObjectsClientContract: appContextService.getInternalUserSOClient.bind(
|
||||
appContextService
|
||||
),
|
||||
getElasticsearchClientContract: appContextService.getInternalUserESClient.bind(
|
||||
appContextService
|
||||
),
|
||||
saveAgentEvents: AgentService.saveAgentEvents,
|
||||
})
|
||||
);
|
||||
|
@ -313,6 +316,9 @@ export const registerElasticAgentRoutes = (router: IRouter, config: FleetConfigT
|
|||
getSavedObjectsClientContract: appContextService.getInternalUserSOClient.bind(
|
||||
appContextService
|
||||
),
|
||||
getElasticsearchClientContract: appContextService.getInternalUserESClient.bind(
|
||||
appContextService
|
||||
),
|
||||
saveAgentEvents: AgentService.saveAgentEvents,
|
||||
})
|
||||
);
|
||||
|
|
|
@ -18,9 +18,10 @@ export const postAgentUnenrollHandler: RequestHandler<
|
|||
TypeOf<typeof PostAgentUnenrollRequestSchema.body>
|
||||
> = async (context, request, response) => {
|
||||
const soClient = context.core.savedObjects.client;
|
||||
const esClient = context.core.elasticsearch.client.asInternalUser;
|
||||
try {
|
||||
if (request.body?.force === true) {
|
||||
await AgentService.forceUnenrollAgent(soClient, request.params.agentId);
|
||||
await AgentService.forceUnenrollAgent(soClient, esClient, request.params.agentId);
|
||||
} else {
|
||||
await AgentService.unenrollAgent(soClient, request.params.agentId);
|
||||
}
|
||||
|
@ -44,14 +45,15 @@ export const postBulkAgentsUnenrollHandler: RequestHandler<
|
|||
});
|
||||
}
|
||||
const soClient = context.core.savedObjects.client;
|
||||
const esClient = context.core.elasticsearch.client.asInternalUser;
|
||||
const unenrollAgents =
|
||||
request.body?.force === true ? AgentService.forceUnenrollAgents : AgentService.unenrollAgents;
|
||||
|
||||
try {
|
||||
if (Array.isArray(request.body.agents)) {
|
||||
await unenrollAgents(soClient, { agentIds: request.body.agents });
|
||||
await unenrollAgents(soClient, esClient, { agentIds: request.body.agents });
|
||||
} else {
|
||||
await unenrollAgents(soClient, { kuery: request.body.agents });
|
||||
await unenrollAgents(soClient, esClient, { kuery: request.body.agents });
|
||||
}
|
||||
|
||||
const body: PostBulkAgentUnenrollResponse = {};
|
||||
|
|
|
@ -83,6 +83,7 @@ export const postBulkAgentsUpgradeHandler: RequestHandler<
|
|||
TypeOf<typeof PostBulkAgentUpgradeRequestSchema.body>
|
||||
> = async (context, request, response) => {
|
||||
const soClient = context.core.savedObjects.client;
|
||||
const esClient = context.core.elasticsearch.client.asInternalUser;
|
||||
const { version, source_uri: sourceUri, agents, force } = request.body;
|
||||
const kibanaVersion = appContextService.getKibanaVersion();
|
||||
try {
|
||||
|
@ -98,14 +99,14 @@ export const postBulkAgentsUpgradeHandler: RequestHandler<
|
|||
|
||||
try {
|
||||
if (Array.isArray(agents)) {
|
||||
await AgentService.sendUpgradeAgentsActions(soClient, {
|
||||
await AgentService.sendUpgradeAgentsActions(soClient, esClient, {
|
||||
agentIds: agents,
|
||||
sourceUri,
|
||||
version,
|
||||
force,
|
||||
});
|
||||
} else {
|
||||
await AgentService.sendUpgradeAgentsActions(soClient, {
|
||||
await AgentService.sendUpgradeAgentsActions(soClient, esClient, {
|
||||
kuery: agents,
|
||||
sourceUri,
|
||||
version,
|
||||
|
|
|
@ -39,6 +39,7 @@ export const getAgentPoliciesHandler: RequestHandler<
|
|||
TypeOf<typeof GetAgentPoliciesRequestSchema.query>
|
||||
> = async (context, request, response) => {
|
||||
const soClient = context.core.savedObjects.client;
|
||||
const esClient = context.core.elasticsearch.client.asCurrentUser;
|
||||
const { full: withPackagePolicies = false, ...restOfQuery } = request.query;
|
||||
try {
|
||||
const { items, total, page, perPage } = await agentPolicyService.list(soClient, {
|
||||
|
@ -55,7 +56,7 @@ export const getAgentPoliciesHandler: RequestHandler<
|
|||
await bluebird.map(
|
||||
items,
|
||||
(agentPolicy: GetAgentPoliciesResponseItem) =>
|
||||
listAgents(soClient, {
|
||||
listAgents(soClient, esClient, {
|
||||
showInactive: false,
|
||||
perPage: 0,
|
||||
page: 1,
|
||||
|
@ -100,6 +101,7 @@ export const createAgentPolicyHandler: RequestHandler<
|
|||
TypeOf<typeof CreateAgentPolicyRequestSchema.body>
|
||||
> = async (context, request, response) => {
|
||||
const soClient = context.core.savedObjects.client;
|
||||
const esClient = context.core.elasticsearch.client.asCurrentUser;
|
||||
const callCluster = context.core.elasticsearch.legacy.client.callAsCurrentUser;
|
||||
const user = (await appContextService.getSecurity()?.authc.getCurrentUser(request)) || undefined;
|
||||
const withSysMonitoring = request.query.sys_monitoring ?? false;
|
||||
|
@ -109,7 +111,7 @@ export const createAgentPolicyHandler: RequestHandler<
|
|||
AgentPolicy,
|
||||
NewPackagePolicy | undefined
|
||||
>([
|
||||
agentPolicyService.create(soClient, request.body, {
|
||||
agentPolicyService.create(soClient, esClient, request.body, {
|
||||
user,
|
||||
}),
|
||||
// If needed, retrieve System package information and build a new package policy for the system package
|
||||
|
@ -126,7 +128,7 @@ export const createAgentPolicyHandler: RequestHandler<
|
|||
if (withSysMonitoring && newSysPackagePolicy !== undefined && agentPolicy !== undefined) {
|
||||
newSysPackagePolicy.policy_id = agentPolicy.id;
|
||||
newSysPackagePolicy.namespace = agentPolicy.namespace;
|
||||
await packagePolicyService.create(soClient, callCluster, newSysPackagePolicy, {
|
||||
await packagePolicyService.create(soClient, esClient, callCluster, newSysPackagePolicy, {
|
||||
user,
|
||||
bumpRevision: false,
|
||||
});
|
||||
|
@ -152,10 +154,12 @@ export const updateAgentPolicyHandler: RequestHandler<
|
|||
TypeOf<typeof UpdateAgentPolicyRequestSchema.body>
|
||||
> = async (context, request, response) => {
|
||||
const soClient = context.core.savedObjects.client;
|
||||
const esClient = context.core.elasticsearch.client.asCurrentUser;
|
||||
const user = await appContextService.getSecurity()?.authc.getCurrentUser(request);
|
||||
try {
|
||||
const agentPolicy = await agentPolicyService.update(
|
||||
soClient,
|
||||
esClient,
|
||||
request.params.agentPolicyId,
|
||||
request.body,
|
||||
{
|
||||
|
@ -177,10 +181,12 @@ export const copyAgentPolicyHandler: RequestHandler<
|
|||
TypeOf<typeof CopyAgentPolicyRequestSchema.body>
|
||||
> = async (context, request, response) => {
|
||||
const soClient = context.core.savedObjects.client;
|
||||
const esClient = context.core.elasticsearch.client.asCurrentUser;
|
||||
const user = await appContextService.getSecurity()?.authc.getCurrentUser(request);
|
||||
try {
|
||||
const agentPolicy = await agentPolicyService.copy(
|
||||
soClient,
|
||||
esClient,
|
||||
request.params.agentPolicyId,
|
||||
request.body,
|
||||
{
|
||||
|
@ -203,9 +209,11 @@ export const deleteAgentPoliciesHandler: RequestHandler<
|
|||
TypeOf<typeof DeleteAgentPolicyRequestSchema.body>
|
||||
> = async (context, request, response) => {
|
||||
const soClient = context.core.savedObjects.client;
|
||||
const esClient = context.core.elasticsearch.client.asCurrentUser;
|
||||
try {
|
||||
const body: DeleteAgentPolicyResponse = await agentPolicyService.delete(
|
||||
soClient,
|
||||
esClient,
|
||||
request.body.agentPolicyId
|
||||
);
|
||||
return response.ok({
|
||||
|
|
|
@ -26,12 +26,18 @@ export const getEnrollmentApiKeysHandler: RequestHandler<
|
|||
TypeOf<typeof GetEnrollmentAPIKeysRequestSchema.query>
|
||||
> = async (context, request, response) => {
|
||||
const soClient = context.core.savedObjects.client;
|
||||
const esClient = context.core.elasticsearch.client.asCurrentUser;
|
||||
|
||||
try {
|
||||
const { items, total, page, perPage } = await APIKeyService.listEnrollmentApiKeys(soClient, {
|
||||
page: request.query.page,
|
||||
perPage: request.query.perPage,
|
||||
kuery: request.query.kuery,
|
||||
});
|
||||
const { items, total, page, perPage } = await APIKeyService.listEnrollmentApiKeys(
|
||||
soClient,
|
||||
esClient,
|
||||
{
|
||||
page: request.query.page,
|
||||
perPage: request.query.perPage,
|
||||
kuery: request.query.kuery,
|
||||
}
|
||||
);
|
||||
const body: GetEnrollmentAPIKeysResponse = { list: items, total, page, perPage };
|
||||
|
||||
return response.ok({ body });
|
||||
|
@ -45,8 +51,9 @@ export const postEnrollmentApiKeyHandler: RequestHandler<
|
|||
TypeOf<typeof PostEnrollmentAPIKeyRequestSchema.body>
|
||||
> = async (context, request, response) => {
|
||||
const soClient = context.core.savedObjects.client;
|
||||
const esClient = context.core.elasticsearch.client.asCurrentUser;
|
||||
try {
|
||||
const apiKey = await APIKeyService.generateEnrollmentAPIKey(soClient, {
|
||||
const apiKey = await APIKeyService.generateEnrollmentAPIKey(soClient, esClient, {
|
||||
name: request.body.name,
|
||||
expiration: request.body.expiration,
|
||||
agentPolicyId: request.body.policy_id,
|
||||
|
@ -64,8 +71,9 @@ export const deleteEnrollmentApiKeyHandler: RequestHandler<
|
|||
TypeOf<typeof DeleteEnrollmentAPIKeyRequestSchema.params>
|
||||
> = async (context, request, response) => {
|
||||
const soClient = context.core.savedObjects.client;
|
||||
const esClient = context.core.elasticsearch.client.asCurrentUser;
|
||||
try {
|
||||
await APIKeyService.deleteEnrollmentApiKey(soClient, request.params.keyId);
|
||||
await APIKeyService.deleteEnrollmentApiKey(soClient, esClient, request.params.keyId);
|
||||
|
||||
const body: DeleteEnrollmentAPIKeyResponse = { action: 'deleted' };
|
||||
|
||||
|
@ -84,8 +92,13 @@ export const getOneEnrollmentApiKeyHandler: RequestHandler<
|
|||
TypeOf<typeof GetOneEnrollmentAPIKeyRequestSchema.params>
|
||||
> = async (context, request, response) => {
|
||||
const soClient = context.core.savedObjects.client;
|
||||
const esClient = context.core.elasticsearch.client.asCurrentUser;
|
||||
try {
|
||||
const apiKey = await APIKeyService.getEnrollmentAPIKey(soClient, request.params.keyId);
|
||||
const apiKey = await APIKeyService.getEnrollmentAPIKey(
|
||||
soClient,
|
||||
esClient,
|
||||
request.params.keyId
|
||||
);
|
||||
const body: GetOneEnrollmentAPIKeyResponse = { item: apiKey };
|
||||
|
||||
return response.ok({ body });
|
||||
|
|
|
@ -25,7 +25,7 @@ jest.mock('../../services/package_policy', (): {
|
|||
compilePackagePolicyInputs: jest.fn((packageInfo, dataInputs) => Promise.resolve(dataInputs)),
|
||||
buildPackagePolicyFromPackage: jest.fn(),
|
||||
bulkCreate: jest.fn(),
|
||||
create: jest.fn((soClient, callCluster, newData) =>
|
||||
create: jest.fn((soClient, esClient, callCluster, newData) =>
|
||||
Promise.resolve({
|
||||
...newData,
|
||||
inputs: newData.inputs.map((input) => ({
|
||||
|
@ -201,7 +201,7 @@ describe('When calling package policy', () => {
|
|||
);
|
||||
await routeHandler(context, request, response);
|
||||
expect(response.ok).toHaveBeenCalled();
|
||||
expect(packagePolicyServiceMock.create.mock.calls[0][2]).toEqual({
|
||||
expect(packagePolicyServiceMock.create.mock.calls[0][3]).toEqual({
|
||||
policy_id: 'a5ca00c0-b30c-11ea-9732-1bb05811278c',
|
||||
description: '',
|
||||
enabled: true,
|
||||
|
|
|
@ -74,6 +74,7 @@ export const createPackagePolicyHandler: RequestHandler<
|
|||
TypeOf<typeof CreatePackagePolicyRequestSchema.body>
|
||||
> = async (context, request, response) => {
|
||||
const soClient = context.core.savedObjects.client;
|
||||
const esClient = context.core.elasticsearch.client.asCurrentUser;
|
||||
const callCluster = context.core.elasticsearch.legacy.client.callAsCurrentUser;
|
||||
const user = (await appContextService.getSecurity()?.authc.getCurrentUser(request)) || undefined;
|
||||
let newData = { ...request.body };
|
||||
|
@ -86,9 +87,15 @@ export const createPackagePolicyHandler: RequestHandler<
|
|||
);
|
||||
|
||||
// Create package policy
|
||||
const packagePolicy = await packagePolicyService.create(soClient, callCluster, newData, {
|
||||
user,
|
||||
});
|
||||
const packagePolicy = await packagePolicyService.create(
|
||||
soClient,
|
||||
esClient,
|
||||
callCluster,
|
||||
newData,
|
||||
{
|
||||
user,
|
||||
}
|
||||
);
|
||||
const body: CreatePackagePolicyResponse = { item: packagePolicy };
|
||||
return response.ok({
|
||||
body,
|
||||
|
@ -110,6 +117,7 @@ export const updatePackagePolicyHandler: RequestHandler<
|
|||
TypeOf<typeof UpdatePackagePolicyRequestSchema.body>
|
||||
> = async (context, request, response) => {
|
||||
const soClient = context.core.savedObjects.client;
|
||||
const esClient = context.core.elasticsearch.client.asCurrentUser;
|
||||
const user = (await appContextService.getSecurity()?.authc.getCurrentUser(request)) || undefined;
|
||||
const packagePolicy = await packagePolicyService.get(soClient, request.params.packagePolicyId);
|
||||
|
||||
|
@ -131,6 +139,7 @@ export const updatePackagePolicyHandler: RequestHandler<
|
|||
|
||||
const updatedPackagePolicy = await packagePolicyService.update(
|
||||
soClient,
|
||||
esClient,
|
||||
request.params.packagePolicyId,
|
||||
{ ...newData, package: pkg, inputs },
|
||||
{ user }
|
||||
|
@ -149,10 +158,12 @@ export const deletePackagePolicyHandler: RequestHandler<
|
|||
TypeOf<typeof DeletePackagePoliciesRequestSchema.body>
|
||||
> = async (context, request, response) => {
|
||||
const soClient = context.core.savedObjects.client;
|
||||
const esClient = context.core.elasticsearch.client.asCurrentUser;
|
||||
const user = (await appContextService.getSecurity()?.authc.getCurrentUser(request)) || undefined;
|
||||
try {
|
||||
const body: DeletePackagePoliciesResponse = await packagePolicyService.delete(
|
||||
soClient,
|
||||
esClient,
|
||||
request.body.packagePolicyIds,
|
||||
{ user }
|
||||
);
|
||||
|
|
|
@ -36,10 +36,12 @@ export const putSettingsHandler: RequestHandler<
|
|||
TypeOf<typeof PutSettingsRequestSchema.body>
|
||||
> = async (context, request, response) => {
|
||||
const soClient = context.core.savedObjects.client;
|
||||
const esClient = context.core.elasticsearch.client.asCurrentUser;
|
||||
const user = await appContextService.getSecurity()?.authc.getCurrentUser(request);
|
||||
|
||||
try {
|
||||
const settings = await settingsService.saveSettings(soClient, request.body);
|
||||
await agentPolicyService.bumpAllAgentPolicies(soClient, {
|
||||
await agentPolicyService.bumpAllAgentPolicies(soClient, esClient, {
|
||||
user: user || undefined,
|
||||
});
|
||||
const body = {
|
||||
|
|
|
@ -59,9 +59,10 @@ export const createFleetSetupHandler: RequestHandler<
|
|||
> = async (context, request, response) => {
|
||||
try {
|
||||
const soClient = context.core.savedObjects.client;
|
||||
const esClient = context.core.elasticsearch.client.asCurrentUser;
|
||||
const callCluster = context.core.elasticsearch.legacy.client.callAsCurrentUser;
|
||||
await setupIngestManager(soClient, callCluster);
|
||||
await setupFleet(soClient, callCluster, {
|
||||
await setupIngestManager(soClient, esClient, callCluster);
|
||||
await setupFleet(soClient, esClient, callCluster, {
|
||||
forceRecreate: request.body?.forceRecreate ?? false,
|
||||
});
|
||||
|
||||
|
@ -75,11 +76,12 @@ export const createFleetSetupHandler: RequestHandler<
|
|||
|
||||
export const FleetSetupHandler: RequestHandler = async (context, request, response) => {
|
||||
const soClient = context.core.savedObjects.client;
|
||||
const esClient = context.core.elasticsearch.client.asCurrentUser;
|
||||
const callCluster = context.core.elasticsearch.legacy.client.callAsCurrentUser;
|
||||
|
||||
try {
|
||||
const body: PostIngestSetupResponse = { isInitialized: true };
|
||||
await setupIngestManager(soClient, callCluster);
|
||||
await setupIngestManager(soClient, esClient, callCluster);
|
||||
return response.ok({
|
||||
body,
|
||||
});
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { savedObjectsClientMock } from 'src/core/server/mocks';
|
||||
import { elasticsearchServiceMock, savedObjectsClientMock } from 'src/core/server/mocks';
|
||||
import { agentPolicyService } from './agent_policy';
|
||||
import { agentPolicyUpdateEventHandler } from './agent_policy_update';
|
||||
import { Output } from '../types';
|
||||
|
@ -78,7 +78,9 @@ describe('agent policy', () => {
|
|||
revision: 1,
|
||||
monitoring_enabled: ['metrics'],
|
||||
});
|
||||
await agentPolicyService.bumpRevision(soClient, 'agent-policy');
|
||||
const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
|
||||
|
||||
await agentPolicyService.bumpRevision(soClient, esClient, 'agent-policy');
|
||||
|
||||
expect(agentPolicyUpdateEventHandler).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
@ -90,7 +92,9 @@ describe('agent policy', () => {
|
|||
revision: 1,
|
||||
monitoring_enabled: ['metrics'],
|
||||
});
|
||||
await agentPolicyService.bumpAllAgentPolicies(soClient);
|
||||
const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
|
||||
|
||||
await agentPolicyService.bumpAllAgentPolicies(soClient, esClient, undefined);
|
||||
|
||||
expect(agentPolicyUpdateEventHandler).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
|
|
@ -5,7 +5,12 @@
|
|||
*/
|
||||
import { uniq } from 'lodash';
|
||||
import { safeLoad } from 'js-yaml';
|
||||
import { SavedObjectsClientContract, SavedObjectsBulkUpdateResponse } from 'src/core/server';
|
||||
import uuid from 'uuid/v4';
|
||||
import {
|
||||
ElasticsearchClient,
|
||||
SavedObjectsClientContract,
|
||||
SavedObjectsBulkUpdateResponse,
|
||||
} from 'src/core/server';
|
||||
import { AuthenticatedUser } from '../../../security/server';
|
||||
import {
|
||||
DEFAULT_AGENT_POLICY,
|
||||
|
@ -26,6 +31,8 @@ import {
|
|||
agentPolicyStatuses,
|
||||
storedPackagePoliciesToAgentInputs,
|
||||
dataTypes,
|
||||
FleetServerPolicy,
|
||||
AGENT_POLICY_INDEX,
|
||||
} from '../../common';
|
||||
import { AgentPolicyNameExistsError } from '../errors';
|
||||
import { createAgentPolicyAction, listAgents } from './agents';
|
||||
|
@ -36,20 +43,23 @@ import { getSettings } from './settings';
|
|||
import { normalizeKuery, escapeSearchQueryPhrase } from './saved_object';
|
||||
import { getFullAgentPolicyKibanaConfig } from '../../common/services/full_agent_policy_kibana_config';
|
||||
import { isAgentsSetup } from './agents/setup';
|
||||
import { appContextService } from './app_context';
|
||||
|
||||
const SAVED_OBJECT_TYPE = AGENT_POLICY_SAVED_OBJECT_TYPE;
|
||||
|
||||
class AgentPolicyService {
|
||||
private triggerAgentPolicyUpdatedEvent = async (
|
||||
soClient: SavedObjectsClientContract,
|
||||
esClient: ElasticsearchClient,
|
||||
action: 'created' | 'updated' | 'deleted',
|
||||
agentPolicyId: string
|
||||
) => {
|
||||
return agentPolicyUpdateEventHandler(soClient, action, agentPolicyId);
|
||||
return agentPolicyUpdateEventHandler(soClient, esClient, action, agentPolicyId);
|
||||
};
|
||||
|
||||
private async _update(
|
||||
soClient: SavedObjectsClientContract,
|
||||
esClient: ElasticsearchClient,
|
||||
id: string,
|
||||
agentPolicy: Partial<AgentPolicySOAttributes>,
|
||||
user?: AuthenticatedUser,
|
||||
|
@ -78,14 +88,15 @@ class AgentPolicyService {
|
|||
});
|
||||
|
||||
if (options.bumpRevision) {
|
||||
await this.triggerAgentPolicyUpdatedEvent(soClient, 'updated', id);
|
||||
await this.triggerAgentPolicyUpdatedEvent(soClient, esClient, 'updated', id);
|
||||
}
|
||||
|
||||
return (await this.get(soClient, id)) as AgentPolicy;
|
||||
}
|
||||
|
||||
public async ensureDefaultAgentPolicy(
|
||||
soClient: SavedObjectsClientContract
|
||||
soClient: SavedObjectsClientContract,
|
||||
esClient: ElasticsearchClient
|
||||
): Promise<{
|
||||
created: boolean;
|
||||
defaultAgentPolicy: AgentPolicy;
|
||||
|
@ -103,7 +114,7 @@ class AgentPolicyService {
|
|||
|
||||
return {
|
||||
created: true,
|
||||
defaultAgentPolicy: await this.create(soClient, newDefaultAgentPolicy),
|
||||
defaultAgentPolicy: await this.create(soClient, esClient, newDefaultAgentPolicy),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -118,6 +129,7 @@ class AgentPolicyService {
|
|||
|
||||
public async create(
|
||||
soClient: SavedObjectsClientContract,
|
||||
esClient: ElasticsearchClient,
|
||||
agentPolicy: NewAgentPolicy,
|
||||
options?: { id?: string; user?: AuthenticatedUser }
|
||||
): Promise<AgentPolicy> {
|
||||
|
@ -134,7 +146,7 @@ class AgentPolicyService {
|
|||
);
|
||||
|
||||
if (!agentPolicy.is_default) {
|
||||
await this.triggerAgentPolicyUpdatedEvent(soClient, 'created', newSo.id);
|
||||
await this.triggerAgentPolicyUpdatedEvent(soClient, esClient, 'created', newSo.id);
|
||||
}
|
||||
|
||||
return { id: newSo.id, ...newSo.attributes };
|
||||
|
@ -244,6 +256,7 @@ class AgentPolicyService {
|
|||
|
||||
public async update(
|
||||
soClient: SavedObjectsClientContract,
|
||||
esClient: ElasticsearchClient,
|
||||
id: string,
|
||||
agentPolicy: Partial<AgentPolicy>,
|
||||
options?: { user?: AuthenticatedUser }
|
||||
|
@ -254,11 +267,12 @@ class AgentPolicyService {
|
|||
name: agentPolicy.name,
|
||||
});
|
||||
}
|
||||
return this._update(soClient, id, agentPolicy, options?.user);
|
||||
return this._update(soClient, esClient, id, agentPolicy, options?.user);
|
||||
}
|
||||
|
||||
public async copy(
|
||||
soClient: SavedObjectsClientContract,
|
||||
esClient: ElasticsearchClient,
|
||||
id: string,
|
||||
newAgentPolicyProps: Pick<AgentPolicy, 'name' | 'description'>,
|
||||
options?: { user?: AuthenticatedUser }
|
||||
|
@ -272,6 +286,7 @@ class AgentPolicyService {
|
|||
const { namespace, monitoring_enabled } = baseAgentPolicy;
|
||||
const newAgentPolicy = await this.create(
|
||||
soClient,
|
||||
esClient,
|
||||
{
|
||||
namespace,
|
||||
monitoring_enabled,
|
||||
|
@ -288,10 +303,16 @@ class AgentPolicyService {
|
|||
return newPackagePolicy;
|
||||
}
|
||||
);
|
||||
await packagePolicyService.bulkCreate(soClient, newPackagePolicies, newAgentPolicy.id, {
|
||||
...options,
|
||||
bumpRevision: false,
|
||||
});
|
||||
await packagePolicyService.bulkCreate(
|
||||
soClient,
|
||||
esClient,
|
||||
newPackagePolicies,
|
||||
newAgentPolicy.id,
|
||||
{
|
||||
...options,
|
||||
bumpRevision: false,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Get updated agent policy
|
||||
|
@ -307,15 +328,18 @@ class AgentPolicyService {
|
|||
|
||||
public async bumpRevision(
|
||||
soClient: SavedObjectsClientContract,
|
||||
esClient: ElasticsearchClient,
|
||||
id: string,
|
||||
options?: { user?: AuthenticatedUser }
|
||||
): Promise<AgentPolicy> {
|
||||
const res = await this._update(soClient, id, {}, options?.user);
|
||||
const res = await this._update(soClient, esClient, id, {}, options?.user);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
public async bumpAllAgentPolicies(
|
||||
soClient: SavedObjectsClientContract,
|
||||
esClient: ElasticsearchClient,
|
||||
options?: { user?: AuthenticatedUser }
|
||||
): Promise<Promise<SavedObjectsBulkUpdateResponse<AgentPolicy>>> {
|
||||
const currentPolicies = await soClient.find<AgentPolicySOAttributes>({
|
||||
|
@ -335,7 +359,7 @@ class AgentPolicyService {
|
|||
|
||||
await Promise.all(
|
||||
currentPolicies.saved_objects.map((policy) =>
|
||||
this.triggerAgentPolicyUpdatedEvent(soClient, 'updated', policy.id)
|
||||
this.triggerAgentPolicyUpdatedEvent(soClient, esClient, 'updated', policy.id)
|
||||
)
|
||||
);
|
||||
|
||||
|
@ -344,6 +368,7 @@ class AgentPolicyService {
|
|||
|
||||
public async assignPackagePolicies(
|
||||
soClient: SavedObjectsClientContract,
|
||||
esClient: ElasticsearchClient,
|
||||
id: string,
|
||||
packagePolicyIds: string[],
|
||||
options: { user?: AuthenticatedUser; bumpRevision: boolean } = { bumpRevision: true }
|
||||
|
@ -356,6 +381,7 @@ class AgentPolicyService {
|
|||
|
||||
return await this._update(
|
||||
soClient,
|
||||
esClient,
|
||||
id,
|
||||
{
|
||||
package_policies: uniq(
|
||||
|
@ -369,6 +395,7 @@ class AgentPolicyService {
|
|||
|
||||
public async unassignPackagePolicies(
|
||||
soClient: SavedObjectsClientContract,
|
||||
esClient: ElasticsearchClient,
|
||||
id: string,
|
||||
packagePolicyIds: string[],
|
||||
options?: { user?: AuthenticatedUser }
|
||||
|
@ -381,6 +408,7 @@ class AgentPolicyService {
|
|||
|
||||
return await this._update(
|
||||
soClient,
|
||||
esClient,
|
||||
id,
|
||||
{
|
||||
package_policies: uniq(
|
||||
|
@ -409,6 +437,7 @@ class AgentPolicyService {
|
|||
|
||||
public async delete(
|
||||
soClient: SavedObjectsClientContract,
|
||||
esClient: ElasticsearchClient,
|
||||
id: string
|
||||
): Promise<DeleteAgentPolicyResponse> {
|
||||
const agentPolicy = await this.get(soClient, id, false);
|
||||
|
@ -418,12 +447,12 @@ class AgentPolicyService {
|
|||
|
||||
const {
|
||||
defaultAgentPolicy: { id: defaultAgentPolicyId },
|
||||
} = await this.ensureDefaultAgentPolicy(soClient);
|
||||
} = await this.ensureDefaultAgentPolicy(soClient, esClient);
|
||||
if (id === defaultAgentPolicyId) {
|
||||
throw new Error('The default agent policy cannot be deleted');
|
||||
}
|
||||
|
||||
const { total } = await listAgents(soClient, {
|
||||
const { total } = await listAgents(soClient, esClient, {
|
||||
showInactive: false,
|
||||
perPage: 0,
|
||||
page: 1,
|
||||
|
@ -435,12 +464,17 @@ class AgentPolicyService {
|
|||
}
|
||||
|
||||
if (agentPolicy.package_policies && agentPolicy.package_policies.length) {
|
||||
await packagePolicyService.delete(soClient, agentPolicy.package_policies as string[], {
|
||||
skipUnassignFromAgentPolicies: true,
|
||||
});
|
||||
await packagePolicyService.delete(
|
||||
soClient,
|
||||
esClient,
|
||||
agentPolicy.package_policies as string[],
|
||||
{
|
||||
skipUnassignFromAgentPolicies: true,
|
||||
}
|
||||
);
|
||||
}
|
||||
await soClient.delete(SAVED_OBJECT_TYPE, id);
|
||||
await this.triggerAgentPolicyUpdatedEvent(soClient, 'deleted', id);
|
||||
await this.triggerAgentPolicyUpdatedEvent(soClient, esClient, 'deleted', id);
|
||||
return {
|
||||
id,
|
||||
name: agentPolicy.name,
|
||||
|
@ -450,6 +484,19 @@ class AgentPolicyService {
|
|||
public async createFleetPolicyChangeAction(
|
||||
soClient: SavedObjectsClientContract,
|
||||
agentPolicyId: string
|
||||
) {
|
||||
return appContextService.getConfig()?.agents.fleetServerEnabled
|
||||
? this.createFleetPolicyChangeFleetServer(
|
||||
soClient,
|
||||
appContextService.getInternalUserESClient(),
|
||||
agentPolicyId
|
||||
)
|
||||
: this.createFleetPolicyChangeActionSO(soClient, agentPolicyId);
|
||||
}
|
||||
|
||||
public async createFleetPolicyChangeActionSO(
|
||||
soClient: SavedObjectsClientContract,
|
||||
agentPolicyId: string
|
||||
) {
|
||||
// If Agents is not setup skip the creation of POLICY_CHANGE agent actions
|
||||
// the action will be created during the fleet setup
|
||||
|
@ -478,6 +525,38 @@ class AgentPolicyService {
|
|||
});
|
||||
}
|
||||
|
||||
public async createFleetPolicyChangeFleetServer(
|
||||
soClient: SavedObjectsClientContract,
|
||||
esClient: ElasticsearchClient,
|
||||
agentPolicyId: string
|
||||
) {
|
||||
// If Agents is not setup skip the creation of POLICY_CHANGE agent actions
|
||||
// the action will be created during the fleet setup
|
||||
if (!(await isAgentsSetup(soClient))) {
|
||||
return;
|
||||
}
|
||||
const policy = await agentPolicyService.getFullAgentPolicy(soClient, agentPolicyId);
|
||||
if (!policy || !policy.revision) {
|
||||
return;
|
||||
}
|
||||
|
||||
const fleetServerPolicy: FleetServerPolicy = {
|
||||
'@timestamp': new Date().toISOString(),
|
||||
revision_idx: policy.revision,
|
||||
coordinator_idx: 0,
|
||||
data: (policy as unknown) as FleetServerPolicy['data'],
|
||||
policy_id: policy.id,
|
||||
default_fleet_server: false,
|
||||
};
|
||||
|
||||
await esClient.create({
|
||||
index: AGENT_POLICY_INDEX,
|
||||
body: fleetServerPolicy,
|
||||
id: uuid(),
|
||||
refresh: 'wait_for',
|
||||
});
|
||||
}
|
||||
|
||||
public async getFullAgentPolicy(
|
||||
soClient: SavedObjectsClientContract,
|
||||
id: string,
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { KibanaRequest, SavedObjectsClientContract } from 'src/core/server';
|
||||
import { ElasticsearchClient, KibanaRequest, SavedObjectsClientContract } from 'src/core/server';
|
||||
import { generateEnrollmentAPIKey, deleteEnrollmentApiKeyForAgentPolicyId } from './api_keys';
|
||||
import { isAgentsSetup, unenrollForAgentPolicyId } from './agents';
|
||||
import { agentPolicyService } from './agent_policy';
|
||||
|
@ -27,6 +27,7 @@ const fakeRequest = ({
|
|||
|
||||
export async function agentPolicyUpdateEventHandler(
|
||||
soClient: SavedObjectsClientContract,
|
||||
esClient: ElasticsearchClient,
|
||||
action: string,
|
||||
agentPolicyId: string
|
||||
) {
|
||||
|
@ -40,7 +41,7 @@ export async function agentPolicyUpdateEventHandler(
|
|||
const internalSoClient = appContextService.getInternalUserSOClient(fakeRequest);
|
||||
|
||||
if (action === 'created') {
|
||||
await generateEnrollmentAPIKey(soClient, {
|
||||
await generateEnrollmentAPIKey(soClient, esClient, {
|
||||
agentPolicyId,
|
||||
});
|
||||
await agentPolicyService.createFleetPolicyChangeAction(internalSoClient, agentPolicyId);
|
||||
|
@ -51,7 +52,7 @@ export async function agentPolicyUpdateEventHandler(
|
|||
}
|
||||
|
||||
if (action === 'deleted') {
|
||||
await unenrollForAgentPolicyId(soClient, agentPolicyId);
|
||||
await unenrollForAgentPolicyId(soClient, esClient, agentPolicyId);
|
||||
await deleteEnrollmentApiKeyForAgentPolicyId(soClient, agentPolicyId);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
*/
|
||||
import Boom from '@hapi/boom';
|
||||
import { SavedObjectsBulkResponse } from 'kibana/server';
|
||||
import { savedObjectsClientMock } from 'src/core/server/mocks';
|
||||
import { elasticsearchServiceMock, savedObjectsClientMock } from 'src/core/server/mocks';
|
||||
|
||||
import {
|
||||
Agent,
|
||||
|
@ -19,6 +19,7 @@ import { acknowledgeAgentActions } from './acks';
|
|||
describe('test agent acks services', () => {
|
||||
it('should succeed on valid and matched actions', async () => {
|
||||
const mockSavedObjectsClient = savedObjectsClientMock.create();
|
||||
const mockElasticsearchClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
|
||||
|
||||
mockSavedObjectsClient.bulkGet.mockReturnValue(
|
||||
Promise.resolve({
|
||||
|
@ -41,6 +42,7 @@ describe('test agent acks services', () => {
|
|||
|
||||
await acknowledgeAgentActions(
|
||||
mockSavedObjectsClient,
|
||||
mockElasticsearchClient,
|
||||
({
|
||||
id: 'id',
|
||||
type: AGENT_TYPE_PERMANENT,
|
||||
|
@ -59,6 +61,7 @@ describe('test agent acks services', () => {
|
|||
|
||||
it('should update config field on the agent if a policy change is acknowledged with an agent without policy', async () => {
|
||||
const mockSavedObjectsClient = savedObjectsClientMock.create();
|
||||
const mockElasticsearchClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
|
||||
|
||||
const actionAttributes = {
|
||||
type: 'POLICY_CHANGE',
|
||||
|
@ -85,6 +88,7 @@ describe('test agent acks services', () => {
|
|||
|
||||
await acknowledgeAgentActions(
|
||||
mockSavedObjectsClient,
|
||||
mockElasticsearchClient,
|
||||
({
|
||||
id: 'id',
|
||||
type: AGENT_TYPE_PERMANENT,
|
||||
|
@ -118,6 +122,7 @@ describe('test agent acks services', () => {
|
|||
|
||||
it('should update config field on the agent if a policy change is acknowledged with a higher revision than the agent one', async () => {
|
||||
const mockSavedObjectsClient = savedObjectsClientMock.create();
|
||||
const mockElasticsearchClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
|
||||
|
||||
const actionAttributes = {
|
||||
type: 'POLICY_CHANGE',
|
||||
|
@ -144,6 +149,7 @@ describe('test agent acks services', () => {
|
|||
|
||||
await acknowledgeAgentActions(
|
||||
mockSavedObjectsClient,
|
||||
mockElasticsearchClient,
|
||||
({
|
||||
id: 'id',
|
||||
type: AGENT_TYPE_PERMANENT,
|
||||
|
@ -178,6 +184,7 @@ describe('test agent acks services', () => {
|
|||
|
||||
it('should not update config field on the agent if a policy change is acknowledged with a lower revision than the agent one', async () => {
|
||||
const mockSavedObjectsClient = savedObjectsClientMock.create();
|
||||
const mockElasticsearchClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
|
||||
|
||||
const actionAttributes = {
|
||||
type: 'POLICY_CHANGE',
|
||||
|
@ -204,6 +211,7 @@ describe('test agent acks services', () => {
|
|||
|
||||
await acknowledgeAgentActions(
|
||||
mockSavedObjectsClient,
|
||||
mockElasticsearchClient,
|
||||
({
|
||||
id: 'id',
|
||||
type: AGENT_TYPE_PERMANENT,
|
||||
|
@ -226,6 +234,7 @@ describe('test agent acks services', () => {
|
|||
|
||||
it('should not update config field on the agent if a policy change for an old revision is acknowledged', async () => {
|
||||
const mockSavedObjectsClient = savedObjectsClientMock.create();
|
||||
const mockElasticsearchClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
|
||||
|
||||
mockSavedObjectsClient.bulkGet.mockReturnValue(
|
||||
Promise.resolve({
|
||||
|
@ -249,6 +258,7 @@ describe('test agent acks services', () => {
|
|||
|
||||
await acknowledgeAgentActions(
|
||||
mockSavedObjectsClient,
|
||||
mockElasticsearchClient,
|
||||
({
|
||||
id: 'id',
|
||||
type: AGENT_TYPE_PERMANENT,
|
||||
|
@ -271,6 +281,7 @@ describe('test agent acks services', () => {
|
|||
|
||||
it('should fail for actions that cannot be found on agent actions list', async () => {
|
||||
const mockSavedObjectsClient = savedObjectsClientMock.create();
|
||||
const mockElasticsearchClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
|
||||
mockSavedObjectsClient.bulkGet.mockReturnValue(
|
||||
Promise.resolve({
|
||||
saved_objects: [
|
||||
|
@ -288,6 +299,7 @@ describe('test agent acks services', () => {
|
|||
try {
|
||||
await acknowledgeAgentActions(
|
||||
mockSavedObjectsClient,
|
||||
mockElasticsearchClient,
|
||||
({
|
||||
id: 'id',
|
||||
type: AGENT_TYPE_PERMANENT,
|
||||
|
@ -310,6 +322,7 @@ describe('test agent acks services', () => {
|
|||
|
||||
it('should fail for events that have types not in the allowed acknowledgement type list', async () => {
|
||||
const mockSavedObjectsClient = savedObjectsClientMock.create();
|
||||
const mockElasticsearchClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
|
||||
|
||||
mockSavedObjectsClient.bulkGet.mockReturnValue(
|
||||
Promise.resolve({
|
||||
|
@ -333,6 +346,7 @@ describe('test agent acks services', () => {
|
|||
try {
|
||||
await acknowledgeAgentActions(
|
||||
mockSavedObjectsClient,
|
||||
mockElasticsearchClient,
|
||||
({
|
||||
id: 'id',
|
||||
type: AGENT_TYPE_PERMANENT,
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
*/
|
||||
|
||||
import {
|
||||
ElasticsearchClient,
|
||||
KibanaRequest,
|
||||
SavedObjectsBulkCreateObject,
|
||||
SavedObjectsBulkResponse,
|
||||
|
@ -40,6 +41,7 @@ const actionCache = new LRU<string, AgentAction>({
|
|||
|
||||
export async function acknowledgeAgentActions(
|
||||
soClient: SavedObjectsClientContract,
|
||||
esClient: ElasticsearchClient,
|
||||
agent: Agent,
|
||||
agentEvents: AgentEvent[]
|
||||
): Promise<AgentAction[]> {
|
||||
|
@ -79,7 +81,7 @@ export async function acknowledgeAgentActions(
|
|||
|
||||
const isAgentUnenrolled = actions.some((action) => action.type === 'UNENROLL');
|
||||
if (isAgentUnenrolled) {
|
||||
await forceUnenrollAgent(soClient, agent.id);
|
||||
await forceUnenrollAgent(soClient, esClient, agent.id);
|
||||
}
|
||||
|
||||
const upgradeAction = actions.find((action) => action.type === 'UPGRADE');
|
||||
|
@ -196,6 +198,7 @@ export async function saveAgentEvents(
|
|||
export interface AcksService {
|
||||
acknowledgeAgentActions: (
|
||||
soClient: SavedObjectsClientContract,
|
||||
esClient: ElasticsearchClient,
|
||||
agent: Agent,
|
||||
actionIds: AgentEvent[]
|
||||
) => Promise<AgentAction[]>;
|
||||
|
@ -207,6 +210,8 @@ export interface AcksService {
|
|||
|
||||
getSavedObjectsClientContract: (kibanaRequest: KibanaRequest) => SavedObjectsClientContract;
|
||||
|
||||
getElasticsearchClientContract: () => ElasticsearchClient;
|
||||
|
||||
saveAgentEvents: (
|
||||
soClient: SavedObjectsClientContract,
|
||||
events: AgentEvent[]
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { SavedObjectsClientContract } from 'kibana/server';
|
||||
import { ElasticsearchClient, SavedObjectsClientContract } from 'kibana/server';
|
||||
import {
|
||||
Agent,
|
||||
AgentAction,
|
||||
|
@ -307,7 +307,11 @@ export async function getLatestConfigChangeAction(
|
|||
}
|
||||
|
||||
export interface ActionsService {
|
||||
getAgent: (soClient: SavedObjectsClientContract, agentId: string) => Promise<Agent>;
|
||||
getAgent: (
|
||||
soClient: SavedObjectsClientContract,
|
||||
esClient: ElasticsearchClient,
|
||||
agentId: string
|
||||
) => Promise<Agent>;
|
||||
|
||||
createAgentAction: (
|
||||
soClient: SavedObjectsClientContract,
|
||||
|
|
|
@ -5,7 +5,11 @@
|
|||
*/
|
||||
|
||||
import deepEqual from 'fast-deep-equal';
|
||||
import { SavedObjectsClientContract, SavedObjectsBulkCreateObject } from 'src/core/server';
|
||||
import {
|
||||
ElasticsearchClient,
|
||||
SavedObjectsClientContract,
|
||||
SavedObjectsBulkCreateObject,
|
||||
} from 'src/core/server';
|
||||
import {
|
||||
Agent,
|
||||
NewAgentEvent,
|
||||
|
@ -20,6 +24,7 @@ import { getAgentActionsForCheckin } from '../actions';
|
|||
|
||||
export async function agentCheckin(
|
||||
soClient: SavedObjectsClientContract,
|
||||
esClient: ElasticsearchClient,
|
||||
agent: Agent,
|
||||
data: {
|
||||
events: NewAgentEvent[];
|
||||
|
@ -54,7 +59,7 @@ export async function agentCheckin(
|
|||
}
|
||||
|
||||
// Wait for new actions
|
||||
actions = await agentCheckinState.subscribeToNewActions(soClient, agent, options);
|
||||
actions = await agentCheckinState.subscribeToNewActions(soClient, esClient, agent, options);
|
||||
|
||||
return { actions };
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { SavedObjectsClientContract } from 'src/core/server';
|
||||
import { ElasticsearchClient, SavedObjectsClientContract } from 'src/core/server';
|
||||
import { Agent } from '../../../types';
|
||||
import { appContextService } from '../../app_context';
|
||||
import { agentCheckinStateConnectedAgentsFactory } from './state_connected_agents';
|
||||
|
@ -35,6 +35,7 @@ function agentCheckinStateFactory() {
|
|||
return {
|
||||
subscribeToNewActions: async (
|
||||
soClient: SavedObjectsClientContract,
|
||||
esClient: ElasticsearchClient,
|
||||
agent: Agent,
|
||||
options?: { signal: AbortSignal }
|
||||
) => {
|
||||
|
@ -44,7 +45,7 @@ function agentCheckinStateFactory() {
|
|||
|
||||
return agentConnected.wrapPromise(
|
||||
agent.id,
|
||||
newActions.subscribeToNewActions(soClient, agent, options)
|
||||
newActions.subscribeToNewActions(soClient, esClient, agent, options)
|
||||
);
|
||||
},
|
||||
start,
|
||||
|
|
|
@ -21,7 +21,7 @@ import {
|
|||
timeout,
|
||||
take,
|
||||
} from 'rxjs/operators';
|
||||
import { SavedObjectsClientContract, KibanaRequest } from 'src/core/server';
|
||||
import { ElasticsearchClient, SavedObjectsClientContract, KibanaRequest } from 'src/core/server';
|
||||
import {
|
||||
Agent,
|
||||
AgentAction,
|
||||
|
@ -228,6 +228,7 @@ export function agentCheckinStateNewActionsFactory() {
|
|||
|
||||
async function subscribeToNewActions(
|
||||
soClient: SavedObjectsClientContract,
|
||||
esClient: ElasticsearchClient,
|
||||
agent: Agent,
|
||||
options?: { signal: AbortSignal }
|
||||
): Promise<AgentAction[]> {
|
||||
|
@ -262,7 +263,7 @@ export function agentCheckinStateNewActionsFactory() {
|
|||
(action) => action.type === 'INTERNAL_POLICY_REASSIGN'
|
||||
);
|
||||
if (hasConfigReassign) {
|
||||
return from(getAgent(soClient, agent.id)).pipe(
|
||||
return from(getAgent(soClient, esClient, agent.id)).pipe(
|
||||
concatMap((refreshedAgent) => {
|
||||
if (!refreshedAgent.policy_id) {
|
||||
throw new Error('Agent does not have a policy assigned');
|
||||
|
|
|
@ -4,29 +4,19 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import Boom from '@hapi/boom';
|
||||
import { SavedObjectsClientContract } from 'src/core/server';
|
||||
import { isAgentUpgradeable } from '../../../common';
|
||||
import { AGENT_SAVED_OBJECT_TYPE, AGENT_EVENT_SAVED_OBJECT_TYPE } from '../../constants';
|
||||
import { AgentSOAttributes, Agent, AgentEventSOAttributes, ListWithKuery } from '../../types';
|
||||
import { escapeSearchQueryPhrase, normalizeKuery, findAllSOs } from '../saved_object';
|
||||
import { SavedObjectsClientContract, ElasticsearchClient } from 'src/core/server';
|
||||
|
||||
import { AGENT_SAVED_OBJECT_TYPE } from '../../constants';
|
||||
import { AgentSOAttributes, Agent, ListWithKuery } from '../../types';
|
||||
import { escapeSearchQueryPhrase } from '../saved_object';
|
||||
import { savedObjectToAgent } from './saved_objects';
|
||||
import { appContextService } from '../../services';
|
||||
|
||||
const ACTIVE_AGENT_CONDITION = `${AGENT_SAVED_OBJECT_TYPE}.attributes.active:true`;
|
||||
const INACTIVE_AGENT_CONDITION = `NOT (${ACTIVE_AGENT_CONDITION})`;
|
||||
|
||||
function _joinFilters(filters: string[], operator = 'AND') {
|
||||
return filters.reduce((acc: string | undefined, filter) => {
|
||||
if (acc) {
|
||||
return `${acc} ${operator} (${filter})`;
|
||||
}
|
||||
|
||||
return `(${filter})`;
|
||||
}, undefined);
|
||||
}
|
||||
import * as crudServiceSO from './crud_so';
|
||||
import * as crudServiceFleetServer from './crud_fleet_server';
|
||||
|
||||
export async function listAgents(
|
||||
soClient: SavedObjectsClientContract,
|
||||
esClient: ElasticsearchClient,
|
||||
options: ListWithKuery & {
|
||||
showInactive: boolean;
|
||||
}
|
||||
|
@ -36,52 +26,16 @@ export async function listAgents(
|
|||
page: number;
|
||||
perPage: number;
|
||||
}> {
|
||||
const {
|
||||
page = 1,
|
||||
perPage = 20,
|
||||
sortField = 'enrolled_at',
|
||||
sortOrder = 'desc',
|
||||
kuery,
|
||||
showInactive = false,
|
||||
showUpgradeable,
|
||||
} = options;
|
||||
const filters = [];
|
||||
const fleetServerEnabled = appContextService.getConfig()?.agents?.fleetServerEnabled;
|
||||
|
||||
if (kuery && kuery !== '') {
|
||||
filters.push(normalizeKuery(AGENT_SAVED_OBJECT_TYPE, kuery));
|
||||
}
|
||||
|
||||
if (showInactive === false) {
|
||||
filters.push(ACTIVE_AGENT_CONDITION);
|
||||
}
|
||||
|
||||
let { saved_objects: agentSOs, total } = await soClient.find<AgentSOAttributes>({
|
||||
type: AGENT_SAVED_OBJECT_TYPE,
|
||||
filter: _joinFilters(filters),
|
||||
sortField,
|
||||
sortOrder,
|
||||
page,
|
||||
perPage,
|
||||
});
|
||||
// filtering for a range on the version string will not work,
|
||||
// nor does filtering on a flattened field (local_metadata), so filter here
|
||||
if (showUpgradeable) {
|
||||
agentSOs = agentSOs.filter((agent) =>
|
||||
isAgentUpgradeable(savedObjectToAgent(agent), appContextService.getKibanaVersion())
|
||||
);
|
||||
total = agentSOs.length;
|
||||
}
|
||||
|
||||
return {
|
||||
agents: agentSOs.map(savedObjectToAgent),
|
||||
total,
|
||||
page,
|
||||
perPage,
|
||||
};
|
||||
return fleetServerEnabled
|
||||
? crudServiceFleetServer.listAgents(esClient, options)
|
||||
: crudServiceSO.listAgents(soClient, options);
|
||||
}
|
||||
|
||||
export async function listAllAgents(
|
||||
soClient: SavedObjectsClientContract,
|
||||
esClient: ElasticsearchClient,
|
||||
options: Omit<ListWithKuery, 'page' | 'perPage'> & {
|
||||
showInactive: boolean;
|
||||
}
|
||||
|
@ -89,55 +43,34 @@ export async function listAllAgents(
|
|||
agents: Agent[];
|
||||
total: number;
|
||||
}> {
|
||||
const { sortField = 'enrolled_at', sortOrder = 'desc', kuery, showInactive = false } = options;
|
||||
const filters = [];
|
||||
const fleetServerEnabled = appContextService.getConfig()?.agents?.fleetServerEnabled;
|
||||
|
||||
if (kuery && kuery !== '') {
|
||||
filters.push(normalizeKuery(AGENT_SAVED_OBJECT_TYPE, kuery));
|
||||
}
|
||||
|
||||
if (showInactive === false) {
|
||||
filters.push(ACTIVE_AGENT_CONDITION);
|
||||
}
|
||||
|
||||
const { saved_objects: agentSOs, total } = await findAllSOs<AgentSOAttributes>(soClient, {
|
||||
type: AGENT_SAVED_OBJECT_TYPE,
|
||||
kuery: _joinFilters(filters),
|
||||
sortField,
|
||||
sortOrder,
|
||||
});
|
||||
|
||||
return {
|
||||
agents: agentSOs.map(savedObjectToAgent),
|
||||
total,
|
||||
};
|
||||
return fleetServerEnabled
|
||||
? crudServiceFleetServer.listAllAgents(esClient, options)
|
||||
: crudServiceSO.listAllAgents(soClient, options);
|
||||
}
|
||||
|
||||
export async function countInactiveAgents(
|
||||
soClient: SavedObjectsClientContract,
|
||||
esClient: ElasticsearchClient,
|
||||
options: Pick<ListWithKuery, 'kuery'>
|
||||
): Promise<number> {
|
||||
const { kuery } = options;
|
||||
const filters = [INACTIVE_AGENT_CONDITION];
|
||||
const fleetServerEnabled = appContextService.getConfig()?.agents?.fleetServerEnabled;
|
||||
|
||||
if (kuery && kuery !== '') {
|
||||
filters.push(normalizeKuery(AGENT_SAVED_OBJECT_TYPE, kuery));
|
||||
}
|
||||
|
||||
const { total } = await soClient.find<AgentSOAttributes>({
|
||||
type: AGENT_SAVED_OBJECT_TYPE,
|
||||
filter: _joinFilters(filters),
|
||||
perPage: 0,
|
||||
});
|
||||
|
||||
return total;
|
||||
return fleetServerEnabled
|
||||
? crudServiceFleetServer.countInactiveAgents(esClient, options)
|
||||
: crudServiceSO.countInactiveAgents(soClient, options);
|
||||
}
|
||||
|
||||
export async function getAgent(soClient: SavedObjectsClientContract, agentId: string) {
|
||||
const agent = savedObjectToAgent(
|
||||
await soClient.get<AgentSOAttributes>(AGENT_SAVED_OBJECT_TYPE, agentId)
|
||||
);
|
||||
return agent;
|
||||
export async function getAgent(
|
||||
soClient: SavedObjectsClientContract,
|
||||
esClient: ElasticsearchClient,
|
||||
agentId: string
|
||||
) {
|
||||
const fleetServerEnabled = appContextService.getConfig()?.agents?.fleetServerEnabled;
|
||||
return fleetServerEnabled
|
||||
? crudServiceFleetServer.getAgent(esClient, agentId)
|
||||
: crudServiceSO.getAgent(soClient, agentId);
|
||||
}
|
||||
|
||||
export async function getAgents(soClient: SavedObjectsClientContract, agentIds: string[]) {
|
||||
|
@ -187,31 +120,13 @@ export async function updateAgent(
|
|||
});
|
||||
}
|
||||
|
||||
export async function deleteAgent(soClient: SavedObjectsClientContract, agentId: string) {
|
||||
const agent = await getAgent(soClient, agentId);
|
||||
if (agent.type === 'EPHEMERAL') {
|
||||
// Delete events
|
||||
let more = true;
|
||||
while (more === true) {
|
||||
const { saved_objects: events } = await soClient.find<AgentEventSOAttributes>({
|
||||
type: AGENT_EVENT_SAVED_OBJECT_TYPE,
|
||||
fields: ['id'],
|
||||
search: agentId,
|
||||
searchFields: ['agent_id'],
|
||||
perPage: 1000,
|
||||
});
|
||||
if (events.length === 0) {
|
||||
more = false;
|
||||
}
|
||||
for (const event of events) {
|
||||
await soClient.delete(AGENT_EVENT_SAVED_OBJECT_TYPE, event.id);
|
||||
}
|
||||
}
|
||||
await soClient.delete(AGENT_SAVED_OBJECT_TYPE, agentId);
|
||||
return;
|
||||
}
|
||||
|
||||
await soClient.update<AgentSOAttributes>(AGENT_SAVED_OBJECT_TYPE, agentId, {
|
||||
active: false,
|
||||
});
|
||||
export async function deleteAgent(
|
||||
soClient: SavedObjectsClientContract,
|
||||
esClient: ElasticsearchClient,
|
||||
agentId: string
|
||||
) {
|
||||
const fleetServerEnabled = appContextService.getConfig()?.agents?.fleetServerEnabled;
|
||||
return fleetServerEnabled
|
||||
? crudServiceFleetServer.deleteAgent(esClient, agentId)
|
||||
: crudServiceSO.deleteAgent(soClient, agentId);
|
||||
}
|
||||
|
|
197
x-pack/plugins/fleet/server/services/agents/crud_fleet_server.ts
Normal file
197
x-pack/plugins/fleet/server/services/agents/crud_fleet_server.ts
Normal file
|
@ -0,0 +1,197 @@
|
|||
/*
|
||||
* 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 Boom from '@hapi/boom';
|
||||
import { SavedObjectsClientContract, ElasticsearchClient } from 'src/core/server';
|
||||
|
||||
import { isAgentUpgradeable, SO_SEARCH_LIMIT } from '../../../common';
|
||||
import { AGENT_SAVED_OBJECT_TYPE, AGENTS_INDEX } from '../../constants';
|
||||
import { ESSearchHit } from '../../../../../typings/elasticsearch';
|
||||
import { AgentSOAttributes, Agent, ListWithKuery } from '../../types';
|
||||
import { escapeSearchQueryPhrase, normalizeKuery } from '../saved_object';
|
||||
import { savedObjectToAgent } from './saved_objects';
|
||||
import { searchHitToAgent } from './helpers';
|
||||
import { appContextService } from '../../services';
|
||||
|
||||
const ACTIVE_AGENT_CONDITION = 'active:true';
|
||||
const INACTIVE_AGENT_CONDITION = `NOT (${ACTIVE_AGENT_CONDITION})`;
|
||||
|
||||
function _joinFilters(filters: string[], operator = 'AND') {
|
||||
return filters.reduce((acc: string | undefined, filter) => {
|
||||
if (acc) {
|
||||
return `${acc} ${operator} (${filter})`;
|
||||
}
|
||||
|
||||
return `(${filter})`;
|
||||
}, undefined);
|
||||
}
|
||||
|
||||
function removeSOAttributes(kuery: string) {
|
||||
return kuery.replace(/attributes\./g, '').replace(/fleet-agents\./g, '');
|
||||
}
|
||||
|
||||
export async function listAgents(
|
||||
esClient: ElasticsearchClient,
|
||||
options: ListWithKuery & {
|
||||
showInactive: boolean;
|
||||
}
|
||||
): Promise<{
|
||||
agents: Agent[];
|
||||
total: number;
|
||||
page: number;
|
||||
perPage: number;
|
||||
}> {
|
||||
const {
|
||||
page = 1,
|
||||
perPage = 20,
|
||||
sortField = 'enrolled_at',
|
||||
sortOrder = 'desc',
|
||||
kuery,
|
||||
showInactive = false,
|
||||
showUpgradeable,
|
||||
} = options;
|
||||
const filters = [];
|
||||
|
||||
if (kuery && kuery !== '') {
|
||||
filters.push(removeSOAttributes(kuery));
|
||||
}
|
||||
|
||||
if (showInactive === false) {
|
||||
filters.push(ACTIVE_AGENT_CONDITION);
|
||||
}
|
||||
|
||||
const res = await esClient.search({
|
||||
index: AGENTS_INDEX,
|
||||
from: (page - 1) * perPage,
|
||||
size: perPage,
|
||||
sort: `${sortField}:${sortOrder}`,
|
||||
track_total_hits: true,
|
||||
q: _joinFilters(filters),
|
||||
});
|
||||
|
||||
let agentResults: Agent[] = res.body.hits.hits.map(searchHitToAgent);
|
||||
let total = res.body.hits.total.value;
|
||||
|
||||
// filtering for a range on the version string will not work,
|
||||
// nor does filtering on a flattened field (local_metadata), so filter here
|
||||
if (showUpgradeable) {
|
||||
agentResults = agentResults.filter((agent) =>
|
||||
isAgentUpgradeable(agent, appContextService.getKibanaVersion())
|
||||
);
|
||||
total = agentResults.length;
|
||||
}
|
||||
|
||||
return {
|
||||
agents: res.body.hits.hits.map(searchHitToAgent),
|
||||
total,
|
||||
page,
|
||||
perPage,
|
||||
};
|
||||
}
|
||||
|
||||
export async function listAllAgents(
|
||||
esClient: ElasticsearchClient,
|
||||
options: Omit<ListWithKuery, 'page' | 'perPage'> & {
|
||||
showInactive: boolean;
|
||||
}
|
||||
): Promise<{
|
||||
agents: Agent[];
|
||||
total: number;
|
||||
}> {
|
||||
const res = await listAgents(esClient, { ...options, page: 1, perPage: SO_SEARCH_LIMIT });
|
||||
|
||||
return {
|
||||
agents: res.agents,
|
||||
total: res.total,
|
||||
};
|
||||
}
|
||||
|
||||
export async function countInactiveAgents(
|
||||
esClient: ElasticsearchClient,
|
||||
options: Pick<ListWithKuery, 'kuery'>
|
||||
): Promise<number> {
|
||||
const { kuery } = options;
|
||||
const filters = [INACTIVE_AGENT_CONDITION];
|
||||
|
||||
if (kuery && kuery !== '') {
|
||||
filters.push(normalizeKuery(AGENT_SAVED_OBJECT_TYPE, kuery));
|
||||
}
|
||||
|
||||
const res = await esClient.search({
|
||||
index: AGENTS_INDEX,
|
||||
size: 0,
|
||||
track_total_hits: true,
|
||||
q: _joinFilters(filters),
|
||||
});
|
||||
|
||||
return res.body.hits.total.value;
|
||||
}
|
||||
|
||||
export async function getAgent(esClient: ElasticsearchClient, agentId: string) {
|
||||
const agentHit = await esClient.get<ESSearchHit<AgentSOAttributes>>({
|
||||
index: AGENTS_INDEX,
|
||||
id: agentId,
|
||||
});
|
||||
const agent = searchHitToAgent(agentHit.body);
|
||||
|
||||
return agent;
|
||||
}
|
||||
|
||||
export async function getAgents(soClient: SavedObjectsClientContract, agentIds: string[]) {
|
||||
const agentSOs = await soClient.bulkGet<AgentSOAttributes>(
|
||||
agentIds.map((agentId) => ({
|
||||
id: agentId,
|
||||
type: AGENT_SAVED_OBJECT_TYPE,
|
||||
}))
|
||||
);
|
||||
const agents = agentSOs.saved_objects.map(savedObjectToAgent);
|
||||
return agents;
|
||||
}
|
||||
|
||||
export async function getAgentByAccessAPIKeyId(
|
||||
soClient: SavedObjectsClientContract,
|
||||
accessAPIKeyId: string
|
||||
): Promise<Agent> {
|
||||
const response = await soClient.find<AgentSOAttributes>({
|
||||
type: AGENT_SAVED_OBJECT_TYPE,
|
||||
searchFields: ['access_api_key_id'],
|
||||
search: escapeSearchQueryPhrase(accessAPIKeyId),
|
||||
});
|
||||
const [agent] = response.saved_objects.map(savedObjectToAgent);
|
||||
|
||||
if (!agent) {
|
||||
throw Boom.notFound('Agent not found');
|
||||
}
|
||||
if (agent.access_api_key_id !== accessAPIKeyId) {
|
||||
throw new Error('Agent api key id is not matching');
|
||||
}
|
||||
if (!agent.active) {
|
||||
throw Boom.forbidden('Agent inactive');
|
||||
}
|
||||
|
||||
return agent;
|
||||
}
|
||||
|
||||
export async function updateAgent(
|
||||
soClient: SavedObjectsClientContract,
|
||||
agentId: string,
|
||||
data: {
|
||||
userProvidedMetatada: any;
|
||||
}
|
||||
) {
|
||||
await soClient.update<AgentSOAttributes>(AGENT_SAVED_OBJECT_TYPE, agentId, {
|
||||
user_provided_metadata: data.userProvidedMetatada,
|
||||
});
|
||||
}
|
||||
|
||||
export async function deleteAgent(esClient: ElasticsearchClient, agentId: string) {
|
||||
await esClient.update({
|
||||
id: agentId,
|
||||
index: AGENT_SAVED_OBJECT_TYPE,
|
||||
body: {
|
||||
active: false,
|
||||
},
|
||||
});
|
||||
}
|
195
x-pack/plugins/fleet/server/services/agents/crud_so.ts
Normal file
195
x-pack/plugins/fleet/server/services/agents/crud_so.ts
Normal file
|
@ -0,0 +1,195 @@
|
|||
/*
|
||||
* 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 Boom from '@hapi/boom';
|
||||
import { SavedObjectsClientContract } from 'src/core/server';
|
||||
|
||||
import { isAgentUpgradeable } from '../../../common';
|
||||
import { AGENT_SAVED_OBJECT_TYPE } from '../../constants';
|
||||
import { AgentSOAttributes, Agent, ListWithKuery } from '../../types';
|
||||
import { escapeSearchQueryPhrase, normalizeKuery, findAllSOs } from '../saved_object';
|
||||
import { savedObjectToAgent } from './saved_objects';
|
||||
import { appContextService } from '../../services';
|
||||
|
||||
const ACTIVE_AGENT_CONDITION = `${AGENT_SAVED_OBJECT_TYPE}.attributes.active:true`;
|
||||
const INACTIVE_AGENT_CONDITION = `NOT (${ACTIVE_AGENT_CONDITION})`;
|
||||
|
||||
function _joinFilters(filters: string[], operator = 'AND') {
|
||||
return filters.reduce((acc: string | undefined, filter) => {
|
||||
if (acc) {
|
||||
return `${acc} ${operator} (${filter})`;
|
||||
}
|
||||
|
||||
return `(${filter})`;
|
||||
}, undefined);
|
||||
}
|
||||
|
||||
export async function listAgents(
|
||||
soClient: SavedObjectsClientContract,
|
||||
options: ListWithKuery & {
|
||||
showInactive: boolean;
|
||||
}
|
||||
): Promise<{
|
||||
agents: Agent[];
|
||||
total: number;
|
||||
page: number;
|
||||
perPage: number;
|
||||
}> {
|
||||
const {
|
||||
page = 1,
|
||||
perPage = 20,
|
||||
sortField = 'enrolled_at',
|
||||
sortOrder = 'desc',
|
||||
kuery,
|
||||
showInactive = false,
|
||||
showUpgradeable,
|
||||
} = options;
|
||||
const filters = [];
|
||||
|
||||
if (kuery && kuery !== '') {
|
||||
filters.push(normalizeKuery(AGENT_SAVED_OBJECT_TYPE, kuery));
|
||||
}
|
||||
|
||||
if (showInactive === false) {
|
||||
filters.push(ACTIVE_AGENT_CONDITION);
|
||||
}
|
||||
|
||||
let { saved_objects: agentSOs, total } = await soClient.find<AgentSOAttributes>({
|
||||
type: AGENT_SAVED_OBJECT_TYPE,
|
||||
filter: _joinFilters(filters),
|
||||
sortField,
|
||||
sortOrder,
|
||||
page,
|
||||
perPage,
|
||||
});
|
||||
// filtering for a range on the version string will not work,
|
||||
// nor does filtering on a flattened field (local_metadata), so filter here
|
||||
if (showUpgradeable) {
|
||||
agentSOs = agentSOs.filter((agent) =>
|
||||
isAgentUpgradeable(savedObjectToAgent(agent), appContextService.getKibanaVersion())
|
||||
);
|
||||
total = agentSOs.length;
|
||||
}
|
||||
|
||||
return {
|
||||
agents: agentSOs.map(savedObjectToAgent),
|
||||
total,
|
||||
page,
|
||||
perPage,
|
||||
};
|
||||
}
|
||||
|
||||
export async function listAllAgents(
|
||||
soClient: SavedObjectsClientContract,
|
||||
options: Omit<ListWithKuery, 'page' | 'perPage'> & {
|
||||
showInactive: boolean;
|
||||
}
|
||||
): Promise<{
|
||||
agents: Agent[];
|
||||
total: number;
|
||||
}> {
|
||||
const { sortField = 'enrolled_at', sortOrder = 'desc', kuery, showInactive = false } = options;
|
||||
const filters = [];
|
||||
|
||||
if (kuery && kuery !== '') {
|
||||
filters.push(normalizeKuery(AGENT_SAVED_OBJECT_TYPE, kuery));
|
||||
}
|
||||
|
||||
if (showInactive === false) {
|
||||
filters.push(ACTIVE_AGENT_CONDITION);
|
||||
}
|
||||
|
||||
const { saved_objects: agentSOs, total } = await findAllSOs<AgentSOAttributes>(soClient, {
|
||||
type: AGENT_SAVED_OBJECT_TYPE,
|
||||
kuery: _joinFilters(filters),
|
||||
sortField,
|
||||
sortOrder,
|
||||
});
|
||||
|
||||
return {
|
||||
agents: agentSOs.map(savedObjectToAgent),
|
||||
total,
|
||||
};
|
||||
}
|
||||
|
||||
export async function countInactiveAgents(
|
||||
soClient: SavedObjectsClientContract,
|
||||
options: Pick<ListWithKuery, 'kuery'>
|
||||
): Promise<number> {
|
||||
const { kuery } = options;
|
||||
const filters = [INACTIVE_AGENT_CONDITION];
|
||||
|
||||
if (kuery && kuery !== '') {
|
||||
filters.push(normalizeKuery(AGENT_SAVED_OBJECT_TYPE, kuery));
|
||||
}
|
||||
|
||||
const { total } = await soClient.find<AgentSOAttributes>({
|
||||
type: AGENT_SAVED_OBJECT_TYPE,
|
||||
filter: _joinFilters(filters),
|
||||
perPage: 0,
|
||||
});
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
export async function getAgent(soClient: SavedObjectsClientContract, agentId: string) {
|
||||
const agent = savedObjectToAgent(
|
||||
await soClient.get<AgentSOAttributes>(AGENT_SAVED_OBJECT_TYPE, agentId)
|
||||
);
|
||||
return agent;
|
||||
}
|
||||
|
||||
export async function getAgents(soClient: SavedObjectsClientContract, agentIds: string[]) {
|
||||
const agentSOs = await soClient.bulkGet<AgentSOAttributes>(
|
||||
agentIds.map((agentId) => ({
|
||||
id: agentId,
|
||||
type: AGENT_SAVED_OBJECT_TYPE,
|
||||
}))
|
||||
);
|
||||
const agents = agentSOs.saved_objects.map(savedObjectToAgent);
|
||||
return agents;
|
||||
}
|
||||
|
||||
export async function getAgentByAccessAPIKeyId(
|
||||
soClient: SavedObjectsClientContract,
|
||||
accessAPIKeyId: string
|
||||
): Promise<Agent> {
|
||||
const response = await soClient.find<AgentSOAttributes>({
|
||||
type: AGENT_SAVED_OBJECT_TYPE,
|
||||
searchFields: ['access_api_key_id'],
|
||||
search: escapeSearchQueryPhrase(accessAPIKeyId),
|
||||
});
|
||||
const [agent] = response.saved_objects.map(savedObjectToAgent);
|
||||
|
||||
if (!agent) {
|
||||
throw Boom.notFound('Agent not found');
|
||||
}
|
||||
if (agent.access_api_key_id !== accessAPIKeyId) {
|
||||
throw new Error('Agent api key id is not matching');
|
||||
}
|
||||
if (!agent.active) {
|
||||
throw Boom.forbidden('Agent inactive');
|
||||
}
|
||||
|
||||
return agent;
|
||||
}
|
||||
|
||||
export async function updateAgent(
|
||||
soClient: SavedObjectsClientContract,
|
||||
agentId: string,
|
||||
data: {
|
||||
userProvidedMetatada: any;
|
||||
}
|
||||
) {
|
||||
await soClient.update<AgentSOAttributes>(AGENT_SAVED_OBJECT_TYPE, agentId, {
|
||||
user_provided_metadata: data.userProvidedMetatada,
|
||||
});
|
||||
}
|
||||
|
||||
export async function deleteAgent(soClient: SavedObjectsClientContract, agentId: string) {
|
||||
await soClient.update<AgentSOAttributes>(AGENT_SAVED_OBJECT_TYPE, agentId, {
|
||||
active: false,
|
||||
});
|
||||
}
|
21
x-pack/plugins/fleet/server/services/agents/helpers.ts
Normal file
21
x-pack/plugins/fleet/server/services/agents/helpers.ts
Normal file
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* 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 { ESSearchHit } from '../../../../../typings/elasticsearch';
|
||||
import { Agent, AgentSOAttributes } from '../../types';
|
||||
|
||||
export function searchHitToAgent(hit: ESSearchHit<AgentSOAttributes>): Agent {
|
||||
return {
|
||||
id: hit._id,
|
||||
...hit._source,
|
||||
current_error_events: hit._source.current_error_events
|
||||
? JSON.parse(hit._source.current_error_events)
|
||||
: [],
|
||||
access_api_key: undefined,
|
||||
status: undefined,
|
||||
packages: hit._source.packages ?? [],
|
||||
};
|
||||
}
|
|
@ -4,7 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { SavedObjectsClientContract } from 'kibana/server';
|
||||
import { SavedObjectsClientContract, ElasticsearchClient } from 'kibana/server';
|
||||
import Boom from '@hapi/boom';
|
||||
import { AGENT_SAVED_OBJECT_TYPE } from '../../constants';
|
||||
import { AgentSOAttributes } from '../../types';
|
||||
|
@ -14,6 +14,7 @@ import { createAgentAction, bulkCreateAgentActions } from './actions';
|
|||
|
||||
export async function reassignAgent(
|
||||
soClient: SavedObjectsClientContract,
|
||||
esClient: ElasticsearchClient,
|
||||
agentId: string,
|
||||
newAgentPolicyId: string
|
||||
) {
|
||||
|
@ -36,6 +37,7 @@ export async function reassignAgent(
|
|||
|
||||
export async function reassignAgents(
|
||||
soClient: SavedObjectsClientContract,
|
||||
esClient: ElasticsearchClient,
|
||||
options:
|
||||
| {
|
||||
agentIds: string[];
|
||||
|
@ -55,7 +57,7 @@ export async function reassignAgents(
|
|||
'agentIds' in options
|
||||
? await getAgents(soClient, options.agentIds)
|
||||
: (
|
||||
await listAllAgents(soClient, {
|
||||
await listAllAgents(soClient, esClient, {
|
||||
kuery: options.kuery,
|
||||
showInactive: false,
|
||||
})
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { savedObjectsClientMock } from 'src/core/server/mocks';
|
||||
import { elasticsearchServiceMock, savedObjectsClientMock } from 'src/core/server/mocks';
|
||||
import { getAgentStatusById } from './status';
|
||||
import { AGENT_TYPE_PERMANENT } from '../../../common/constants';
|
||||
import { AgentSOAttributes } from '../../../common/types/models';
|
||||
|
@ -13,6 +13,7 @@ import { SavedObject } from 'kibana/server';
|
|||
describe('Agent status service', () => {
|
||||
it('should return inactive when agent is not active', async () => {
|
||||
const mockSavedObjectsClient = savedObjectsClientMock.create();
|
||||
const mockElasticsearchClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
|
||||
mockSavedObjectsClient.get = jest.fn().mockReturnValue({
|
||||
id: 'id',
|
||||
type: AGENT_TYPE_PERMANENT,
|
||||
|
@ -22,12 +23,13 @@ describe('Agent status service', () => {
|
|||
user_provided_metadata: {},
|
||||
},
|
||||
} as SavedObject<AgentSOAttributes>);
|
||||
const status = await getAgentStatusById(mockSavedObjectsClient, 'id');
|
||||
const status = await getAgentStatusById(mockSavedObjectsClient, mockElasticsearchClient, 'id');
|
||||
expect(status).toEqual('inactive');
|
||||
});
|
||||
|
||||
it('should return online when agent is active', async () => {
|
||||
const mockSavedObjectsClient = savedObjectsClientMock.create();
|
||||
const mockElasticsearchClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
|
||||
mockSavedObjectsClient.get = jest.fn().mockReturnValue({
|
||||
id: 'id',
|
||||
type: AGENT_TYPE_PERMANENT,
|
||||
|
@ -38,12 +40,13 @@ describe('Agent status service', () => {
|
|||
user_provided_metadata: {},
|
||||
},
|
||||
} as SavedObject<AgentSOAttributes>);
|
||||
const status = await getAgentStatusById(mockSavedObjectsClient, 'id');
|
||||
const status = await getAgentStatusById(mockSavedObjectsClient, mockElasticsearchClient, 'id');
|
||||
expect(status).toEqual('online');
|
||||
});
|
||||
|
||||
it('should return enrolling when agent is active but never checkin', async () => {
|
||||
const mockSavedObjectsClient = savedObjectsClientMock.create();
|
||||
const mockElasticsearchClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
|
||||
mockSavedObjectsClient.get = jest.fn().mockReturnValue({
|
||||
id: 'id',
|
||||
type: AGENT_TYPE_PERMANENT,
|
||||
|
@ -53,12 +56,13 @@ describe('Agent status service', () => {
|
|||
user_provided_metadata: {},
|
||||
},
|
||||
} as SavedObject<AgentSOAttributes>);
|
||||
const status = await getAgentStatusById(mockSavedObjectsClient, 'id');
|
||||
const status = await getAgentStatusById(mockSavedObjectsClient, mockElasticsearchClient, 'id');
|
||||
expect(status).toEqual('enrolling');
|
||||
});
|
||||
|
||||
it('should return unenrolling when agent is unenrolling', async () => {
|
||||
const mockSavedObjectsClient = savedObjectsClientMock.create();
|
||||
const mockElasticsearchClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
|
||||
mockSavedObjectsClient.get = jest.fn().mockReturnValue({
|
||||
id: 'id',
|
||||
type: AGENT_TYPE_PERMANENT,
|
||||
|
@ -70,7 +74,7 @@ describe('Agent status service', () => {
|
|||
user_provided_metadata: {},
|
||||
},
|
||||
} as SavedObject<AgentSOAttributes>);
|
||||
const status = await getAgentStatusById(mockSavedObjectsClient, 'id');
|
||||
const status = await getAgentStatusById(mockSavedObjectsClient, mockElasticsearchClient, 'id');
|
||||
expect(status).toEqual('unenrolling');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { SavedObjectsClientContract } from 'src/core/server';
|
||||
import { ElasticsearchClient, SavedObjectsClientContract } from 'src/core/server';
|
||||
import pMap from 'p-map';
|
||||
import { getAgent, listAgents } from './crud';
|
||||
import { AGENT_EVENT_SAVED_OBJECT_TYPE, AGENT_SAVED_OBJECT_TYPE } from '../../constants';
|
||||
|
@ -14,9 +14,10 @@ import { AgentStatusKueryHelper } from '../../../common/services';
|
|||
|
||||
export async function getAgentStatusById(
|
||||
soClient: SavedObjectsClientContract,
|
||||
esClient: ElasticsearchClient,
|
||||
agentId: string
|
||||
): Promise<AgentStatus> {
|
||||
const agent = await getAgent(soClient, agentId);
|
||||
const agent = await getAgent(soClient, esClient, agentId);
|
||||
return AgentStatusKueryHelper.getAgentStatus(agent);
|
||||
}
|
||||
|
||||
|
@ -36,6 +37,7 @@ function joinKuerys(...kuerys: Array<string | undefined>) {
|
|||
|
||||
export async function getAgentStatusForAgentPolicy(
|
||||
soClient: SavedObjectsClientContract,
|
||||
esClient: ElasticsearchClient,
|
||||
agentPolicyId?: string,
|
||||
filterKuery?: string
|
||||
) {
|
||||
|
@ -48,7 +50,7 @@ export async function getAgentStatusForAgentPolicy(
|
|||
AgentStatusKueryHelper.buildKueryForUpdatingAgents(),
|
||||
],
|
||||
(kuery) =>
|
||||
listAgents(soClient, {
|
||||
listAgents(soClient, esClient, {
|
||||
showInactive: false,
|
||||
perPage: 0,
|
||||
page: 1,
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import { SavedObjectsClientContract } from 'src/core/server';
|
||||
import { ElasticsearchClient, SavedObjectsClientContract } from 'src/core/server';
|
||||
import { AgentSOAttributes } from '../../types';
|
||||
import { AGENT_SAVED_OBJECT_TYPE } from '../../constants';
|
||||
import { getAgent } from './crud';
|
||||
|
@ -25,6 +25,7 @@ export async function unenrollAgent(soClient: SavedObjectsClientContract, agentI
|
|||
|
||||
export async function unenrollAgents(
|
||||
soClient: SavedObjectsClientContract,
|
||||
esClient: ElasticsearchClient,
|
||||
options:
|
||||
| {
|
||||
agentIds: string[];
|
||||
|
@ -38,7 +39,7 @@ export async function unenrollAgents(
|
|||
'agentIds' in options
|
||||
? await getAgents(soClient, options.agentIds)
|
||||
: (
|
||||
await listAllAgents(soClient, {
|
||||
await listAllAgents(soClient, esClient, {
|
||||
kuery: options.kuery,
|
||||
showInactive: false,
|
||||
})
|
||||
|
@ -70,8 +71,12 @@ export async function unenrollAgents(
|
|||
);
|
||||
}
|
||||
|
||||
export async function forceUnenrollAgent(soClient: SavedObjectsClientContract, agentId: string) {
|
||||
const agent = await getAgent(soClient, agentId);
|
||||
export async function forceUnenrollAgent(
|
||||
soClient: SavedObjectsClientContract,
|
||||
esClient: ElasticsearchClient,
|
||||
agentId: string
|
||||
) {
|
||||
const agent = await getAgent(soClient, esClient, agentId);
|
||||
|
||||
await Promise.all([
|
||||
agent.access_api_key_id
|
||||
|
@ -90,6 +95,7 @@ export async function forceUnenrollAgent(soClient: SavedObjectsClientContract, a
|
|||
|
||||
export async function forceUnenrollAgents(
|
||||
soClient: SavedObjectsClientContract,
|
||||
esClient: ElasticsearchClient,
|
||||
options:
|
||||
| {
|
||||
agentIds: string[];
|
||||
|
@ -103,7 +109,7 @@ export async function forceUnenrollAgents(
|
|||
'agentIds' in options
|
||||
? await getAgents(soClient, options.agentIds)
|
||||
: (
|
||||
await listAllAgents(soClient, {
|
||||
await listAllAgents(soClient, esClient, {
|
||||
kuery: options.kuery,
|
||||
showInactive: false,
|
||||
})
|
||||
|
|
|
@ -4,19 +4,20 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { SavedObjectsClientContract } from 'src/core/server';
|
||||
import { ElasticsearchClient, SavedObjectsClientContract } from 'src/core/server';
|
||||
import { listAgents } from './crud';
|
||||
import { AGENT_SAVED_OBJECT_TYPE } from '../../constants';
|
||||
import { unenrollAgent } from './unenroll';
|
||||
|
||||
export async function unenrollForAgentPolicyId(
|
||||
soClient: SavedObjectsClientContract,
|
||||
esClient: ElasticsearchClient,
|
||||
policyId: string
|
||||
) {
|
||||
let hasMore = true;
|
||||
let page = 1;
|
||||
while (hasMore) {
|
||||
const { agents } = await listAgents(soClient, {
|
||||
const { agents } = await listAgents(soClient, esClient, {
|
||||
kuery: `${AGENT_SAVED_OBJECT_TYPE}.policy_id:"${policyId}"`,
|
||||
page: page++,
|
||||
perPage: 1000,
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { SavedObjectsClientContract } from 'src/core/server';
|
||||
import { ElasticsearchClient, SavedObjectsClientContract } from 'src/core/server';
|
||||
import { AgentSOAttributes, AgentAction, AgentActionSOAttributes } from '../../types';
|
||||
import { AGENT_ACTION_SAVED_OBJECT_TYPE, AGENT_SAVED_OBJECT_TYPE } from '../../constants';
|
||||
import { bulkCreateAgentActions, createAgentAction } from './actions';
|
||||
|
@ -59,6 +59,7 @@ export async function ackAgentUpgraded(
|
|||
|
||||
export async function sendUpgradeAgentsActions(
|
||||
soClient: SavedObjectsClientContract,
|
||||
esClient: ElasticsearchClient,
|
||||
options:
|
||||
| {
|
||||
agentIds: string[];
|
||||
|
@ -79,7 +80,7 @@ export async function sendUpgradeAgentsActions(
|
|||
'agentIds' in options
|
||||
? await getAgents(soClient, options.agentIds)
|
||||
: (
|
||||
await listAllAgents(soClient, {
|
||||
await listAllAgents(soClient, esClient, {
|
||||
kuery: options.kuery,
|
||||
showInactive: false,
|
||||
})
|
||||
|
|
|
@ -4,18 +4,15 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import uuid from 'uuid';
|
||||
import Boom from '@hapi/boom';
|
||||
import { SavedObjectsClientContract, SavedObject } from 'src/core/server';
|
||||
import { EnrollmentAPIKey, EnrollmentAPIKeySOAttributes } from '../../types';
|
||||
import { ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE } from '../../constants';
|
||||
import { createAPIKey, invalidateAPIKeys } from './security';
|
||||
import { agentPolicyService } from '../agent_policy';
|
||||
import { SavedObjectsClientContract, ElasticsearchClient } from 'src/core/server';
|
||||
import { EnrollmentAPIKey } from '../../types';
|
||||
import { appContextService } from '../app_context';
|
||||
import { normalizeKuery } from '../saved_object';
|
||||
import * as enrollmentApiKeyServiceSO from './enrollment_api_key_so';
|
||||
import * as enrollmentApiKeyServiceFleetServer from './enrollment_api_key_fleet_server';
|
||||
|
||||
export async function listEnrollmentApiKeys(
|
||||
soClient: SavedObjectsClientContract,
|
||||
esClient: ElasticsearchClient,
|
||||
options: {
|
||||
page?: number;
|
||||
perPage?: number;
|
||||
|
@ -23,39 +20,23 @@ export async function listEnrollmentApiKeys(
|
|||
showInactive?: boolean;
|
||||
}
|
||||
): Promise<{ items: EnrollmentAPIKey[]; total: any; page: any; perPage: any }> {
|
||||
const { page = 1, perPage = 20, kuery } = options;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
const { saved_objects, total } = await soClient.find<EnrollmentAPIKeySOAttributes>({
|
||||
type: ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE,
|
||||
page,
|
||||
perPage,
|
||||
sortField: 'created_at',
|
||||
sortOrder: 'desc',
|
||||
filter:
|
||||
kuery && kuery !== ''
|
||||
? normalizeKuery(ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE, kuery)
|
||||
: undefined,
|
||||
});
|
||||
|
||||
const items = saved_objects.map(savedObjectToEnrollmentApiKey);
|
||||
|
||||
return {
|
||||
items,
|
||||
total,
|
||||
page,
|
||||
perPage,
|
||||
};
|
||||
if (appContextService.getConfig()?.agents?.fleetServerEnabled === true) {
|
||||
return enrollmentApiKeyServiceFleetServer.listEnrollmentApiKeys(esClient, options);
|
||||
} else {
|
||||
return enrollmentApiKeyServiceSO.listEnrollmentApiKeys(soClient, options);
|
||||
}
|
||||
}
|
||||
|
||||
export async function getEnrollmentAPIKey(soClient: SavedObjectsClientContract, id: string) {
|
||||
const so = await appContextService
|
||||
.getEncryptedSavedObjects()
|
||||
.getDecryptedAsInternalUser<EnrollmentAPIKeySOAttributes>(
|
||||
ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE,
|
||||
id
|
||||
);
|
||||
return savedObjectToEnrollmentApiKey(so);
|
||||
export async function getEnrollmentAPIKey(
|
||||
soClient: SavedObjectsClientContract,
|
||||
esClient: ElasticsearchClient,
|
||||
id: string
|
||||
) {
|
||||
if (appContextService.getConfig()?.agents?.fleetServerEnabled === true) {
|
||||
return enrollmentApiKeyServiceFleetServer.getEnrollmentAPIKey(esClient, id);
|
||||
} else {
|
||||
return enrollmentApiKeyServiceSO.getEnrollmentAPIKey(soClient, id);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -63,112 +44,37 @@ export async function getEnrollmentAPIKey(soClient: SavedObjectsClientContract,
|
|||
* @param soClient
|
||||
* @param id
|
||||
*/
|
||||
export async function deleteEnrollmentApiKey(soClient: SavedObjectsClientContract, id: string) {
|
||||
const enrollmentApiKey = await getEnrollmentAPIKey(soClient, id);
|
||||
|
||||
await invalidateAPIKeys(soClient, [enrollmentApiKey.api_key_id]);
|
||||
|
||||
await soClient.update(ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE, id, {
|
||||
active: false,
|
||||
});
|
||||
export async function deleteEnrollmentApiKey(
|
||||
soClient: SavedObjectsClientContract,
|
||||
esClient: ElasticsearchClient,
|
||||
id: string
|
||||
) {
|
||||
if (appContextService.getConfig()?.agents?.fleetServerEnabled === true) {
|
||||
return enrollmentApiKeyServiceFleetServer.deleteEnrollmentApiKey(soClient, esClient, id);
|
||||
} else {
|
||||
return enrollmentApiKeyServiceSO.deleteEnrollmentApiKey(soClient, id);
|
||||
}
|
||||
}
|
||||
|
||||
export async function deleteEnrollmentApiKeyForAgentPolicyId(
|
||||
soClient: SavedObjectsClientContract,
|
||||
agentPolicyId: string
|
||||
) {
|
||||
let hasMore = true;
|
||||
let page = 1;
|
||||
while (hasMore) {
|
||||
const { items } = await listEnrollmentApiKeys(soClient, {
|
||||
page: page++,
|
||||
perPage: 100,
|
||||
kuery: `${ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE}.policy_id:${agentPolicyId}`,
|
||||
});
|
||||
|
||||
if (items.length === 0) {
|
||||
hasMore = false;
|
||||
}
|
||||
|
||||
for (const apiKey of items) {
|
||||
await deleteEnrollmentApiKey(soClient, apiKey.id);
|
||||
}
|
||||
}
|
||||
return enrollmentApiKeyServiceSO.deleteEnrollmentApiKeyForAgentPolicyId(soClient, agentPolicyId);
|
||||
}
|
||||
|
||||
export async function generateEnrollmentAPIKey(
|
||||
soClient: SavedObjectsClientContract,
|
||||
esClient: ElasticsearchClient,
|
||||
data: {
|
||||
name?: string;
|
||||
expiration?: string;
|
||||
agentPolicyId?: string;
|
||||
}
|
||||
) {
|
||||
const id = uuid.v4();
|
||||
const { name: providedKeyName } = data;
|
||||
if (data.agentPolicyId) {
|
||||
await validateAgentPolicyId(soClient, data.agentPolicyId);
|
||||
}
|
||||
const agentPolicyId =
|
||||
data.agentPolicyId ?? (await agentPolicyService.getDefaultAgentPolicyId(soClient));
|
||||
const name = providedKeyName ? `${providedKeyName} (${id})` : id;
|
||||
const key = await createAPIKey(soClient, name, {
|
||||
// Useless role to avoid to have the privilege of the user that created the key
|
||||
'fleet-apikey-enroll': {
|
||||
cluster: [],
|
||||
applications: [
|
||||
{
|
||||
application: '.fleet',
|
||||
privileges: ['no-privileges'],
|
||||
resources: ['*'],
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
if (!key) {
|
||||
throw new Error('Unable to create an enrollment api key');
|
||||
}
|
||||
|
||||
const apiKey = Buffer.from(`${key.id}:${key.api_key}`).toString('base64');
|
||||
|
||||
const so = await soClient.create<EnrollmentAPIKeySOAttributes>(
|
||||
ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE,
|
||||
{
|
||||
active: true,
|
||||
api_key_id: key.id,
|
||||
api_key: apiKey,
|
||||
name,
|
||||
policy_id: agentPolicyId,
|
||||
created_at: new Date().toISOString(),
|
||||
}
|
||||
);
|
||||
|
||||
return getEnrollmentAPIKey(soClient, so.id);
|
||||
}
|
||||
|
||||
async function validateAgentPolicyId(soClient: SavedObjectsClientContract, agentPolicyId: string) {
|
||||
try {
|
||||
await agentPolicyService.get(soClient, agentPolicyId);
|
||||
} catch (e) {
|
||||
if (e.isBoom && e.output.statusCode === 404) {
|
||||
throw Boom.badRequest(`Agent policy ${agentPolicyId} does not exist`);
|
||||
}
|
||||
throw e;
|
||||
if (appContextService.getConfig()?.agents?.fleetServerEnabled === true) {
|
||||
return enrollmentApiKeyServiceFleetServer.generateEnrollmentAPIKey(soClient, esClient, data);
|
||||
} else {
|
||||
return enrollmentApiKeyServiceSO.generateEnrollmentAPIKey(soClient, data);
|
||||
}
|
||||
}
|
||||
|
||||
function savedObjectToEnrollmentApiKey({
|
||||
error,
|
||||
attributes,
|
||||
id,
|
||||
}: SavedObject<EnrollmentAPIKeySOAttributes>): EnrollmentAPIKey {
|
||||
if (error) {
|
||||
throw new Error(error.message);
|
||||
}
|
||||
|
||||
return {
|
||||
id,
|
||||
...attributes,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -0,0 +1,205 @@
|
|||
/*
|
||||
* 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 uuid from 'uuid';
|
||||
import Boom from '@hapi/boom';
|
||||
import { ResponseError } from '@elastic/elasticsearch/lib/errors';
|
||||
import { SavedObjectsClientContract, ElasticsearchClient } from 'src/core/server';
|
||||
import { EnrollmentAPIKey, FleetServerEnrollmentAPIKey } from '../../types';
|
||||
import { ENROLLMENT_API_KEYS_INDEX } from '../../constants';
|
||||
import { createAPIKey, invalidateAPIKeys } from './security';
|
||||
import { agentPolicyService } from '../agent_policy';
|
||||
|
||||
// TODO Move these types to another file
|
||||
interface SearchResponse<T> {
|
||||
took: number;
|
||||
timed_out: boolean;
|
||||
_scroll_id?: string;
|
||||
hits: {
|
||||
total: {
|
||||
value: number;
|
||||
relation: string;
|
||||
};
|
||||
max_score: number;
|
||||
hits: Array<{
|
||||
_index: string;
|
||||
_type: string;
|
||||
_id: string;
|
||||
_score: number;
|
||||
_source: T;
|
||||
_version?: number;
|
||||
fields?: any;
|
||||
highlight?: any;
|
||||
inner_hits?: any;
|
||||
matched_queries?: string[];
|
||||
sort?: string[];
|
||||
}>;
|
||||
};
|
||||
}
|
||||
|
||||
type SearchHit<T> = SearchResponse<T>['hits']['hits'][0];
|
||||
|
||||
export async function listEnrollmentApiKeys(
|
||||
esClient: ElasticsearchClient,
|
||||
options: {
|
||||
page?: number;
|
||||
perPage?: number;
|
||||
kuery?: string;
|
||||
showInactive?: boolean;
|
||||
}
|
||||
): Promise<{ items: EnrollmentAPIKey[]; total: any; page: any; perPage: any }> {
|
||||
const { page = 1, perPage = 20, kuery } = options;
|
||||
|
||||
const res = await esClient.search<SearchResponse<FleetServerEnrollmentAPIKey>>({
|
||||
index: ENROLLMENT_API_KEYS_INDEX,
|
||||
from: (page - 1) * perPage,
|
||||
size: perPage,
|
||||
sort: 'created_at:desc',
|
||||
track_total_hits: true,
|
||||
q: kuery,
|
||||
});
|
||||
|
||||
const items = res.body.hits.hits.map(esDocToEnrollmentApiKey);
|
||||
|
||||
return {
|
||||
items,
|
||||
total: res.body.hits.total.value,
|
||||
page,
|
||||
perPage,
|
||||
};
|
||||
}
|
||||
|
||||
export async function getEnrollmentAPIKey(
|
||||
esClient: ElasticsearchClient,
|
||||
id: string
|
||||
): Promise<EnrollmentAPIKey> {
|
||||
try {
|
||||
const res = await esClient.get<SearchHit<FleetServerEnrollmentAPIKey>>({
|
||||
index: ENROLLMENT_API_KEYS_INDEX,
|
||||
id,
|
||||
});
|
||||
|
||||
return esDocToEnrollmentApiKey(res.body);
|
||||
} catch (e) {
|
||||
if (e instanceof ResponseError && e.statusCode === 404) {
|
||||
throw Boom.notFound(`Enrollment api key ${id} not found`);
|
||||
}
|
||||
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidate an api key and mark it as inactive
|
||||
* @param soClient
|
||||
* @param id
|
||||
*/
|
||||
export async function deleteEnrollmentApiKey(
|
||||
soClient: SavedObjectsClientContract,
|
||||
esClient: ElasticsearchClient,
|
||||
id: string
|
||||
) {
|
||||
const enrollmentApiKey = await getEnrollmentAPIKey(esClient, id);
|
||||
|
||||
await invalidateAPIKeys(soClient, [enrollmentApiKey.api_key_id]);
|
||||
|
||||
await esClient.update({
|
||||
index: ENROLLMENT_API_KEYS_INDEX,
|
||||
id,
|
||||
body: {
|
||||
doc: {
|
||||
active: false,
|
||||
},
|
||||
},
|
||||
refresh: 'wait_for',
|
||||
});
|
||||
}
|
||||
|
||||
export async function deleteEnrollmentApiKeyForAgentPolicyId(
|
||||
soClient: SavedObjectsClientContract,
|
||||
agentPolicyId: string
|
||||
) {
|
||||
throw new Error('NOT IMPLEMENTED');
|
||||
}
|
||||
|
||||
export async function generateEnrollmentAPIKey(
|
||||
soClient: SavedObjectsClientContract,
|
||||
esClient: ElasticsearchClient,
|
||||
data: {
|
||||
name?: string;
|
||||
expiration?: string;
|
||||
agentPolicyId?: string;
|
||||
}
|
||||
): Promise<EnrollmentAPIKey> {
|
||||
const id = uuid.v4();
|
||||
const { name: providedKeyName } = data;
|
||||
if (data.agentPolicyId) {
|
||||
await validateAgentPolicyId(soClient, data.agentPolicyId);
|
||||
}
|
||||
const agentPolicyId =
|
||||
data.agentPolicyId ?? (await agentPolicyService.getDefaultAgentPolicyId(soClient));
|
||||
const name = providedKeyName ? `${providedKeyName} (${id})` : id;
|
||||
const key = await createAPIKey(soClient, name, {
|
||||
// Useless role to avoid to have the privilege of the user that created the key
|
||||
'fleet-apikey-enroll': {
|
||||
cluster: [],
|
||||
applications: [
|
||||
{
|
||||
application: '.fleet',
|
||||
privileges: ['no-privileges'],
|
||||
resources: ['*'],
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
if (!key) {
|
||||
throw new Error('Unable to create an enrollment api key');
|
||||
}
|
||||
|
||||
const apiKey = Buffer.from(`${key.id}:${key.api_key}`).toString('base64');
|
||||
|
||||
const body = {
|
||||
active: true,
|
||||
api_key_id: key.id,
|
||||
api_key: apiKey,
|
||||
name,
|
||||
policy_id: agentPolicyId,
|
||||
created_at: new Date().toISOString(),
|
||||
};
|
||||
|
||||
const res = await esClient.create({
|
||||
index: ENROLLMENT_API_KEYS_INDEX,
|
||||
body,
|
||||
id,
|
||||
refresh: 'wait_for',
|
||||
});
|
||||
|
||||
return {
|
||||
id: res.body._id,
|
||||
...body,
|
||||
};
|
||||
}
|
||||
|
||||
async function validateAgentPolicyId(soClient: SavedObjectsClientContract, agentPolicyId: string) {
|
||||
try {
|
||||
await agentPolicyService.get(soClient, agentPolicyId);
|
||||
} catch (e) {
|
||||
if (e.isBoom && e.output.statusCode === 404) {
|
||||
throw Boom.badRequest(`Agent policy ${agentPolicyId} does not exist`);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
function esDocToEnrollmentApiKey(doc: SearchHit<FleetServerEnrollmentAPIKey>): EnrollmentAPIKey {
|
||||
return {
|
||||
id: doc._id,
|
||||
...doc._source,
|
||||
created_at: doc._source.created_at as string,
|
||||
active: doc._source.active || false,
|
||||
};
|
||||
}
|
|
@ -0,0 +1,174 @@
|
|||
/*
|
||||
* 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 uuid from 'uuid';
|
||||
import Boom from '@hapi/boom';
|
||||
import { SavedObjectsClientContract, SavedObject } from 'src/core/server';
|
||||
import { EnrollmentAPIKey, EnrollmentAPIKeySOAttributes } from '../../types';
|
||||
import { ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE } from '../../constants';
|
||||
import { createAPIKey, invalidateAPIKeys } from './security';
|
||||
import { agentPolicyService } from '../agent_policy';
|
||||
import { appContextService } from '../app_context';
|
||||
import { normalizeKuery } from '../saved_object';
|
||||
|
||||
export async function listEnrollmentApiKeys(
|
||||
soClient: SavedObjectsClientContract,
|
||||
options: {
|
||||
page?: number;
|
||||
perPage?: number;
|
||||
kuery?: string;
|
||||
showInactive?: boolean;
|
||||
}
|
||||
): Promise<{ items: EnrollmentAPIKey[]; total: any; page: any; perPage: any }> {
|
||||
const { page = 1, perPage = 20, kuery } = options;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
const { saved_objects, total } = await soClient.find<EnrollmentAPIKeySOAttributes>({
|
||||
type: ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE,
|
||||
page,
|
||||
perPage,
|
||||
sortField: 'created_at',
|
||||
sortOrder: 'desc',
|
||||
filter:
|
||||
kuery && kuery !== ''
|
||||
? normalizeKuery(ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE, kuery)
|
||||
: undefined,
|
||||
});
|
||||
|
||||
const items = saved_objects.map(savedObjectToEnrollmentApiKey);
|
||||
|
||||
return {
|
||||
items,
|
||||
total,
|
||||
page,
|
||||
perPage,
|
||||
};
|
||||
}
|
||||
|
||||
export async function getEnrollmentAPIKey(soClient: SavedObjectsClientContract, id: string) {
|
||||
const so = await appContextService
|
||||
.getEncryptedSavedObjects()
|
||||
.getDecryptedAsInternalUser<EnrollmentAPIKeySOAttributes>(
|
||||
ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE,
|
||||
id
|
||||
);
|
||||
return savedObjectToEnrollmentApiKey(so);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidate an api key and mark it as inactive
|
||||
* @param soClient
|
||||
* @param id
|
||||
*/
|
||||
export async function deleteEnrollmentApiKey(soClient: SavedObjectsClientContract, id: string) {
|
||||
const enrollmentApiKey = await getEnrollmentAPIKey(soClient, id);
|
||||
|
||||
await invalidateAPIKeys(soClient, [enrollmentApiKey.api_key_id]);
|
||||
|
||||
await soClient.update(ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE, id, {
|
||||
active: false,
|
||||
});
|
||||
}
|
||||
|
||||
export async function deleteEnrollmentApiKeyForAgentPolicyId(
|
||||
soClient: SavedObjectsClientContract,
|
||||
agentPolicyId: string
|
||||
) {
|
||||
let hasMore = true;
|
||||
let page = 1;
|
||||
while (hasMore) {
|
||||
const { items } = await listEnrollmentApiKeys(soClient, {
|
||||
page: page++,
|
||||
perPage: 100,
|
||||
kuery: `${ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE}.policy_id:${agentPolicyId}`,
|
||||
});
|
||||
|
||||
if (items.length === 0) {
|
||||
hasMore = false;
|
||||
}
|
||||
|
||||
for (const apiKey of items) {
|
||||
await deleteEnrollmentApiKey(soClient, apiKey.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function generateEnrollmentAPIKey(
|
||||
soClient: SavedObjectsClientContract,
|
||||
data: {
|
||||
name?: string;
|
||||
expiration?: string;
|
||||
agentPolicyId?: string;
|
||||
}
|
||||
) {
|
||||
const id = uuid.v4();
|
||||
const { name: providedKeyName } = data;
|
||||
if (data.agentPolicyId) {
|
||||
await validateAgentPolicyId(soClient, data.agentPolicyId);
|
||||
}
|
||||
const agentPolicyId =
|
||||
data.agentPolicyId ?? (await agentPolicyService.getDefaultAgentPolicyId(soClient));
|
||||
const name = providedKeyName ? `${providedKeyName} (${id})` : id;
|
||||
const key = await createAPIKey(soClient, name, {
|
||||
// Useless role to avoid to have the privilege of the user that created the key
|
||||
'fleet-apikey-enroll': {
|
||||
cluster: [],
|
||||
applications: [
|
||||
{
|
||||
application: '.fleet',
|
||||
privileges: ['no-privileges'],
|
||||
resources: ['*'],
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
if (!key) {
|
||||
throw new Error('Unable to create an enrollment api key');
|
||||
}
|
||||
|
||||
const apiKey = Buffer.from(`${key.id}:${key.api_key}`).toString('base64');
|
||||
|
||||
const so = await soClient.create<EnrollmentAPIKeySOAttributes>(
|
||||
ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE,
|
||||
{
|
||||
active: true,
|
||||
api_key_id: key.id,
|
||||
api_key: apiKey,
|
||||
name,
|
||||
policy_id: agentPolicyId,
|
||||
created_at: new Date().toISOString(),
|
||||
}
|
||||
);
|
||||
|
||||
return getEnrollmentAPIKey(soClient, so.id);
|
||||
}
|
||||
|
||||
async function validateAgentPolicyId(soClient: SavedObjectsClientContract, agentPolicyId: string) {
|
||||
try {
|
||||
await agentPolicyService.get(soClient, agentPolicyId);
|
||||
} catch (e) {
|
||||
if (e.isBoom && e.output.statusCode === 404) {
|
||||
throw Boom.badRequest(`Agent policy ${agentPolicyId} does not exist`);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
function savedObjectToEnrollmentApiKey({
|
||||
error,
|
||||
attributes,
|
||||
id,
|
||||
}: SavedObject<EnrollmentAPIKeySOAttributes>): EnrollmentAPIKey {
|
||||
if (error) {
|
||||
throw new Error(error.message);
|
||||
}
|
||||
|
||||
return {
|
||||
id,
|
||||
...attributes,
|
||||
};
|
||||
}
|
|
@ -5,7 +5,13 @@
|
|||
*/
|
||||
import { BehaviorSubject, Observable } from 'rxjs';
|
||||
import { first } from 'rxjs/operators';
|
||||
import { SavedObjectsServiceStart, HttpServiceSetup, Logger, KibanaRequest } from 'src/core/server';
|
||||
import {
|
||||
ElasticsearchClient,
|
||||
SavedObjectsServiceStart,
|
||||
HttpServiceSetup,
|
||||
Logger,
|
||||
KibanaRequest,
|
||||
} from 'src/core/server';
|
||||
import {
|
||||
EncryptedSavedObjectsClient,
|
||||
EncryptedSavedObjectsPluginSetup,
|
||||
|
@ -19,6 +25,7 @@ import { CloudSetup } from '../../../cloud/server';
|
|||
class AppContextService {
|
||||
private encryptedSavedObjects: EncryptedSavedObjectsClient | undefined;
|
||||
private encryptedSavedObjectsSetup: EncryptedSavedObjectsPluginSetup | undefined;
|
||||
private esClient: ElasticsearchClient | undefined;
|
||||
private security: SecurityPluginStart | undefined;
|
||||
private config$?: Observable<FleetConfigType>;
|
||||
private configSubject$?: BehaviorSubject<FleetConfigType>;
|
||||
|
@ -32,6 +39,7 @@ class AppContextService {
|
|||
private externalCallbacks: ExternalCallbacksStorage = new Map();
|
||||
|
||||
public async start(appContext: FleetAppContext) {
|
||||
this.esClient = appContext.elasticsearch.client.asInternalUser;
|
||||
this.encryptedSavedObjects = appContext.encryptedSavedObjectsStart?.getClient();
|
||||
this.encryptedSavedObjectsSetup = appContext.encryptedSavedObjectsSetup;
|
||||
this.security = appContext.security;
|
||||
|
@ -96,12 +104,20 @@ class AppContextService {
|
|||
}
|
||||
|
||||
public getInternalUserSOClient(request: KibanaRequest) {
|
||||
// soClient as kibana internal users, be carefull on how you use it, security is not enabled
|
||||
// soClient as kibana internal users, be careful on how you use it, security is not enabled
|
||||
return appContextService.getSavedObjects().getScopedClient(request, {
|
||||
excludedWrappers: ['security'],
|
||||
});
|
||||
}
|
||||
|
||||
public getInternalUserESClient() {
|
||||
if (!this.esClient) {
|
||||
throw new Error('Elasticsearch start service not set.');
|
||||
}
|
||||
// soClient as kibana internal users, be careful on how you use it, security is not enabled
|
||||
return this.esClient;
|
||||
}
|
||||
|
||||
public getIsProductionMode() {
|
||||
return this.isProductionMode;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* 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 { KibanaRequest } from 'src/core/server';
|
||||
import {
|
||||
ENROLLMENT_API_KEYS_INDEX,
|
||||
ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE,
|
||||
FleetServerEnrollmentAPIKey,
|
||||
} from '../../common';
|
||||
import { listEnrollmentApiKeys, getEnrollmentAPIKey } from './api_keys/enrollment_api_key_so';
|
||||
import { appContextService } from './app_context';
|
||||
|
||||
export async function runFleetServerMigration() {
|
||||
const logger = appContextService.getLogger();
|
||||
logger.info('Starting fleet server migration');
|
||||
await migrateEnrollmentApiKeys();
|
||||
logger.info('Fleet server migration finished');
|
||||
}
|
||||
|
||||
function getInternalUserSOClient() {
|
||||
const fakeRequest = ({
|
||||
headers: {},
|
||||
getBasePath: () => '',
|
||||
path: '/',
|
||||
route: { settings: {} },
|
||||
url: {
|
||||
href: '/',
|
||||
},
|
||||
raw: {
|
||||
req: {
|
||||
url: '/',
|
||||
},
|
||||
},
|
||||
} as unknown) as KibanaRequest;
|
||||
|
||||
return appContextService.getInternalUserSOClient(fakeRequest);
|
||||
}
|
||||
|
||||
async function migrateEnrollmentApiKeys() {
|
||||
const esClient = appContextService.getInternalUserESClient();
|
||||
const soClient = getInternalUserSOClient();
|
||||
let hasMore = true;
|
||||
while (hasMore) {
|
||||
const res = await listEnrollmentApiKeys(soClient, {
|
||||
page: 1,
|
||||
perPage: 100,
|
||||
});
|
||||
if (res.total === 0) {
|
||||
hasMore = false;
|
||||
}
|
||||
for (const item of res.items) {
|
||||
const key = await getEnrollmentAPIKey(soClient, item.id);
|
||||
|
||||
const body: FleetServerEnrollmentAPIKey = {
|
||||
api_key: key.api_key,
|
||||
api_key_id: key.api_key_id,
|
||||
active: key.active,
|
||||
created_at: key.created_at,
|
||||
name: key.name,
|
||||
policy_id: key.policy_id,
|
||||
};
|
||||
await esClient.create({
|
||||
index: ENROLLMENT_API_KEYS_INDEX,
|
||||
body,
|
||||
id: key.id,
|
||||
refresh: 'wait_for',
|
||||
});
|
||||
|
||||
await soClient.delete(ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE, key.id);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,7 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { SavedObjectsClientContract, KibanaRequest } from 'kibana/server';
|
||||
import { ElasticsearchClient, SavedObjectsClientContract, KibanaRequest } from 'kibana/server';
|
||||
import { AgentStatus, Agent, EsAssetReference } from '../types';
|
||||
import * as settingsService from './settings';
|
||||
import { getAgent, listAgents } from './agents';
|
||||
|
@ -53,7 +53,11 @@ export interface AgentService {
|
|||
/**
|
||||
* Return the status by the Agent's id
|
||||
*/
|
||||
getAgentStatusById(soClient: SavedObjectsClientContract, agentId: string): Promise<AgentStatus>;
|
||||
getAgentStatusById(
|
||||
soClient: SavedObjectsClientContract,
|
||||
esClient: ElasticsearchClient,
|
||||
agentId: string
|
||||
): Promise<AgentStatus>;
|
||||
/**
|
||||
* List agents
|
||||
*/
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { savedObjectsClientMock } from 'src/core/server/mocks';
|
||||
import { elasticsearchServiceMock, savedObjectsClientMock } from 'src/core/server/mocks';
|
||||
import { createPackagePolicyMock } from '../../common/mocks';
|
||||
import { packagePolicyService } from './package_policy';
|
||||
import { PackageInfo, PackagePolicySOAttributes } from '../types';
|
||||
|
@ -345,9 +345,11 @@ describe('Package policy service', () => {
|
|||
throw savedObjectsClient.errors.createConflictError('abc', '123');
|
||||
}
|
||||
);
|
||||
const elasticsearchClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
|
||||
await expect(
|
||||
packagePolicyService.update(
|
||||
savedObjectsClient,
|
||||
elasticsearchClient,
|
||||
'the-package-policy-id',
|
||||
createPackagePolicyMock()
|
||||
)
|
||||
|
|
|
@ -3,7 +3,12 @@
|
|||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import { KibanaRequest, RequestHandlerContext, SavedObjectsClientContract } from 'src/core/server';
|
||||
import {
|
||||
ElasticsearchClient,
|
||||
KibanaRequest,
|
||||
RequestHandlerContext,
|
||||
SavedObjectsClientContract,
|
||||
} from 'src/core/server';
|
||||
import uuid from 'uuid';
|
||||
import { AuthenticatedUser } from '../../../security/server';
|
||||
import {
|
||||
|
@ -47,6 +52,7 @@ function getDataset(st: string) {
|
|||
class PackagePolicyService {
|
||||
public async create(
|
||||
soClient: SavedObjectsClientContract,
|
||||
esClient: ElasticsearchClient,
|
||||
callCluster: CallESAsCurrentUser,
|
||||
packagePolicy: NewPackagePolicy,
|
||||
options?: { id?: string; user?: AuthenticatedUser; bumpRevision?: boolean }
|
||||
|
@ -116,10 +122,16 @@ class PackagePolicyService {
|
|||
);
|
||||
|
||||
// Assign it to the given agent policy
|
||||
await agentPolicyService.assignPackagePolicies(soClient, packagePolicy.policy_id, [newSo.id], {
|
||||
user: options?.user,
|
||||
bumpRevision: options?.bumpRevision ?? true,
|
||||
});
|
||||
await agentPolicyService.assignPackagePolicies(
|
||||
soClient,
|
||||
esClient,
|
||||
packagePolicy.policy_id,
|
||||
[newSo.id],
|
||||
{
|
||||
user: options?.user,
|
||||
bumpRevision: options?.bumpRevision ?? true,
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
id: newSo.id,
|
||||
|
@ -130,6 +142,7 @@ class PackagePolicyService {
|
|||
|
||||
public async bulkCreate(
|
||||
soClient: SavedObjectsClientContract,
|
||||
esClient: ElasticsearchClient,
|
||||
packagePolicies: NewPackagePolicy[],
|
||||
agentPolicyId: string,
|
||||
options?: { user?: AuthenticatedUser; bumpRevision?: boolean }
|
||||
|
@ -167,6 +180,7 @@ class PackagePolicyService {
|
|||
// Assign it to the given agent policy
|
||||
await agentPolicyService.assignPackagePolicies(
|
||||
soClient,
|
||||
esClient,
|
||||
agentPolicyId,
|
||||
newSos.map((newSo) => newSo.id),
|
||||
{
|
||||
|
@ -252,6 +266,7 @@ class PackagePolicyService {
|
|||
|
||||
public async update(
|
||||
soClient: SavedObjectsClientContract,
|
||||
esClient: ElasticsearchClient,
|
||||
id: string,
|
||||
packagePolicy: UpdatePackagePolicy,
|
||||
options?: { user?: AuthenticatedUser }
|
||||
|
@ -308,7 +323,7 @@ class PackagePolicyService {
|
|||
);
|
||||
|
||||
// Bump revision of associated agent policy
|
||||
await agentPolicyService.bumpRevision(soClient, packagePolicy.policy_id, {
|
||||
await agentPolicyService.bumpRevision(soClient, esClient, packagePolicy.policy_id, {
|
||||
user: options?.user,
|
||||
});
|
||||
|
||||
|
@ -317,6 +332,7 @@ class PackagePolicyService {
|
|||
|
||||
public async delete(
|
||||
soClient: SavedObjectsClientContract,
|
||||
esClient: ElasticsearchClient,
|
||||
ids: string[],
|
||||
options?: { user?: AuthenticatedUser; skipUnassignFromAgentPolicies?: boolean }
|
||||
): Promise<DeletePackagePoliciesResponse> {
|
||||
|
@ -331,6 +347,7 @@ class PackagePolicyService {
|
|||
if (!options?.skipUnassignFromAgentPolicies) {
|
||||
await agentPolicyService.unassignPackagePolicies(
|
||||
soClient,
|
||||
esClient,
|
||||
packagePolicy.policy_id,
|
||||
[packagePolicy.id],
|
||||
{
|
||||
|
|
|
@ -41,8 +41,9 @@ describe('setupIngestManager', () => {
|
|||
soClient.find = mockedMethodThrowsError();
|
||||
soClient.get = mockedMethodThrowsError();
|
||||
soClient.update = mockedMethodThrowsError();
|
||||
const esClient = context.core.elasticsearch.client.asCurrentUser;
|
||||
|
||||
const setupPromise = setupIngestManager(soClient, jest.fn());
|
||||
const setupPromise = setupIngestManager(soClient, esClient, jest.fn());
|
||||
await expect(setupPromise).rejects.toThrow('SO method mocked to throw');
|
||||
await expect(setupPromise).rejects.toThrow(Error);
|
||||
});
|
||||
|
@ -53,8 +54,9 @@ describe('setupIngestManager', () => {
|
|||
soClient.find = mockedMethodThrowsCustom();
|
||||
soClient.get = mockedMethodThrowsCustom();
|
||||
soClient.update = mockedMethodThrowsCustom();
|
||||
const esClient = context.core.elasticsearch.client.asCurrentUser;
|
||||
|
||||
const setupPromise = setupIngestManager(soClient, jest.fn());
|
||||
const setupPromise = setupIngestManager(soClient, esClient, jest.fn());
|
||||
await expect(setupPromise).rejects.toThrow('method mocked to throw');
|
||||
await expect(setupPromise).rejects.toThrow(CustomTestError);
|
||||
});
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
*/
|
||||
|
||||
import uuid from 'uuid';
|
||||
import { SavedObjectsClientContract } from 'src/core/server';
|
||||
import { ElasticsearchClient, SavedObjectsClientContract } from 'src/core/server';
|
||||
import { CallESAsCurrentUser } from '../types';
|
||||
import { agentPolicyService } from './agent_policy';
|
||||
import { outputService } from './output';
|
||||
|
@ -39,13 +39,15 @@ export interface SetupStatus {
|
|||
|
||||
export async function setupIngestManager(
|
||||
soClient: SavedObjectsClientContract,
|
||||
esClient: ElasticsearchClient,
|
||||
callCluster: CallESAsCurrentUser
|
||||
): Promise<SetupStatus> {
|
||||
return awaitIfPending(async () => createSetupSideEffects(soClient, callCluster));
|
||||
return awaitIfPending(async () => createSetupSideEffects(soClient, esClient, callCluster));
|
||||
}
|
||||
|
||||
async function createSetupSideEffects(
|
||||
soClient: SavedObjectsClientContract,
|
||||
esClient: ElasticsearchClient,
|
||||
callCluster: CallESAsCurrentUser
|
||||
): Promise<SetupStatus> {
|
||||
const [
|
||||
|
@ -56,7 +58,7 @@ async function createSetupSideEffects(
|
|||
// packages installed by default
|
||||
ensureInstalledDefaultPackages(soClient, callCluster),
|
||||
outputService.ensureDefaultOutput(soClient),
|
||||
agentPolicyService.ensureDefaultAgentPolicy(soClient),
|
||||
agentPolicyService.ensureDefaultAgentPolicy(soClient, esClient),
|
||||
settingsService.getSettings(soClient).catch((e: any) => {
|
||||
if (e.isBoom && e.output.statusCode === 404) {
|
||||
const defaultSettings = createDefaultSettings();
|
||||
|
@ -109,6 +111,7 @@ async function createSetupSideEffects(
|
|||
if (!isInstalled) {
|
||||
await addPackageToAgentPolicy(
|
||||
soClient,
|
||||
esClient,
|
||||
callCluster,
|
||||
installedPackage,
|
||||
agentPolicyWithPackagePolicies,
|
||||
|
@ -125,6 +128,7 @@ async function createSetupSideEffects(
|
|||
|
||||
export async function setupFleet(
|
||||
soClient: SavedObjectsClientContract,
|
||||
esClient: ElasticsearchClient,
|
||||
callCluster: CallESAsCurrentUser,
|
||||
options?: { forceRecreate?: boolean }
|
||||
) {
|
||||
|
@ -189,7 +193,7 @@ export async function setupFleet(
|
|||
|
||||
await Promise.all(
|
||||
agentPolicies.map((agentPolicy) => {
|
||||
return generateEnrollmentAPIKey(soClient, {
|
||||
return generateEnrollmentAPIKey(soClient, esClient, {
|
||||
name: `Default`,
|
||||
agentPolicyId: agentPolicy.id,
|
||||
});
|
||||
|
@ -209,6 +213,7 @@ function generateRandomPassword() {
|
|||
|
||||
async function addPackageToAgentPolicy(
|
||||
soClient: SavedObjectsClientContract,
|
||||
esClient: ElasticsearchClient,
|
||||
callCluster: CallESAsCurrentUser,
|
||||
packageToInstall: Installation,
|
||||
agentPolicy: AgentPolicy,
|
||||
|
@ -227,7 +232,7 @@ async function addPackageToAgentPolicy(
|
|||
agentPolicy.namespace
|
||||
);
|
||||
|
||||
await packagePolicyService.create(soClient, callCluster, newPackagePolicy, {
|
||||
await packagePolicyService.create(soClient, esClient, callCluster, newPackagePolicy, {
|
||||
bumpRevision: false,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -77,6 +77,8 @@ export {
|
|||
PostAgentCheckinRequest,
|
||||
DataType,
|
||||
dataTypes,
|
||||
// Fleet Server types
|
||||
FleetServerEnrollmentAPIKey,
|
||||
} from '../../common';
|
||||
|
||||
export type CallESAsCurrentUser = LegacyScopedClusterClient['callAsCurrentUser'];
|
||||
|
|
|
@ -5,7 +5,11 @@
|
|||
*/
|
||||
|
||||
import { Subject } from 'rxjs';
|
||||
import { loggingSystemMock, savedObjectsServiceMock } from 'src/core/server/mocks';
|
||||
import {
|
||||
elasticsearchServiceMock,
|
||||
loggingSystemMock,
|
||||
savedObjectsServiceMock,
|
||||
} from 'src/core/server/mocks';
|
||||
import { LicenseService } from '../../../../common/license/license';
|
||||
import { createPackagePolicyServiceMock } from '../../../../../fleet/server/mocks';
|
||||
import { PolicyWatcher } from './license_watch';
|
||||
|
@ -31,6 +35,7 @@ const MockPPWithEndpointPolicy = (cb?: (p: PolicyConfig) => PolicyConfig): Packa
|
|||
describe('Policy-Changing license watcher', () => {
|
||||
const logger = loggingSystemMock.create().get('license_watch.test');
|
||||
const soStartMock = savedObjectsServiceMock.createStartContract();
|
||||
const esStartMock = elasticsearchServiceMock.createStart();
|
||||
let packagePolicySvcMock: jest.Mocked<PackagePolicyServiceInterface>;
|
||||
|
||||
const Platinum = licenseMock.createLicense({ license: { type: 'platinum', mode: 'platinum' } });
|
||||
|
@ -45,7 +50,7 @@ describe('Policy-Changing license watcher', () => {
|
|||
// mock a license-changing service to test reactivity
|
||||
const licenseEmitter: Subject<ILicense> = new Subject();
|
||||
const licenseService = new LicenseService();
|
||||
const pw = new PolicyWatcher(packagePolicySvcMock, soStartMock, logger);
|
||||
const pw = new PolicyWatcher(packagePolicySvcMock, soStartMock, esStartMock, logger);
|
||||
|
||||
// swap out watch function, just to ensure it gets called when a license change happens
|
||||
const mockWatch = jest.fn();
|
||||
|
@ -90,7 +95,7 @@ describe('Policy-Changing license watcher', () => {
|
|||
perPage: 100,
|
||||
});
|
||||
|
||||
const pw = new PolicyWatcher(packagePolicySvcMock, soStartMock, logger);
|
||||
const pw = new PolicyWatcher(packagePolicySvcMock, soStartMock, esStartMock, logger);
|
||||
await pw.watch(Gold); // just manually trigger with a given license
|
||||
|
||||
expect(packagePolicySvcMock.list.mock.calls.length).toBe(3); // should have asked for 3 pages of resuts
|
||||
|
@ -119,14 +124,14 @@ describe('Policy-Changing license watcher', () => {
|
|||
perPage: 100,
|
||||
});
|
||||
|
||||
const pw = new PolicyWatcher(packagePolicySvcMock, soStartMock, logger);
|
||||
const pw = new PolicyWatcher(packagePolicySvcMock, soStartMock, esStartMock, logger);
|
||||
|
||||
// emulate a license change below paid tier
|
||||
await pw.watch(Basic);
|
||||
|
||||
expect(packagePolicySvcMock.update).toHaveBeenCalled();
|
||||
expect(
|
||||
packagePolicySvcMock.update.mock.calls[0][2].inputs[0].config!.policy.value.windows.popup
|
||||
packagePolicySvcMock.update.mock.calls[0][3].inputs[0].config!.policy.value.windows.popup
|
||||
.malware.message
|
||||
).not.toEqual(CustomMessage);
|
||||
});
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
import { Subscription } from 'rxjs';
|
||||
|
||||
import {
|
||||
ElasticsearchClient,
|
||||
ElasticsearchServiceStart,
|
||||
KibanaRequest,
|
||||
Logger,
|
||||
SavedObjectsClientContract,
|
||||
|
@ -28,15 +30,18 @@ import { isAtLeast, LicenseService } from '../../../../common/license/license';
|
|||
export class PolicyWatcher {
|
||||
private logger: Logger;
|
||||
private soClient: SavedObjectsClientContract;
|
||||
private esClient: ElasticsearchClient;
|
||||
private policyService: PackagePolicyServiceInterface;
|
||||
private subscription: Subscription | undefined;
|
||||
constructor(
|
||||
policyService: PackagePolicyServiceInterface,
|
||||
soStart: SavedObjectsServiceStart,
|
||||
esStart: ElasticsearchServiceStart,
|
||||
logger: Logger
|
||||
) {
|
||||
this.policyService = policyService;
|
||||
this.soClient = this.makeInternalSOClient(soStart);
|
||||
this.esClient = esStart.client.asInternalUser;
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
|
@ -113,11 +118,16 @@ export class PolicyWatcher {
|
|||
license
|
||||
);
|
||||
try {
|
||||
await this.policyService.update(this.soClient, policy.id, updatePolicy);
|
||||
await this.policyService.update(this.soClient, this.esClient, policy.id, updatePolicy);
|
||||
} catch (e) {
|
||||
// try again for transient issues
|
||||
try {
|
||||
await this.policyService.update(this.soClient, policy.id, updatePolicy);
|
||||
await this.policyService.update(
|
||||
this.soClient,
|
||||
this.esClient,
|
||||
policy.id,
|
||||
updatePolicy
|
||||
);
|
||||
} catch (ee) {
|
||||
this.logger.warn(
|
||||
`Unable to remove platinum features from policy ${policy.id}: ${ee.message}`
|
||||
|
|
|
@ -68,13 +68,15 @@ export const getMetadataListRequestHandler = function (
|
|||
|
||||
const unenrolledAgentIds = await findAllUnenrolledAgentIds(
|
||||
agentService,
|
||||
context.core.savedObjects.client
|
||||
context.core.savedObjects.client,
|
||||
context.core.elasticsearch.client.asCurrentUser
|
||||
);
|
||||
|
||||
const statusIDs = request?.body?.filters?.host_status?.length
|
||||
? await findAgentIDsByStatus(
|
||||
agentService,
|
||||
context.core.savedObjects.client,
|
||||
context.core.elasticsearch.client.asCurrentUser,
|
||||
request.body?.filters?.host_status
|
||||
)
|
||||
: undefined;
|
||||
|
@ -193,6 +195,7 @@ async function findAgent(
|
|||
?.getAgentService()
|
||||
?.getAgent(
|
||||
metadataRequestContext.requestHandlerContext.core.savedObjects.client,
|
||||
metadataRequestContext.requestHandlerContext.core.elasticsearch.client.asCurrentUser,
|
||||
hostMetadata.elastic.agent.id
|
||||
);
|
||||
} catch (e) {
|
||||
|
@ -267,6 +270,7 @@ export async function enrichHostMetadata(
|
|||
?.getAgentService()
|
||||
?.getAgentStatusById(
|
||||
metadataRequestContext.requestHandlerContext.core.savedObjects.client,
|
||||
metadataRequestContext.requestHandlerContext.core.elasticsearch.client.asCurrentUser,
|
||||
elasticAgentId
|
||||
);
|
||||
hostStatus = HOST_STATUS_MAPPING.get(status!) || HostStatus.ERROR;
|
||||
|
@ -289,6 +293,7 @@ export async function enrichHostMetadata(
|
|||
?.getAgentService()
|
||||
?.getAgent(
|
||||
metadataRequestContext.requestHandlerContext.core.savedObjects.client,
|
||||
metadataRequestContext.requestHandlerContext.core.elasticsearch.client.asCurrentUser,
|
||||
elasticAgentId
|
||||
);
|
||||
const agentPolicy = await metadataRequestContext.endpointAppContextService
|
||||
|
|
|
@ -4,9 +4,12 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { SavedObjectsClientContract } from 'kibana/server';
|
||||
import { ElasticsearchClient, SavedObjectsClientContract } from 'kibana/server';
|
||||
import { findAgentIDsByStatus } from './agent_status';
|
||||
import { savedObjectsClientMock } from '../../../../../../../../src/core/server/mocks';
|
||||
import {
|
||||
elasticsearchServiceMock,
|
||||
savedObjectsClientMock,
|
||||
} from '../../../../../../../../src/core/server/mocks';
|
||||
import { AgentService } from '../../../../../../fleet/server/services';
|
||||
import { createMockAgentService } from '../../../../../../fleet/server/mocks';
|
||||
import { Agent } from '../../../../../../fleet/common/types/models';
|
||||
|
@ -14,9 +17,11 @@ import { AgentStatusKueryHelper } from '../../../../../../fleet/common/services'
|
|||
|
||||
describe('test filtering endpoint hosts by agent status', () => {
|
||||
let mockSavedObjectClient: jest.Mocked<SavedObjectsClientContract>;
|
||||
let mockElasticsearchClient: jest.Mocked<ElasticsearchClient>;
|
||||
let mockAgentService: jest.Mocked<AgentService>;
|
||||
beforeEach(() => {
|
||||
mockSavedObjectClient = savedObjectsClientMock.create();
|
||||
mockElasticsearchClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
|
||||
mockAgentService = createMockAgentService();
|
||||
});
|
||||
|
||||
|
@ -30,7 +35,12 @@ describe('test filtering endpoint hosts by agent status', () => {
|
|||
})
|
||||
);
|
||||
|
||||
const result = await findAgentIDsByStatus(mockAgentService, mockSavedObjectClient, ['online']);
|
||||
const result = await findAgentIDsByStatus(
|
||||
mockAgentService,
|
||||
mockSavedObjectClient,
|
||||
mockElasticsearchClient,
|
||||
['online']
|
||||
);
|
||||
expect(result).toBeDefined();
|
||||
});
|
||||
|
||||
|
@ -53,9 +63,14 @@ describe('test filtering endpoint hosts by agent status', () => {
|
|||
})
|
||||
);
|
||||
|
||||
const result = await findAgentIDsByStatus(mockAgentService, mockSavedObjectClient, ['offline']);
|
||||
const result = await findAgentIDsByStatus(
|
||||
mockAgentService,
|
||||
mockSavedObjectClient,
|
||||
mockElasticsearchClient,
|
||||
['offline']
|
||||
);
|
||||
const offlineKuery = AgentStatusKueryHelper.buildKueryForOfflineAgents();
|
||||
expect(mockAgentService.listAgents.mock.calls[0][1].kuery).toEqual(
|
||||
expect(mockAgentService.listAgents.mock.calls[0][2].kuery).toEqual(
|
||||
expect.stringContaining(offlineKuery)
|
||||
);
|
||||
expect(result).toBeDefined();
|
||||
|
@ -81,13 +96,15 @@ describe('test filtering endpoint hosts by agent status', () => {
|
|||
})
|
||||
);
|
||||
|
||||
const result = await findAgentIDsByStatus(mockAgentService, mockSavedObjectClient, [
|
||||
'unenrolling',
|
||||
'error',
|
||||
]);
|
||||
const result = await findAgentIDsByStatus(
|
||||
mockAgentService,
|
||||
mockSavedObjectClient,
|
||||
mockElasticsearchClient,
|
||||
['unenrolling', 'error']
|
||||
);
|
||||
const unenrollKuery = AgentStatusKueryHelper.buildKueryForUnenrollingAgents();
|
||||
const errorKuery = AgentStatusKueryHelper.buildKueryForErrorAgents();
|
||||
expect(mockAgentService.listAgents.mock.calls[0][1].kuery).toEqual(
|
||||
expect(mockAgentService.listAgents.mock.calls[0][2].kuery).toEqual(
|
||||
expect.stringContaining(`${unenrollKuery} OR ${errorKuery}`)
|
||||
);
|
||||
expect(result).toBeDefined();
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { SavedObjectsClientContract } from 'kibana/server';
|
||||
import { ElasticsearchClient, SavedObjectsClientContract } from 'kibana/server';
|
||||
import { AgentService } from '../../../../../../fleet/server';
|
||||
import { AgentStatusKueryHelper } from '../../../../../../fleet/common/services';
|
||||
import { Agent } from '../../../../../../fleet/common/types/models';
|
||||
|
@ -20,6 +20,7 @@ const STATUS_QUERY_MAP = new Map([
|
|||
export async function findAgentIDsByStatus(
|
||||
agentService: AgentService,
|
||||
soClient: SavedObjectsClientContract,
|
||||
esClient: ElasticsearchClient,
|
||||
status: string[],
|
||||
pageSize: number = 1000
|
||||
): Promise<string[]> {
|
||||
|
@ -39,7 +40,7 @@ export async function findAgentIDsByStatus(
|
|||
let hasMore = true;
|
||||
|
||||
while (hasMore) {
|
||||
const agents = await agentService.listAgents(soClient, searchOptions(page++));
|
||||
const agents = await agentService.listAgents(soClient, esClient, searchOptions(page++));
|
||||
result.push(...agents.agents.map((agent: Agent) => agent.id));
|
||||
hasMore = agents.agents.length > 0;
|
||||
}
|
||||
|
|
|
@ -4,18 +4,23 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { SavedObjectsClientContract } from 'kibana/server';
|
||||
import { ElasticsearchClient, SavedObjectsClientContract } from 'kibana/server';
|
||||
import { findAllUnenrolledAgentIds } from './unenroll';
|
||||
import { savedObjectsClientMock } from '../../../../../../../../src/core/server/mocks';
|
||||
import {
|
||||
elasticsearchServiceMock,
|
||||
savedObjectsClientMock,
|
||||
} from '../../../../../../../../src/core/server/mocks';
|
||||
import { AgentService } from '../../../../../../fleet/server/services';
|
||||
import { createMockAgentService } from '../../../../../../fleet/server/mocks';
|
||||
import { Agent } from '../../../../../../fleet/common/types/models';
|
||||
|
||||
describe('test find all unenrolled Agent id', () => {
|
||||
let mockSavedObjectClient: jest.Mocked<SavedObjectsClientContract>;
|
||||
let mockElasticsearchClient: jest.Mocked<ElasticsearchClient>;
|
||||
let mockAgentService: jest.Mocked<AgentService>;
|
||||
beforeEach(() => {
|
||||
mockSavedObjectClient = savedObjectsClientMock.create();
|
||||
mockElasticsearchClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
|
||||
mockAgentService = createMockAgentService();
|
||||
});
|
||||
|
||||
|
@ -53,7 +58,11 @@ describe('test find all unenrolled Agent id', () => {
|
|||
perPage: 1,
|
||||
})
|
||||
);
|
||||
const agentIds = await findAllUnenrolledAgentIds(mockAgentService, mockSavedObjectClient);
|
||||
const agentIds = await findAllUnenrolledAgentIds(
|
||||
mockAgentService,
|
||||
mockSavedObjectClient,
|
||||
mockElasticsearchClient
|
||||
);
|
||||
expect(agentIds).toBeTruthy();
|
||||
expect(agentIds).toEqual(['id1', 'id2']);
|
||||
});
|
||||
|
|
|
@ -4,13 +4,14 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { SavedObjectsClientContract } from 'kibana/server';
|
||||
import { ElasticsearchClient, SavedObjectsClientContract } from 'kibana/server';
|
||||
import { AgentService } from '../../../../../../fleet/server';
|
||||
import { Agent } from '../../../../../../fleet/common/types/models';
|
||||
|
||||
export async function findAllUnenrolledAgentIds(
|
||||
agentService: AgentService,
|
||||
soClient: SavedObjectsClientContract,
|
||||
esClient: ElasticsearchClient,
|
||||
pageSize: number = 1000
|
||||
): Promise<string[]> {
|
||||
const searchOptions = (pageNum: number) => {
|
||||
|
@ -29,7 +30,11 @@ export async function findAllUnenrolledAgentIds(
|
|||
let hasMore = true;
|
||||
|
||||
while (hasMore) {
|
||||
const unenrolledAgents = await agentService.listAgents(soClient, searchOptions(page++));
|
||||
const unenrolledAgents = await agentService.listAgents(
|
||||
soClient,
|
||||
esClient,
|
||||
searchOptions(page++)
|
||||
);
|
||||
result.push(...unenrolledAgents.agents.map((agent: Agent) => agent.id));
|
||||
hasMore = unenrolledAgents.agents.length > 0;
|
||||
}
|
||||
|
|
|
@ -44,6 +44,7 @@ export const getAgentPolicySummaryHandler = function (
|
|||
const result = await getAgentPolicySummary(
|
||||
endpointAppContext,
|
||||
context.core.savedObjects.client,
|
||||
context.core.elasticsearch.client.asCurrentUser,
|
||||
request.query.package_name,
|
||||
request.query?.policy_id || undefined
|
||||
);
|
||||
|
|
|
@ -5,7 +5,11 @@
|
|||
*/
|
||||
|
||||
import { SearchResponse } from 'elasticsearch';
|
||||
import { ILegacyScopedClusterClient, SavedObjectsClientContract } from 'kibana/server';
|
||||
import {
|
||||
ElasticsearchClient,
|
||||
ILegacyScopedClusterClient,
|
||||
SavedObjectsClientContract,
|
||||
} from 'kibana/server';
|
||||
import { GetHostPolicyResponse, HostPolicyResponse } from '../../../../common/endpoint/types';
|
||||
import { INITIAL_POLICY_ID } from './index';
|
||||
import { Agent } from '../../../../../fleet/common/types/models';
|
||||
|
@ -73,6 +77,7 @@ const transformAgentVersionMap = (versionMap: Map<string, number>): { [key: stri
|
|||
export async function getAgentPolicySummary(
|
||||
endpointAppContext: EndpointAppContext,
|
||||
soClient: SavedObjectsClientContract,
|
||||
esClient: ElasticsearchClient,
|
||||
packageName: string,
|
||||
policyId?: string,
|
||||
pageSize: number = 1000
|
||||
|
@ -83,6 +88,7 @@ export async function getAgentPolicySummary(
|
|||
await agentVersionsMap(
|
||||
endpointAppContext,
|
||||
soClient,
|
||||
esClient,
|
||||
`${agentQuery} AND ${AGENT_SAVED_OBJECT_TYPE}.policy_id:${policyId}`,
|
||||
pageSize
|
||||
)
|
||||
|
@ -90,13 +96,14 @@ export async function getAgentPolicySummary(
|
|||
}
|
||||
|
||||
return transformAgentVersionMap(
|
||||
await agentVersionsMap(endpointAppContext, soClient, agentQuery, pageSize)
|
||||
await agentVersionsMap(endpointAppContext, soClient, esClient, agentQuery, pageSize)
|
||||
);
|
||||
}
|
||||
|
||||
export async function agentVersionsMap(
|
||||
endpointAppContext: EndpointAppContext,
|
||||
soClient: SavedObjectsClientContract,
|
||||
esClient: ElasticsearchClient,
|
||||
kqlQuery: string,
|
||||
pageSize: number = 1000
|
||||
): Promise<Map<string, number>> {
|
||||
|
@ -115,7 +122,7 @@ export async function agentVersionsMap(
|
|||
while (hasMore) {
|
||||
const queryResult = await endpointAppContext.service
|
||||
.getAgentService()!
|
||||
.listAgents(soClient, searchOptions(page++));
|
||||
.listAgents(soClient, esClient, searchOptions(page++));
|
||||
queryResult.agents.forEach((agent: Agent) => {
|
||||
const agentVersion = agent.local_metadata?.elastic?.agent?.version;
|
||||
if (result.has(agentVersion)) {
|
||||
|
|
|
@ -191,7 +191,7 @@ describe('manifest_manager', () => {
|
|||
expect(packagePolicyService.update.mock.calls.length).toEqual(2);
|
||||
|
||||
expect(
|
||||
packagePolicyService.update.mock.calls[0][2].inputs[0].config!.artifact_manifest.value
|
||||
packagePolicyService.update.mock.calls[0][3].inputs[0].config!.artifact_manifest.value
|
||||
).toEqual({
|
||||
manifest_version: '1.0.1',
|
||||
schema_version: 'v1',
|
||||
|
|
|
@ -293,7 +293,13 @@ export class ManifestManager {
|
|||
};
|
||||
|
||||
try {
|
||||
await this.packagePolicyService.update(this.savedObjectsClient, id, newPackagePolicy);
|
||||
await this.packagePolicyService.update(
|
||||
this.savedObjectsClient,
|
||||
// @ts-ignore
|
||||
undefined,
|
||||
id,
|
||||
newPackagePolicy
|
||||
);
|
||||
this.logger.debug(
|
||||
`Updated package policy ${id} with manifest version ${manifest.getSemanticVersion()}`
|
||||
);
|
||||
|
|
|
@ -355,6 +355,7 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S
|
|||
this.policyWatcher = new PolicyWatcher(
|
||||
plugins.fleet!.packagePolicyService,
|
||||
core.savedObjects,
|
||||
core.elasticsearch,
|
||||
this.logger
|
||||
);
|
||||
this.policyWatcher.start(licenseService);
|
||||
|
|
Loading…
Reference in a new issue