Skip to content

Commit

Permalink
[Sessions] Reindexing the .kibana_security_session_1 index to the 8.x…
Browse files Browse the repository at this point in the history
… format. (#204097)

Closes #200603


## Summary

Reindexes the Kibana Security session system index to the 8.x format to
support 9.0 readiness.

### Release note
Creates Kibana Security session index to only if the
`kibana_security_session_1` index or the reindexed version do not exist.

### Notes


### How to test

For this test, you'll need at least 3 copies of Kibana cloned locally.
One each on 7.17, 8.x and main - ensuring you've run `yarn kbn
bootstrap` on each of them.


Step 0.  Verify on the PR branch
-----
- Start ES as `yarn es snapshot --license=trial`
- Start kibana `yarn start --no-base-path`
- Login to kibana in a private browsing window
- Navigate to dev tools and run
```
GET .kibana_security_session/_alias
```
- You should see 
```
{
  ".kibana_security_session_1": {
    "aliases": {
      ".kibana_security_session": {
        "is_write_index": true,
        "is_hidden": true
      }
    }
  }
}
```
This indicates that there were no aliases/index present and the new
index was created.

Step 1. On 7.17
-----
- Run ES with `yarn es snapshot --license=trial -E
path.data=/tmp/esdata`
- Run kibana
- Login with the `elastic` user
- Navigate to dev tools and run the following query
```
GET .kibana_security_session_1/_search
{
  "query": {
    "match_all": {}
  }
}
```
- You should see your current session being returned as the result for
this query
- You can now shut down ES and kibana.


Step 2. On 8.x
-----
- Run ES with `yarn es snapshot --license=trial -E
path.data=/tmp/esdata` <--- point to the same folder as the previous run
- Run kibana, open a private browser window and login.
- Navigate to Kibana upgrade assistant and Migrate system indices and
wait for it to run.
- Now in Dev tools, run the same query. You should see two sessions. 
- One with the idleSessionTimeout returned as null and the other one
containing a value - indicating one was created on 7.x and the other in
8.x
- Make a backup of the data folder `cp -r /tmp/esdata /tmp/esdatabkp`

Step 3(OPTIONAL). On main (without the changes in this PR)
-----
- Run ES with `yarn es snapshot --license=trial -E
path.data=/tmp/esdata`
- This should throw an error 


Step 4. On 8.x
-----
- First use the backup for the path `cp -r /tmp/esdatabkp /tmp/esdata2`
- Start ES only (do not run Kibana yet) by pointing to the copy: `yarn
es snapshot --license=trial -E path.data=/tmp/esdata2`
- ES should start up and you need to delete 1 index and 2 datastreams
using the ES APIs and any method you prefer. For your convenience, you
can use the same script as mine:
```ts
import axios from 'axios';

const clearIndexAndDatastream = async () => {
  {
    const res = await axios.delete(
      "http://localhost:9200/.kibana-event-log-7.17.28-000001",
      {
        headers: {
          Authorization: "Basic ZWxhc3RpYzpjaGFuZ2VtZQ==",
          accept: "*/*",
          "Content-Type": "application/json",
          "Kbn-Xsrf": "true",
        },
      }
    );
    console.log("deleted index:", JSON.stringify(res.data));
  }

  {
    const res = await axios.delete(
      "http://localhost:9200/_data_stream/ilm-history-5",
      {
        headers: {
          Authorization: "Basic ZWxhc3RpYzpjaGFuZ2VtZQ==",
          accept: "*/*",
          "Content-Type": "application/json",
          "Kbn-Xsrf": "true",
        },
      }
    );
    console.log("deleted ds1:", JSON.stringify(res.data));
  }
  {
    const res = await axios.delete(
      "http://localhost:9200/_data_stream/.logs-deprecation.elasticsearch-default",
      {
        headers: {
          Authorization: "Basic ZWxhc3RpYzpjaGFuZ2VtZQ==",
          accept: "*/*",
          "Content-Type": "application/json",
          "Kbn-Xsrf": "true",
        },
      }
    );
    console.log("deleted ds2:", JSON.stringify(res.data));
  }
};

clearIndexAndDatastream();
```
You should see the result as:

```
deleted index: {"acknowledged":true}
deleted ds1: {"acknowledged":true}
deleted ds2: {"acknowledged":true}
```
- Now login to Kibana in a private browsing window and navigate to
Upgrade assistant and run the migration.
- Navigating to devtools and running the same query as above will show
you three results. One with no idleTimeout and 2 with timeouts (One on
7.x and two on 8.x format respectively)
```
GET .kibana_security_session_1/_search
{
  "query": {
    "match_all": {}
  }
}
```
- You can now shut ES and kibana at this point. 

Step 5. On the branch of this PR
-----
- Run ES with `yarn es snapshot --license=trial -E
path.data=/tmp/esdata2`
- Run Kibana and login using a private window. 
- Navigating to dev tools and run:
```
GET .kibana_security_session/_alias
```
To show a result as:
```
{
  ".kibana_security_session_1-reindexed-for-9": {
    "aliases": {
      ".kibana_security_session": {
        "is_hidden": true
      },
      ".kibana_security_session_1": {
        "is_hidden": true
      }
    }
  }
}
```
This indicates that no new index was created and we are using the
reindexed version from 8.x.

- You should also run the query to check for sessions:
```
GET .kibana_security_session_1/_search
{
  "query": {
    "match_all": {}
  }
}
```
- This should return 4 sessions in the results

This confirms that the session was re-indexed correctly using the right
aliases.



### Checklist

Check the PR satisfies following conditions. 

Reviewers should verify this PR satisfies this list as well.

- [ ] [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
- [ ] [Flaky Test
Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was
used on any tests changed
- [ ] The PR description includes the appropriate Release Notes section,
and the correct `release_note:*` label is applied per the
[guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)

### Identify risks

Does this PR introduce any risks? For example, consider risks like hard
to test bugs, performance regression, potential of data loss.

Describe the risk, its severity, and mitigation for each identified
risk. Invite stakeholders and evaluate how to proceed before merging.

- [ ] [See some risk
examples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx)
- [ ] ...

---------

Co-authored-by: Elastic Machine <[email protected]>
  • Loading branch information
SiddharthMantri and elasticmachine authored Jan 22, 2025
1 parent 3be8acd commit 26350ff
Show file tree
Hide file tree
Showing 2 changed files with 39 additions and 53 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ describe('Session index', () => {
name: indexTemplateName,
});
expect(mockElasticsearchClient.indices.exists).toHaveBeenCalledWith({
index: getSessionIndexSettings({ indexName, aliasName }).index,
index: aliasName,
});
}

Expand Down Expand Up @@ -96,7 +96,7 @@ describe('Session index', () => {

expect(mockElasticsearchClient.indices.deleteTemplate).not.toHaveBeenCalled();
expect(mockElasticsearchClient.indices.putIndexTemplate).not.toHaveBeenCalled();
expect(mockElasticsearchClient.indices.putAlias).toHaveBeenCalledTimes(1);
expect(mockElasticsearchClient.indices.putAlias).not.toHaveBeenCalled();
expect(mockElasticsearchClient.indices.create).not.toHaveBeenCalled();
});

Expand All @@ -116,7 +116,7 @@ describe('Session index', () => {

expect(mockElasticsearchClient.indices.deleteTemplate).not.toHaveBeenCalled();
expect(mockElasticsearchClient.indices.deleteIndexTemplate).not.toHaveBeenCalled();
expect(mockElasticsearchClient.indices.putAlias).not.toHaveBeenCalled();
expect(mockElasticsearchClient.indices.putAlias).toHaveBeenCalledTimes(1);
expect(mockElasticsearchClient.indices.getMapping).not.toHaveBeenCalled();
expect(mockElasticsearchClient.indices.putMapping).not.toHaveBeenCalled();
expect(mockElasticsearchClient.indices.create).toHaveBeenCalledWith(
Expand All @@ -140,7 +140,7 @@ describe('Session index', () => {

expect(mockElasticsearchClient.indices.deleteTemplate).not.toHaveBeenCalled();
expect(mockElasticsearchClient.indices.deleteIndexTemplate).not.toHaveBeenCalled();
expect(mockElasticsearchClient.indices.putAlias).not.toHaveBeenCalled();
expect(mockElasticsearchClient.indices.putAlias).toHaveBeenCalledTimes(1);
expect(mockElasticsearchClient.indices.getMapping).not.toHaveBeenCalled();
expect(mockElasticsearchClient.indices.putMapping).not.toHaveBeenCalled();
expect(mockElasticsearchClient.indices.create).toHaveBeenCalledWith(
Expand All @@ -162,7 +162,7 @@ describe('Session index', () => {
expect(mockElasticsearchClient.indices.create).toHaveBeenCalledWith(
getSessionIndexSettings({ indexName, aliasName })
);
expect(mockElasticsearchClient.indices.putAlias).not.toHaveBeenCalled();
expect(mockElasticsearchClient.indices.putAlias).toHaveBeenCalledTimes(1);
});

it('deletes legacy & modern index templates if needed and creates index if it does not exist', async () => {
Expand All @@ -182,7 +182,7 @@ describe('Session index', () => {
expect(mockElasticsearchClient.indices.create).toHaveBeenCalledWith(
getSessionIndexSettings({ indexName, aliasName })
);
expect(mockElasticsearchClient.indices.putAlias).not.toHaveBeenCalled();
expect(mockElasticsearchClient.indices.putAlias).toHaveBeenCalledTimes(1);
});

it('deletes modern index template if needed and creates index if it does not exist', async () => {
Expand All @@ -197,7 +197,7 @@ describe('Session index', () => {
expect(mockElasticsearchClient.indices.deleteIndexTemplate).toHaveBeenCalledWith({
name: indexTemplateName,
});
expect(mockElasticsearchClient.indices.putAlias).not.toHaveBeenCalled();
expect(mockElasticsearchClient.indices.putAlias).toHaveBeenCalledTimes(1);
expect(mockElasticsearchClient.indices.create).toHaveBeenCalledWith(
getSessionIndexSettings({ indexName, aliasName })
);
Expand All @@ -216,11 +216,7 @@ describe('Session index', () => {
expect(mockElasticsearchClient.indices.deleteIndexTemplate).not.toHaveBeenCalled();
expect(mockElasticsearchClient.indices.create).not.toHaveBeenCalled();

expect(mockElasticsearchClient.indices.putAlias).toHaveBeenCalledTimes(1);
expect(mockElasticsearchClient.indices.putAlias).toHaveBeenCalledWith({
index: indexName,
name: aliasName,
});
expect(mockElasticsearchClient.indices.putAlias).not.toHaveBeenCalled();
});

it('updates mappings for existing index without version in the meta', async () => {
Expand All @@ -241,16 +237,13 @@ describe('Session index', () => {
expect(mockElasticsearchClient.indices.deleteIndexTemplate).not.toHaveBeenCalled();
expect(mockElasticsearchClient.indices.create).not.toHaveBeenCalled();

expect(mockElasticsearchClient.indices.putAlias).toHaveBeenCalledTimes(1);
expect(mockElasticsearchClient.indices.putAlias).toHaveBeenCalledWith({
index: indexName,
name: aliasName,
});
expect(mockElasticsearchClient.indices.putAlias).not.toHaveBeenCalled();

expect(mockElasticsearchClient.indices.getMapping).toHaveBeenCalledTimes(1);
expect(mockElasticsearchClient.indices.getMapping).toHaveBeenCalledWith({ index: indexName });
expect(mockElasticsearchClient.indices.getMapping).toHaveBeenCalledWith({ index: aliasName });
expect(mockElasticsearchClient.indices.putMapping).toHaveBeenCalledTimes(1);
expect(mockElasticsearchClient.indices.putMapping).toHaveBeenCalledWith({
index: indexName,
index: aliasName,
...getSessionIndexSettings({ indexName, aliasName }).mappings,
});
});
Expand All @@ -273,16 +266,13 @@ describe('Session index', () => {
expect(mockElasticsearchClient.indices.deleteIndexTemplate).not.toHaveBeenCalled();
expect(mockElasticsearchClient.indices.create).not.toHaveBeenCalled();

expect(mockElasticsearchClient.indices.putAlias).toHaveBeenCalledTimes(1);
expect(mockElasticsearchClient.indices.putAlias).toHaveBeenCalledWith({
index: indexName,
name: aliasName,
});
expect(mockElasticsearchClient.indices.putAlias).not.toHaveBeenCalled();

expect(mockElasticsearchClient.indices.getMapping).toHaveBeenCalledTimes(1);
expect(mockElasticsearchClient.indices.getMapping).toHaveBeenCalledWith({ index: indexName });
expect(mockElasticsearchClient.indices.getMapping).toHaveBeenCalledWith({ index: aliasName });
expect(mockElasticsearchClient.indices.putMapping).toHaveBeenCalledTimes(1);
expect(mockElasticsearchClient.indices.putMapping).toHaveBeenCalledWith({
index: indexName,
index: aliasName,
...getSessionIndexSettings({ indexName, aliasName }).mappings,
});
});
Expand All @@ -305,13 +295,10 @@ describe('Session index', () => {
expect(mockElasticsearchClient.indices.deleteIndexTemplate).not.toHaveBeenCalled();
expect(mockElasticsearchClient.indices.create).not.toHaveBeenCalled();

expect(mockElasticsearchClient.indices.putAlias).toHaveBeenCalledTimes(1);
expect(mockElasticsearchClient.indices.putAlias).toHaveBeenCalledWith({
index: indexName,
name: aliasName,
});
expect(mockElasticsearchClient.indices.putAlias).not.toHaveBeenCalled();

expect(mockElasticsearchClient.indices.getMapping).toHaveBeenCalledTimes(1);
expect(mockElasticsearchClient.indices.getMapping).toHaveBeenCalledWith({ index: indexName });
expect(mockElasticsearchClient.indices.getMapping).toHaveBeenCalledWith({ index: aliasName });
expect(mockElasticsearchClient.indices.putMapping).not.toHaveBeenCalled();
});

Expand All @@ -325,7 +312,7 @@ describe('Session index', () => {
assertExistenceChecksPerformed();
expect(mockElasticsearchClient.indices.deleteTemplate).not.toHaveBeenCalled();
expect(mockElasticsearchClient.indices.deleteIndexTemplate).not.toHaveBeenCalled();
expect(mockElasticsearchClient.indices.putAlias).not.toHaveBeenCalled();
expect(mockElasticsearchClient.indices.putAlias).toHaveBeenCalledTimes(1);
expect(mockElasticsearchClient.indices.getMapping).not.toHaveBeenCalled();
expect(mockElasticsearchClient.indices.putMapping).not.toHaveBeenCalled();
expect(mockElasticsearchClient.indices.create).toHaveBeenCalledWith(
Expand Down Expand Up @@ -462,7 +449,7 @@ describe('Session index', () => {

expect(mockElasticsearchClient.indices.exists).toHaveBeenCalledTimes(1);
expect(mockElasticsearchClient.indices.create).toHaveBeenCalledTimes(1);
expect(mockElasticsearchClient.indices.putAlias).not.toHaveBeenCalled();
expect(mockElasticsearchClient.indices.putAlias).toHaveBeenCalledTimes(1);
expect(mockElasticsearchClient.search).toHaveBeenCalledTimes(1);
expect(mockElasticsearchClient.bulk).toHaveBeenCalledTimes(1);
expect(mockElasticsearchClient.closePointInTime).toHaveBeenCalledTimes(1); // since we attempted to delete sessions, we still refresh the index
Expand Down Expand Up @@ -1445,7 +1432,7 @@ describe('Session index', () => {

expect(mockElasticsearchClient.indices.exists).toHaveBeenCalledTimes(1);
expect(mockElasticsearchClient.indices.create).toHaveBeenCalledTimes(1);
expect(mockElasticsearchClient.indices.putAlias).not.toHaveBeenCalled();
expect(mockElasticsearchClient.indices.putAlias).toHaveBeenCalledTimes(1);

expect(mockElasticsearchClient.create).toHaveBeenCalledTimes(2);
expect(mockElasticsearchClient.create).toHaveBeenNthCalledWith(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -633,7 +633,7 @@ export class SessionIndex {
let indexExists = false;
try {
indexExists = await this.options.elasticsearchClient.indices.exists({
index: this.indexName,
index: this.aliasName,
});
} catch (err) {
this.options.logger.error(`Failed to check if session index exists: ${err.message}`);
Expand Down Expand Up @@ -662,33 +662,32 @@ export class SessionIndex {
}
}

// Prior to https://github.com/elastic/kibana/pull/134900, sessions would be written directly against the session index.
// Now, we write sessions against a new session index alias. This call ensures that the alias exists, and is attached to the index.
// This operation is safe to repeat, even if the alias already exists. This seems safer than retrieving the index details, and inspecting
// it to see if the alias already exists.
try {
await this.options.elasticsearchClient.indices.putAlias({
index: this.indexName,
name: this.aliasName,
});
} catch (err) {
this.options.logger.error(`Failed to attach alias to session index: ${err.message}`);
throw err;
}

return;
}

this.options.logger.debug(
'Session index already exists. Attaching alias to the index and ensuring up-to-date mappings...'
);

// Prior to https://github.com/elastic/kibana/pull/134900, sessions would be written directly against the session index.
// Now, we write sessions against a new session index alias. This call ensures that the alias exists, and is attached to the index.
// This operation is safe to repeat, even if the alias already exists. This seems safer than retrieving the index details, and inspecting
// it to see if the alias already exists.
try {
await this.options.elasticsearchClient.indices.putAlias({
index: this.indexName,
name: this.aliasName,
});
} catch (err) {
this.options.logger.error(`Failed to attach alias to session index: ${err.message}`);
throw err;
}

let indexMappingsVersion: string | undefined;
try {
const indexMappings = await this.options.elasticsearchClient.indices.getMapping({
index: this.indexName,
index: this.aliasName,
});

indexMappingsVersion =
indexMappings[this.indexName]?.mappings?._meta?.[
SESSION_INDEX_MAPPINGS_VERSION_META_FIELD_NAME
Expand All @@ -706,7 +705,7 @@ export class SessionIndex {
);
try {
await this.options.elasticsearchClient.indices.putMapping({
index: this.indexName,
index: this.aliasName,
...sessionIndexSettings.mappings,
});
this.options.logger.debug('Successfully updated session index mappings.');
Expand Down

0 comments on commit 26350ff

Please sign in to comment.