-
Notifications
You must be signed in to change notification settings - Fork 8.2k
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
Stop relying on index template for session index #134900
Merged
legrego
merged 15 commits into
elastic:main
from
legrego:security/remove-session-index-template
Jun 30, 2022
Merged
Changes from 3 commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
c25e378
Remove session index template
legrego 10c0ed1
Attempt to ensure session index exists before creating session document
legrego 667c565
improved comments
legrego b890529
Attempt to use index alias + require_alias
legrego 47f3393
more docs and testing
legrego f10e428
Merge branch 'main' of github.com:elastic/kibana into security/remove…
legrego 33d26b6
Merge branch 'main' into security/remove-session-index-template
legrego 3e34e6a
Apply suggestions from code review
legrego deb7d46
Merge branch 'main' of github.com:elastic/kibana into security/remove…
legrego 0b37e65
update session via alias
legrego a69acc6
get session via alias
legrego 9040699
invalidate session via alias
legrego ed48c6a
use alias in cleanup and getSessionValuesInBatches
legrego c4c0b4d
Merge branch 'main' of github.com:elastic/kibana into security/remove…
legrego 1411a68
use alias for index refresh
legrego File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,6 +5,7 @@ | |
* 2.0. | ||
*/ | ||
|
||
import type { IndicesCreateRequest } from '@elastic/elasticsearch/lib/api/types'; | ||
import type { | ||
BulkOperationContainer, | ||
SortResults, | ||
|
@@ -59,34 +60,29 @@ const SESSION_INDEX_CLEANUP_BATCH_LIMIT = 10; | |
const SESSION_INDEX_CLEANUP_KEEP_ALIVE = '5m'; | ||
|
||
/** | ||
* Returns index template that is used for the current version of the session index. | ||
* Returns index settings that are used for the current version of the session index. | ||
*/ | ||
export function getSessionIndexTemplate(templateName: string, indexName: string) { | ||
export function getSessionIndexSettings(indexName: string): IndicesCreateRequest { | ||
return Object.freeze({ | ||
name: templateName, | ||
index_patterns: [indexName], | ||
template: { | ||
settings: { | ||
index: { | ||
number_of_shards: 1, | ||
number_of_replicas: 0, | ||
auto_expand_replicas: '0-1', | ||
priority: 1000, | ||
refresh_interval: '1s', | ||
hidden: true, | ||
}, | ||
index: indexName, | ||
settings: { | ||
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' }, | ||
}, | ||
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, | ||
}, | ||
}); | ||
} | ||
|
@@ -213,17 +209,30 @@ export class SessionIndex { | |
'Attempted to create a new session while session index is initializing.' | ||
); | ||
await this.indexInitialization; | ||
} else { | ||
await this.ensureSessionIndexExists(); | ||
} | ||
|
||
// ************************************************** | ||
legrego marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// !! There is potential for a race condition here !! | ||
// ************************************************** | ||
// Prior to https://github.com/elastic/kibana/pull/134900, we maintained an index template with | ||
// our desired settings and mappings for this session index. This allowed our index to almost | ||
// always have the correct settings/mappings, even if the index was auto-created by this step. | ||
// Now that the template is removed, we run the risk of the index being created without our desired | ||
// settings/mappings, because they can't be specified as part of this `create` operation. | ||
// | ||
// The call to `this.ensureSessionIndexExists()` above attempts to mitigate this by creating the session index | ||
// explicitly with our desired settings/mappings. A race condition exists because it's possible to delete the session index | ||
// _after_ we've ensured it exists, but _before_ we make the call below to store the session document. | ||
// | ||
// The chances of this happening are very small. | ||
|
||
const { sid, ...sessionValueToStore } = sessionValue; | ||
try { | ||
const { _primary_term: primaryTerm, _seq_no: sequenceNumber } = | ||
await this.options.elasticsearchClient.create({ | ||
id: sid, | ||
// We cannot control whether index is created automatically during this operation or not. | ||
// But we can reduce probability of getting into a weird state when session is being created | ||
// while session index is missing for some reason. This way we'll recreate index with a | ||
// proper name and alias. But this will only work if we still have a proper index template. | ||
index: this.indexName, | ||
body: sessionValueToStore, | ||
refresh: 'wait_for', | ||
|
@@ -372,7 +381,7 @@ export class SessionIndex { | |
} | ||
} | ||
|
||
// Check if required index template exists. | ||
// Check if index template exists. | ||
let indexTemplateExists = false; | ||
try { | ||
indexTemplateExists = await this.options.elasticsearchClient.indices.existsIndexTemplate({ | ||
|
@@ -385,50 +394,21 @@ export class SessionIndex { | |
return reject(err); | ||
} | ||
|
||
// Create index template if it doesn't exist. | ||
// Delete index template if it does. | ||
if (indexTemplateExists) { | ||
this.options.logger.debug('Session index template already exists.'); | ||
} else { | ||
this.options.logger.debug('Deleting unused session index template.'); | ||
try { | ||
await this.options.elasticsearchClient.indices.putIndexTemplate( | ||
getSessionIndexTemplate(sessionIndexTemplateName, this.indexName) | ||
); | ||
this.options.logger.debug('Successfully created session index template.'); | ||
await this.options.elasticsearchClient.indices.deleteIndexTemplate({ | ||
name: sessionIndexTemplateName, | ||
}); | ||
this.options.logger.debug('Successfully deleted session index template.'); | ||
} catch (err) { | ||
this.options.logger.error(`Failed to create session index template: ${err.message}`); | ||
this.options.logger.error(`Failed to delete session index template: ${err.message}`); | ||
return reject(err); | ||
} | ||
} | ||
|
||
// Check if required index exists. We cannot be sure that automatic creation of indices is | ||
// always enabled, so we create session index explicitly. | ||
let indexExists = false; | ||
try { | ||
indexExists = await this.options.elasticsearchClient.indices.exists({ | ||
index: this.indexName, | ||
}); | ||
} catch (err) { | ||
this.options.logger.error(`Failed to check if session index exists: ${err.message}`); | ||
return reject(err); | ||
} | ||
|
||
// Create index if it doesn't exist. | ||
if (indexExists) { | ||
this.options.logger.debug('Session index already exists.'); | ||
} else { | ||
try { | ||
await this.options.elasticsearchClient.indices.create({ index: this.indexName }); | ||
this.options.logger.debug('Successfully created session index.'); | ||
} catch (err) { | ||
// There can be a race condition if index is created by another Kibana instance. | ||
if (err?.body?.error?.type === 'resource_already_exists_exception') { | ||
this.options.logger.debug('Session index already exists.'); | ||
} else { | ||
this.options.logger.error(`Failed to create session index: ${err.message}`); | ||
return reject(err); | ||
} | ||
} | ||
} | ||
await this.ensureSessionIndexExists(); | ||
|
||
// Notify any consumers that are awaiting on this promise and immediately reset it. | ||
resolve(); | ||
|
@@ -509,6 +489,42 @@ export class SessionIndex { | |
} | ||
} | ||
|
||
/** | ||
* Creates the session index if it doesn't already exist. | ||
*/ | ||
private async ensureSessionIndexExists() { | ||
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. note This was extracted from the |
||
// Check if required index exists. | ||
let indexExists = false; | ||
try { | ||
indexExists = await this.options.elasticsearchClient.indices.exists({ | ||
index: this.indexName, | ||
}); | ||
} catch (err) { | ||
this.options.logger.error(`Failed to check if session index exists: ${err.message}`); | ||
throw err; | ||
} | ||
|
||
// Create index if it doesn't exist. | ||
if (indexExists) { | ||
this.options.logger.debug('Session index already exists.'); | ||
} else { | ||
try { | ||
await this.options.elasticsearchClient.indices.create( | ||
getSessionIndexSettings(this.indexName) | ||
); | ||
this.options.logger.debug('Successfully created session index.'); | ||
} catch (err) { | ||
// There can be a race condition if index is created by another Kibana instance. | ||
if (err?.body?.error?.type === 'resource_already_exists_exception') { | ||
this.options.logger.debug('Session index already exists.'); | ||
} else { | ||
this.options.logger.error(`Failed to create session index: ${err.message}`); | ||
throw err; | ||
} | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* Fetches session values from session index in batches of 10,000. | ||
*/ | ||
|
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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 don't love the extra round-trip that this incurs, but I don't see a way around it given the design of ES's APIs.