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