From 2ff66a8df65a4b6ba228ec121d938d2c48f1a2f4 Mon Sep 17 00:00:00 2001 From: Aleh Zasypkin Date: Mon, 27 Dec 2021 16:42:32 +0100 Subject: [PATCH] Migrate to the composable session index template on the startup. (#121311) --- .../session_management/session_index.test.ts | 79 +++++++++++++++--- .../session_management/session_index.ts | 82 +++++++++++++------ 2 files changed, 122 insertions(+), 39 deletions(-) diff --git a/x-pack/plugins/security/server/session_management/session_index.test.ts b/x-pack/plugins/security/server/session_management/session_index.test.ts index bf99f4926b1d6..251a0a3edb061 100644 --- a/x-pack/plugins/security/server/session_management/session_index.test.ts +++ b/x-pack/plugins/security/server/session_management/session_index.test.ts @@ -42,13 +42,19 @@ describe('Session index', () => { expect(mockElasticsearchClient.indices.existsTemplate).toHaveBeenCalledWith({ name: indexTemplateName, }); + expect(mockElasticsearchClient.indices.existsIndexTemplate).toHaveBeenCalledWith({ + name: indexTemplateName, + }); expect(mockElasticsearchClient.indices.exists).toHaveBeenCalledWith({ - index: getSessionIndexTemplate(indexName).index_patterns[0], + index: getSessionIndexTemplate(indexTemplateName, indexName).index_patterns[0], }); } it('debounces initialize calls', async () => { mockElasticsearchClient.indices.existsTemplate.mockResolvedValue( + securityMock.createApiResponse({ body: false }) + ); + mockElasticsearchClient.indices.existsIndexTemplate.mockResolvedValue( securityMock.createApiResponse({ body: true }) ); mockElasticsearchClient.indices.exists.mockResolvedValue( @@ -65,8 +71,11 @@ describe('Session index', () => { assertExistenceChecksPerformed(); }); - it('creates neither index template nor index if they exist', async () => { + it('does not delete legacy index template if it does not exist and creates neither index template nor index if they exist', async () => { mockElasticsearchClient.indices.existsTemplate.mockResolvedValue( + securityMock.createApiResponse({ body: false }) + ); + mockElasticsearchClient.indices.existsIndexTemplate.mockResolvedValue( securityMock.createApiResponse({ body: true }) ); mockElasticsearchClient.indices.exists.mockResolvedValue( @@ -76,10 +85,17 @@ describe('Session index', () => { await sessionIndex.initialize(); assertExistenceChecksPerformed(); + + expect(mockElasticsearchClient.indices.deleteTemplate).not.toHaveBeenCalled(); + expect(mockElasticsearchClient.indices.putIndexTemplate).not.toHaveBeenCalled(); + expect(mockElasticsearchClient.indices.create).not.toHaveBeenCalled(); }); - it('creates both index template and index if they do not exist', async () => { + it('deletes legacy index template if needed and creates both index template and index if they do not exist', async () => { mockElasticsearchClient.indices.existsTemplate.mockResolvedValue( + securityMock.createApiResponse({ body: true }) + ); + mockElasticsearchClient.indices.existsIndexTemplate.mockResolvedValue( securityMock.createApiResponse({ body: false }) ); mockElasticsearchClient.indices.exists.mockResolvedValue( @@ -88,12 +104,38 @@ describe('Session index', () => { await sessionIndex.initialize(); - const expectedIndexTemplate = getSessionIndexTemplate(indexName); + const expectedIndexTemplate = getSessionIndexTemplate(indexTemplateName, indexName); assertExistenceChecksPerformed(); - expect(mockElasticsearchClient.indices.putTemplate).toHaveBeenCalledWith({ + expect(mockElasticsearchClient.indices.deleteTemplate).toHaveBeenCalledWith({ name: indexTemplateName, - body: expectedIndexTemplate, }); + expect(mockElasticsearchClient.indices.putIndexTemplate).toHaveBeenCalledWith( + expectedIndexTemplate + ); + expect(mockElasticsearchClient.indices.create).toHaveBeenCalledWith({ + index: expectedIndexTemplate.index_patterns[0], + }); + }); + + it('creates both index template and index if they do not exist', async () => { + mockElasticsearchClient.indices.existsTemplate.mockResolvedValue( + securityMock.createApiResponse({ body: false }) + ); + mockElasticsearchClient.indices.existsIndexTemplate.mockResolvedValue( + securityMock.createApiResponse({ body: false }) + ); + mockElasticsearchClient.indices.exists.mockResolvedValue( + securityMock.createApiResponse({ body: false }) + ); + + await sessionIndex.initialize(); + + const expectedIndexTemplate = getSessionIndexTemplate(indexTemplateName, indexName); + assertExistenceChecksPerformed(); + expect(mockElasticsearchClient.indices.deleteTemplate).not.toHaveBeenCalled(); + expect(mockElasticsearchClient.indices.putIndexTemplate).toHaveBeenCalledWith( + expectedIndexTemplate + ); expect(mockElasticsearchClient.indices.create).toHaveBeenCalledWith({ index: expectedIndexTemplate.index_patterns[0], }); @@ -103,6 +145,9 @@ describe('Session index', () => { mockElasticsearchClient.indices.existsTemplate.mockResolvedValue( securityMock.createApiResponse({ body: false }) ); + mockElasticsearchClient.indices.existsIndexTemplate.mockResolvedValue( + securityMock.createApiResponse({ body: false }) + ); mockElasticsearchClient.indices.exists.mockResolvedValue( securityMock.createApiResponse({ body: true }) ); @@ -110,14 +155,17 @@ describe('Session index', () => { await sessionIndex.initialize(); assertExistenceChecksPerformed(); - expect(mockElasticsearchClient.indices.putTemplate).toHaveBeenCalledWith({ - name: indexTemplateName, - body: getSessionIndexTemplate(indexName), - }); + expect(mockElasticsearchClient.indices.deleteTemplate).not.toHaveBeenCalled(); + expect(mockElasticsearchClient.indices.putIndexTemplate).toHaveBeenCalledWith( + getSessionIndexTemplate(indexTemplateName, indexName) + ); }); it('creates only index if it does not exist even if index template exists', async () => { mockElasticsearchClient.indices.existsTemplate.mockResolvedValue( + securityMock.createApiResponse({ body: false }) + ); + mockElasticsearchClient.indices.existsIndexTemplate.mockResolvedValue( securityMock.createApiResponse({ body: true }) ); mockElasticsearchClient.indices.exists.mockResolvedValue( @@ -127,13 +175,18 @@ describe('Session index', () => { await sessionIndex.initialize(); assertExistenceChecksPerformed(); + expect(mockElasticsearchClient.indices.deleteTemplate).not.toHaveBeenCalled(); + expect(mockElasticsearchClient.indices.putIndexTemplate).not.toHaveBeenCalled(); expect(mockElasticsearchClient.indices.create).toHaveBeenCalledWith({ - index: getSessionIndexTemplate(indexName).index_patterns[0], + index: getSessionIndexTemplate(indexTemplateName, indexName).index_patterns[0], }); }); it('does not fail if tries to create index when it exists already', async () => { mockElasticsearchClient.indices.existsTemplate.mockResolvedValue( + securityMock.createApiResponse({ body: false }) + ); + mockElasticsearchClient.indices.existsIndexTemplate.mockResolvedValue( securityMock.createApiResponse({ body: true }) ); mockElasticsearchClient.indices.exists.mockResolvedValue( @@ -154,8 +207,8 @@ describe('Session index', () => { const unexpectedError = new errors.ResponseError( securityMock.createApiResponse(securityMock.createApiResponse({ body: { type: 'Uh oh.' } })) ); - mockElasticsearchClient.indices.existsTemplate.mockRejectedValueOnce(unexpectedError); - mockElasticsearchClient.indices.existsTemplate.mockResolvedValueOnce( + mockElasticsearchClient.indices.existsIndexTemplate.mockRejectedValueOnce(unexpectedError); + mockElasticsearchClient.indices.existsIndexTemplate.mockResolvedValueOnce( securityMock.createApiResponse({ body: true }) ); diff --git a/x-pack/plugins/security/server/session_management/session_index.ts b/x-pack/plugins/security/server/session_management/session_index.ts index 58ee0d6956511..801597dad6baf 100644 --- a/x-pack/plugins/security/server/session_management/session_index.ts +++ b/x-pack/plugins/security/server/session_management/session_index.ts @@ -37,31 +37,33 @@ const SESSION_INDEX_TEMPLATE_VERSION = 1; /** * Returns index template that is used for the current version of the session index. */ -export function getSessionIndexTemplate(indexName: string) { +export function getSessionIndexTemplate(templateName: string, indexName: string) { return Object.freeze({ + name: templateName, index_patterns: [indexName], - order: 1000, - settings: { - index: { - number_of_shards: 1, - number_of_replicas: 0, - auto_expand_replicas: '0-1', - priority: 1000, - refresh_interval: '1s', - hidden: true, + template: { + settings: { + index: { + number_of_shards: 1, + number_of_replicas: 0, + auto_expand_replicas: '0-1', + priority: 1000, + refresh_interval: '1s', + hidden: true, + }, }, + mappings: { + dynamic: 'strict', + properties: { + usernameHash: { type: 'keyword' }, + provider: { properties: { name: { type: 'keyword' }, type: { type: 'keyword' } } }, + idleTimeoutExpiration: { type: 'date' }, + lifespanExpiration: { type: 'date' }, + accessAgreementAcknowledged: { type: 'boolean' }, + content: { type: 'binary' }, + }, + } as const, }, - mappings: { - dynamic: 'strict', - properties: { - usernameHash: { type: 'keyword' }, - provider: { properties: { name: { type: 'keyword' }, type: { type: 'keyword' } } }, - idleTimeoutExpiration: { type: 'date' }, - lifespanExpiration: { type: 'date' }, - accessAgreementAcknowledged: { type: 'boolean' }, - content: { type: 'binary' }, - }, - } as const, }); } @@ -318,11 +320,40 @@ export class SessionIndex { const sessionIndexTemplateName = `${this.options.kibanaIndexName}_security_session_index_template_${SESSION_INDEX_TEMPLATE_VERSION}`; return (this.indexInitialization = new Promise(async (resolve, reject) => { try { + // Check if legacy index template exists, and remove it if it does. + let legacyIndexTemplateExists = false; + try { + legacyIndexTemplateExists = ( + await this.options.elasticsearchClient.indices.existsTemplate({ + name: sessionIndexTemplateName, + }) + ).body; + } catch (err) { + this.options.logger.error( + `Failed to check if session legacy index template exists: ${err.message}` + ); + return reject(err); + } + + if (legacyIndexTemplateExists) { + try { + await this.options.elasticsearchClient.indices.deleteTemplate({ + name: sessionIndexTemplateName, + }); + this.options.logger.debug('Successfully deleted session legacy index template.'); + } catch (err) { + this.options.logger.error( + `Failed to delete session legacy index template: ${err.message}` + ); + return reject(err); + } + } + // Check if required index template exists. let indexTemplateExists = false; try { indexTemplateExists = ( - await this.options.elasticsearchClient.indices.existsTemplate({ + await this.options.elasticsearchClient.indices.existsIndexTemplate({ name: sessionIndexTemplateName, }) ).body; @@ -338,10 +369,9 @@ export class SessionIndex { this.options.logger.debug('Session index template already exists.'); } else { try { - await this.options.elasticsearchClient.indices.putTemplate({ - name: sessionIndexTemplateName, - body: getSessionIndexTemplate(this.indexName), - }); + await this.options.elasticsearchClient.indices.putIndexTemplate( + getSessionIndexTemplate(sessionIndexTemplateName, this.indexName) + ); this.options.logger.debug('Successfully created session index template.'); } catch (err) { this.options.logger.error(`Failed to create session index template: ${err.message}`);