Skip to content

Commit

Permalink
[8.14] [Security Solution] [Attack discovery] Fixes attack discovery …
Browse files Browse the repository at this point in the history
…cache issues (#183005) (#183009)

# Backport

This will backport the following commits from `main` to `8.14`:
- [[Security Solution] [Attack discovery] Fixes attack discovery cache
issues (#183005)](#183005)

<!--- Backport version: 9.4.3 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT [{"author":{"name":"Andrew
Macri","email":"[email protected]"},"sourceCommit":{"committedDate":"2024-05-09T05:01:49Z","message":"[Security
Solution] [Attack discovery] Fixes attack discovery cache issues
(#183005)\n\n## [Security Solution] [Attack discovery] Fixes attack
discovery cache issues\r\n\r\n### Summary\r\n\r\nThis PR fixes an issue
where cached attack discoveries (for the same connector) in session
storage were available in the same browser when switching
spaces.\r\n\r\n### Test setup\r\n\r\n- Configure at least two generative
AI connectors (e.g. one OpenAI and one Claude (Sonnet))\r\n- Create
another space to switch to during testing\r\n - Make sure the other
space has alerts in the last 24 hours\r\n\r\n### Desk testing\r\n\r\n1.
Clear the browser's local storage\r\n\r\n2. Close all open browser tabs
connected to Kibana, to clear the browser's session storage\r\n\r\n3.
Open the browser, and Navigate to Security > Attack discovery\r\n\r\n4.
Select a connector\r\n\r\n5. Click `Generate`\r\n\r\n**Expected
results**\r\n\r\n- A loading countdown is NOT displayed for the current
connector during loading (because it's the first run for the selected
connector)\r\n- Attack discoveries are generated for the
connector\r\n\r\n6. Once again, click `Generate`\r\n\r\n**Expected
result**\r\n\r\n- A loading countdown is displayed for the current
connector\r\n\r\n7. Click on the loading countdown's (i)
icon\r\n\r\n**Expected result**\r\n\r\n- The tooltip displays the timing
for the previous run\r\n- Attack discoveries are (once again) generated
for the connector\r\n\r\n8. Select a different
connector\r\n\r\n**Expected result**\r\n\r\n- The `Up to 20 alerts will
be analyzed` empty state is displayed\r\n\r\n9. Click the `Generate`
button\r\n\r\n**Expected results**\r\n\r\n- A loading countdown is NOT
displayed for the newly-selected connector during loading (because it's
the first run for the newly-selected connector)\r\n- Attack discoveries
are generated for the connector\r\n- The header displays the text
`Generated a few seconds ago`\r\n\r\n10. Once again, select the first
connector\r\n\r\n**Expected results**\r\n\r\n- The previous connector's
results are displayed\r\n- The header displays the text `Generated: 3
minutes ago`\r\n\r\n11. Navigate to Security > Cases\r\n\r\n12. Navigate
back to Security > Attack discovery\r\n\r\n**Expected results**\r\n\r\n-
The previous connector's results are displayed\r\n- The header displays
the text `Generated: 4 minutes ago`\r\n\r\n13. Once again, select the
other connector\r\n\r\n**Expected results**\r\n\r\n- The other
connector's results are displayed\r\n- The header displays the text
`Generated: n minutes ago` that's different than the previously-selected
connector\r\n\r\n14. Change to another space\r\n\r\n15. Navgiate to
Security > Attack discovery\r\n\r\n16. Select a
connector\r\n\r\n**Expected results**\r\n\r\n- The results from the
other space (for the selected connector) are NOT displayed\r\n\r\n17.
Once again, select the _other_ connector\r\n\r\n**Expected
results**\r\n\r\n- Once again, the results from the other space (for the
selected connector) are NOT displayed\r\n\r\n18. Click the `Generate`
button\r\n\r\n**Expected results**\r\n\r\n- A loading countdown is NOT
displayed for the newly-selected connector during loading (because it's
the first run for the newly-selected connector in this Space)\r\n-
Attack discoveries are generated for the connector\r\n\r\n19. Once
again, click the `Generate` button\r\n\r\n**Expected results**\r\n\r\n-
A loading countdown is displayed for the current connector\r\n- Attack
discoveries are once again generated\r\n- The header displays the text
`Generated a few seconds ago`\r\n\r\n20. Navigate back to the original
space\r\n\r\n21. Navigate to Security > Attack discovery in the orignal
space\r\n\r\n23. Re-select the previous connector\r\n\r\n**Expected
results**\r\n\r\n- The (much older) attack discovery results from the
original space are
displayed","sha":"4f56501860cff6df09058921448ec0b46efe9441","branchLabelMapping":{"^v8.15.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["bug","release_note:skip","Team:
SecuritySolution","Team:Security Generative
AI","v8.14.0","v8.15.0","Feature:Attack Discovery"],"title":"[Security
Solution] [Attack discovery] Fixes attack discovery cache
issues","number":183005,"url":"https://github.com/elastic/kibana/pull/183005","mergeCommit":{"message":"[Security
Solution] [Attack discovery] Fixes attack discovery cache issues
(#183005)\n\n## [Security Solution] [Attack discovery] Fixes attack
discovery cache issues\r\n\r\n### Summary\r\n\r\nThis PR fixes an issue
where cached attack discoveries (for the same connector) in session
storage were available in the same browser when switching
spaces.\r\n\r\n### Test setup\r\n\r\n- Configure at least two generative
AI connectors (e.g. one OpenAI and one Claude (Sonnet))\r\n- Create
another space to switch to during testing\r\n - Make sure the other
space has alerts in the last 24 hours\r\n\r\n### Desk testing\r\n\r\n1.
Clear the browser's local storage\r\n\r\n2. Close all open browser tabs
connected to Kibana, to clear the browser's session storage\r\n\r\n3.
Open the browser, and Navigate to Security > Attack discovery\r\n\r\n4.
Select a connector\r\n\r\n5. Click `Generate`\r\n\r\n**Expected
results**\r\n\r\n- A loading countdown is NOT displayed for the current
connector during loading (because it's the first run for the selected
connector)\r\n- Attack discoveries are generated for the
connector\r\n\r\n6. Once again, click `Generate`\r\n\r\n**Expected
result**\r\n\r\n- A loading countdown is displayed for the current
connector\r\n\r\n7. Click on the loading countdown's (i)
icon\r\n\r\n**Expected result**\r\n\r\n- The tooltip displays the timing
for the previous run\r\n- Attack discoveries are (once again) generated
for the connector\r\n\r\n8. Select a different
connector\r\n\r\n**Expected result**\r\n\r\n- The `Up to 20 alerts will
be analyzed` empty state is displayed\r\n\r\n9. Click the `Generate`
button\r\n\r\n**Expected results**\r\n\r\n- A loading countdown is NOT
displayed for the newly-selected connector during loading (because it's
the first run for the newly-selected connector)\r\n- Attack discoveries
are generated for the connector\r\n- The header displays the text
`Generated a few seconds ago`\r\n\r\n10. Once again, select the first
connector\r\n\r\n**Expected results**\r\n\r\n- The previous connector's
results are displayed\r\n- The header displays the text `Generated: 3
minutes ago`\r\n\r\n11. Navigate to Security > Cases\r\n\r\n12. Navigate
back to Security > Attack discovery\r\n\r\n**Expected results**\r\n\r\n-
The previous connector's results are displayed\r\n- The header displays
the text `Generated: 4 minutes ago`\r\n\r\n13. Once again, select the
other connector\r\n\r\n**Expected results**\r\n\r\n- The other
connector's results are displayed\r\n- The header displays the text
`Generated: n minutes ago` that's different than the previously-selected
connector\r\n\r\n14. Change to another space\r\n\r\n15. Navgiate to
Security > Attack discovery\r\n\r\n16. Select a
connector\r\n\r\n**Expected results**\r\n\r\n- The results from the
other space (for the selected connector) are NOT displayed\r\n\r\n17.
Once again, select the _other_ connector\r\n\r\n**Expected
results**\r\n\r\n- Once again, the results from the other space (for the
selected connector) are NOT displayed\r\n\r\n18. Click the `Generate`
button\r\n\r\n**Expected results**\r\n\r\n- A loading countdown is NOT
displayed for the newly-selected connector during loading (because it's
the first run for the newly-selected connector in this Space)\r\n-
Attack discoveries are generated for the connector\r\n\r\n19. Once
again, click the `Generate` button\r\n\r\n**Expected results**\r\n\r\n-
A loading countdown is displayed for the current connector\r\n- Attack
discoveries are once again generated\r\n- The header displays the text
`Generated a few seconds ago`\r\n\r\n20. Navigate back to the original
space\r\n\r\n21. Navigate to Security > Attack discovery in the orignal
space\r\n\r\n23. Re-select the previous connector\r\n\r\n**Expected
results**\r\n\r\n- The (much older) attack discovery results from the
original space are
displayed","sha":"4f56501860cff6df09058921448ec0b46efe9441"}},"sourceBranch":"main","suggestedTargetBranches":["8.14"],"targetPullRequestStates":[{"branch":"8.14","label":"v8.14.0","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"main","label":"v8.15.0","branchLabelMappingKey":"^v8.15.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/183005","number":183005,"mergeCommit":{"message":"[Security
Solution] [Attack discovery] Fixes attack discovery cache issues
(#183005)\n\n## [Security Solution] [Attack discovery] Fixes attack
discovery cache issues\r\n\r\n### Summary\r\n\r\nThis PR fixes an issue
where cached attack discoveries (for the same connector) in session
storage were available in the same browser when switching
spaces.\r\n\r\n### Test setup\r\n\r\n- Configure at least two generative
AI connectors (e.g. one OpenAI and one Claude (Sonnet))\r\n- Create
another space to switch to during testing\r\n - Make sure the other
space has alerts in the last 24 hours\r\n\r\n### Desk testing\r\n\r\n1.
Clear the browser's local storage\r\n\r\n2. Close all open browser tabs
connected to Kibana, to clear the browser's session storage\r\n\r\n3.
Open the browser, and Navigate to Security > Attack discovery\r\n\r\n4.
Select a connector\r\n\r\n5. Click `Generate`\r\n\r\n**Expected
results**\r\n\r\n- A loading countdown is NOT displayed for the current
connector during loading (because it's the first run for the selected
connector)\r\n- Attack discoveries are generated for the
connector\r\n\r\n6. Once again, click `Generate`\r\n\r\n**Expected
result**\r\n\r\n- A loading countdown is displayed for the current
connector\r\n\r\n7. Click on the loading countdown's (i)
icon\r\n\r\n**Expected result**\r\n\r\n- The tooltip displays the timing
for the previous run\r\n- Attack discoveries are (once again) generated
for the connector\r\n\r\n8. Select a different
connector\r\n\r\n**Expected result**\r\n\r\n- The `Up to 20 alerts will
be analyzed` empty state is displayed\r\n\r\n9. Click the `Generate`
button\r\n\r\n**Expected results**\r\n\r\n- A loading countdown is NOT
displayed for the newly-selected connector during loading (because it's
the first run for the newly-selected connector)\r\n- Attack discoveries
are generated for the connector\r\n- The header displays the text
`Generated a few seconds ago`\r\n\r\n10. Once again, select the first
connector\r\n\r\n**Expected results**\r\n\r\n- The previous connector's
results are displayed\r\n- The header displays the text `Generated: 3
minutes ago`\r\n\r\n11. Navigate to Security > Cases\r\n\r\n12. Navigate
back to Security > Attack discovery\r\n\r\n**Expected results**\r\n\r\n-
The previous connector's results are displayed\r\n- The header displays
the text `Generated: 4 minutes ago`\r\n\r\n13. Once again, select the
other connector\r\n\r\n**Expected results**\r\n\r\n- The other
connector's results are displayed\r\n- The header displays the text
`Generated: n minutes ago` that's different than the previously-selected
connector\r\n\r\n14. Change to another space\r\n\r\n15. Navgiate to
Security > Attack discovery\r\n\r\n16. Select a
connector\r\n\r\n**Expected results**\r\n\r\n- The results from the
other space (for the selected connector) are NOT displayed\r\n\r\n17.
Once again, select the _other_ connector\r\n\r\n**Expected
results**\r\n\r\n- Once again, the results from the other space (for the
selected connector) are NOT displayed\r\n\r\n18. Click the `Generate`
button\r\n\r\n**Expected results**\r\n\r\n- A loading countdown is NOT
displayed for the newly-selected connector during loading (because it's
the first run for the newly-selected connector in this Space)\r\n-
Attack discoveries are generated for the connector\r\n\r\n19. Once
again, click the `Generate` button\r\n\r\n**Expected results**\r\n\r\n-
A loading countdown is displayed for the current connector\r\n- Attack
discoveries are once again generated\r\n- The header displays the text
`Generated a few seconds ago`\r\n\r\n20. Navigate back to the original
space\r\n\r\n21. Navigate to Security > Attack discovery in the orignal
space\r\n\r\n23. Re-select the previous connector\r\n\r\n**Expected
results**\r\n\r\n- The (much older) attack discovery results from the
original space are
displayed","sha":"4f56501860cff6df09058921448ec0b46efe9441"}}]}]
BACKPORT-->

Co-authored-by: Andrew Macri <[email protected]>
  • Loading branch information
kibanamachine and andrew-goldstein authored May 9, 2024
1 parent a14ee25 commit b032740
Show file tree
Hide file tree
Showing 2 changed files with 140 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
*/

import type { Replacements } from '@kbn/elastic-assistant-common';
import type { AttackDiscovery } from '../../types';
import { isEmpty } from 'lodash/fp';

import type { AttackDiscovery, GenerationInterval } from '../../types';

export interface CachedAttackDiscoveries {
connectorId: string;
Expand All @@ -19,7 +21,7 @@ export const encodeCachedAttackDiscoveries = (
cachedAttackDiscoveries: Record<string, CachedAttackDiscoveries>
): string | null => {
try {
return JSON.stringify(cachedAttackDiscoveries, null, 2);
return JSON.stringify(cachedAttackDiscoveries);
} catch {
return null;
}
Expand All @@ -34,3 +36,75 @@ export const decodeCachedAttackDiscoveries = (
return null;
}
};

export const getSessionStorageCachedAttackDiscoveries = (
key: string
): Record<string, CachedAttackDiscoveries> | null => {
if (!isEmpty(key)) {
return decodeCachedAttackDiscoveries(sessionStorage.getItem(key) ?? '');
}

return null;
};

export const setSessionStorageCachedAttackDiscoveries = ({
key,
cachedAttackDiscoveries,
}: {
key: string;
cachedAttackDiscoveries: Record<string, CachedAttackDiscoveries>;
}) => {
if (!isEmpty(key)) {
const encoded = encodeCachedAttackDiscoveries(cachedAttackDiscoveries);

if (encoded != null) {
sessionStorage.setItem(key, encoded);
}
}
};

export const encodeGenerationIntervals = (
generationIntervals: Record<string, GenerationInterval[]>
): string | null => {
try {
return JSON.stringify(generationIntervals);
} catch {
return null;
}
};

export const decodeGenerationIntervals = (
generationIntervals: string
): Record<string, GenerationInterval[]> | null => {
try {
return JSON.parse(generationIntervals);
} catch {
return null;
}
};

export const getLocalStorageGenerationIntervals = (
key: string
): Record<string, GenerationInterval[]> | null => {
if (!isEmpty(key)) {
return decodeGenerationIntervals(sessionStorage.getItem(key) ?? '');
}

return null;
};

export const setLocalStorageGenerationIntervals = ({
key,
generationIntervals,
}: {
key: string;
generationIntervals: Record<string, GenerationInterval[]>;
}) => {
if (!isEmpty(key)) {
const encoded = encodeGenerationIntervals(generationIntervals);

if (encoded != null) {
localStorage.setItem(key, encoded);
}
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@ import {
} from '@kbn/elastic-assistant-common';
import { uniq } from 'lodash/fp';
import moment from 'moment';
import React, { useCallback, useMemo, useState } from 'react';
import { useLocalStorage, useSessionStorage } from 'react-use';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import * as uuid from 'uuid';
import { useFetchAnonymizationFields } from '@kbn/elastic-assistant/impl/assistant/api/anonymization_fields/use_fetch_anonymization_fields';

Expand All @@ -36,8 +35,10 @@ import {
import { getAverageIntervalSeconds } from '../pages/loading_callout/countdown/last_times_popover/helpers';
import type { CachedAttackDiscoveries } from '../pages/session_storage';
import {
encodeCachedAttackDiscoveries,
decodeCachedAttackDiscoveries,
getLocalStorageGenerationIntervals,
getSessionStorageCachedAttackDiscoveries,
setLocalStorageGenerationIntervals,
setSessionStorageCachedAttackDiscoveries,
} from '../pages/session_storage';
import { ERROR_GENERATING_ATTACK_DISCOVERIES } from '../pages/translations';
import type { AttackDiscovery, GenerationInterval } from '../types';
Expand Down Expand Up @@ -67,7 +68,7 @@ export const useAttackDiscovery = ({
setLoadingConnectorId?: (loadingConnectorId: string | null) => void;
}): UseAttackDiscovery => {
const { reportAttackDiscoveriesGenerated } = useAttackDiscoveryTelemetry();
const spaceId = useSpaceId() ?? 'default';
const spaceId: string | undefined = useSpaceId();

// get Kibana services and connectors
const {
Expand All @@ -86,24 +87,60 @@ export const useAttackDiscovery = ({

const { data: anonymizationFields } = useFetchAnonymizationFields();

// get cached attack discoveries from session storage:
const [sessionStorageCachedAttackDiscoveries, setSessionStorageCachedAttackDiscoveries] =
useSessionStorage<string>(
`${DEFAULT_ASSISTANT_NAMESPACE}.${ATTACK_DISCOVERY_STORAGE_KEY}.${spaceId}.${CACHED_ATTACK_DISCOVERIES_SESSION_STORAGE_KEY}`
);
const sessionStorageKey = useMemo(
() =>
spaceId != null // spaceId is undefined while the useSpaceId hook is loading
? `${DEFAULT_ASSISTANT_NAMESPACE}.${ATTACK_DISCOVERY_STORAGE_KEY}.${spaceId}.${CACHED_ATTACK_DISCOVERIES_SESSION_STORAGE_KEY}`
: '',
[spaceId]
);

const [cachedAttackDiscoveries, setCachedAttackDiscoveries] = useState<
Record<string, CachedAttackDiscoveries>
>(decodeCachedAttackDiscoveries(sessionStorageCachedAttackDiscoveries) ?? {});
>({});

useEffect(() => {
const decoded = getSessionStorageCachedAttackDiscoveries(sessionStorageKey);

if (decoded != null) {
setCachedAttackDiscoveries(decoded);

const decodedAttackDiscoveries = decoded[connectorId ?? '']?.attackDiscoveries;
if (decodedAttackDiscoveries != null) {
setAttackDiscoveries(decodedAttackDiscoveries);
}

const decodedReplacements = decoded[connectorId ?? '']?.replacements;
if (decodedReplacements != null) {
setReplacements(decodedReplacements);
}

// get generation intervals from local storage:
const [localStorageGenerationIntervals, setLocalStorageGenerationIntervals] = useLocalStorage<
Record<string, GenerationInterval[]>
>(
`${DEFAULT_ASSISTANT_NAMESPACE}.${ATTACK_DISCOVERY_STORAGE_KEY}.${spaceId}.${GENERATION_INTERVALS_LOCAL_STORAGE_KEY}`
const decodedLastUpdated = decoded[connectorId ?? '']?.updated;
if (decodedLastUpdated != null) {
setLastUpdated(decodedLastUpdated);
}
}
}, [connectorId, sessionStorageKey]);

const localStorageKey = useMemo(
() =>
spaceId != null // spaceId is undefined while the useSpaceId hook is loading
? `${DEFAULT_ASSISTANT_NAMESPACE}.${ATTACK_DISCOVERY_STORAGE_KEY}.${spaceId}.${GENERATION_INTERVALS_LOCAL_STORAGE_KEY}`
: '',
[spaceId]
);

const [generationIntervals, setGenerationIntervals] = React.useState<
Record<string, GenerationInterval[]> | undefined
>(localStorageGenerationIntervals);
>(undefined);

useEffect(() => {
const decoded = getLocalStorageGenerationIntervals(localStorageKey);

if (decoded != null) {
setGenerationIntervals(decoded);
}
}, [localStorageKey]);

// get connector intervals from generation intervals:
const connectorIntervals = useMemo(
Expand Down Expand Up @@ -199,9 +236,10 @@ export const useAttackDiscovery = ({
};

setCachedAttackDiscoveries(newCachedAttackDiscoveries);
setSessionStorageCachedAttackDiscoveries(
encodeCachedAttackDiscoveries(newCachedAttackDiscoveries) ?? ''
);
setSessionStorageCachedAttackDiscoveries({
key: sessionStorageKey,
cachedAttackDiscoveries: newCachedAttackDiscoveries,
});

// update the generation intervals with the latest timing:
const previousConnectorIntervals: GenerationInterval[] =
Expand All @@ -227,7 +265,10 @@ export const useAttackDiscovery = ({
// only update the generation intervals if alerts were sent as context to the LLM:
if (newAlertsContextCount != null && newAlertsContextCount > 0) {
setGenerationIntervals(newGenerationIntervals);
setLocalStorageGenerationIntervals(newGenerationIntervals);
setLocalStorageGenerationIntervals({
key: localStorageKey,
generationIntervals: newGenerationIntervals,
});
}

setReplacements(newReplacements);
Expand Down Expand Up @@ -266,12 +307,12 @@ export const useAttackDiscovery = ({
generationIntervals,
http,
knowledgeBase,
localStorageKey,
replacements,
reportAttackDiscoveriesGenerated,
sessionStorageKey,
setConnectorId,
setLoadingConnectorId,
setLocalStorageGenerationIntervals,
setSessionStorageCachedAttackDiscoveries,
toasts,
traceOptions,
]);
Expand Down

0 comments on commit b032740

Please sign in to comment.