mirror of
https://github.com/elastic/kibana.git
synced 2025-04-25 02:09:32 -04:00
[UII] Remove references to .fleet-servers
index (#183868)
## Summary Resolves https://github.com/elastic/kibana/issues/173537. This PR removes all usages and references to `.fleet-servers` index. A small refactoring was done that moves helper functions in enrollment settings API to fleet server services. It's a better place for them because they are related to fleet server anyway. With this change, we now use these functions to calculate the readiness of Fleet (`hasFleetServers` was previously reading on `.fleet-servers` index). The readiness of a fleet server is defined as at least one online agent enrolled into an agent policy that contains a fleet server policy. ### Changes in Security Solution (@paul-tavares) - Updated `enableFleetServerIfNecessary()` utility _(used for testing, dev)_ to not use `.fleet-servers` index and instead write a Agent record to the `.fleet-agents` index for Fleet Server. This record writing is skipped when running tests in serverless mode, instead, `xpack.fleet.internal.fleetServerStandalone=true` is added to mimic skipping checks for Fleet Server in real serverless. ### Checklist - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --------- Co-authored-by: Paul Tavares <paul.tavares@elastic.co>
This commit is contained in:
parent
1772203328
commit
dceae3e5c3
30 changed files with 539 additions and 462 deletions
|
@ -37,8 +37,6 @@ export const FLEET_SERVER_INDICES_VERSION = 1;
|
||||||
|
|
||||||
export const FLEET_SERVER_ARTIFACTS_INDEX = '.fleet-artifacts';
|
export const FLEET_SERVER_ARTIFACTS_INDEX = '.fleet-artifacts';
|
||||||
|
|
||||||
export const FLEET_SERVER_SERVERS_INDEX = '.fleet-servers';
|
|
||||||
|
|
||||||
export const FLEET_SERVER_INDICES = [
|
export const FLEET_SERVER_INDICES = [
|
||||||
'.fleet-actions',
|
'.fleet-actions',
|
||||||
'.fleet-actions-results',
|
'.fleet-actions-results',
|
||||||
|
@ -47,7 +45,6 @@ export const FLEET_SERVER_INDICES = [
|
||||||
'.fleet-enrollment-api-keys',
|
'.fleet-enrollment-api-keys',
|
||||||
'.fleet-policies',
|
'.fleet-policies',
|
||||||
'.fleet-policies-leader',
|
'.fleet-policies-leader',
|
||||||
FLEET_SERVER_SERVERS_INDEX,
|
|
||||||
];
|
];
|
||||||
|
|
||||||
// Nodes that can be queried by datastreams API
|
// Nodes that can be queried by datastreams API
|
||||||
|
|
|
@ -32,7 +32,6 @@ export {
|
||||||
MESSAGE_SIGNING_KEYS_SAVED_OBJECT_TYPE,
|
MESSAGE_SIGNING_KEYS_SAVED_OBJECT_TYPE,
|
||||||
UNINSTALL_TOKENS_SAVED_OBJECT_TYPE,
|
UNINSTALL_TOKENS_SAVED_OBJECT_TYPE,
|
||||||
// Fleet server index
|
// Fleet server index
|
||||||
FLEET_SERVER_SERVERS_INDEX,
|
|
||||||
FLEET_SERVER_ARTIFACTS_INDEX,
|
FLEET_SERVER_ARTIFACTS_INDEX,
|
||||||
AGENTS_INDEX,
|
AGENTS_INDEX,
|
||||||
AGENT_POLICY_INDEX,
|
AGENT_POLICY_INDEX,
|
||||||
|
|
|
@ -9,7 +9,7 @@ import { FLEET_AGENT_LIST_PAGE } from '../../screens/fleet';
|
||||||
|
|
||||||
import { createAgentDoc } from '../../tasks/agents';
|
import { createAgentDoc } from '../../tasks/agents';
|
||||||
import { setupFleetServer } from '../../tasks/fleet_server';
|
import { setupFleetServer } from '../../tasks/fleet_server';
|
||||||
import { deleteFleetServerDocs, deleteAgentDocs, cleanupAgentPolicies } from '../../tasks/cleanup';
|
import { deleteAgentDocs, cleanupAgentPolicies } from '../../tasks/cleanup';
|
||||||
import type { CreateAgentPolicyRequest } from '../../../common/types';
|
import type { CreateAgentPolicyRequest } from '../../../common/types';
|
||||||
import { setUISettings } from '../../tasks/ui_settings';
|
import { setUISettings } from '../../tasks/ui_settings';
|
||||||
|
|
||||||
|
@ -87,7 +87,6 @@ function assertTableIsEmpty() {
|
||||||
|
|
||||||
describe('View agents list', () => {
|
describe('View agents list', () => {
|
||||||
before(() => {
|
before(() => {
|
||||||
deleteFleetServerDocs(true);
|
|
||||||
deleteAgentDocs(true);
|
deleteAgentDocs(true);
|
||||||
cleanupAgentPolicies();
|
cleanupAgentPolicies();
|
||||||
setupFleetServer();
|
setupFleetServer();
|
||||||
|
@ -103,7 +102,6 @@ describe('View agents list', () => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
after(() => {
|
after(() => {
|
||||||
deleteFleetServerDocs(true);
|
|
||||||
deleteAgentDocs(true);
|
deleteAgentDocs(true);
|
||||||
cleanupAgentPolicies();
|
cleanupAgentPolicies();
|
||||||
});
|
});
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ADD_AGENT_BUTTON, AGENT_FLYOUT } from '../screens/fleet';
|
import { ADD_AGENT_BUTTON, AGENT_FLYOUT } from '../screens/fleet';
|
||||||
import { cleanupAgentPolicies, deleteFleetServerDocs, deleteAgentDocs } from '../tasks/cleanup';
|
import { cleanupAgentPolicies, deleteAgentDocs } from '../tasks/cleanup';
|
||||||
import { createAgentDoc } from '../tasks/agents';
|
import { createAgentDoc } from '../tasks/agents';
|
||||||
import { setFleetServerHost } from '../tasks/fleet_server';
|
import { setFleetServerHost } from '../tasks/fleet_server';
|
||||||
import { FLEET, navigateTo } from '../tasks/navigation';
|
import { FLEET, navigateTo } from '../tasks/navigation';
|
||||||
|
@ -18,7 +18,6 @@ import { login } from '../tasks/login';
|
||||||
const FLEET_SERVER_POLICY_ID = 'fleet-server-policy';
|
const FLEET_SERVER_POLICY_ID = 'fleet-server-policy';
|
||||||
|
|
||||||
function cleanUp() {
|
function cleanUp() {
|
||||||
deleteFleetServerDocs(true);
|
|
||||||
deleteAgentDocs(true);
|
deleteAgentDocs(true);
|
||||||
cleanupAgentPolicies();
|
cleanupAgentPolicies();
|
||||||
}
|
}
|
||||||
|
@ -53,14 +52,6 @@ describe('Fleet add agent flyout', () => {
|
||||||
index: '.fleet-agents',
|
index: '.fleet-agents',
|
||||||
docs: [createAgentDoc('agent1', policyId, 'online', kibanaVersion)],
|
docs: [createAgentDoc('agent1', policyId, 'online', kibanaVersion)],
|
||||||
});
|
});
|
||||||
cy.task('insertDocs', {
|
|
||||||
index: '.fleet-servers',
|
|
||||||
docs: [
|
|
||||||
{
|
|
||||||
'@timestamp': new Date().toISOString(),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
setFleetServerHost();
|
setFleetServerHost();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,7 @@ import {
|
||||||
import { cleanupAgentPolicies, unenrollAgent } from '../tasks/cleanup';
|
import { cleanupAgentPolicies, unenrollAgent } from '../tasks/cleanup';
|
||||||
import { request } from '../tasks/common';
|
import { request } from '../tasks/common';
|
||||||
import { verifyPolicy, verifyAgentPackage, navigateToTab } from '../tasks/fleet';
|
import { verifyPolicy, verifyAgentPackage, navigateToTab } from '../tasks/fleet';
|
||||||
import { deleteFleetServer, setFleetServerHost } from '../tasks/fleet_server';
|
import { setFleetServerHost } from '../tasks/fleet_server';
|
||||||
import { login } from '../tasks/login';
|
import { login } from '../tasks/login';
|
||||||
import { FLEET, navigateTo } from '../tasks/navigation';
|
import { FLEET, navigateTo } from '../tasks/navigation';
|
||||||
|
|
||||||
|
@ -28,8 +28,6 @@ describe('Fleet startup', () => {
|
||||||
before(() => {
|
before(() => {
|
||||||
unenrollAgent();
|
unenrollAgent();
|
||||||
cleanupAgentPolicies();
|
cleanupAgentPolicies();
|
||||||
deleteFleetServer();
|
|
||||||
|
|
||||||
setFleetServerHost();
|
setFleetServerHost();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -48,13 +48,6 @@ export function cleanupDownloadSources() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function deleteFleetServerDocs(ignoreUnavailable: boolean = false) {
|
|
||||||
cy.task('deleteDocsByQuery', {
|
|
||||||
index: '.fleet-servers',
|
|
||||||
query: { match_all: {} },
|
|
||||||
ignoreUnavailable,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
export function deleteAgentDocs(ignoreUnavailable: boolean = false) {
|
export function deleteAgentDocs(ignoreUnavailable: boolean = false) {
|
||||||
cy.task('deleteDocsByQuery', {
|
cy.task('deleteDocsByQuery', {
|
||||||
index: '.fleet-agents',
|
index: '.fleet-agents',
|
||||||
|
|
|
@ -44,26 +44,10 @@ export async function setupFleetServer() {
|
||||||
index: '.fleet-agents',
|
index: '.fleet-agents',
|
||||||
docs: [createAgentDoc('fleet-server', policyId, 'online', kibanaVersion)],
|
docs: [createAgentDoc('fleet-server', policyId, 'online', kibanaVersion)],
|
||||||
});
|
});
|
||||||
cy.task('insertDocs', {
|
|
||||||
index: '.fleet-servers',
|
|
||||||
docs: [
|
|
||||||
{
|
|
||||||
'@timestamp': new Date().toISOString(),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
setFleetServerHost();
|
setFleetServerHost();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function deleteFleetServer() {
|
|
||||||
cy.task('deleteDocsByQuery', {
|
|
||||||
index: '.fleet-servers',
|
|
||||||
query: { match_all: {} },
|
|
||||||
ignoreUnavailable: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function setFleetServerHost(host = 'https://fleetserver:8220') {
|
export function setFleetServerHost(host = 'https://fleetserver:8220') {
|
||||||
request({
|
request({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
|
|
@ -58,10 +58,6 @@ The total schema for actions is represented by the `FleetServerAgentAction` type
|
||||||
|
|
||||||
- Cleanup model: N/A
|
- Cleanup model: N/A
|
||||||
|
|
||||||
### `.fleet-servers`
|
|
||||||
|
|
||||||
- Cleanup model: N/A
|
|
||||||
|
|
||||||
### `.fleet-artifacts`
|
### `.fleet-artifacts`
|
||||||
|
|
||||||
- Cleanup model: N/A
|
- Cleanup model: N/A
|
||||||
|
|
|
@ -43,7 +43,6 @@ export const FleetIndexDebugger = () => {
|
||||||
const indices = [
|
const indices = [
|
||||||
{ label: '.fleet-agents', value: '.fleet-agents' },
|
{ label: '.fleet-agents', value: '.fleet-agents' },
|
||||||
{ label: '.fleet-actions', value: '.fleet-actions' },
|
{ label: '.fleet-actions', value: '.fleet-actions' },
|
||||||
{ label: '.fleet-servers', value: '.fleet-servers' },
|
|
||||||
{ label: '.fleet-enrollment-api-keys', value: '.fleet-enrollment-api-keys' },
|
{ label: '.fleet-enrollment-api-keys', value: '.fleet-enrollment-api-keys' },
|
||||||
];
|
];
|
||||||
const [index, setIndex] = useState<string | undefined>();
|
const [index, setIndex] = useState<string | undefined>();
|
||||||
|
|
|
@ -58,7 +58,6 @@ export {
|
||||||
PACKAGE_POLICY_DEFAULT_INDEX_PRIVILEGES,
|
PACKAGE_POLICY_DEFAULT_INDEX_PRIVILEGES,
|
||||||
AGENT_POLICY_DEFAULT_MONITORING_DATASETS,
|
AGENT_POLICY_DEFAULT_MONITORING_DATASETS,
|
||||||
// Fleet Server index
|
// Fleet Server index
|
||||||
FLEET_SERVER_SERVERS_INDEX,
|
|
||||||
ENROLLMENT_API_KEYS_INDEX,
|
ENROLLMENT_API_KEYS_INDEX,
|
||||||
AGENTS_INDEX,
|
AGENTS_INDEX,
|
||||||
// Preconfiguration
|
// Preconfiguration
|
||||||
|
|
|
@ -4,21 +4,14 @@
|
||||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
import { savedObjectsClientMock, elasticsearchServiceMock } from '@kbn/core/server/mocks';
|
import { savedObjectsClientMock } from '@kbn/core/server/mocks';
|
||||||
|
|
||||||
import { packagePolicyService, agentPolicyService } from '../../services';
|
import { agentPolicyService } from '../../services';
|
||||||
import { getAgentStatusForAgentPolicy } from '../../services/agents';
|
import { getFleetServerPolicies } from '../../services/fleet_server';
|
||||||
|
|
||||||
import {
|
import { getFleetServerOrAgentPolicies, getDownloadSource } from './enrollment_settings_handler';
|
||||||
getFleetServerPolicies,
|
|
||||||
hasActiveFleetServersForPolicies,
|
|
||||||
getDownloadSource,
|
|
||||||
} from './enrollment_settings_handler';
|
|
||||||
|
|
||||||
jest.mock('../../services', () => ({
|
jest.mock('../../services', () => ({
|
||||||
packagePolicyService: {
|
|
||||||
list: jest.fn(),
|
|
||||||
},
|
|
||||||
agentPolicyService: {
|
agentPolicyService: {
|
||||||
get: jest.fn(),
|
get: jest.fn(),
|
||||||
getByIDs: jest.fn(),
|
getByIDs: jest.fn(),
|
||||||
|
@ -44,13 +37,12 @@ jest.mock('../../services', () => ({
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
jest.mock('../../services/agents', () => ({
|
jest.mock('../../services/fleet_server', () => ({
|
||||||
getAgentStatusForAgentPolicy: jest.fn(),
|
getFleetServerPolicies: jest.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
describe('EnrollmentSettingsHandler utils', () => {
|
describe('EnrollmentSettingsHandler utils', () => {
|
||||||
const mockSoClient = savedObjectsClientMock.create();
|
const mockSoClient = savedObjectsClientMock.create();
|
||||||
const mockEsClient = elasticsearchServiceMock.createInternalClient();
|
|
||||||
const mockAgentPolicies = [
|
const mockAgentPolicies = [
|
||||||
{
|
{
|
||||||
id: 'agent-policy-1',
|
id: 'agent-policy-1',
|
||||||
|
@ -124,20 +116,21 @@ describe('EnrollmentSettingsHandler utils', () => {
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
describe('getFleetServerPolicies', () => {
|
describe('getFleetServerOrAgentPolicies', () => {
|
||||||
it('returns only fleet server policies if there are any when no agent policy ID is provided', async () => {
|
it('returns only fleet server policies if there are any when no agent policy ID is provided', async () => {
|
||||||
(packagePolicyService.list as jest.Mock).mockResolvedValueOnce({
|
(getFleetServerPolicies as jest.Mock).mockResolvedValueOnce(mockFleetServerPolicies);
|
||||||
items: [{ policy_id: 'fs-policy-1' }, { policy_id: 'fs-policy-2' }],
|
const { fleetServerPolicies, scopedAgentPolicy } = await getFleetServerOrAgentPolicies(
|
||||||
});
|
mockSoClient
|
||||||
(agentPolicyService.getByIDs as jest.Mock).mockResolvedValueOnce(mockFleetServerPolicies);
|
);
|
||||||
const { fleetServerPolicies, scopedAgentPolicy } = await getFleetServerPolicies(mockSoClient);
|
|
||||||
expect(fleetServerPolicies).toEqual(mockFleetServerPolicies);
|
expect(fleetServerPolicies).toEqual(mockFleetServerPolicies);
|
||||||
expect(scopedAgentPolicy).toBeUndefined();
|
expect(scopedAgentPolicy).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns no fleet server policies when there are none and no agent policy ID is provided', async () => {
|
it('returns no fleet server policies when there are none and no agent policy ID is provided', async () => {
|
||||||
(packagePolicyService.list as jest.Mock).mockResolvedValueOnce({ items: [] });
|
(getFleetServerPolicies as jest.Mock).mockResolvedValueOnce([]);
|
||||||
const { fleetServerPolicies, scopedAgentPolicy } = await getFleetServerPolicies(mockSoClient);
|
const { fleetServerPolicies, scopedAgentPolicy } = await getFleetServerOrAgentPolicies(
|
||||||
|
mockSoClient
|
||||||
|
);
|
||||||
expect(fleetServerPolicies).toEqual([]);
|
expect(fleetServerPolicies).toEqual([]);
|
||||||
expect(scopedAgentPolicy).toBeUndefined();
|
expect(scopedAgentPolicy).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
@ -147,7 +140,7 @@ describe('EnrollmentSettingsHandler utils', () => {
|
||||||
...mockFleetServerPolicies[1],
|
...mockFleetServerPolicies[1],
|
||||||
package_policies: [mockPackagePolicies[1]],
|
package_policies: [mockPackagePolicies[1]],
|
||||||
});
|
});
|
||||||
const { fleetServerPolicies, scopedAgentPolicy } = await getFleetServerPolicies(
|
const { fleetServerPolicies, scopedAgentPolicy } = await getFleetServerOrAgentPolicies(
|
||||||
mockSoClient,
|
mockSoClient,
|
||||||
'fs-policy-2'
|
'fs-policy-2'
|
||||||
);
|
);
|
||||||
|
@ -160,7 +153,7 @@ describe('EnrollmentSettingsHandler utils', () => {
|
||||||
...mockAgentPolicies[1],
|
...mockAgentPolicies[1],
|
||||||
package_policies: [mockPackagePolicies[2]],
|
package_policies: [mockPackagePolicies[2]],
|
||||||
});
|
});
|
||||||
const { fleetServerPolicies, scopedAgentPolicy } = await getFleetServerPolicies(
|
const { fleetServerPolicies, scopedAgentPolicy } = await getFleetServerOrAgentPolicies(
|
||||||
mockSoClient,
|
mockSoClient,
|
||||||
'agent-policy-2'
|
'agent-policy-2'
|
||||||
);
|
);
|
||||||
|
@ -170,7 +163,7 @@ describe('EnrollmentSettingsHandler utils', () => {
|
||||||
|
|
||||||
it('returns no policies when specified agent policy ID is not found', async () => {
|
it('returns no policies when specified agent policy ID is not found', async () => {
|
||||||
(agentPolicyService.get as jest.Mock).mockResolvedValueOnce(undefined);
|
(agentPolicyService.get as jest.Mock).mockResolvedValueOnce(undefined);
|
||||||
const { fleetServerPolicies, scopedAgentPolicy } = await getFleetServerPolicies(
|
const { fleetServerPolicies, scopedAgentPolicy } = await getFleetServerOrAgentPolicies(
|
||||||
mockSoClient,
|
mockSoClient,
|
||||||
'agent-policy-3'
|
'agent-policy-3'
|
||||||
);
|
);
|
||||||
|
@ -179,73 +172,6 @@ describe('EnrollmentSettingsHandler utils', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('hasActiveFleetServersForPolicies', () => {
|
|
||||||
it('returns false when no agent IDs are provided', async () => {
|
|
||||||
const hasActive = await hasActiveFleetServersForPolicies(mockEsClient, mockSoClient, []);
|
|
||||||
expect(hasActive).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns true when at least one agent is online', async () => {
|
|
||||||
(getAgentStatusForAgentPolicy as jest.Mock).mockResolvedValueOnce({
|
|
||||||
other: 0,
|
|
||||||
events: 0,
|
|
||||||
total: 1,
|
|
||||||
all: 1,
|
|
||||||
active: 0,
|
|
||||||
updating: 0,
|
|
||||||
offline: 0,
|
|
||||||
inactive: 0,
|
|
||||||
unenrolled: 0,
|
|
||||||
online: 1,
|
|
||||||
error: 0,
|
|
||||||
});
|
|
||||||
const hasActive = await hasActiveFleetServersForPolicies(mockEsClient, mockSoClient, [
|
|
||||||
'policy-1',
|
|
||||||
]);
|
|
||||||
expect(hasActive).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns true when at least one agent is updating', async () => {
|
|
||||||
(getAgentStatusForAgentPolicy as jest.Mock).mockResolvedValueOnce({
|
|
||||||
other: 0,
|
|
||||||
events: 0,
|
|
||||||
total: 1,
|
|
||||||
all: 1,
|
|
||||||
active: 0,
|
|
||||||
updating: 1,
|
|
||||||
offline: 0,
|
|
||||||
inactive: 0,
|
|
||||||
unenrolled: 0,
|
|
||||||
online: 0,
|
|
||||||
error: 0,
|
|
||||||
});
|
|
||||||
const hasActive = await hasActiveFleetServersForPolicies(mockEsClient, mockSoClient, [
|
|
||||||
'policy-1',
|
|
||||||
]);
|
|
||||||
expect(hasActive).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns false when no agents are updating or online', async () => {
|
|
||||||
(getAgentStatusForAgentPolicy as jest.Mock).mockResolvedValueOnce({
|
|
||||||
other: 0,
|
|
||||||
events: 0,
|
|
||||||
total: 3,
|
|
||||||
all: 3,
|
|
||||||
active: 1,
|
|
||||||
updating: 0,
|
|
||||||
offline: 1,
|
|
||||||
inactive: 1,
|
|
||||||
unenrolled: 1,
|
|
||||||
online: 0,
|
|
||||||
error: 1,
|
|
||||||
});
|
|
||||||
const hasActive = await hasActiveFleetServersForPolicies(mockEsClient, mockSoClient, [
|
|
||||||
'policy-1',
|
|
||||||
]);
|
|
||||||
expect(hasActive).toBe(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('getDownloadSource', () => {
|
describe('getDownloadSource', () => {
|
||||||
it('returns the default download source when no id is specified', async () => {
|
it('returns the default download source when no id is specified', async () => {
|
||||||
const source = await getDownloadSource(mockSoClient);
|
const source = await getDownloadSource(mockSoClient);
|
||||||
|
|
|
@ -7,9 +7,9 @@
|
||||||
|
|
||||||
import type { TypeOf } from '@kbn/config-schema';
|
import type { TypeOf } from '@kbn/config-schema';
|
||||||
|
|
||||||
import type { ElasticsearchClient, SavedObjectsClientContract } from '@kbn/core/server';
|
import type { SavedObjectsClientContract } from '@kbn/core/server';
|
||||||
|
|
||||||
import { PACKAGE_POLICY_SAVED_OBJECT_TYPE, FLEET_SERVER_PACKAGE } from '../../../common/constants';
|
import { FLEET_SERVER_PACKAGE } from '../../../common/constants';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
GetEnrollmentSettingsResponse,
|
GetEnrollmentSettingsResponse,
|
||||||
|
@ -18,10 +18,10 @@ import type {
|
||||||
} from '../../../common/types';
|
} from '../../../common/types';
|
||||||
import type { FleetRequestHandler, GetEnrollmentSettingsRequestSchema } from '../../types';
|
import type { FleetRequestHandler, GetEnrollmentSettingsRequestSchema } from '../../types';
|
||||||
import { defaultFleetErrorHandler } from '../../errors';
|
import { defaultFleetErrorHandler } from '../../errors';
|
||||||
import { agentPolicyService, packagePolicyService, downloadSourceService } from '../../services';
|
import { agentPolicyService, downloadSourceService } from '../../services';
|
||||||
import { getAgentStatusForAgentPolicy } from '../../services/agents';
|
|
||||||
import { getFleetServerHostsForAgentPolicy } from '../../services/fleet_server_host';
|
import { getFleetServerHostsForAgentPolicy } from '../../services/fleet_server_host';
|
||||||
import { getFleetProxy } from '../../services/fleet_proxies';
|
import { getFleetProxy } from '../../services/fleet_proxies';
|
||||||
|
import { getFleetServerPolicies, hasFleetServersForPolicies } from '../../services/fleet_server';
|
||||||
|
|
||||||
export const getEnrollmentSettingsHandler: FleetRequestHandler<
|
export const getEnrollmentSettingsHandler: FleetRequestHandler<
|
||||||
undefined,
|
undefined,
|
||||||
|
@ -40,7 +40,7 @@ export const getEnrollmentSettingsHandler: FleetRequestHandler<
|
||||||
try {
|
try {
|
||||||
// Get all possible fleet server or scoped normal agent policies
|
// Get all possible fleet server or scoped normal agent policies
|
||||||
const { fleetServerPolicies, scopedAgentPolicy: scopedAgentPolicyResponse } =
|
const { fleetServerPolicies, scopedAgentPolicy: scopedAgentPolicyResponse } =
|
||||||
await getFleetServerPolicies(soClient, agentPolicyId);
|
await getFleetServerOrAgentPolicies(soClient, agentPolicyId);
|
||||||
const scopedAgentPolicy = scopedAgentPolicyResponse || {
|
const scopedAgentPolicy = scopedAgentPolicyResponse || {
|
||||||
id: undefined,
|
id: undefined,
|
||||||
name: undefined,
|
name: undefined,
|
||||||
|
@ -51,10 +51,11 @@ export const getEnrollmentSettingsHandler: FleetRequestHandler<
|
||||||
// Check if there is any active fleet server enrolled into the fleet server policies policies
|
// Check if there is any active fleet server enrolled into the fleet server policies policies
|
||||||
if (fleetServerPolicies) {
|
if (fleetServerPolicies) {
|
||||||
settingsResponse.fleet_server.policies = fleetServerPolicies;
|
settingsResponse.fleet_server.policies = fleetServerPolicies;
|
||||||
settingsResponse.fleet_server.has_active = await hasActiveFleetServersForPolicies(
|
settingsResponse.fleet_server.has_active = await hasFleetServersForPolicies(
|
||||||
esClient,
|
esClient,
|
||||||
soClient,
|
soClient,
|
||||||
fleetServerPolicies.map((p) => p.id)
|
fleetServerPolicies.map((p) => p.id),
|
||||||
|
true
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,7 +100,7 @@ export const getEnrollmentSettingsHandler: FleetRequestHandler<
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getFleetServerPolicies = async (
|
export const getFleetServerOrAgentPolicies = async (
|
||||||
soClient: SavedObjectsClientContract,
|
soClient: SavedObjectsClientContract,
|
||||||
agentPolicyId?: string
|
agentPolicyId?: string
|
||||||
): Promise<{
|
): Promise<{
|
||||||
|
@ -134,42 +135,9 @@ export const getFleetServerPolicies = async (
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
// If an agent policy is not specified, perform default behavior to retrieve all fleet server policies
|
// If an agent policy is not specified, return all fleet server policies
|
||||||
const fleetServerPackagePolicies = await packagePolicyService.list(soClient, {
|
const fleetServerPolicies = (await getFleetServerPolicies(soClient)).map(mapPolicy);
|
||||||
kuery: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name:${FLEET_SERVER_PACKAGE}`,
|
return { fleetServerPolicies };
|
||||||
});
|
|
||||||
|
|
||||||
// Extract associated fleet server agent policy IDs
|
|
||||||
const fleetServerAgentPolicyIds = [
|
|
||||||
...new Set(fleetServerPackagePolicies.items.map((p) => p.policy_id)),
|
|
||||||
];
|
|
||||||
|
|
||||||
// Retrieve associated agent policies
|
|
||||||
const fleetServerAgentPolicies = fleetServerAgentPolicyIds.length
|
|
||||||
? await agentPolicyService.getByIDs(soClient, fleetServerAgentPolicyIds)
|
|
||||||
: [];
|
|
||||||
|
|
||||||
return {
|
|
||||||
fleetServerPolicies: fleetServerAgentPolicies.map(mapPolicy),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const hasActiveFleetServersForPolicies = async (
|
|
||||||
esClient: ElasticsearchClient,
|
|
||||||
soClient: SavedObjectsClientContract,
|
|
||||||
agentPolicyIds: string[]
|
|
||||||
): Promise<boolean> => {
|
|
||||||
if (agentPolicyIds.length > 0) {
|
|
||||||
const agentStatusesRes = await getAgentStatusForAgentPolicy(
|
|
||||||
esClient,
|
|
||||||
soClient,
|
|
||||||
undefined,
|
|
||||||
agentPolicyIds.map((id) => `policy_id:${id}`).join(' or ')
|
|
||||||
);
|
|
||||||
|
|
||||||
return agentStatusesRes.online > 0 || agentStatusesRes.updating > 0;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getDownloadSource = async (
|
export const getDownloadSource = async (
|
||||||
|
|
|
@ -25,9 +25,7 @@ export const getFleetStatusHandler: FleetRequestHandler = async (context, reques
|
||||||
const isApiKeysEnabled = await appContextService
|
const isApiKeysEnabled = await appContextService
|
||||||
.getSecurity()
|
.getSecurity()
|
||||||
.authc.apiKeys.areAPIKeysEnabled();
|
.authc.apiKeys.areAPIKeysEnabled();
|
||||||
const isFleetServerMissing = !(await hasFleetServers(
|
const isFleetServerMissing = !(await hasFleetServers(esClient, soClient));
|
||||||
coreContext.elasticsearch.client.asInternalUser
|
|
||||||
));
|
|
||||||
|
|
||||||
const isFleetServerStandalone =
|
const isFleetServerStandalone =
|
||||||
appContextService.getConfig()?.internal?.fleetServerStandalone ?? false;
|
appContextService.getConfig()?.internal?.fleetServerStandalone ?? false;
|
||||||
|
|
|
@ -130,14 +130,14 @@ export async function getAgentStatusForAgentPolicy(
|
||||||
const allActive = allStatuses - combinedStatuses.unenrolled - combinedStatuses.inactive;
|
const allActive = allStatuses - combinedStatuses.unenrolled - combinedStatuses.inactive;
|
||||||
return {
|
return {
|
||||||
...combinedStatuses,
|
...combinedStatuses,
|
||||||
|
all: allStatuses,
|
||||||
|
active: allActive,
|
||||||
/* @deprecated no agents will have other status */
|
/* @deprecated no agents will have other status */
|
||||||
other: 0,
|
other: 0,
|
||||||
/* @deprecated Agent events do not exists anymore */
|
/* @deprecated Agent events do not exists anymore */
|
||||||
events: 0,
|
events: 0,
|
||||||
/* @deprecated use active instead */
|
/* @deprecated use active instead */
|
||||||
total: allActive,
|
total: allActive,
|
||||||
all: allStatuses,
|
|
||||||
active: allActive,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
export async function getIncomingDataByAgentsId(
|
export async function getIncomingDataByAgentsId(
|
||||||
|
|
|
@ -15,9 +15,13 @@ import { createAppContextStartContractMock } from '../../mocks';
|
||||||
|
|
||||||
import { agentPolicyService } from '../agent_policy';
|
import { agentPolicyService } from '../agent_policy';
|
||||||
import { packagePolicyService } from '../package_policy';
|
import { packagePolicyService } from '../package_policy';
|
||||||
import { getAgentsByKuery, getAgentStatusById } from '../agents';
|
import { getAgentsByKuery, getAgentStatusById, getAgentStatusForAgentPolicy } from '../agents';
|
||||||
|
|
||||||
import { checkFleetServerVersionsForSecretsStorage } from '.';
|
import {
|
||||||
|
checkFleetServerVersionsForSecretsStorage,
|
||||||
|
hasFleetServersForPolicies,
|
||||||
|
getFleetServerPolicies,
|
||||||
|
} from '.';
|
||||||
|
|
||||||
jest.mock('../agent_policy');
|
jest.mock('../agent_policy');
|
||||||
jest.mock('../package_policy');
|
jest.mock('../package_policy');
|
||||||
|
@ -111,3 +115,177 @@ describe('checkFleetServerVersionsForSecretsStorage', () => {
|
||||||
expect(result).toBe(true);
|
expect(result).toBe(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('getFleetServerPolicies', () => {
|
||||||
|
const soClient = savedObjectsClientMock.create();
|
||||||
|
const mockPackagePolicies = [
|
||||||
|
{
|
||||||
|
id: 'package-policy-1',
|
||||||
|
name: 'Package Policy 1',
|
||||||
|
package: {
|
||||||
|
name: 'fleet_server',
|
||||||
|
title: 'Fleet Server',
|
||||||
|
version: '1.0.0',
|
||||||
|
},
|
||||||
|
policy_id: 'fs-policy-1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'package-policy-2',
|
||||||
|
name: 'Package Policy 2',
|
||||||
|
package: {
|
||||||
|
name: 'fleet_server',
|
||||||
|
title: 'Fleet Server',
|
||||||
|
version: '1.0.0',
|
||||||
|
},
|
||||||
|
policy_id: 'fs-policy-2',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'package-policy-3',
|
||||||
|
name: 'Package Policy 3',
|
||||||
|
package: {
|
||||||
|
name: 'system',
|
||||||
|
title: 'System',
|
||||||
|
version: '1.0.0',
|
||||||
|
},
|
||||||
|
policy_id: 'agent-policy-2',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const mockFleetServerPolicies = [
|
||||||
|
{
|
||||||
|
id: 'fs-policy-1',
|
||||||
|
name: 'FS Policy 1',
|
||||||
|
is_managed: true,
|
||||||
|
is_default_fleet_server: true,
|
||||||
|
has_fleet_server: true,
|
||||||
|
download_source_id: undefined,
|
||||||
|
fleet_server_host_id: undefined,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'fs-policy-2',
|
||||||
|
name: 'FS Policy 2',
|
||||||
|
is_managed: true,
|
||||||
|
is_default_fleet_server: false,
|
||||||
|
has_fleet_server: false,
|
||||||
|
download_source_id: undefined,
|
||||||
|
fleet_server_host_id: undefined,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
it('should return no policies if there are no fleet server package policies', async () => {
|
||||||
|
(mockedPackagePolicyService.list as jest.Mock).mockResolvedValueOnce({
|
||||||
|
items: [],
|
||||||
|
});
|
||||||
|
const result = await getFleetServerPolicies(soClient);
|
||||||
|
expect(result).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return agent policies with fleet server package policies', async () => {
|
||||||
|
(mockedPackagePolicyService.list as jest.Mock).mockResolvedValueOnce({
|
||||||
|
items: mockPackagePolicies,
|
||||||
|
});
|
||||||
|
(mockedAgentPolicyService.getByIDs as jest.Mock).mockResolvedValueOnce(mockFleetServerPolicies);
|
||||||
|
const result = await getFleetServerPolicies(soClient);
|
||||||
|
expect(result).toEqual(mockFleetServerPolicies);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('hasActiveFleetServersForPolicies', () => {
|
||||||
|
const mockSoClient = savedObjectsClientMock.create();
|
||||||
|
const mockEsClient = elasticsearchServiceMock.createInternalClient();
|
||||||
|
|
||||||
|
it('returns false when no agent IDs are provided', async () => {
|
||||||
|
const hasFs = await hasFleetServersForPolicies(mockEsClient, mockSoClient, []);
|
||||||
|
expect(hasFs).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('activeOnly is true', () => {
|
||||||
|
it('returns true when at least one agent is online', async () => {
|
||||||
|
(getAgentStatusForAgentPolicy as jest.Mock).mockResolvedValueOnce({
|
||||||
|
other: 0,
|
||||||
|
events: 0,
|
||||||
|
total: 1,
|
||||||
|
all: 1,
|
||||||
|
active: 0,
|
||||||
|
updating: 0,
|
||||||
|
offline: 0,
|
||||||
|
inactive: 0,
|
||||||
|
unenrolled: 0,
|
||||||
|
online: 1,
|
||||||
|
error: 0,
|
||||||
|
});
|
||||||
|
const hasFs = await hasFleetServersForPolicies(
|
||||||
|
mockEsClient,
|
||||||
|
mockSoClient,
|
||||||
|
['policy-1'],
|
||||||
|
true
|
||||||
|
);
|
||||||
|
expect(hasFs).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns true when at least one agent is updating', async () => {
|
||||||
|
(getAgentStatusForAgentPolicy as jest.Mock).mockResolvedValueOnce({
|
||||||
|
other: 0,
|
||||||
|
events: 0,
|
||||||
|
total: 1,
|
||||||
|
all: 1,
|
||||||
|
active: 0,
|
||||||
|
updating: 1,
|
||||||
|
offline: 0,
|
||||||
|
inactive: 0,
|
||||||
|
unenrolled: 0,
|
||||||
|
online: 0,
|
||||||
|
error: 0,
|
||||||
|
});
|
||||||
|
const hasFs = await hasFleetServersForPolicies(
|
||||||
|
mockEsClient,
|
||||||
|
mockSoClient,
|
||||||
|
['policy-1'],
|
||||||
|
true
|
||||||
|
);
|
||||||
|
expect(hasFs).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns false when no agents are updating or online', async () => {
|
||||||
|
(getAgentStatusForAgentPolicy as jest.Mock).mockResolvedValueOnce({
|
||||||
|
other: 0,
|
||||||
|
events: 0,
|
||||||
|
total: 3,
|
||||||
|
all: 3,
|
||||||
|
active: 1,
|
||||||
|
updating: 0,
|
||||||
|
offline: 1,
|
||||||
|
inactive: 1,
|
||||||
|
unenrolled: 1,
|
||||||
|
online: 0,
|
||||||
|
error: 1,
|
||||||
|
});
|
||||||
|
const hasFs = await hasFleetServersForPolicies(
|
||||||
|
mockEsClient,
|
||||||
|
mockSoClient,
|
||||||
|
['policy-1'],
|
||||||
|
true
|
||||||
|
);
|
||||||
|
expect(hasFs).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('activeOnly is false', () => {
|
||||||
|
it('returns true when at least one agent is found regardless of its status', async () => {
|
||||||
|
(getAgentStatusForAgentPolicy as jest.Mock).mockResolvedValueOnce({
|
||||||
|
other: 0,
|
||||||
|
events: 0,
|
||||||
|
total: 0,
|
||||||
|
all: 1,
|
||||||
|
active: 0,
|
||||||
|
updating: 0,
|
||||||
|
offline: 1,
|
||||||
|
inactive: 0,
|
||||||
|
unenrolled: 0,
|
||||||
|
online: 0,
|
||||||
|
error: 0,
|
||||||
|
});
|
||||||
|
const hasFs = await hasFleetServersForPolicies(mockEsClient, mockSoClient, ['policy-1']);
|
||||||
|
expect(hasFs).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
@ -9,26 +9,78 @@ import type { ElasticsearchClient, SavedObjectsClientContract } from '@kbn/core/
|
||||||
import semverGte from 'semver/functions/gte';
|
import semverGte from 'semver/functions/gte';
|
||||||
import semverCoerce from 'semver/functions/coerce';
|
import semverCoerce from 'semver/functions/coerce';
|
||||||
|
|
||||||
import { FLEET_SERVER_SERVERS_INDEX, SO_SEARCH_LIMIT } from '../../constants';
|
import type { AgentPolicy } from '../../../common/types';
|
||||||
|
import { PACKAGE_POLICY_SAVED_OBJECT_TYPE, FLEET_SERVER_PACKAGE } from '../../../common/constants';
|
||||||
|
|
||||||
|
import { SO_SEARCH_LIMIT } from '../../constants';
|
||||||
import { getAgentsByKuery, getAgentStatusById } from '../agents';
|
import { getAgentsByKuery, getAgentStatusById } from '../agents';
|
||||||
|
|
||||||
import { packagePolicyService } from '../package_policy';
|
import { packagePolicyService } from '../package_policy';
|
||||||
import { agentPolicyService } from '../agent_policy';
|
import { agentPolicyService } from '../agent_policy';
|
||||||
|
import { getAgentStatusForAgentPolicy } from '../agents';
|
||||||
import { appContextService } from '..';
|
import { appContextService } from '..';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if at least one fleet server is connected
|
* Retrieve all agent policies which has a Fleet Server package policy
|
||||||
*/
|
*/
|
||||||
export async function hasFleetServers(esClient: ElasticsearchClient) {
|
export const getFleetServerPolicies = async (
|
||||||
const res = await esClient.search<{}, {}>({
|
soClient: SavedObjectsClientContract
|
||||||
index: FLEET_SERVER_SERVERS_INDEX,
|
): Promise<AgentPolicy[]> => {
|
||||||
ignore_unavailable: true,
|
const fleetServerPackagePolicies = await packagePolicyService.list(soClient, {
|
||||||
filter_path: 'hits.total',
|
kuery: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name:${FLEET_SERVER_PACKAGE}`,
|
||||||
track_total_hits: true,
|
|
||||||
rest_total_hits_as_int: true,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return (res.hits.total as number) > 0;
|
// Extract associated fleet server agent policy IDs
|
||||||
|
const fleetServerAgentPolicyIds = [
|
||||||
|
...new Set(fleetServerPackagePolicies.items.map((p) => p.policy_id)),
|
||||||
|
];
|
||||||
|
|
||||||
|
// Retrieve associated agent policies
|
||||||
|
const fleetServerAgentPolicies = fleetServerAgentPolicyIds.length
|
||||||
|
? await agentPolicyService.getByIDs(soClient, fleetServerAgentPolicyIds)
|
||||||
|
: [];
|
||||||
|
|
||||||
|
return fleetServerAgentPolicies;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if there is at least one agent enrolled into the given agent policies.
|
||||||
|
* Assumes that `agentPolicyIds` contains list of Fleet Server agent policies.
|
||||||
|
* `activeOnly` flag can be used to filter only active agents.
|
||||||
|
*/
|
||||||
|
export const hasFleetServersForPolicies = async (
|
||||||
|
esClient: ElasticsearchClient,
|
||||||
|
soClient: SavedObjectsClientContract,
|
||||||
|
agentPolicyIds: string[],
|
||||||
|
activeOnly: boolean = false
|
||||||
|
): Promise<boolean> => {
|
||||||
|
if (agentPolicyIds.length > 0) {
|
||||||
|
const agentStatusesRes = await getAgentStatusForAgentPolicy(
|
||||||
|
esClient,
|
||||||
|
soClient,
|
||||||
|
undefined,
|
||||||
|
agentPolicyIds.map((id) => `policy_id:${id}`).join(' or ')
|
||||||
|
);
|
||||||
|
|
||||||
|
return activeOnly
|
||||||
|
? agentStatusesRes.online > 0 || agentStatusesRes.updating > 0
|
||||||
|
: agentStatusesRes.all > 0;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if at least one fleet server agent exists, regardless of its online status
|
||||||
|
*/
|
||||||
|
export async function hasFleetServers(
|
||||||
|
esClient: ElasticsearchClient,
|
||||||
|
soClient: SavedObjectsClientContract
|
||||||
|
) {
|
||||||
|
return await hasFleetServersForPolicies(
|
||||||
|
esClient,
|
||||||
|
soClient,
|
||||||
|
(await getFleetServerPolicies(soClient)).map((policy) => policy.id)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -84,6 +84,7 @@ export class FleetAgentGenerator extends BaseDataGenerator<Agent> {
|
||||||
const hostname = this.randomHostname();
|
const hostname = this.randomHostname();
|
||||||
const now = new Date().toISOString();
|
const now = new Date().toISOString();
|
||||||
const osFamily = this.randomOSFamily();
|
const osFamily = this.randomOSFamily();
|
||||||
|
const version = overrides?._source?.agent?.version ?? this.randomVersion();
|
||||||
const componentStatus = this.randomChoice<FleetServerAgentComponentStatus>(
|
const componentStatus = this.randomChoice<FleetServerAgentComponentStatus>(
|
||||||
FleetServerAgentComponentStatuses
|
FleetServerAgentComponentStatuses
|
||||||
);
|
);
|
||||||
|
@ -113,19 +114,19 @@ export class FleetAgentGenerator extends BaseDataGenerator<Agent> {
|
||||||
enrolled_at: now,
|
enrolled_at: now,
|
||||||
agent: {
|
agent: {
|
||||||
id: agentId,
|
id: agentId,
|
||||||
version: this.randomVersion(),
|
version,
|
||||||
},
|
},
|
||||||
local_metadata: {
|
local_metadata: {
|
||||||
elastic: {
|
elastic: {
|
||||||
agent: {
|
agent: {
|
||||||
'build.original': `8.0.0-SNAPSHOT (build: ${this.randomString(
|
'build.original': `${version} (build: ${this.randomString(
|
||||||
5
|
5
|
||||||
)} at 2021-05-07 18:42:49 +0000 UTC)`,
|
)} at 2021-05-07 18:42:49 +0000 UTC)`,
|
||||||
id: agentId,
|
id: agentId,
|
||||||
log_level: 'info',
|
log_level: 'info',
|
||||||
snapshot: true,
|
snapshot: true,
|
||||||
upgradeable: true,
|
upgradeable: true,
|
||||||
version: '8.0.0',
|
version,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
host: {
|
host: {
|
||||||
|
|
|
@ -6,15 +6,20 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Client } from '@elastic/elasticsearch';
|
import type { Client } from '@elastic/elasticsearch';
|
||||||
import type { DeleteByQueryResponse } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
import type {
|
||||||
|
DeleteByQueryResponse,
|
||||||
|
IndexRequest,
|
||||||
|
} from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||||
import type { KbnClient } from '@kbn/test';
|
import type { KbnClient } from '@kbn/test';
|
||||||
import type { FleetServerAgent } from '@kbn/fleet-plugin/common';
|
import type { FleetServerAgent } from '@kbn/fleet-plugin/common';
|
||||||
import { AGENTS_INDEX } from '@kbn/fleet-plugin/common';
|
import { AGENTS_INDEX } from '@kbn/fleet-plugin/common';
|
||||||
import type { BulkRequest } from '@elastic/elasticsearch/lib/api/types';
|
import type { BulkRequest } from '@elastic/elasticsearch/lib/api/types';
|
||||||
|
import type { DeepPartial } from 'utility-types';
|
||||||
|
import type { ToolingLog } from '@kbn/tooling-log';
|
||||||
import { usageTracker } from './usage_tracker';
|
import { usageTracker } from './usage_tracker';
|
||||||
import type { HostMetadata } from '../types';
|
import type { HostMetadata } from '../types';
|
||||||
import { FleetAgentGenerator } from '../data_generators/fleet_agent_generator';
|
import { FleetAgentGenerator } from '../data_generators/fleet_agent_generator';
|
||||||
import { wrapErrorAndRejectPromise } from './utils';
|
import { createToolingLogger, wrapErrorAndRejectPromise } from './utils';
|
||||||
|
|
||||||
const defaultFleetAgentGenerator = new FleetAgentGenerator();
|
const defaultFleetAgentGenerator = new FleetAgentGenerator();
|
||||||
|
|
||||||
|
@ -189,3 +194,31 @@ export const deleteIndexedFleetAgents = async (
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const indexFleetServerAgent = async (
|
||||||
|
esClient: Client,
|
||||||
|
log: ToolingLog = createToolingLogger(),
|
||||||
|
overrides: DeepPartial<FleetServerAgent> = {}
|
||||||
|
): Promise<IndexedFleetAgentResponse> => {
|
||||||
|
const doc = defaultFleetAgentGenerator.generateEsHit({
|
||||||
|
_source: overrides,
|
||||||
|
});
|
||||||
|
|
||||||
|
const indexRequest: IndexRequest<FleetServerAgent> = {
|
||||||
|
index: doc._index,
|
||||||
|
id: doc._id,
|
||||||
|
body: doc._source,
|
||||||
|
op_type: 'create',
|
||||||
|
refresh: 'wait_for',
|
||||||
|
};
|
||||||
|
|
||||||
|
log.verbose(`Indexing new fleet agent with:\n${JSON.stringify(indexRequest, null, 2)}`);
|
||||||
|
|
||||||
|
await esClient.index<FleetServerAgent>(indexRequest).catch(wrapErrorAndRejectPromise);
|
||||||
|
|
||||||
|
return {
|
||||||
|
fleetAgentsIndex: doc._index,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
|
agents: [doc._source!],
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
|
@ -6,53 +6,155 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Client } from '@elastic/elasticsearch';
|
import type { Client } from '@elastic/elasticsearch';
|
||||||
import { FLEET_SERVER_SERVERS_INDEX } from '@kbn/fleet-plugin/common';
|
import { kibanaPackageJson } from '@kbn/repo-info';
|
||||||
|
import type { KbnClient } from '@kbn/test';
|
||||||
|
import type {
|
||||||
|
GetPackagePoliciesResponse,
|
||||||
|
AgentPolicy,
|
||||||
|
GetOneAgentPolicyResponse,
|
||||||
|
CreateAgentPolicyResponse,
|
||||||
|
} from '@kbn/fleet-plugin/common';
|
||||||
|
import {
|
||||||
|
AGENT_POLICY_API_ROUTES,
|
||||||
|
agentPolicyRouteService,
|
||||||
|
AGENTS_INDEX,
|
||||||
|
FLEET_SERVER_PACKAGE,
|
||||||
|
PACKAGE_POLICY_SAVED_OBJECT_TYPE,
|
||||||
|
packagePolicyRouteService,
|
||||||
|
} from '@kbn/fleet-plugin/common';
|
||||||
|
import type { ToolingLog } from '@kbn/tooling-log';
|
||||||
|
import { indexFleetServerAgent } from './index_fleet_agent';
|
||||||
|
import { catchAxiosErrorFormatAndThrow } from '../format_axios_error';
|
||||||
import { usageTracker } from './usage_tracker';
|
import { usageTracker } from './usage_tracker';
|
||||||
import { wrapErrorAndRejectPromise } from './utils';
|
import { createToolingLogger, wrapErrorAndRejectPromise } from './utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Will ensure that at least one fleet server is present in the `.fleet-servers` index. This will
|
* Will ensure that at least one fleet server is present in the `.fleet-agents` index. This will
|
||||||
* enable the `Agent` section of kibana Fleet to be displayed
|
* enable the `Agent` section of kibana Fleet to be displayed. We skip on serverless because
|
||||||
|
* Fleet Server agents are not checked against there.
|
||||||
*
|
*
|
||||||
* @param esClient
|
* @param esClient
|
||||||
|
* @param kbnClient
|
||||||
|
* @param log
|
||||||
* @param version
|
* @param version
|
||||||
*/
|
*/
|
||||||
export const enableFleetServerIfNecessary = usageTracker.track(
|
export const enableFleetServerIfNecessary = usageTracker.track(
|
||||||
'enableFleetServerIfNecessary',
|
'enableFleetServerIfNecessary',
|
||||||
async (esClient: Client, version: string = '8.0.0') => {
|
async (
|
||||||
const res = await esClient.search({
|
esClient: Client,
|
||||||
index: FLEET_SERVER_SERVERS_INDEX,
|
isServerless: boolean = false,
|
||||||
ignore_unavailable: true,
|
kbnClient: KbnClient,
|
||||||
rest_total_hits_as_int: true,
|
log: ToolingLog = createToolingLogger(),
|
||||||
|
version: string = kibanaPackageJson.version
|
||||||
|
) => {
|
||||||
|
const agentPolicy = await getOrCreateFleetServerAgentPolicy(kbnClient, log);
|
||||||
|
|
||||||
|
if (!isServerless && !(await hasFleetServerAgent(esClient, agentPolicy.id))) {
|
||||||
|
log.debug(`Indexing a new fleet server agent`);
|
||||||
|
const lastCheckin = new Date();
|
||||||
|
lastCheckin.setFullYear(lastCheckin.getFullYear() + 1);
|
||||||
|
|
||||||
|
const indexedAgent = await indexFleetServerAgent(esClient, log, {
|
||||||
|
policy_id: agentPolicy.id,
|
||||||
|
agent: { version },
|
||||||
|
last_checkin_status: 'online',
|
||||||
|
last_checkin: lastCheckin.toISOString(),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (res.hits.total) {
|
log.verbose(`New fleet server agent indexed:\n${JSON.stringify(indexedAgent)}`);
|
||||||
return;
|
} else {
|
||||||
|
log.debug(`Nothing to do. A Fleet Server agent is already registered with Fleet`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a Fake fleet-server in this kibana instance
|
|
||||||
await esClient
|
|
||||||
.index({
|
|
||||||
index: FLEET_SERVER_SERVERS_INDEX,
|
|
||||||
refresh: 'wait_for',
|
|
||||||
body: {
|
|
||||||
agent: {
|
|
||||||
id: '12988155-475c-430d-ac89-84dc84b67cd1',
|
|
||||||
version,
|
|
||||||
},
|
|
||||||
host: {
|
|
||||||
architecture: 'linux',
|
|
||||||
id: 'c3e5f4f690b4a3ff23e54900701a9513',
|
|
||||||
ip: ['127.0.0.1', '::1', '10.201.0.213', 'fe80::4001:aff:fec9:d5'],
|
|
||||||
name: 'endpoint-data-generator',
|
|
||||||
},
|
|
||||||
server: {
|
|
||||||
id: '12988155-475c-430d-ac89-84dc84b67cd1',
|
|
||||||
version,
|
|
||||||
},
|
|
||||||
'@timestamp': '2021-05-12T18:42:52.009482058Z',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.catch(wrapErrorAndRejectPromise);
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const getOrCreateFleetServerAgentPolicy = async (
|
||||||
|
kbnClient: KbnClient,
|
||||||
|
log: ToolingLog = createToolingLogger()
|
||||||
|
): Promise<AgentPolicy> => {
|
||||||
|
const packagePolicies = await kbnClient
|
||||||
|
.request<GetPackagePoliciesResponse>({
|
||||||
|
method: 'GET',
|
||||||
|
headers: { 'elastic-api-version': '2023-10-31' },
|
||||||
|
path: packagePolicyRouteService.getListPath(),
|
||||||
|
query: {
|
||||||
|
perPage: 1,
|
||||||
|
kuery: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name: "${FLEET_SERVER_PACKAGE}"`,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.catch(catchAxiosErrorFormatAndThrow);
|
||||||
|
|
||||||
|
if (packagePolicies.data.items[0]) {
|
||||||
|
log.debug(`Found an existing package policy - fetching associated agent policy`);
|
||||||
|
log.verbose(JSON.stringify(packagePolicies.data.items[0]));
|
||||||
|
|
||||||
|
return kbnClient
|
||||||
|
.request<GetOneAgentPolicyResponse>({
|
||||||
|
headers: { 'elastic-api-version': '2023-10-31' },
|
||||||
|
method: 'GET',
|
||||||
|
path: agentPolicyRouteService.getInfoPath(packagePolicies.data.items[0].policy_id),
|
||||||
|
})
|
||||||
|
.catch(catchAxiosErrorFormatAndThrow)
|
||||||
|
.then((response) => {
|
||||||
|
log.verbose(
|
||||||
|
`Existing agent policy for Fleet Server:\n${JSON.stringify(response.data.item)}`
|
||||||
|
);
|
||||||
|
|
||||||
|
return response.data.item;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
log.debug(`Creating a new fleet server agent policy`);
|
||||||
|
|
||||||
|
// create new Fleet Server agent policy
|
||||||
|
return kbnClient
|
||||||
|
.request<CreateAgentPolicyResponse>({
|
||||||
|
method: 'POST',
|
||||||
|
path: AGENT_POLICY_API_ROUTES.CREATE_PATTERN,
|
||||||
|
headers: { 'elastic-api-version': '2023-10-31' },
|
||||||
|
body: {
|
||||||
|
name: `Fleet Server policy (${Math.random().toString(32).substring(2)})`,
|
||||||
|
description: `Created by CLI Tool via: ${__filename}`,
|
||||||
|
namespace: 'default',
|
||||||
|
monitoring_enabled: [],
|
||||||
|
// This will ensure the Fleet Server integration policy
|
||||||
|
// is also created and added to the agent policy
|
||||||
|
has_fleet_server: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
log.verbose(
|
||||||
|
`No fleet server agent policy found. Created a new one:\n${JSON.stringify(
|
||||||
|
response.data.item
|
||||||
|
)}`
|
||||||
|
);
|
||||||
|
|
||||||
|
return response.data.item;
|
||||||
|
})
|
||||||
|
.catch(catchAxiosErrorFormatAndThrow);
|
||||||
|
};
|
||||||
|
|
||||||
|
const hasFleetServerAgent = async (
|
||||||
|
esClient: Client,
|
||||||
|
fleetServerAgentPolicyId: string
|
||||||
|
): Promise<boolean> => {
|
||||||
|
const searchResponse = await esClient
|
||||||
|
.search(
|
||||||
|
{
|
||||||
|
index: AGENTS_INDEX,
|
||||||
|
ignore_unavailable: true,
|
||||||
|
rest_total_hits_as_int: true,
|
||||||
|
size: 1,
|
||||||
|
_source: false,
|
||||||
|
query: {
|
||||||
|
match: {
|
||||||
|
policy_id: fleetServerAgentPolicyId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ ignore: [404] }
|
||||||
|
)
|
||||||
|
.catch(wrapErrorAndRejectPromise);
|
||||||
|
|
||||||
|
return Boolean(searchResponse?.hits.total);
|
||||||
|
};
|
||||||
|
|
|
@ -10,13 +10,14 @@ import type { ToolingLogTextWriterConfig } from '@kbn/tooling-log';
|
||||||
import { ToolingLog } from '@kbn/tooling-log';
|
import { ToolingLog } from '@kbn/tooling-log';
|
||||||
import type { Flags } from '@kbn/dev-cli-runner';
|
import type { Flags } from '@kbn/dev-cli-runner';
|
||||||
import moment from 'moment/moment';
|
import moment from 'moment/moment';
|
||||||
|
import { EndpointError } from '../errors';
|
||||||
|
|
||||||
export const RETRYABLE_TRANSIENT_ERRORS: Readonly<Array<string | RegExp>> = [
|
export const RETRYABLE_TRANSIENT_ERRORS: Readonly<Array<string | RegExp>> = [
|
||||||
'no_shard_available_action_exception',
|
'no_shard_available_action_exception',
|
||||||
'illegal_index_shard_state_exception',
|
'illegal_index_shard_state_exception',
|
||||||
];
|
];
|
||||||
|
|
||||||
export class EndpointDataLoadingError extends Error {
|
export class EndpointDataLoadingError extends EndpointError {
|
||||||
constructor(message: string, public meta?: unknown) {
|
constructor(message: string, public meta?: unknown) {
|
||||||
super(message);
|
super(message);
|
||||||
}
|
}
|
||||||
|
@ -88,7 +89,8 @@ export const retryOnError = async <T>(
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
log.warning(msg(`attempt ${thisAttempt} failed with: ${err.message}`), err);
|
log.warning(msg(`attempt ${thisAttempt} failed with: ${err.message.split('\n').at(0)}`));
|
||||||
|
log.verbose(err);
|
||||||
|
|
||||||
// If not an error that is retryable, then end loop here and return that error;
|
// If not an error that is retryable, then end loop here and return that error;
|
||||||
if (!isRetryableError(err)) {
|
if (!isRetryableError(err)) {
|
||||||
|
|
|
@ -74,6 +74,7 @@ export const indexHostsAndAlerts = usageTracker.track(
|
||||||
withResponseActions = true,
|
withResponseActions = true,
|
||||||
numResponseActions?: number,
|
numResponseActions?: number,
|
||||||
alertIds?: string[],
|
alertIds?: string[],
|
||||||
|
isServerless: boolean = false,
|
||||||
logger_?: ToolingLog
|
logger_?: ToolingLog
|
||||||
): Promise<IndexedHostsAndAlertsResponse> => {
|
): Promise<IndexedHostsAndAlertsResponse> => {
|
||||||
const random = seedrandom(seed);
|
const random = seedrandom(seed);
|
||||||
|
@ -103,7 +104,7 @@ export const indexHostsAndAlerts = usageTracker.track(
|
||||||
|
|
||||||
// If `fleet` integration is true, then ensure a (fake) fleet-server is connected
|
// If `fleet` integration is true, then ensure a (fake) fleet-server is connected
|
||||||
if (fleet) {
|
if (fleet) {
|
||||||
await enableFleetServerIfNecessary(client);
|
await enableFleetServerIfNecessary(client, isServerless, kbnClient, logger);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keep a map of host applied policy ids (fake) to real ingest package configs (policy record)
|
// Keep a map of host applied policy ids (fake) to real ingest package configs (policy record)
|
||||||
|
|
|
@ -17,6 +17,9 @@ export const setupStackServicesUsingCypressConfig = async (config: Cypress.Plugi
|
||||||
return RUNTIME_SERVICES_CACHE.get(config)!;
|
return RUNTIME_SERVICES_CACHE.get(config)!;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isServerless = config.env.IS_SERVERLESS;
|
||||||
|
const isCloudServerless = config.env.CLOUD_SERVERLESS;
|
||||||
|
|
||||||
const stackServices = await createRuntimeServices({
|
const stackServices = await createRuntimeServices({
|
||||||
kibanaUrl: config.env.KIBANA_URL,
|
kibanaUrl: config.env.KIBANA_URL,
|
||||||
elasticsearchUrl: config.env.ELASTICSEARCH_URL,
|
elasticsearchUrl: config.env.ELASTICSEARCH_URL,
|
||||||
|
@ -25,7 +28,8 @@ export const setupStackServicesUsingCypressConfig = async (config: Cypress.Plugi
|
||||||
password: config.env.KIBANA_PASSWORD,
|
password: config.env.KIBANA_PASSWORD,
|
||||||
esUsername: config.env.ELASTICSEARCH_USERNAME,
|
esUsername: config.env.ELASTICSEARCH_USERNAME,
|
||||||
esPassword: config.env.ELASTICSEARCH_PASSWORD,
|
esPassword: config.env.ELASTICSEARCH_PASSWORD,
|
||||||
asSuperuser: !config.env.CLOUD_SERVERLESS,
|
asSuperuser: !isCloudServerless,
|
||||||
|
useCertForSsl: !isCloudServerless && isServerless,
|
||||||
}).then(({ log, ...others }) => {
|
}).then(({ log, ...others }) => {
|
||||||
return {
|
return {
|
||||||
...others,
|
...others,
|
||||||
|
|
|
@ -217,6 +217,7 @@ export const dataLoaders = (
|
||||||
withResponseActions,
|
withResponseActions,
|
||||||
numResponseActions,
|
numResponseActions,
|
||||||
alertIds,
|
alertIds,
|
||||||
|
isServerless,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -45,6 +45,7 @@ export interface CyLoadEndpointDataOptions
|
||||||
isolation: boolean;
|
isolation: boolean;
|
||||||
bothIsolatedAndNormalEndpoints?: boolean;
|
bothIsolatedAndNormalEndpoints?: boolean;
|
||||||
alertIds?: string[];
|
alertIds?: string[];
|
||||||
|
isServerless?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -73,6 +74,7 @@ export const cyLoadEndpointDataHandler = async (
|
||||||
isolation,
|
isolation,
|
||||||
numResponseActions,
|
numResponseActions,
|
||||||
alertIds,
|
alertIds,
|
||||||
|
isServerless = false,
|
||||||
} = options;
|
} = options;
|
||||||
|
|
||||||
const DocGenerator = EndpointDocGenerator.custom({
|
const DocGenerator = EndpointDocGenerator.custom({
|
||||||
|
@ -80,6 +82,8 @@ export const cyLoadEndpointDataHandler = async (
|
||||||
});
|
});
|
||||||
|
|
||||||
if (waitUntilTransformed) {
|
if (waitUntilTransformed) {
|
||||||
|
log.info(`Stopping transforms...`);
|
||||||
|
|
||||||
// need this before indexing docs so that the united transform doesn't
|
// need this before indexing docs so that the united transform doesn't
|
||||||
// create a checkpoint with a timestamp after the doc timestamps
|
// create a checkpoint with a timestamp after the doc timestamps
|
||||||
await stopTransform(esClient, log, metadataTransformPrefix);
|
await stopTransform(esClient, log, metadataTransformPrefix);
|
||||||
|
@ -88,6 +92,8 @@ export const cyLoadEndpointDataHandler = async (
|
||||||
await stopTransform(esClient, log, METADATA_UNITED_TRANSFORM_V2);
|
await stopTransform(esClient, log, METADATA_UNITED_TRANSFORM_V2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.info(`Calling indexHostAndAlerts() to index [${numHosts}] endpoint hosts...`);
|
||||||
|
|
||||||
// load data into the system
|
// load data into the system
|
||||||
const indexedData = await indexHostsAndAlerts(
|
const indexedData = await indexHostsAndAlerts(
|
||||||
esClient as Client,
|
esClient as Client,
|
||||||
|
@ -106,25 +112,31 @@ export const cyLoadEndpointDataHandler = async (
|
||||||
withResponseActions,
|
withResponseActions,
|
||||||
numResponseActions,
|
numResponseActions,
|
||||||
alertIds,
|
alertIds,
|
||||||
|
isServerless,
|
||||||
log
|
log
|
||||||
);
|
);
|
||||||
|
|
||||||
|
log.info(`Hosts have been indexed`);
|
||||||
|
|
||||||
if (waitUntilTransformed) {
|
if (waitUntilTransformed) {
|
||||||
|
log.info(`starting transforms...`);
|
||||||
|
|
||||||
// missing transforms are ignored, start either name
|
// missing transforms are ignored, start either name
|
||||||
await startTransform(esClient, metadataTransformPrefix);
|
await startTransform(esClient, log, metadataTransformPrefix);
|
||||||
await startTransform(esClient, METADATA_CURRENT_TRANSFORM_V2);
|
await startTransform(esClient, log, METADATA_CURRENT_TRANSFORM_V2);
|
||||||
|
|
||||||
const metadataIds = Array.from(new Set(indexedData.hosts.map((host) => host.agent.id)));
|
const metadataIds = Array.from(new Set(indexedData.hosts.map((host) => host.agent.id)));
|
||||||
await waitForEndpoints(esClient, 'endpoint_index', metadataIds);
|
await waitForEndpoints(esClient, log, 'endpoint_index', metadataIds);
|
||||||
|
|
||||||
await startTransform(esClient, METADATA_UNITED_TRANSFORM);
|
await startTransform(esClient, log, METADATA_UNITED_TRANSFORM);
|
||||||
await startTransform(esClient, METADATA_UNITED_TRANSFORM_V2);
|
await startTransform(esClient, log, METADATA_UNITED_TRANSFORM_V2);
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
const agentIds = Array.from(new Set(indexedData.agents.map((agent) => agent.agent!.id)));
|
const agentIds = Array.from(new Set(indexedData.agents.map((agent) => agent.agent!.id)));
|
||||||
await waitForEndpoints(esClient, 'united_index', agentIds);
|
await waitForEndpoints(esClient, log, 'united_index', agentIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.info(`Done - [${numHosts}] endpoint hosts have been indexed and are now available in kibana`);
|
||||||
return indexedData;
|
return indexedData;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -133,6 +145,8 @@ const stopTransform = async (
|
||||||
log: ToolingLog,
|
log: ToolingLog,
|
||||||
transformId: string
|
transformId: string
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
|
log.debug(`Stopping transform id: ${transformId}`);
|
||||||
|
|
||||||
await esClient.transform
|
await esClient.transform
|
||||||
.stopTransform({
|
.stopTransform({
|
||||||
transform_id: `${transformId}*`,
|
transform_id: `${transformId}*`,
|
||||||
|
@ -147,17 +161,27 @@ const stopTransform = async (
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const startTransform = async (esClient: Client, transformId: string): Promise<void> => {
|
const startTransform = async (
|
||||||
|
esClient: Client,
|
||||||
|
log: ToolingLog,
|
||||||
|
transformId: string
|
||||||
|
): Promise<void> => {
|
||||||
const transformsResponse = await esClient.transform.getTransformStats({
|
const transformsResponse = await esClient.transform.getTransformStats({
|
||||||
transform_id: `${transformId}*`,
|
transform_id: `${transformId}*`,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
log.verbose(
|
||||||
|
`Transform status found for [${transformId}*] returned:\n${dump(transformsResponse)}`
|
||||||
|
);
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
transformsResponse.transforms.map((transform) => {
|
transformsResponse.transforms.map((transform) => {
|
||||||
if (STARTED_TRANSFORM_STATES.has(transform.state)) {
|
if (STARTED_TRANSFORM_STATES.has(transform.state)) {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.debug(`Staring transform id: [${transform.id}]`);
|
||||||
|
|
||||||
return esClient.transform.startTransform({ transform_id: transform.id });
|
return esClient.transform.startTransform({ transform_id: transform.id });
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
@ -173,6 +197,7 @@ const startTransform = async (esClient: Client, transformId: string): Promise<vo
|
||||||
*/
|
*/
|
||||||
const waitForEndpoints = async (
|
const waitForEndpoints = async (
|
||||||
esClient: Client,
|
esClient: Client,
|
||||||
|
log: ToolingLog,
|
||||||
location: 'endpoint_index' | 'united_index',
|
location: 'endpoint_index' | 'united_index',
|
||||||
ids: string[] = []
|
ids: string[] = []
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
|
@ -218,8 +243,13 @@ const waitForEndpoints = async (
|
||||||
|
|
||||||
const expectedSize = ids.length || 1;
|
const expectedSize = ids.length || 1;
|
||||||
|
|
||||||
|
log.info(`Waiting for [${expectedSize}] endpoint hosts to be available`);
|
||||||
|
log.verbose(`Query for searching index [${index}]:\n${dump(body, 10)}`);
|
||||||
|
|
||||||
await pRetry(
|
await pRetry(
|
||||||
async () => {
|
async (attemptCount) => {
|
||||||
|
log.debug(`Attempt [${attemptCount}]: Searching [${index}] to check if hosts are availble`);
|
||||||
|
|
||||||
const response = await esClient.search({
|
const response = await esClient.search({
|
||||||
index,
|
index,
|
||||||
size: expectedSize,
|
size: expectedSize,
|
||||||
|
@ -227,12 +257,16 @@ const waitForEndpoints = async (
|
||||||
rest_total_hits_as_int: true,
|
rest_total_hits_as_int: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
log.verbose(`Attempt [${attemptCount}]: Search response:\n${dump(response, 10)}`);
|
||||||
|
|
||||||
// If not the expected number of Endpoints, then throw an error so we keep trying
|
// If not the expected number of Endpoints, then throw an error so we keep trying
|
||||||
if (response.hits.total !== expectedSize) {
|
if (response.hits.total !== expectedSize) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Expected number of endpoints not found. Looking for ${expectedSize} but received ${response.hits.total}`
|
`Expected number of endpoints not found. Looking for ${expectedSize} but received ${response.hits.total}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.info(`Attempt [${attemptCount}]: Done - [${expectedSize}] host are now available`);
|
||||||
},
|
},
|
||||||
{ forever: false }
|
{ forever: false }
|
||||||
);
|
);
|
||||||
|
|
|
@ -21,6 +21,7 @@ import { METADATA_DATASTREAM } from '../../../../common/endpoint/constants';
|
||||||
import { EndpointMetadataGenerator } from '../../../../common/endpoint/data_generators/endpoint_metadata_generator';
|
import { EndpointMetadataGenerator } from '../../../../common/endpoint/data_generators/endpoint_metadata_generator';
|
||||||
import { getEndpointPackageInfo } from '../../../../common/endpoint/utils/package';
|
import { getEndpointPackageInfo } from '../../../../common/endpoint/utils/package';
|
||||||
import { ENDPOINT_ALERTS_INDEX, ENDPOINT_EVENTS_INDEX } from '../../common/constants';
|
import { ENDPOINT_ALERTS_INDEX, ENDPOINT_EVENTS_INDEX } from '../../common/constants';
|
||||||
|
import { isServerlessKibanaFlavor } from '../../common/stack_services';
|
||||||
|
|
||||||
let WAS_FLEET_SETUP_DONE = false;
|
let WAS_FLEET_SETUP_DONE = false;
|
||||||
|
|
||||||
|
@ -90,8 +91,9 @@ export const loadEndpoints = async ({
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!WAS_FLEET_SETUP_DONE) {
|
if (!WAS_FLEET_SETUP_DONE) {
|
||||||
|
const isServerless = await isServerlessKibanaFlavor(kbnClient);
|
||||||
await setupFleetForEndpoint(kbnClient);
|
await setupFleetForEndpoint(kbnClient);
|
||||||
await enableFleetServerIfNecessary(esClient);
|
await enableFleetServerIfNecessary(esClient, isServerless, kbnClient, log);
|
||||||
// eslint-disable-next-line require-atomic-updates
|
// eslint-disable-next-line require-atomic-updates
|
||||||
WAS_FLEET_SETUP_DONE = true;
|
WAS_FLEET_SETUP_DONE = true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,6 @@ import {
|
||||||
AGENT_POLICY_API_ROUTES,
|
AGENT_POLICY_API_ROUTES,
|
||||||
API_VERSIONS,
|
API_VERSIONS,
|
||||||
FLEET_SERVER_PACKAGE,
|
FLEET_SERVER_PACKAGE,
|
||||||
FLEET_SERVER_SERVERS_INDEX,
|
|
||||||
PACKAGE_POLICY_SAVED_OBJECT_TYPE,
|
PACKAGE_POLICY_SAVED_OBJECT_TYPE,
|
||||||
} from '@kbn/fleet-plugin/common';
|
} from '@kbn/fleet-plugin/common';
|
||||||
import type {
|
import type {
|
||||||
|
@ -42,13 +41,8 @@ import {
|
||||||
} from '@kbn/dev-utils';
|
} from '@kbn/dev-utils';
|
||||||
import { maybeCreateDockerNetwork, SERVERLESS_NODES, verifyDockerInstalled } from '@kbn/es';
|
import { maybeCreateDockerNetwork, SERVERLESS_NODES, verifyDockerInstalled } from '@kbn/es';
|
||||||
import { resolve } from 'path';
|
import { resolve } from 'path';
|
||||||
import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
|
||||||
import { captureCallingStack, dump, prefixedOutputLogger } from '../utils';
|
import { captureCallingStack, dump, prefixedOutputLogger } from '../utils';
|
||||||
import {
|
import { createToolingLogger } from '../../../../common/endpoint/data_loaders/utils';
|
||||||
createToolingLogger,
|
|
||||||
RETRYABLE_TRANSIENT_ERRORS,
|
|
||||||
retryOnError,
|
|
||||||
} from '../../../../common/endpoint/data_loaders/utils';
|
|
||||||
import { isServerlessKibanaFlavor } from '../stack_services';
|
import { isServerlessKibanaFlavor } from '../stack_services';
|
||||||
import type { FormattedAxiosError } from '../../../../common/endpoint/format_axios_error';
|
import type { FormattedAxiosError } from '../../../../common/endpoint/format_axios_error';
|
||||||
import { catchAxiosErrorFormatAndThrow } from '../../../../common/endpoint/format_axios_error';
|
import { catchAxiosErrorFormatAndThrow } from '../../../../common/endpoint/format_axios_error';
|
||||||
|
@ -329,9 +323,12 @@ const startFleetServerWithDocker = async ({
|
||||||
await updateFleetElasticsearchOutputHostNames(kbnClient, log);
|
await updateFleetElasticsearchOutputHostNames(kbnClient, log);
|
||||||
|
|
||||||
if (isServerless) {
|
if (isServerless) {
|
||||||
log.info(`Waiting for server [${hostname}] to register with Elasticsearch`);
|
log.info(`Waiting for Fleet Server [${hostname}] to start running`);
|
||||||
await waitForFleetServerToRegisterWithElasticsearch(kbnClient, hostname, 180000);
|
if (!(await isFleetServerRunning(kbnClient, log))) {
|
||||||
|
throw Error(`Unable to start Fleet Server [${hostname}]`);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
|
log.info(`Waiting for Fleet Server [${hostname}] to enroll with Fleet`);
|
||||||
await waitForHostToEnroll(kbnClient, log, hostname, 120000);
|
await waitForHostToEnroll(kbnClient, log, hostname, 120000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -683,7 +680,7 @@ export const isFleetServerRunning = async (
|
||||||
const url = new URL(fleetServerUrl);
|
const url = new URL(fleetServerUrl);
|
||||||
url.pathname = '/api/status';
|
url.pathname = '/api/status';
|
||||||
|
|
||||||
return pRetry<boolean>(
|
return pRetry(
|
||||||
async () => {
|
async () => {
|
||||||
return axios
|
return axios
|
||||||
.request({
|
.request({
|
||||||
|
@ -698,75 +695,20 @@ export const isFleetServerRunning = async (
|
||||||
`Fleet server is up and running at [${fleetServerUrl}]. Status: `,
|
`Fleet server is up and running at [${fleetServerUrl}]. Status: `,
|
||||||
response.data
|
response.data
|
||||||
);
|
);
|
||||||
return true;
|
|
||||||
})
|
})
|
||||||
.catch(catchAxiosErrorFormatAndThrow)
|
|
||||||
.catch((e) => {
|
|
||||||
log.debug(`Fleet server not up at [${fleetServerUrl}]`);
|
|
||||||
log.verbose(`Call to [${url.toString()}] failed with:`, e);
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
{ maxTimeout: 10000 }
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks and waits until the given fleet server hostname has been registered into elasticsearch.
|
|
||||||
* This check can be used when enrolling a standalone fleet-server, since those would not show up
|
|
||||||
* in Kibana's Fleet UI.
|
|
||||||
*/
|
|
||||||
const waitForFleetServerToRegisterWithElasticsearch = async (
|
|
||||||
kbnClient: KbnClient,
|
|
||||||
fleetServerHostname: string,
|
|
||||||
timeoutMs: number = 30000
|
|
||||||
): Promise<void> => {
|
|
||||||
const started = new Date();
|
|
||||||
const hasTimedOut = (): boolean => {
|
|
||||||
const elapsedTime = Date.now() - started.getTime();
|
|
||||||
return elapsedTime > timeoutMs;
|
|
||||||
};
|
|
||||||
let found = false;
|
|
||||||
|
|
||||||
while (!found && !hasTimedOut()) {
|
|
||||||
found = await retryOnError(async () => {
|
|
||||||
const fleetServerRecord = await kbnClient
|
|
||||||
.request<estypes.SearchResponse>({
|
|
||||||
method: 'POST',
|
|
||||||
path: '/api/console/proxy',
|
|
||||||
query: {
|
|
||||||
path: `${FLEET_SERVER_SERVERS_INDEX}/_search`,
|
|
||||||
method: 'GET',
|
|
||||||
},
|
|
||||||
body: {
|
|
||||||
query: {
|
|
||||||
bool: {
|
|
||||||
filter: [
|
|
||||||
{
|
|
||||||
term: {
|
|
||||||
'host.name': fleetServerHostname,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.then((response) => response.data)
|
|
||||||
.catch(catchAxiosErrorFormatAndThrow);
|
.catch(catchAxiosErrorFormatAndThrow);
|
||||||
|
},
|
||||||
return ((fleetServerRecord?.hits?.total as estypes.SearchTotalHits)?.value ?? 0) === 1;
|
{
|
||||||
}, RETRYABLE_TRANSIENT_ERRORS);
|
maxTimeout: 10000,
|
||||||
|
retries: 5,
|
||||||
if (!found) {
|
onFailedAttempt: (e) => {
|
||||||
// sleep and check again
|
log.warning(
|
||||||
await new Promise((r) => setTimeout(r, 2000));
|
`Fleet server not (yet) up at [${fleetServerUrl}]. Retrying... (attempt #${e.attemptNumber}, ${e.retriesLeft} retries left)`
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!found) {
|
|
||||||
throw new Error(
|
|
||||||
`Timed out waiting for fleet server [${fleetServerHostname}] to register with Elasticsearch`
|
|
||||||
);
|
);
|
||||||
|
log.verbose(`Call to [${url.toString()}] failed with:`, e);
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
.then(() => true)
|
||||||
|
.catch(() => false);
|
||||||
};
|
};
|
||||||
|
|
|
@ -51,6 +51,11 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) {
|
||||||
`--xpack.fleet.agents.elasticsearch.host=http://${hostIp}:${defendWorkflowsCypressConfig.get(
|
`--xpack.fleet.agents.elasticsearch.host=http://${hostIp}:${defendWorkflowsCypressConfig.get(
|
||||||
'servers.elasticsearch.port'
|
'servers.elasticsearch.port'
|
||||||
)}`,
|
)}`,
|
||||||
|
|
||||||
|
// Enable Fleet server standalone so that no checks are done to see if fleet-server has
|
||||||
|
// registered with Kibana and we are able to access the Agents page of Fleet
|
||||||
|
'--xpack.fleet.internal.fleetServerStandalone=true',
|
||||||
|
|
||||||
// set the packagerTaskInterval to 5s in order to speed up test executions when checking fleet artifacts
|
// set the packagerTaskInterval to 5s in order to speed up test executions when checking fleet artifacts
|
||||||
'--xpack.securitySolution.packagerTaskInterval=5s',
|
'--xpack.securitySolution.packagerTaskInterval=5s',
|
||||||
`--xpack.securitySolution.enableExperimental=${JSON.stringify(enabledFeatureFlags)}`,
|
`--xpack.securitySolution.enableExperimental=${JSON.stringify(enabledFeatureFlags)}`,
|
||||||
|
|
|
@ -375,67 +375,3 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
|
||||||
"type": "index",
|
|
||||||
"value": {
|
|
||||||
"aliases": {
|
|
||||||
".fleet-servers": {
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"index": ".fleet-servers-7",
|
|
||||||
"mappings": {
|
|
||||||
"_meta": {
|
|
||||||
"migrationHash": "e2782448c7235ec9af66ca7997e867d715ac379c"
|
|
||||||
},
|
|
||||||
"dynamic": "false",
|
|
||||||
"properties": {
|
|
||||||
"@timestamp": {
|
|
||||||
"type": "date"
|
|
||||||
},
|
|
||||||
"agent": {
|
|
||||||
"properties": {
|
|
||||||
"id": {
|
|
||||||
"type": "keyword"
|
|
||||||
},
|
|
||||||
"version": {
|
|
||||||
"type": "keyword"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"host": {
|
|
||||||
"properties": {
|
|
||||||
"architecture": {
|
|
||||||
"type": "keyword"
|
|
||||||
},
|
|
||||||
"id": {
|
|
||||||
"type": "keyword"
|
|
||||||
},
|
|
||||||
"ip": {
|
|
||||||
"type": "keyword"
|
|
||||||
},
|
|
||||||
"name": {
|
|
||||||
"type": "keyword"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"server": {
|
|
||||||
"properties": {
|
|
||||||
"id": {
|
|
||||||
"type": "keyword"
|
|
||||||
},
|
|
||||||
"version": {
|
|
||||||
"type": "keyword"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"settings": {
|
|
||||||
"index": {
|
|
||||||
"number_of_replicas": "1",
|
|
||||||
"number_of_shards": "1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -375,66 +375,3 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
|
||||||
"type": "index",
|
|
||||||
"value": {
|
|
||||||
"aliases": {
|
|
||||||
".fleet-servers": {
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"index": ".fleet-servers-7",
|
|
||||||
"mappings": {
|
|
||||||
"_meta": {
|
|
||||||
"migrationHash": "e2782448c7235ec9af66ca7997e867d715ac379c"
|
|
||||||
},
|
|
||||||
"dynamic": "false",
|
|
||||||
"properties": {
|
|
||||||
"@timestamp": {
|
|
||||||
"type": "date"
|
|
||||||
},
|
|
||||||
"agent": {
|
|
||||||
"properties": {
|
|
||||||
"id": {
|
|
||||||
"type": "keyword"
|
|
||||||
},
|
|
||||||
"version": {
|
|
||||||
"type": "keyword"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"host": {
|
|
||||||
"properties": {
|
|
||||||
"architecture": {
|
|
||||||
"type": "keyword"
|
|
||||||
},
|
|
||||||
"id": {
|
|
||||||
"type": "keyword"
|
|
||||||
},
|
|
||||||
"ip": {
|
|
||||||
"type": "keyword"
|
|
||||||
},
|
|
||||||
"name": {
|
|
||||||
"type": "keyword"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"server": {
|
|
||||||
"properties": {
|
|
||||||
"id": {
|
|
||||||
"type": "keyword"
|
|
||||||
},
|
|
||||||
"version": {
|
|
||||||
"type": "keyword"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"settings": {
|
|
||||||
"index": {
|
|
||||||
"number_of_replicas": "1",
|
|
||||||
"number_of_shards": "1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -160,6 +160,7 @@ export class EndpointTestResources extends FtrService {
|
||||||
undefined,
|
undefined,
|
||||||
undefined,
|
undefined,
|
||||||
undefined,
|
undefined,
|
||||||
|
undefined,
|
||||||
this.log
|
this.log
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue