-
Notifications
You must be signed in to change notification settings - Fork 8.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Fleet] Setup fleet server indices in Kibana without packages #90658
Changes from 13 commits
399d3fd
cc82e38
9398b52
f351a96
e840294
03fa41f
24001c1
b8ddec8
c726637
8e1643b
e8e8e17
5725797
88ac8a9
16bd007
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,156 @@ | ||
/* | ||
* 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 { elasticsearchServiceMock } from 'src/core/server/mocks'; | ||
import hash from 'object-hash'; | ||
import { setupFleetServerIndexes } from './elastic_index'; | ||
import ESFleetAgentIndex from './elasticsearch/fleet_agents.json'; | ||
import ESFleetPoliciesIndex from './elasticsearch/fleet_policies.json'; | ||
import ESFleetPoliciesLeaderIndex from './elasticsearch/fleet_policies_leader.json'; | ||
import ESFleetServersIndex from './elasticsearch/fleet_servers.json'; | ||
import ESFleetEnrollmentApiKeysIndex from './elasticsearch/fleet_enrollment_api_keys.json'; | ||
import EsFleetActionsIndex from './elasticsearch/fleet_actions.json'; | ||
|
||
const FLEET_INDEXES_MIGRATION_HASH = { | ||
'.fleet-actions': hash(EsFleetActionsIndex), | ||
'.fleet-agents': hash(ESFleetAgentIndex), | ||
'.fleet-enrollment-apy-keys': hash(ESFleetEnrollmentApiKeysIndex), | ||
'.fleet-policies': hash(ESFleetPoliciesIndex), | ||
'.fleet-policies-leader': hash(ESFleetPoliciesLeaderIndex), | ||
'.fleet-servers': hash(ESFleetServersIndex), | ||
}; | ||
|
||
describe('setupFleetServerIndexes ', () => { | ||
it('should create all the indices and aliases if nothings exists', async () => { | ||
const esMock = elasticsearchServiceMock.createInternalClient(); | ||
await setupFleetServerIndexes(esMock); | ||
|
||
const indexesCreated = esMock.indices.create.mock.calls.map((call) => call[0].index).sort(); | ||
expect(indexesCreated).toEqual([ | ||
'.fleet-actions_1', | ||
'.fleet-agents_1', | ||
'.fleet-enrollment-api-keys_1', | ||
'.fleet-policies-leader_1', | ||
'.fleet-policies_1', | ||
'.fleet-servers_1', | ||
]); | ||
const aliasesCreated = esMock.indices.updateAliases.mock.calls | ||
.map((call) => (call[0].body as any)?.actions[0].add.alias) | ||
.sort(); | ||
|
||
expect(aliasesCreated).toEqual([ | ||
'.fleet-actions', | ||
'.fleet-agents', | ||
'.fleet-enrollment-api-keys', | ||
'.fleet-policies', | ||
'.fleet-policies-leader', | ||
'.fleet-servers', | ||
]); | ||
}); | ||
|
||
it('should not create any indices and create aliases if indices exists but not the aliases', async () => { | ||
const esMock = elasticsearchServiceMock.createInternalClient(); | ||
// @ts-expect-error | ||
esMock.indices.exists.mockResolvedValue({ body: true }); | ||
// @ts-expect-error | ||
esMock.indices.getMapping.mockImplementation((params: { index: string }) => { | ||
return { | ||
body: { | ||
[params.index]: { | ||
mappings: { | ||
_meta: { | ||
// @ts-expect-error | ||
migrationHash: FLEET_INDEXES_MIGRATION_HASH[params.index.replace(/_1$/, '')], | ||
}, | ||
}, | ||
}, | ||
}, | ||
}; | ||
}); | ||
|
||
await setupFleetServerIndexes(esMock); | ||
|
||
expect(esMock.indices.create).not.toBeCalled(); | ||
const aliasesCreated = esMock.indices.updateAliases.mock.calls | ||
.map((call) => (call[0].body as any)?.actions[0].add.alias) | ||
.sort(); | ||
|
||
expect(aliasesCreated).toEqual([ | ||
'.fleet-actions', | ||
'.fleet-agents', | ||
'.fleet-enrollment-api-keys', | ||
'.fleet-policies', | ||
'.fleet-policies-leader', | ||
'.fleet-servers', | ||
]); | ||
}); | ||
|
||
it('should put new indices mapping if the mapping has been updated ', async () => { | ||
const esMock = elasticsearchServiceMock.createInternalClient(); | ||
// @ts-expect-error | ||
esMock.indices.exists.mockResolvedValue({ body: true }); | ||
// @ts-expect-error | ||
esMock.indices.getMapping.mockImplementation((params: { index: string }) => { | ||
return { | ||
body: { | ||
[params.index]: { | ||
mappings: { | ||
_meta: { | ||
migrationHash: 'NOT_VALID_HASH', | ||
}, | ||
}, | ||
}, | ||
}, | ||
}; | ||
}); | ||
|
||
await setupFleetServerIndexes(esMock); | ||
|
||
expect(esMock.indices.create).not.toBeCalled(); | ||
const indexesMappingUpdated = esMock.indices.putMapping.mock.calls | ||
.map((call) => call[0].index) | ||
.sort(); | ||
|
||
expect(indexesMappingUpdated).toEqual([ | ||
'.fleet-actions_1', | ||
'.fleet-agents_1', | ||
'.fleet-enrollment-api-keys_1', | ||
'.fleet-policies-leader_1', | ||
'.fleet-policies_1', | ||
'.fleet-servers_1', | ||
]); | ||
}); | ||
|
||
it('should not create any indices or aliases if indices and aliases already exists', async () => { | ||
const esMock = elasticsearchServiceMock.createInternalClient(); | ||
|
||
// @ts-expect-error | ||
esMock.indices.exists.mockResolvedValue({ body: true }); | ||
// @ts-expect-error | ||
esMock.indices.getMapping.mockImplementation((params: { index: string }) => { | ||
return { | ||
body: { | ||
[params.index]: { | ||
mappings: { | ||
_meta: { | ||
// @ts-expect-error | ||
migrationHash: FLEET_INDEXES_MIGRATION_HASH[params.index.replace(/_1$/, '')], | ||
}, | ||
}, | ||
}, | ||
}, | ||
}; | ||
}); | ||
// @ts-expect-error | ||
esMock.indices.existsAlias.mockResolvedValue({ body: true }); | ||
|
||
await setupFleetServerIndexes(esMock); | ||
|
||
expect(esMock.indices.create).not.toBeCalled(); | ||
expect(esMock.indices.updateAliases).not.toBeCalled(); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
/* | ||
* 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 { ElasticsearchClient } from 'kibana/server'; | ||
import hash from 'object-hash'; | ||
|
||
import { FLEET_SERVER_INDICES, FLEET_SERVER_INDICES_VERSION } from '../../../common'; | ||
import { appContextService } from '../app_context'; | ||
import ESFleetAgentIndex from './elasticsearch/fleet_agents.json'; | ||
import ESFleetPoliciesIndex from './elasticsearch/fleet_policies.json'; | ||
import ESFleetPoliciesLeaderIndex from './elasticsearch/fleet_policies_leader.json'; | ||
import ESFleetServersIndex from './elasticsearch/fleet_servers.json'; | ||
import ESFleetEnrollmentApiKeysIndex from './elasticsearch/fleet_enrollment_api_keys.json'; | ||
import EsFleetActionsIndex from './elasticsearch/fleet_actions.json'; | ||
|
||
const FLEET_INDEXES: Array<[typeof FLEET_SERVER_INDICES[number], any]> = [ | ||
['.fleet-actions', EsFleetActionsIndex], | ||
['.fleet-agents', ESFleetAgentIndex], | ||
['.fleet-enrollment-api-keys', ESFleetEnrollmentApiKeysIndex], | ||
['.fleet-policies', ESFleetPoliciesIndex], | ||
['.fleet-policies-leader', ESFleetPoliciesLeaderIndex], | ||
Comment on lines
+24
to
+25
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "policies" here is a bit confusing as we already have the concept of agent and integration policies within fleet. can you help me understand the purpose of these indices? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I will try to explain what are these indices, first Fleet Server do not know about kibana saved object so all the data used by fleet server come from these indices:
|
||
['.fleet-servers', ESFleetServersIndex], | ||
]; | ||
|
||
export async function setupFleetServerIndexes( | ||
esClient = appContextService.getInternalUserESClient() | ||
) { | ||
await Promise.all( | ||
FLEET_INDEXES.map(async ([indexAlias, indexData]) => { | ||
const index = `${indexAlias}_${FLEET_SERVER_INDICES_VERSION}`; | ||
await createOrUpdateIndex(esClient, index, indexData); | ||
await createAliasIfDoNotExists(esClient, indexAlias, index); | ||
}) | ||
); | ||
} | ||
|
||
export async function createAliasIfDoNotExists( | ||
esClient: ElasticsearchClient, | ||
alias: string, | ||
index: string | ||
) { | ||
const { body: exists } = await esClient.indices.existsAlias({ | ||
name: alias, | ||
}); | ||
|
||
if (exists === true) { | ||
return; | ||
} | ||
await esClient.indices.updateAliases({ | ||
body: { | ||
actions: [ | ||
{ | ||
add: { index, alias }, | ||
}, | ||
], | ||
}, | ||
}); | ||
} | ||
|
||
async function createOrUpdateIndex( | ||
esClient: ElasticsearchClient, | ||
indexName: string, | ||
indexData: any | ||
) { | ||
const resExists = await esClient.indices.exists({ | ||
index: indexName, | ||
}); | ||
|
||
// Support non destructive migration only (adding new field) | ||
if (resExists.body === true) { | ||
return updateIndex(esClient, indexName, indexData); | ||
} | ||
|
||
return createIndex(esClient, indexName, indexData); | ||
} | ||
|
||
async function updateIndex(esClient: ElasticsearchClient, indexName: string, indexData: any) { | ||
const res = await esClient.indices.getMapping({ | ||
index: indexName, | ||
}); | ||
|
||
const migrationHash = hash(indexData); | ||
if (res.body[indexName].mappings?._meta?.migrationHash !== migrationHash) { | ||
await esClient.indices.putMapping({ | ||
index: indexName, | ||
body: Object.assign({ | ||
...indexData.mappings, | ||
_meta: { ...(indexData.mappings._meta || {}), migrationHash }, | ||
}), | ||
}); | ||
} | ||
} | ||
|
||
async function createIndex(esClient: ElasticsearchClient, indexName: string, indexData: any) { | ||
try { | ||
const migrationHash = hash(indexData); | ||
await esClient.indices.create({ | ||
index: indexName, | ||
body: { | ||
...indexData, | ||
mappings: Object.assign({ | ||
...indexData.mappings, | ||
_meta: { ...(indexData.mappings._meta || {}), migrationHash }, | ||
}), | ||
}, | ||
}); | ||
} catch (err) { | ||
// Swallow already exists errors as concurent Kibana can try to create that indice | ||
if (err?.body?.error?.type !== 'resource_already_exists_exception') { | ||
throw err; | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
{ | ||
"settings": {}, | ||
"mappings": { | ||
"dynamic": false, | ||
"properties": { | ||
"action_id": { | ||
"type": "keyword" | ||
}, | ||
"agents": { | ||
"type": "keyword" | ||
}, | ||
"data": { | ||
"enabled": false, | ||
"type": "object" | ||
}, | ||
"expiration": { | ||
"type": "date" | ||
}, | ||
"input_type": { | ||
"type": "keyword" | ||
}, | ||
"@timestamp": { | ||
"type": "date" | ||
}, | ||
"type": { | ||
"type": "keyword" | ||
} | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if we should have another condition to check on if central management is enabled yet? setting up central management === setting up Fleet server, according to the code here. what do you think? of course, this would mean we trigger setup after user clicks to enable central management too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's okay to create the indices even if central management is not enabled later, and doing it here outside of central management setup allow us to have a seamless migration