mirror of
https://github.com/elastic/kibana.git
synced 2025-04-25 02:09:32 -04:00
[Fleet] add uninstall token service (#154610)
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
ba6f36757e
commit
cb7c0f4a6b
19 changed files with 1207 additions and 6 deletions
|
@ -937,14 +937,14 @@
|
||||||
"description": {
|
"description": {
|
||||||
"type": "text"
|
"type": "text"
|
||||||
},
|
},
|
||||||
"kibanaSavedObjectMeta": {
|
|
||||||
"properties": {}
|
|
||||||
},
|
|
||||||
"title": {
|
"title": {
|
||||||
"type": "text"
|
"type": "text"
|
||||||
},
|
},
|
||||||
"version": {
|
"version": {
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"kibanaSavedObjectMeta": {
|
||||||
|
"properties": {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -1864,6 +1864,20 @@
|
||||||
"dynamic": false,
|
"dynamic": false,
|
||||||
"properties": {}
|
"properties": {}
|
||||||
},
|
},
|
||||||
|
"fleet-uninstall-tokens": {
|
||||||
|
"dynamic": false,
|
||||||
|
"properties": {
|
||||||
|
"created_at": {
|
||||||
|
"type": "date"
|
||||||
|
},
|
||||||
|
"policy_id": {
|
||||||
|
"type": "keyword"
|
||||||
|
},
|
||||||
|
"token_plain": {
|
||||||
|
"type": "keyword"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"osquery-manager-usage-metric": {
|
"osquery-manager-usage-metric": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"count": {
|
"count": {
|
||||||
|
|
|
@ -95,6 +95,7 @@ describe('checking migration metadata changes on all registered SO types', () =>
|
||||||
"fleet-message-signing-keys": "0c6da6a680807e568540b2aa263ae52331ba66db",
|
"fleet-message-signing-keys": "0c6da6a680807e568540b2aa263ae52331ba66db",
|
||||||
"fleet-preconfiguration-deletion-record": "3afad160748b430427086985a3445fd8697566d5",
|
"fleet-preconfiguration-deletion-record": "3afad160748b430427086985a3445fd8697566d5",
|
||||||
"fleet-proxy": "94d0a902a0fd22578d7d3a20873b95d902e25245",
|
"fleet-proxy": "94d0a902a0fd22578d7d3a20873b95d902e25245",
|
||||||
|
"fleet-uninstall-tokens": "404109e8624f6585e4837a2d01f6e405e0c36d9d",
|
||||||
"graph-workspace": "565642a208fe7413b487aea979b5b153e4e74abe",
|
"graph-workspace": "565642a208fe7413b487aea979b5b153e4e74abe",
|
||||||
"guided-onboarding-guide-state": "3257825ae840309cb676d64b347107db7b76f30a",
|
"guided-onboarding-guide-state": "3257825ae840309cb676d64b347107db7b76f30a",
|
||||||
"guided-onboarding-plugin-state": "2d3ef3069ca8e981cafe8647c0c4a4c20739db10",
|
"guided-onboarding-plugin-state": "2d3ef3069ca8e981cafe8647c0c4a4c20739db10",
|
||||||
|
|
|
@ -202,6 +202,7 @@ describe('split .kibana index into multiple system indices', () => {
|
||||||
"fleet-message-signing-keys",
|
"fleet-message-signing-keys",
|
||||||
"fleet-preconfiguration-deletion-record",
|
"fleet-preconfiguration-deletion-record",
|
||||||
"fleet-proxy",
|
"fleet-proxy",
|
||||||
|
"fleet-uninstall-tokens",
|
||||||
"graph-workspace",
|
"graph-workspace",
|
||||||
"guided-onboarding-guide-state",
|
"guided-onboarding-guide-state",
|
||||||
"guided-onboarding-plugin-state",
|
"guided-onboarding-plugin-state",
|
||||||
|
|
|
@ -62,6 +62,7 @@ const previouslyRegisteredTypes = [
|
||||||
'fleet-message-signing-keys',
|
'fleet-message-signing-keys',
|
||||||
'fleet-preconfiguration-deletion-record',
|
'fleet-preconfiguration-deletion-record',
|
||||||
'fleet-proxy',
|
'fleet-proxy',
|
||||||
|
'fleet-uninstall-tokens',
|
||||||
'graph-workspace',
|
'graph-workspace',
|
||||||
'guided-setup-state',
|
'guided-setup-state',
|
||||||
'guided-onboarding-guide-state',
|
'guided-onboarding-guide-state',
|
||||||
|
|
|
@ -22,6 +22,7 @@ export * from './authz';
|
||||||
export * from './file_storage';
|
export * from './file_storage';
|
||||||
export * from './message_signing_keys';
|
export * from './message_signing_keys';
|
||||||
export * from './locators';
|
export * from './locators';
|
||||||
|
export * from './uninstall_tokens';
|
||||||
|
|
||||||
// TODO: This is the default `index.max_result_window` ES setting, which dictates
|
// TODO: This is the default `index.max_result_window` ES setting, which dictates
|
||||||
// the maximum amount of results allowed to be returned from a search. It's possible
|
// the maximum amount of results allowed to be returned from a search. It's possible
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
|
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||||
|
* 2.0.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const UNINSTALL_TOKENS_SAVED_OBJECT_TYPE = 'fleet-uninstall-tokens';
|
|
@ -27,6 +27,7 @@ export {
|
||||||
PRECONFIGURATION_DELETION_RECORD_SAVED_OBJECT_TYPE,
|
PRECONFIGURATION_DELETION_RECORD_SAVED_OBJECT_TYPE,
|
||||||
ASSETS_SAVED_OBJECT_TYPE,
|
ASSETS_SAVED_OBJECT_TYPE,
|
||||||
MESSAGE_SIGNING_KEYS_SAVED_OBJECT_TYPE,
|
MESSAGE_SIGNING_KEYS_SAVED_OBJECT_TYPE,
|
||||||
|
UNINSTALL_TOKENS_SAVED_OBJECT_TYPE,
|
||||||
// Fleet server index
|
// Fleet server index
|
||||||
FLEET_SERVER_SERVERS_INDEX,
|
FLEET_SERVER_SERVERS_INDEX,
|
||||||
FLEET_SERVER_ARTIFACTS_INDEX,
|
FLEET_SERVER_ARTIFACTS_INDEX,
|
||||||
|
|
|
@ -13,7 +13,7 @@ const meta = getESAssetMetadata();
|
||||||
|
|
||||||
export const FLEET_INSTALL_FORMAT_VERSION = '1.0.0';
|
export const FLEET_INSTALL_FORMAT_VERSION = '1.0.0';
|
||||||
|
|
||||||
export const FLEET_AGENT_POLICIES_SCHEMA_VERSION = '1.1.0';
|
export const FLEET_AGENT_POLICIES_SCHEMA_VERSION = '1.1.1';
|
||||||
|
|
||||||
export const FLEET_FINAL_PIPELINE_ID = '.fleet_final_pipeline-1';
|
export const FLEET_FINAL_PIPELINE_ID = '.fleet_final_pipeline-1';
|
||||||
|
|
||||||
|
|
|
@ -47,6 +47,7 @@ export {
|
||||||
ASSETS_SAVED_OBJECT_TYPE,
|
ASSETS_SAVED_OBJECT_TYPE,
|
||||||
GLOBAL_SETTINGS_SAVED_OBJECT_TYPE,
|
GLOBAL_SETTINGS_SAVED_OBJECT_TYPE,
|
||||||
MESSAGE_SIGNING_KEYS_SAVED_OBJECT_TYPE,
|
MESSAGE_SIGNING_KEYS_SAVED_OBJECT_TYPE,
|
||||||
|
UNINSTALL_TOKENS_SAVED_OBJECT_TYPE,
|
||||||
// Defaults
|
// Defaults
|
||||||
DEFAULT_OUTPUT,
|
DEFAULT_OUTPUT,
|
||||||
DEFAULT_OUTPUT_ID,
|
DEFAULT_OUTPUT_ID,
|
||||||
|
|
|
@ -76,6 +76,7 @@ export const createAppContextStartContractMock = (
|
||||||
telemetryEventsSender: createMockTelemetryEventsSender(),
|
telemetryEventsSender: createMockTelemetryEventsSender(),
|
||||||
bulkActionsResolver: {} as any,
|
bulkActionsResolver: {} as any,
|
||||||
messageSigningService: createMessageSigningServiceMock(),
|
messageSigningService: createMessageSigningServiceMock(),
|
||||||
|
uninstallTokenService: createUninstallTokenServiceMock(),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -171,3 +172,18 @@ export function createMessageSigningServiceMock() {
|
||||||
rotateKeyPair: jest.fn(),
|
rotateKeyPair: jest.fn(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function createUninstallTokenServiceMock() {
|
||||||
|
return {
|
||||||
|
getTokenForPolicyId: jest.fn(),
|
||||||
|
getTokensForPolicyIds: jest.fn(),
|
||||||
|
getAllTokens: jest.fn(),
|
||||||
|
getHashedTokenForPolicyId: jest.fn(),
|
||||||
|
getHashedTokensForPolicyIds: jest.fn(),
|
||||||
|
getAllHashedTokens: jest.fn(),
|
||||||
|
generateTokenForPolicyId: jest.fn(),
|
||||||
|
generateTokensForPolicyIds: jest.fn(),
|
||||||
|
generateTokensForAllPolicies: jest.fn(),
|
||||||
|
encryptTokens: jest.fn(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -58,7 +58,11 @@ import type { FleetConfigType } from '../common/types';
|
||||||
import type { FleetAuthz } from '../common';
|
import type { FleetAuthz } from '../common';
|
||||||
import type { ExperimentalFeatures } from '../common/experimental_features';
|
import type { ExperimentalFeatures } from '../common/experimental_features';
|
||||||
|
|
||||||
import { MESSAGE_SIGNING_KEYS_SAVED_OBJECT_TYPE, INTEGRATIONS_PLUGIN_ID } from '../common';
|
import {
|
||||||
|
MESSAGE_SIGNING_KEYS_SAVED_OBJECT_TYPE,
|
||||||
|
INTEGRATIONS_PLUGIN_ID,
|
||||||
|
UNINSTALL_TOKENS_SAVED_OBJECT_TYPE,
|
||||||
|
} from '../common';
|
||||||
import { parseExperimentalConfigValue } from '../common/experimental_features';
|
import { parseExperimentalConfigValue } from '../common/experimental_features';
|
||||||
|
|
||||||
import type { MessageSigningServiceInterface } from './services/security';
|
import type { MessageSigningServiceInterface } from './services/security';
|
||||||
|
@ -116,6 +120,10 @@ import { PackagePolicyServiceImpl } from './services/package_policy';
|
||||||
import { registerFleetUsageLogger, startFleetUsageLogger } from './services/fleet_usage_logger';
|
import { registerFleetUsageLogger, startFleetUsageLogger } from './services/fleet_usage_logger';
|
||||||
import { CheckDeletedFilesTask } from './tasks/check_deleted_files_task';
|
import { CheckDeletedFilesTask } from './tasks/check_deleted_files_task';
|
||||||
import { getRequestStore } from './services/request_store';
|
import { getRequestStore } from './services/request_store';
|
||||||
|
import {
|
||||||
|
UninstallTokenService,
|
||||||
|
type UninstallTokenServiceInterface,
|
||||||
|
} from './services/security/uninstall_token_service';
|
||||||
|
|
||||||
export interface FleetSetupDeps {
|
export interface FleetSetupDeps {
|
||||||
security: SecurityPluginSetup;
|
security: SecurityPluginSetup;
|
||||||
|
@ -160,6 +168,7 @@ export interface FleetAppContext {
|
||||||
bulkActionsResolver: BulkActionsResolver;
|
bulkActionsResolver: BulkActionsResolver;
|
||||||
messageSigningService: MessageSigningServiceInterface;
|
messageSigningService: MessageSigningServiceInterface;
|
||||||
auditLogger?: AuditLogger;
|
auditLogger?: AuditLogger;
|
||||||
|
uninstallTokenService: UninstallTokenServiceInterface;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type FleetSetupContract = void;
|
export type FleetSetupContract = void;
|
||||||
|
@ -209,6 +218,7 @@ export interface FleetStartContract {
|
||||||
createArtifactsClient: (packageName: string) => FleetArtifactsClient;
|
createArtifactsClient: (packageName: string) => FleetArtifactsClient;
|
||||||
|
|
||||||
messageSigningService: MessageSigningServiceInterface;
|
messageSigningService: MessageSigningServiceInterface;
|
||||||
|
uninstallTokenService: UninstallTokenServiceInterface;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class FleetPlugin
|
export class FleetPlugin
|
||||||
|
@ -441,6 +451,11 @@ export class FleetPlugin
|
||||||
includedHiddenTypes: [MESSAGE_SIGNING_KEYS_SAVED_OBJECT_TYPE],
|
includedHiddenTypes: [MESSAGE_SIGNING_KEYS_SAVED_OBJECT_TYPE],
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
const uninstallTokenService = new UninstallTokenService(
|
||||||
|
plugins.encryptedSavedObjects.getClient({
|
||||||
|
includedHiddenTypes: [UNINSTALL_TOKENS_SAVED_OBJECT_TYPE],
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
appContextService.start({
|
appContextService.start({
|
||||||
elasticsearch: core.elasticsearch,
|
elasticsearch: core.elasticsearch,
|
||||||
|
@ -465,6 +480,7 @@ export class FleetPlugin
|
||||||
telemetryEventsSender: this.telemetryEventsSender,
|
telemetryEventsSender: this.telemetryEventsSender,
|
||||||
bulkActionsResolver: this.bulkActionsResolver!,
|
bulkActionsResolver: this.bulkActionsResolver!,
|
||||||
messageSigningService,
|
messageSigningService,
|
||||||
|
uninstallTokenService,
|
||||||
});
|
});
|
||||||
licenseService.start(plugins.licensing.license$);
|
licenseService.start(plugins.licensing.license$);
|
||||||
|
|
||||||
|
@ -552,6 +568,7 @@ export class FleetPlugin
|
||||||
return new FleetArtifactsClient(core.elasticsearch.client.asInternalUser, packageName);
|
return new FleetArtifactsClient(core.elasticsearch.client.asInternalUser, packageName);
|
||||||
},
|
},
|
||||||
messageSigningService,
|
messageSigningService,
|
||||||
|
uninstallTokenService,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,7 @@ import {
|
||||||
FLEET_PROXY_SAVED_OBJECT_TYPE,
|
FLEET_PROXY_SAVED_OBJECT_TYPE,
|
||||||
MESSAGE_SIGNING_KEYS_SAVED_OBJECT_TYPE,
|
MESSAGE_SIGNING_KEYS_SAVED_OBJECT_TYPE,
|
||||||
INGEST_SAVED_OBJECT_INDEX,
|
INGEST_SAVED_OBJECT_INDEX,
|
||||||
|
UNINSTALL_TOKENS_SAVED_OBJECT_TYPE,
|
||||||
} from '../constants';
|
} from '../constants';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
@ -392,6 +393,22 @@ const getSavedObjectTypes = (): { [key: string]: SavedObjectsType } => ({
|
||||||
properties: {},
|
properties: {},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
[UNINSTALL_TOKENS_SAVED_OBJECT_TYPE]: {
|
||||||
|
name: UNINSTALL_TOKENS_SAVED_OBJECT_TYPE,
|
||||||
|
hidden: true,
|
||||||
|
namespaceType: 'agnostic',
|
||||||
|
management: {
|
||||||
|
importableAndExportable: false,
|
||||||
|
},
|
||||||
|
mappings: {
|
||||||
|
dynamic: false,
|
||||||
|
properties: {
|
||||||
|
created_at: { type: 'date' },
|
||||||
|
policy_id: { type: 'keyword' },
|
||||||
|
token_plain: { type: 'keyword' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export function registerSavedObjects(savedObjects: SavedObjectsServiceSetup) {
|
export function registerSavedObjects(savedObjects: SavedObjectsServiceSetup) {
|
||||||
|
@ -427,4 +444,8 @@ export function registerEncryptedSavedObjects(
|
||||||
type: MESSAGE_SIGNING_KEYS_SAVED_OBJECT_TYPE,
|
type: MESSAGE_SIGNING_KEYS_SAVED_OBJECT_TYPE,
|
||||||
attributesToEncrypt: new Set(['passphrase']),
|
attributesToEncrypt: new Set(['passphrase']),
|
||||||
});
|
});
|
||||||
|
encryptedSavedObjects.registerType({
|
||||||
|
type: UNINSTALL_TOKENS_SAVED_OBJECT_TYPE,
|
||||||
|
attributesToEncrypt: new Set(['token']),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -188,10 +188,14 @@ export async function getFullAgentPolicy(
|
||||||
const messageSigningService = appContextService.getMessageSigningService();
|
const messageSigningService = appContextService.getMessageSigningService();
|
||||||
if (messageSigningService && fullAgentPolicy.agent) {
|
if (messageSigningService && fullAgentPolicy.agent) {
|
||||||
const publicKey = await messageSigningService.getPublicKey();
|
const publicKey = await messageSigningService.getPublicKey();
|
||||||
|
const tokenHash =
|
||||||
|
(await appContextService
|
||||||
|
.getUninstallTokenService()
|
||||||
|
?.getHashedTokenForPolicyId(fullAgentPolicy.id)) ?? '';
|
||||||
|
|
||||||
fullAgentPolicy.agent.protection = {
|
fullAgentPolicy.agent.protection = {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
uninstall_token_hash: '',
|
uninstall_token_hash: tokenHash,
|
||||||
signing_key: publicKey,
|
signing_key: publicKey,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -246,6 +246,7 @@ class AgentPolicyService {
|
||||||
options
|
options
|
||||||
);
|
);
|
||||||
|
|
||||||
|
await appContextService.getUninstallTokenService()?.generateTokenForPolicyId(newSo.id);
|
||||||
await this.triggerAgentPolicyUpdatedEvent(soClient, esClient, 'created', newSo.id);
|
await this.triggerAgentPolicyUpdatedEvent(soClient, esClient, 'created', newSo.id);
|
||||||
|
|
||||||
return { id: newSo.id, ...newSo.attributes };
|
return { id: newSo.id, ...newSo.attributes };
|
||||||
|
|
|
@ -46,6 +46,7 @@ import type { TelemetryEventsSender } from '../telemetry/sender';
|
||||||
import type { MessageSigningServiceInterface } from '..';
|
import type { MessageSigningServiceInterface } from '..';
|
||||||
|
|
||||||
import type { BulkActionsResolver } from './agents';
|
import type { BulkActionsResolver } from './agents';
|
||||||
|
import type { UninstallTokenServiceInterface } from './security/uninstall_token_service';
|
||||||
|
|
||||||
class AppContextService {
|
class AppContextService {
|
||||||
private encryptedSavedObjects: EncryptedSavedObjectsClient | undefined;
|
private encryptedSavedObjects: EncryptedSavedObjectsClient | undefined;
|
||||||
|
@ -69,6 +70,7 @@ class AppContextService {
|
||||||
private savedObjectsTagging: SavedObjectTaggingStart | undefined;
|
private savedObjectsTagging: SavedObjectTaggingStart | undefined;
|
||||||
private bulkActionsResolver: BulkActionsResolver | undefined;
|
private bulkActionsResolver: BulkActionsResolver | undefined;
|
||||||
private messageSigningService: MessageSigningServiceInterface | undefined;
|
private messageSigningService: MessageSigningServiceInterface | undefined;
|
||||||
|
private uninstallTokenService: UninstallTokenServiceInterface | undefined;
|
||||||
|
|
||||||
public start(appContext: FleetAppContext) {
|
public start(appContext: FleetAppContext) {
|
||||||
this.data = appContext.data;
|
this.data = appContext.data;
|
||||||
|
@ -89,6 +91,7 @@ class AppContextService {
|
||||||
this.savedObjectsTagging = appContext.savedObjectsTagging;
|
this.savedObjectsTagging = appContext.savedObjectsTagging;
|
||||||
this.bulkActionsResolver = appContext.bulkActionsResolver;
|
this.bulkActionsResolver = appContext.bulkActionsResolver;
|
||||||
this.messageSigningService = appContext.messageSigningService;
|
this.messageSigningService = appContext.messageSigningService;
|
||||||
|
this.uninstallTokenService = appContext.uninstallTokenService;
|
||||||
|
|
||||||
if (appContext.config$) {
|
if (appContext.config$) {
|
||||||
this.config$ = appContext.config$;
|
this.config$ = appContext.config$;
|
||||||
|
@ -254,6 +257,10 @@ class AppContextService {
|
||||||
public getMessageSigningService() {
|
public getMessageSigningService() {
|
||||||
return this.messageSigningService;
|
return this.messageSigningService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getUninstallTokenService() {
|
||||||
|
return this.uninstallTokenService;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const appContextService = new AppContextService();
|
export const appContextService = new AppContextService();
|
||||||
|
|
|
@ -270,6 +270,9 @@ jest.mock('./app_context', () => ({
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
|
getUninstallTokenService: () => ({
|
||||||
|
generateTokenForPolicyId: jest.fn(),
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,677 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
|
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||||
|
* 2.0.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { createHash } from 'crypto';
|
||||||
|
|
||||||
|
import type { KibanaRequest } from '@kbn/core-http-server';
|
||||||
|
import type { SavedObjectsClientContract } from '@kbn/core/server';
|
||||||
|
import type { EncryptedSavedObjectsClient } from '@kbn/encrypted-saved-objects-plugin/server';
|
||||||
|
import { encryptedSavedObjectsMock } from '@kbn/encrypted-saved-objects-plugin/server/mocks';
|
||||||
|
|
||||||
|
import { UNINSTALL_TOKENS_SAVED_OBJECT_TYPE } from '../../../constants';
|
||||||
|
import { createAppContextStartContractMock, type MockedFleetAppContext } from '../../../mocks';
|
||||||
|
import { appContextService } from '../../app_context';
|
||||||
|
import { agentPolicyService } from '../../agent_policy';
|
||||||
|
|
||||||
|
import { UninstallTokenService, type UninstallTokenServiceInterface } from '.';
|
||||||
|
|
||||||
|
describe('UninstallTokenService', () => {
|
||||||
|
let soClientMock: jest.Mocked<SavedObjectsClientContract>;
|
||||||
|
let esoClientMock: jest.Mocked<EncryptedSavedObjectsClient>;
|
||||||
|
let mockContext: MockedFleetAppContext;
|
||||||
|
let mockBuckets: any[] = [];
|
||||||
|
let uninstallTokenService: UninstallTokenServiceInterface;
|
||||||
|
|
||||||
|
function getDefaultSO(encrypted: boolean = true) {
|
||||||
|
return encrypted
|
||||||
|
? {
|
||||||
|
id: 'test-so-id',
|
||||||
|
attributes: {
|
||||||
|
policy_id: 'test-policy-id',
|
||||||
|
token: 'test-token',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
id: 'test-so-id',
|
||||||
|
attributes: {
|
||||||
|
policy_id: 'test-policy-id',
|
||||||
|
token_plain: 'test-token-plain',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDefaultSO2(encrypted: boolean = true) {
|
||||||
|
return encrypted
|
||||||
|
? {
|
||||||
|
id: 'test-so-id-two',
|
||||||
|
attributes: {
|
||||||
|
policy_id: 'test-policy-id-two',
|
||||||
|
token: 'test-token-two',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
id: 'test-so-id-two',
|
||||||
|
attributes: {
|
||||||
|
policy_id: 'test-policy-id-two',
|
||||||
|
token_plain: 'test-token-plain-two',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDefaultBuckets(encrypted: boolean = true) {
|
||||||
|
const defaultSO = getDefaultSO(encrypted);
|
||||||
|
const defaultSO2 = getDefaultSO2(encrypted);
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
key: 'test-policy-id',
|
||||||
|
latest: {
|
||||||
|
hits: {
|
||||||
|
hits: [
|
||||||
|
{
|
||||||
|
_id: defaultSO.id,
|
||||||
|
...defaultSO,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'test-policy-id-two',
|
||||||
|
latest: {
|
||||||
|
hits: {
|
||||||
|
hits: [
|
||||||
|
{
|
||||||
|
_id: defaultSO2.id,
|
||||||
|
...defaultSO2,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
function mockFind(encrypted: boolean = true, savedObjects?: any[]) {
|
||||||
|
soClientMock.find = jest.fn().mockResolvedValue({
|
||||||
|
saved_objects:
|
||||||
|
savedObjects ?? getDefaultBuckets(encrypted).map((bucket) => ({ id: bucket.key })),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function mockCreatePointInTimeFinder(encrypted: boolean = true, buckets?: any[]) {
|
||||||
|
mockBuckets = buckets ?? getDefaultBuckets(encrypted);
|
||||||
|
|
||||||
|
soClientMock.createPointInTimeFinder = jest.fn().mockReturnValue({
|
||||||
|
close: jest.fn(),
|
||||||
|
find: function* asyncGenerator() {
|
||||||
|
yield {
|
||||||
|
aggregations: {
|
||||||
|
by_policy_id: {
|
||||||
|
buckets: mockBuckets,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function mockCreatePointInTimeFinderAsInternalUser(savedObjects?: any[]) {
|
||||||
|
esoClientMock.createPointInTimeFinderDecryptedAsInternalUser = jest.fn().mockResolvedValue({
|
||||||
|
close: jest.fn(),
|
||||||
|
find: function* asyncGenerator() {
|
||||||
|
yield {
|
||||||
|
saved_objects: savedObjects ?? mockBuckets.map(({ latest }) => latest.hits.hits[0]),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupMocks(canEncrypt: boolean = true) {
|
||||||
|
mockContext = createAppContextStartContractMock();
|
||||||
|
mockContext.encryptedSavedObjectsSetup = encryptedSavedObjectsMock.createSetup({
|
||||||
|
canEncrypt,
|
||||||
|
});
|
||||||
|
appContextService.start(mockContext);
|
||||||
|
esoClientMock =
|
||||||
|
mockContext.encryptedSavedObjectsStart!.getClient() as jest.Mocked<EncryptedSavedObjectsClient>;
|
||||||
|
soClientMock = appContextService
|
||||||
|
.getSavedObjects()
|
||||||
|
.getScopedClient({} as unknown as KibanaRequest) as jest.Mocked<SavedObjectsClientContract>;
|
||||||
|
agentPolicyService.deployPolicies = jest.fn();
|
||||||
|
|
||||||
|
uninstallTokenService = new UninstallTokenService(esoClientMock);
|
||||||
|
mockFind(canEncrypt);
|
||||||
|
mockCreatePointInTimeFinder(canEncrypt);
|
||||||
|
mockCreatePointInTimeFinderAsInternalUser();
|
||||||
|
}
|
||||||
|
|
||||||
|
function hashToken(token?: string): string {
|
||||||
|
if (!token) return '';
|
||||||
|
return createHash('sha256').update(token).digest('base64');
|
||||||
|
}
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
mockBuckets = [];
|
||||||
|
jest.resetAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('with encryption key configured', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
setupMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('get uninstall tokens', () => {
|
||||||
|
it('can correctly getTokenForPolicyId', async () => {
|
||||||
|
const so = getDefaultSO();
|
||||||
|
const token = await uninstallTokenService.getTokenForPolicyId(so.attributes.policy_id);
|
||||||
|
expect(token).toBe(so.attributes.token);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can correctly getTokensForPolicyIds', async () => {
|
||||||
|
const so = getDefaultSO();
|
||||||
|
const so2 = getDefaultSO2();
|
||||||
|
|
||||||
|
const tokensMap = await uninstallTokenService.getTokensForPolicyIds([
|
||||||
|
so.attributes.policy_id,
|
||||||
|
so2.attributes.policy_id,
|
||||||
|
]);
|
||||||
|
expect(tokensMap).toEqual({
|
||||||
|
[so.attributes.policy_id]: so.attributes.token,
|
||||||
|
[so2.attributes.policy_id]: so2.attributes.token,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can correctly getAllTokens', async () => {
|
||||||
|
const so = getDefaultSO();
|
||||||
|
const so2 = getDefaultSO2();
|
||||||
|
|
||||||
|
const tokensMap = await uninstallTokenService.getAllTokens();
|
||||||
|
expect(tokensMap).toEqual({
|
||||||
|
[so.attributes.policy_id]: so.attributes.token,
|
||||||
|
[so2.attributes.policy_id]: so2.attributes.token,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('get hashed uninstall tokens', () => {
|
||||||
|
it('can correctly getHashedTokenForPolicyId', async () => {
|
||||||
|
const so = getDefaultSO();
|
||||||
|
|
||||||
|
const token = await uninstallTokenService.getHashedTokenForPolicyId(
|
||||||
|
so.attributes.policy_id
|
||||||
|
);
|
||||||
|
expect(token).toBe(hashToken(so.attributes.token));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can correctly getHashedTokensForPolicyIds', async () => {
|
||||||
|
const so = getDefaultSO();
|
||||||
|
const so2 = getDefaultSO2();
|
||||||
|
|
||||||
|
const tokensMap = await uninstallTokenService.getHashedTokensForPolicyIds([
|
||||||
|
so.attributes.policy_id,
|
||||||
|
so2.attributes.policy_id,
|
||||||
|
]);
|
||||||
|
expect(tokensMap).toEqual({
|
||||||
|
[so.attributes.policy_id]: hashToken(so.attributes.token),
|
||||||
|
[so2.attributes.policy_id]: hashToken(so2.attributes.token),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can correctly getAllHashedTokens', async () => {
|
||||||
|
const so = getDefaultSO();
|
||||||
|
const so2 = getDefaultSO2();
|
||||||
|
|
||||||
|
const tokensMap = await uninstallTokenService.getAllHashedTokens();
|
||||||
|
expect(tokensMap).toEqual({
|
||||||
|
[so.attributes.policy_id]: hashToken(so.attributes.token),
|
||||||
|
[so2.attributes.policy_id]: hashToken(so2.attributes.token),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('token generation', () => {
|
||||||
|
describe('existing token', () => {
|
||||||
|
describe('force = false', () => {
|
||||||
|
it('does not create new token when calling generateTokenForPolicyId', async () => {
|
||||||
|
const so = getDefaultSO();
|
||||||
|
await uninstallTokenService.generateTokenForPolicyId(so.attributes.policy_id);
|
||||||
|
expect(soClientMock.bulkCreate).not.toBeCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not create new token when calling generateTokensForPolicyIds', async () => {
|
||||||
|
const so = getDefaultSO();
|
||||||
|
await uninstallTokenService.generateTokensForPolicyIds([so.attributes.policy_id]);
|
||||||
|
expect(soClientMock.bulkCreate).not.toBeCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not create new token when calling generateTokensForAllPolicies', async () => {
|
||||||
|
await uninstallTokenService.generateTokensForAllPolicies();
|
||||||
|
expect(soClientMock.bulkCreate).not.toBeCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('force = true', () => {
|
||||||
|
it('creates a new token when calling generateTokenForPolicyId', async () => {
|
||||||
|
const so = getDefaultSO();
|
||||||
|
await uninstallTokenService.generateTokenForPolicyId(so.attributes.policy_id, true);
|
||||||
|
expect(soClientMock.bulkCreate).toBeCalledWith([
|
||||||
|
{
|
||||||
|
type: UNINSTALL_TOKENS_SAVED_OBJECT_TYPE,
|
||||||
|
attributes: {
|
||||||
|
policy_id: so.attributes.policy_id,
|
||||||
|
token: expect.any(String),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
expect(agentPolicyService.deployPolicies).toBeCalledWith(soClientMock, [
|
||||||
|
so.attributes.policy_id,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('creates a new token when calling generateTokensForPolicyIds', async () => {
|
||||||
|
const so = getDefaultSO();
|
||||||
|
const so2 = getDefaultSO2();
|
||||||
|
|
||||||
|
await uninstallTokenService.generateTokensForPolicyIds(
|
||||||
|
[so.attributes.policy_id, so2.attributes.policy_id],
|
||||||
|
true
|
||||||
|
);
|
||||||
|
expect(soClientMock.bulkCreate).toBeCalledWith([
|
||||||
|
{
|
||||||
|
type: UNINSTALL_TOKENS_SAVED_OBJECT_TYPE,
|
||||||
|
attributes: {
|
||||||
|
policy_id: so.attributes.policy_id,
|
||||||
|
token: expect.any(String),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: UNINSTALL_TOKENS_SAVED_OBJECT_TYPE,
|
||||||
|
attributes: {
|
||||||
|
policy_id: so2.attributes.policy_id,
|
||||||
|
token: expect.any(String),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
expect(agentPolicyService.deployPolicies).toBeCalledWith(soClientMock, [
|
||||||
|
so.attributes.policy_id,
|
||||||
|
so2.attributes.policy_id,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('creates a new token when calling generateTokensForAllPolicies', async () => {
|
||||||
|
const so = getDefaultSO();
|
||||||
|
const so2 = getDefaultSO2();
|
||||||
|
|
||||||
|
await uninstallTokenService.generateTokensForAllPolicies(true);
|
||||||
|
expect(soClientMock.bulkCreate).toBeCalledWith([
|
||||||
|
{
|
||||||
|
type: UNINSTALL_TOKENS_SAVED_OBJECT_TYPE,
|
||||||
|
attributes: {
|
||||||
|
policy_id: so.attributes.policy_id,
|
||||||
|
token: expect.any(String),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: UNINSTALL_TOKENS_SAVED_OBJECT_TYPE,
|
||||||
|
attributes: {
|
||||||
|
policy_id: so2.attributes.policy_id,
|
||||||
|
token: expect.any(String),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
expect(agentPolicyService.deployPolicies).toBeCalledWith(soClientMock, [
|
||||||
|
so.attributes.policy_id,
|
||||||
|
so2.attributes.policy_id,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('no existing token', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
mockCreatePointInTimeFinder(true, []);
|
||||||
|
mockCreatePointInTimeFinderAsInternalUser([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('creates a new token when calling generateTokenForPolicyId', async () => {
|
||||||
|
const so = getDefaultSO();
|
||||||
|
await uninstallTokenService.generateTokenForPolicyId(so.attributes.policy_id);
|
||||||
|
expect(soClientMock.bulkCreate).toBeCalledWith([
|
||||||
|
{
|
||||||
|
type: UNINSTALL_TOKENS_SAVED_OBJECT_TYPE,
|
||||||
|
attributes: {
|
||||||
|
policy_id: so.attributes.policy_id,
|
||||||
|
token: expect.any(String),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('creates a new token when calling generateTokensForPolicyIds', async () => {
|
||||||
|
const so = getDefaultSO();
|
||||||
|
const so2 = getDefaultSO2();
|
||||||
|
|
||||||
|
await uninstallTokenService.generateTokensForPolicyIds([
|
||||||
|
so.attributes.policy_id,
|
||||||
|
so2.attributes.policy_id,
|
||||||
|
]);
|
||||||
|
expect(soClientMock.bulkCreate).toBeCalledWith([
|
||||||
|
{
|
||||||
|
type: UNINSTALL_TOKENS_SAVED_OBJECT_TYPE,
|
||||||
|
attributes: {
|
||||||
|
policy_id: so.attributes.policy_id,
|
||||||
|
token: expect.any(String),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: UNINSTALL_TOKENS_SAVED_OBJECT_TYPE,
|
||||||
|
attributes: {
|
||||||
|
policy_id: so2.attributes.policy_id,
|
||||||
|
token: expect.any(String),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('creates a new token when calling generateTokensForAllPolicies', async () => {
|
||||||
|
const so = getDefaultSO();
|
||||||
|
const so2 = getDefaultSO2();
|
||||||
|
|
||||||
|
await uninstallTokenService.generateTokensForAllPolicies();
|
||||||
|
expect(soClientMock.bulkCreate).toBeCalledWith([
|
||||||
|
{
|
||||||
|
type: UNINSTALL_TOKENS_SAVED_OBJECT_TYPE,
|
||||||
|
attributes: {
|
||||||
|
policy_id: so.attributes.policy_id,
|
||||||
|
token: expect.any(String),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: UNINSTALL_TOKENS_SAVED_OBJECT_TYPE,
|
||||||
|
attributes: {
|
||||||
|
policy_id: so2.attributes.policy_id,
|
||||||
|
token: expect.any(String),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('with encryption key NOT configured', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
setupMocks(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('get uninstall tokens', () => {
|
||||||
|
it('can correctly getTokenForPolicyId', async () => {
|
||||||
|
const so = getDefaultSO(false);
|
||||||
|
const token = await uninstallTokenService.getTokenForPolicyId(so.attributes.policy_id);
|
||||||
|
expect(token).toBe(so.attributes.token_plain);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can correctly getTokensForPolicyIds', async () => {
|
||||||
|
const so = getDefaultSO(false);
|
||||||
|
const so2 = getDefaultSO2(false);
|
||||||
|
|
||||||
|
const tokensMap = await uninstallTokenService.getTokensForPolicyIds([
|
||||||
|
so.attributes.policy_id,
|
||||||
|
so2.attributes.policy_id,
|
||||||
|
]);
|
||||||
|
expect(tokensMap).toEqual({
|
||||||
|
[so.attributes.policy_id]: so.attributes.token_plain,
|
||||||
|
[so2.attributes.policy_id]: so2.attributes.token_plain,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can correctly getAllTokens', async () => {
|
||||||
|
const so = getDefaultSO(false);
|
||||||
|
const so2 = getDefaultSO2(false);
|
||||||
|
|
||||||
|
const tokensMap = await uninstallTokenService.getAllTokens();
|
||||||
|
expect(tokensMap).toEqual({
|
||||||
|
[so.attributes.policy_id]: so.attributes.token_plain,
|
||||||
|
[so2.attributes.policy_id]: so2.attributes.token_plain,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('get hashed uninstall tokens', () => {
|
||||||
|
it('can correctly getHashedTokenForPolicyId', async () => {
|
||||||
|
const so = getDefaultSO(false);
|
||||||
|
|
||||||
|
const token = await uninstallTokenService.getHashedTokenForPolicyId(
|
||||||
|
so.attributes.policy_id
|
||||||
|
);
|
||||||
|
expect(token).toBe(hashToken(so.attributes.token_plain));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can correctly getHashedTokensForPolicyIds', async () => {
|
||||||
|
const so = getDefaultSO(false);
|
||||||
|
const so2 = getDefaultSO2(false);
|
||||||
|
|
||||||
|
const tokensMap = await uninstallTokenService.getHashedTokensForPolicyIds([
|
||||||
|
so.attributes.policy_id,
|
||||||
|
so2.attributes.policy_id,
|
||||||
|
]);
|
||||||
|
expect(tokensMap).toEqual({
|
||||||
|
[so.attributes.policy_id]: hashToken(so.attributes.token_plain),
|
||||||
|
[so2.attributes.policy_id]: hashToken(so2.attributes.token_plain),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can correctly getAllHashedTokens', async () => {
|
||||||
|
const so = getDefaultSO(false);
|
||||||
|
const so2 = getDefaultSO2(false);
|
||||||
|
|
||||||
|
const tokensMap = await uninstallTokenService.getAllHashedTokens();
|
||||||
|
expect(tokensMap).toEqual({
|
||||||
|
[so.attributes.policy_id]: hashToken(so.attributes.token_plain),
|
||||||
|
[so2.attributes.policy_id]: hashToken(so2.attributes.token_plain),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('token generation', () => {
|
||||||
|
describe('existing token', () => {
|
||||||
|
describe('force = false', () => {
|
||||||
|
it('does not create new token when calling generateTokenForPolicyId', async () => {
|
||||||
|
const so = getDefaultSO();
|
||||||
|
await uninstallTokenService.generateTokenForPolicyId(so.attributes.policy_id);
|
||||||
|
expect(soClientMock.bulkCreate).not.toBeCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not create new token when calling generateTokensForPolicyIds', async () => {
|
||||||
|
const so = getDefaultSO();
|
||||||
|
await uninstallTokenService.generateTokensForPolicyIds([so.attributes.policy_id]);
|
||||||
|
expect(soClientMock.bulkCreate).not.toBeCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not create new token when calling generateTokensForAllPolicies', async () => {
|
||||||
|
await uninstallTokenService.generateTokensForAllPolicies();
|
||||||
|
expect(soClientMock.bulkCreate).not.toBeCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('force = true', () => {
|
||||||
|
it('creates a new token when calling generateTokenForPolicyId', async () => {
|
||||||
|
const so = getDefaultSO();
|
||||||
|
await uninstallTokenService.generateTokenForPolicyId(so.attributes.policy_id, true);
|
||||||
|
expect(soClientMock.bulkCreate).toBeCalledWith([
|
||||||
|
{
|
||||||
|
type: UNINSTALL_TOKENS_SAVED_OBJECT_TYPE,
|
||||||
|
attributes: {
|
||||||
|
policy_id: so.attributes.policy_id,
|
||||||
|
token_plain: expect.any(String),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
expect(agentPolicyService.deployPolicies).toBeCalledWith(soClientMock, [
|
||||||
|
so.attributes.policy_id,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('creates a new token when calling generateTokensForPolicyIds', async () => {
|
||||||
|
const so = getDefaultSO();
|
||||||
|
const so2 = getDefaultSO2();
|
||||||
|
|
||||||
|
await uninstallTokenService.generateTokensForPolicyIds(
|
||||||
|
[so.attributes.policy_id, so2.attributes.policy_id],
|
||||||
|
true
|
||||||
|
);
|
||||||
|
expect(soClientMock.bulkCreate).toBeCalledWith([
|
||||||
|
{
|
||||||
|
type: UNINSTALL_TOKENS_SAVED_OBJECT_TYPE,
|
||||||
|
attributes: {
|
||||||
|
policy_id: so.attributes.policy_id,
|
||||||
|
token_plain: expect.any(String),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: UNINSTALL_TOKENS_SAVED_OBJECT_TYPE,
|
||||||
|
attributes: {
|
||||||
|
policy_id: so2.attributes.policy_id,
|
||||||
|
token_plain: expect.any(String),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
expect(agentPolicyService.deployPolicies).toBeCalledWith(soClientMock, [
|
||||||
|
so.attributes.policy_id,
|
||||||
|
so2.attributes.policy_id,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('creates a new token when calling generateTokensForAllPolicies', async () => {
|
||||||
|
const so = getDefaultSO();
|
||||||
|
const so2 = getDefaultSO2();
|
||||||
|
|
||||||
|
await uninstallTokenService.generateTokensForAllPolicies(true);
|
||||||
|
expect(soClientMock.bulkCreate).toBeCalledWith([
|
||||||
|
{
|
||||||
|
type: UNINSTALL_TOKENS_SAVED_OBJECT_TYPE,
|
||||||
|
attributes: {
|
||||||
|
policy_id: so.attributes.policy_id,
|
||||||
|
token_plain: expect.any(String),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: UNINSTALL_TOKENS_SAVED_OBJECT_TYPE,
|
||||||
|
attributes: {
|
||||||
|
policy_id: so2.attributes.policy_id,
|
||||||
|
token_plain: expect.any(String),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
expect(agentPolicyService.deployPolicies).toBeCalledWith(soClientMock, [
|
||||||
|
so.attributes.policy_id,
|
||||||
|
so2.attributes.policy_id,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('no existing token', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
mockCreatePointInTimeFinder(false, []);
|
||||||
|
mockCreatePointInTimeFinderAsInternalUser([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('creates a new token when calling generateTokenForPolicyId', async () => {
|
||||||
|
const so = getDefaultSO();
|
||||||
|
await uninstallTokenService.generateTokenForPolicyId(so.attributes.policy_id);
|
||||||
|
expect(soClientMock.bulkCreate).toBeCalledWith([
|
||||||
|
{
|
||||||
|
type: UNINSTALL_TOKENS_SAVED_OBJECT_TYPE,
|
||||||
|
attributes: {
|
||||||
|
policy_id: so.attributes.policy_id,
|
||||||
|
token_plain: expect.any(String),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('creates a new token when calling generateTokensForPolicyIds', async () => {
|
||||||
|
const so = getDefaultSO();
|
||||||
|
const so2 = getDefaultSO2();
|
||||||
|
|
||||||
|
await uninstallTokenService.generateTokensForPolicyIds([
|
||||||
|
so.attributes.policy_id,
|
||||||
|
so2.attributes.policy_id,
|
||||||
|
]);
|
||||||
|
expect(soClientMock.bulkCreate).toBeCalledWith([
|
||||||
|
{
|
||||||
|
type: UNINSTALL_TOKENS_SAVED_OBJECT_TYPE,
|
||||||
|
attributes: {
|
||||||
|
policy_id: so.attributes.policy_id,
|
||||||
|
token_plain: expect.any(String),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: UNINSTALL_TOKENS_SAVED_OBJECT_TYPE,
|
||||||
|
attributes: {
|
||||||
|
policy_id: so2.attributes.policy_id,
|
||||||
|
token_plain: expect.any(String),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('creates a new token when calling generateTokensForAllPolicies', async () => {
|
||||||
|
const so = getDefaultSO();
|
||||||
|
const so2 = getDefaultSO2();
|
||||||
|
|
||||||
|
await uninstallTokenService.generateTokensForAllPolicies();
|
||||||
|
expect(soClientMock.bulkCreate).toBeCalledWith([
|
||||||
|
{
|
||||||
|
type: UNINSTALL_TOKENS_SAVED_OBJECT_TYPE,
|
||||||
|
attributes: {
|
||||||
|
policy_id: so.attributes.policy_id,
|
||||||
|
token_plain: expect.any(String),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: UNINSTALL_TOKENS_SAVED_OBJECT_TYPE,
|
||||||
|
attributes: {
|
||||||
|
policy_id: so2.attributes.policy_id,
|
||||||
|
token_plain: expect.any(String),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can encryptTokens', async () => {
|
||||||
|
const so = getDefaultSO(false);
|
||||||
|
const so2 = getDefaultSO2(false);
|
||||||
|
|
||||||
|
mockContext!.encryptedSavedObjectsSetup!.canEncrypt = true;
|
||||||
|
mockFind(false, [so, so2]);
|
||||||
|
await uninstallTokenService.encryptTokens();
|
||||||
|
|
||||||
|
expect(soClientMock.bulkUpdate).toBeCalledWith([
|
||||||
|
{
|
||||||
|
id: so.id,
|
||||||
|
attributes: {
|
||||||
|
policy_id: so.attributes.policy_id,
|
||||||
|
token: so.attributes.token_plain,
|
||||||
|
token_plain: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: so2.id,
|
||||||
|
attributes: {
|
||||||
|
policy_id: so2.attributes.policy_id,
|
||||||
|
token: so2.attributes.token_plain,
|
||||||
|
token_plain: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,414 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
|
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||||
|
* 2.0.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { randomBytes, createHash } from 'crypto';
|
||||||
|
|
||||||
|
import { chunk } from 'lodash';
|
||||||
|
|
||||||
|
import type {
|
||||||
|
SavedObjectsClientContract,
|
||||||
|
SavedObjectsCreatePointInTimeFinderOptions,
|
||||||
|
SavedObjectsBulkUpdateObject,
|
||||||
|
SavedObjectsFindResult,
|
||||||
|
} from '@kbn/core-saved-objects-api-server';
|
||||||
|
import type {
|
||||||
|
AggregationsMultiBucketAggregateBase,
|
||||||
|
AggregationsTopHitsAggregate,
|
||||||
|
} from '@elastic/elasticsearch/lib/api/types';
|
||||||
|
import type { EncryptedSavedObjectsClient } from '@kbn/encrypted-saved-objects-plugin/server';
|
||||||
|
import type { KibanaRequest } from '@kbn/core-http-server';
|
||||||
|
import { SECURITY_EXTENSION_ID } from '@kbn/core-saved-objects-server';
|
||||||
|
import { asyncForEach } from '@kbn/std';
|
||||||
|
|
||||||
|
import { UNINSTALL_TOKENS_SAVED_OBJECT_TYPE, SO_SEARCH_LIMIT } from '../../../constants';
|
||||||
|
import { appContextService } from '../../app_context';
|
||||||
|
import { agentPolicyService } from '../../agent_policy';
|
||||||
|
|
||||||
|
interface UninstallTokenSOAttributes {
|
||||||
|
policy_id: string;
|
||||||
|
token: string;
|
||||||
|
token_plain: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UninstallTokenSOAggregationBucket {
|
||||||
|
key: string;
|
||||||
|
latest: AggregationsTopHitsAggregate;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UninstallTokenSOAggregation {
|
||||||
|
by_policy_id: AggregationsMultiBucketAggregateBase<UninstallTokenSOAggregationBucket>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UninstallTokenServiceInterface {
|
||||||
|
getTokenForPolicyId(policyId: string): Promise<string>;
|
||||||
|
getTokensForPolicyIds(policyIds: string[]): Promise<Record<string, string>>;
|
||||||
|
getAllTokens(): Promise<Record<string, string>>;
|
||||||
|
getHashedTokenForPolicyId(policyId: string): Promise<string>;
|
||||||
|
getHashedTokensForPolicyIds(policyIds?: string[]): Promise<Record<string, string>>;
|
||||||
|
getAllHashedTokens(): Promise<Record<string, string>>;
|
||||||
|
generateTokenForPolicyId(policyId: string, force?: boolean): Promise<string>;
|
||||||
|
generateTokensForPolicyIds(policyIds: string[], force?: boolean): Promise<Record<string, string>>;
|
||||||
|
generateTokensForAllPolicies(force?: boolean): Promise<Record<string, string>>;
|
||||||
|
encryptTokens(): Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UninstallTokenService implements UninstallTokenServiceInterface {
|
||||||
|
private _soClient: SavedObjectsClientContract | undefined;
|
||||||
|
|
||||||
|
constructor(private esoClient: EncryptedSavedObjectsClient) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* gets uninstall token for given policy id
|
||||||
|
*
|
||||||
|
* @param policyId agent policy id
|
||||||
|
* @returns token
|
||||||
|
*/
|
||||||
|
public async getTokenForPolicyId(policyId: string): Promise<string> {
|
||||||
|
return (await this.getTokensForPolicyIds([policyId]))[policyId];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* gets uninstall tokens for given policy ids
|
||||||
|
*
|
||||||
|
* @param policyIds agent policy ids
|
||||||
|
* @returns Record<policyId, token>
|
||||||
|
*/
|
||||||
|
public async getTokensForPolicyIds(policyIds: string[]): Promise<Record<string, string>> {
|
||||||
|
let filter = policyIds
|
||||||
|
.map((policyId) => `${UNINSTALL_TOKENS_SAVED_OBJECT_TYPE}.attributes.policy_id: ${policyId}`)
|
||||||
|
.join(' or ');
|
||||||
|
const bucketSize = 10000;
|
||||||
|
const query: SavedObjectsCreatePointInTimeFinderOptions = {
|
||||||
|
type: UNINSTALL_TOKENS_SAVED_OBJECT_TYPE,
|
||||||
|
perPage: 0,
|
||||||
|
filter,
|
||||||
|
aggs: {
|
||||||
|
by_policy_id: {
|
||||||
|
terms: {
|
||||||
|
field: `${UNINSTALL_TOKENS_SAVED_OBJECT_TYPE}.attributes.policy_id`,
|
||||||
|
size: bucketSize,
|
||||||
|
},
|
||||||
|
aggs: {
|
||||||
|
latest: {
|
||||||
|
top_hits: {
|
||||||
|
size: 1,
|
||||||
|
sort: [{ [`${UNINSTALL_TOKENS_SAVED_OBJECT_TYPE}.created_at`]: { order: 'desc' } }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
// encrypted saved objects doesn't decrypt aggregation values so we get
|
||||||
|
// the ids first from saved objects to use with encrypted saved objects
|
||||||
|
const idFinder = this.soClient.createPointInTimeFinder<
|
||||||
|
UninstallTokenSOAttributes,
|
||||||
|
UninstallTokenSOAggregation
|
||||||
|
>(query);
|
||||||
|
|
||||||
|
let aggResults: UninstallTokenSOAggregationBucket[] = [];
|
||||||
|
for await (const result of idFinder.find()) {
|
||||||
|
if (
|
||||||
|
!result?.aggregations?.by_policy_id.buckets ||
|
||||||
|
!Array.isArray(result?.aggregations?.by_policy_id.buckets)
|
||||||
|
) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
aggResults = result.aggregations.by_policy_id.buckets;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
filter = aggResults
|
||||||
|
.reduce((acc, { latest }) => {
|
||||||
|
const id = latest?.hits?.hits?.at(0)?._id;
|
||||||
|
if (!id) return acc;
|
||||||
|
const filterStr = `${UNINSTALL_TOKENS_SAVED_OBJECT_TYPE}.id: "${id}"`;
|
||||||
|
return [...acc, filterStr];
|
||||||
|
}, [] as string[])
|
||||||
|
.join(' or ');
|
||||||
|
|
||||||
|
const tokensFinder =
|
||||||
|
await this.esoClient.createPointInTimeFinderDecryptedAsInternalUser<UninstallTokenSOAttributes>(
|
||||||
|
{
|
||||||
|
type: UNINSTALL_TOKENS_SAVED_OBJECT_TYPE,
|
||||||
|
perPage: SO_SEARCH_LIMIT,
|
||||||
|
filter,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
let tokenObjects: Array<SavedObjectsFindResult<UninstallTokenSOAttributes>> = [];
|
||||||
|
for await (const result of tokensFinder.find()) {
|
||||||
|
tokenObjects = [...tokenObjects, ...result.saved_objects];
|
||||||
|
}
|
||||||
|
tokensFinder.close();
|
||||||
|
|
||||||
|
const tokensMap = tokenObjects.reduce((acc, { attributes }) => {
|
||||||
|
const policyId = attributes.policy_id;
|
||||||
|
const token = attributes.token || attributes.token_plain;
|
||||||
|
if (!policyId || !token) {
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...acc,
|
||||||
|
[policyId]: token,
|
||||||
|
};
|
||||||
|
}, {} as Record<string, string>);
|
||||||
|
|
||||||
|
return tokensMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* gets uninstall tokens for all policies
|
||||||
|
*
|
||||||
|
* @returns Record<policyId, token>
|
||||||
|
*/
|
||||||
|
public async getAllTokens(): Promise<Record<string, string>> {
|
||||||
|
const policyIds = await this.getAllPolicyIds();
|
||||||
|
return this.getTokensForPolicyIds(policyIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get hashed uninstall token for given policy id
|
||||||
|
*
|
||||||
|
* @param policyId agent policy id
|
||||||
|
* @returns hashedToken
|
||||||
|
*/
|
||||||
|
public async getHashedTokenForPolicyId(policyId: string): Promise<string> {
|
||||||
|
return (await this.getHashedTokensForPolicyIds([policyId]))[policyId];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get hashed uninstall tokens for given policy ids
|
||||||
|
*
|
||||||
|
* @param policyIds agent policy ids
|
||||||
|
* @returns Record<policyId, hashedToken>
|
||||||
|
*/
|
||||||
|
public async getHashedTokensForPolicyIds(policyIds: string[]): Promise<Record<string, string>> {
|
||||||
|
const tokensMap = await this.getTokensForPolicyIds(policyIds);
|
||||||
|
return Object.entries(tokensMap).reduce((acc, [policyId, token]) => {
|
||||||
|
if (!policyId || !token) {
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
return { ...acc, [policyId]: this.hashToken(token) };
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get hashed uninstall token for all policies
|
||||||
|
*
|
||||||
|
* @returns Record<policyId, hashedToken>
|
||||||
|
*/
|
||||||
|
public async getAllHashedTokens(): Promise<Record<string, string>> {
|
||||||
|
const policyIds = await this.getAllPolicyIds();
|
||||||
|
return this.getHashedTokensForPolicyIds(policyIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* generate uninstall token for given policy id
|
||||||
|
* will not create a new token if one already exists for a given policy unless force: true is used
|
||||||
|
*
|
||||||
|
* @param policyId agent policy id
|
||||||
|
* @param force generate a new token even if one already exists
|
||||||
|
* @returns hashedToken
|
||||||
|
*/
|
||||||
|
public async generateTokenForPolicyId(policyId: string, force: boolean = false): Promise<string> {
|
||||||
|
return (await this.generateTokensForPolicyIds([policyId], force))[policyId];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* generate uninstall tokens for given policy ids
|
||||||
|
* will not create a new token if one already exists for a given policy unless force: true is used
|
||||||
|
*
|
||||||
|
* @param policyIds agent policy ids
|
||||||
|
* @param force generate a new token even if one already exists
|
||||||
|
* @returns Record<policyId, hashedToken>
|
||||||
|
*/
|
||||||
|
public async generateTokensForPolicyIds(
|
||||||
|
policyIds: string[],
|
||||||
|
force: boolean = false
|
||||||
|
): Promise<Record<string, string>> {
|
||||||
|
if (!policyIds.length) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const existingTokens = force ? {} : await this.getTokensForPolicyIds(policyIds);
|
||||||
|
const missingTokenPolicyIds = force
|
||||||
|
? policyIds
|
||||||
|
: policyIds.filter((policyId) => !existingTokens[policyId]);
|
||||||
|
|
||||||
|
const newTokensMap = missingTokenPolicyIds.reduce((acc, policyId) => {
|
||||||
|
const token = this.generateToken();
|
||||||
|
return {
|
||||||
|
...acc,
|
||||||
|
[policyId]: token,
|
||||||
|
};
|
||||||
|
}, {} as Record<string, string>);
|
||||||
|
|
||||||
|
await this.persistTokens(missingTokenPolicyIds, newTokensMap);
|
||||||
|
if (force) {
|
||||||
|
const config = appContextService.getConfig();
|
||||||
|
const batchSize = config?.setup?.agentPolicySchemaUpgradeBatchSize ?? 100;
|
||||||
|
asyncForEach(
|
||||||
|
chunk(policyIds, batchSize),
|
||||||
|
async (policyIdsBatch) =>
|
||||||
|
await agentPolicyService.deployPolicies(this.soClient, policyIdsBatch)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const tokensMap = {
|
||||||
|
...existingTokens,
|
||||||
|
...newTokensMap,
|
||||||
|
};
|
||||||
|
|
||||||
|
return Object.entries(tokensMap).reduce(
|
||||||
|
(acc, [policyId, token]) => ({
|
||||||
|
...acc,
|
||||||
|
[policyId]: this.hashToken(token),
|
||||||
|
}),
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* generate uninstall tokens all policies
|
||||||
|
* will not create a new token if one already exists for a given policy unless force: true is used
|
||||||
|
*
|
||||||
|
* @param force generate a new token even if one already exists
|
||||||
|
* @returns Record<policyId, hashedToken>
|
||||||
|
*/
|
||||||
|
public async generateTokensForAllPolicies(
|
||||||
|
force: boolean = false
|
||||||
|
): Promise<Record<string, string>> {
|
||||||
|
const policyIds = await this.getAllPolicyIds();
|
||||||
|
return this.generateTokensForPolicyIds(policyIds, force);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* if encryption is available, checks for any plain text uninstall tokens and encrypts them
|
||||||
|
*/
|
||||||
|
public async encryptTokens(): Promise<void> {
|
||||||
|
if (!this.isEncryptionAvailable) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { saved_objects: unencryptedTokenObjects } =
|
||||||
|
await this.soClient.find<UninstallTokenSOAttributes>({
|
||||||
|
type: UNINSTALL_TOKENS_SAVED_OBJECT_TYPE,
|
||||||
|
filter: `${UNINSTALL_TOKENS_SAVED_OBJECT_TYPE}.attributes.token_plain:* AND (NOT ${UNINSTALL_TOKENS_SAVED_OBJECT_TYPE}.attributes.token_plain: "")`,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!unencryptedTokenObjects.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bulkUpdateObjects: Array<SavedObjectsBulkUpdateObject<UninstallTokenSOAttributes>> = [];
|
||||||
|
for (const unencryptedTokenObject of unencryptedTokenObjects) {
|
||||||
|
bulkUpdateObjects.push({
|
||||||
|
...unencryptedTokenObject,
|
||||||
|
attributes: {
|
||||||
|
...unencryptedTokenObject.attributes,
|
||||||
|
token: unencryptedTokenObject.attributes.token_plain,
|
||||||
|
token_plain: '',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.soClient.bulkUpdate(bulkUpdateObjects);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getPolicyIdsBatch(
|
||||||
|
batchSize: number = SO_SEARCH_LIMIT,
|
||||||
|
page: number = 1
|
||||||
|
): Promise<string[]> {
|
||||||
|
return (
|
||||||
|
await agentPolicyService.list(this.soClient, { page, perPage: batchSize, fields: ['id'] })
|
||||||
|
).items.map((policy) => policy.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getAllPolicyIds(): Promise<string[]> {
|
||||||
|
const batchSize = SO_SEARCH_LIMIT;
|
||||||
|
let policyIdsBatch = await this.getPolicyIdsBatch(batchSize);
|
||||||
|
let policyIds = policyIdsBatch;
|
||||||
|
let page = 2;
|
||||||
|
|
||||||
|
while (policyIdsBatch.length === batchSize) {
|
||||||
|
policyIdsBatch = await this.getPolicyIdsBatch(batchSize, page);
|
||||||
|
policyIds = [...policyIds, ...policyIdsBatch];
|
||||||
|
page++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return policyIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async persistTokens(
|
||||||
|
policyIds: string[],
|
||||||
|
tokensMap: Record<string, string>
|
||||||
|
): Promise<void> {
|
||||||
|
if (!policyIds.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const config = appContextService.getConfig();
|
||||||
|
const batchSize = config?.setup?.agentPolicySchemaUpgradeBatchSize ?? 100;
|
||||||
|
|
||||||
|
asyncForEach(chunk(policyIds, batchSize), async (policyIdsBatch) => {
|
||||||
|
await this.soClient.bulkCreate<Partial<UninstallTokenSOAttributes>>(
|
||||||
|
policyIdsBatch.map((policyId) => ({
|
||||||
|
type: UNINSTALL_TOKENS_SAVED_OBJECT_TYPE,
|
||||||
|
attributes: this.isEncryptionAvailable
|
||||||
|
? {
|
||||||
|
policy_id: policyId,
|
||||||
|
token: tokensMap[policyId],
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
policy_id: policyId,
|
||||||
|
token_plain: tokensMap[policyId],
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private generateToken(): string {
|
||||||
|
return randomBytes(32).toString('hex');
|
||||||
|
}
|
||||||
|
|
||||||
|
private hashToken(token: string): string {
|
||||||
|
if (!token) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
const hash = createHash('sha256');
|
||||||
|
hash.update(token);
|
||||||
|
return hash.digest('base64');
|
||||||
|
}
|
||||||
|
|
||||||
|
private get soClient() {
|
||||||
|
if (this._soClient) {
|
||||||
|
return this._soClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fakeRequest = {
|
||||||
|
headers: {},
|
||||||
|
getBasePath: () => '',
|
||||||
|
path: '/',
|
||||||
|
route: { settings: {} },
|
||||||
|
url: { href: {} },
|
||||||
|
raw: { req: { url: '/' } },
|
||||||
|
} as unknown as KibanaRequest;
|
||||||
|
|
||||||
|
this._soClient = appContextService.getSavedObjects().getScopedClient(fakeRequest, {
|
||||||
|
excludedExtensions: [SECURITY_EXTENSION_ID],
|
||||||
|
includedHiddenTypes: [UNINSTALL_TOKENS_SAVED_OBJECT_TYPE],
|
||||||
|
});
|
||||||
|
|
||||||
|
return this._soClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
private get isEncryptionAvailable(): boolean {
|
||||||
|
return appContextService.getEncryptedSavedObjectsSetup()?.canEncrypt ?? false;
|
||||||
|
}
|
||||||
|
}
|
|
@ -177,6 +177,19 @@ async function createSetupSideEffects(
|
||||||
}
|
}
|
||||||
await appContextService.getMessageSigningService()?.generateKeyPair();
|
await appContextService.getMessageSigningService()?.generateKeyPair();
|
||||||
|
|
||||||
|
logger.debug('Generating Agent uninstall tokens');
|
||||||
|
if (!appContextService.getEncryptedSavedObjectsSetup()?.canEncrypt) {
|
||||||
|
logger.warn(
|
||||||
|
'xpack.encryptedSavedObjects.encryptionKey is not configured, agent uninstall tokens are being stored in plain text'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
await appContextService.getUninstallTokenService()?.generateTokensForAllPolicies();
|
||||||
|
|
||||||
|
if (appContextService.getEncryptedSavedObjectsSetup()?.canEncrypt) {
|
||||||
|
logger.debug('Checking for and encrypting plain text uninstall tokens');
|
||||||
|
await appContextService.getUninstallTokenService()?.encryptTokens();
|
||||||
|
}
|
||||||
|
|
||||||
logger.debug('Upgrade Agent policy schema version');
|
logger.debug('Upgrade Agent policy schema version');
|
||||||
await upgradeAgentPolicySchemaVersion(soClient);
|
await upgradeAgentPolicySchemaVersion(soClient);
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue