Skip to content
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

[SecuritySolution] Migrate away from browser-side SO client #154174

Merged
merged 8 commits into from
Apr 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions x-pack/plugins/security_solution/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ export const DEFAULT_RULE_REFRESH_INTERVAL_VALUE = 60000 as const; // ms
export const DEFAULT_RULE_NOTIFICATION_QUERY_SIZE = 100 as const;
export const SECURITY_FEATURE_ID = 'Security' as const;
export const SECURITY_TAG_NAME = 'Security Solution' as const;
export const SECURITY_TAG_DESCRIPTION = 'Security Solution auto-generated tag' as const;
export const DEFAULT_SPACE_ID = 'default' as const;
export const DEFAULT_RELATIVE_DATE_THRESHOLD = 24 as const;

Expand Down Expand Up @@ -303,6 +304,10 @@ export const prebuiltSavedObjectsBulkCreateUrl = (templateName: string) =>
export const PREBUILT_SAVED_OBJECTS_BULK_DELETE = `${INTERNAL_RISK_SCORE_URL}/prebuilt_content/saved_objects/_bulk_delete/{template_name}`;
export const prebuiltSavedObjectsBulkDeleteUrl = (templateName: string) =>
`${INTERNAL_RISK_SCORE_URL}/prebuilt_content/saved_objects/_bulk_delete/${templateName}` as const;

export const INTERNAL_DASHBOARDS_URL = `/internal/dashboards` as const;
export const INTERNAL_TAGS_URL = `/internal/tags`;

export const RISK_SCORE_CREATE_INDEX = `${INTERNAL_RISK_SCORE_URL}/indices/create`;
export const RISK_SCORE_DELETE_INDICES = `${INTERNAL_RISK_SCORE_URL}/indices/delete`;
export const RISK_SCORE_CREATE_STORED_SCRIPT = `${INTERNAL_RISK_SCORE_URL}/stored_scripts/create`;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/*
* 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 { MOCK_TAG_ID, DEFAULT_DASHBOARDS_RESPONSE } from '../api/__mocks__';

export const getSecurityTagIds = jest.fn().mockResolvedValue([MOCK_TAG_ID]);

export const getSecurityDashboards = jest.fn().mockResolvedValue(DEFAULT_DASHBOARDS_RESPONSE);
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* 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 { SECURITY_TAG_NAME, SECURITY_TAG_DESCRIPTION } from '../../../../../../common/constants';

export const MOCK_TAG_ID = 'securityTagId';

export const DEFAULT_TAGS_RESPONSE = [
{
id: MOCK_TAG_ID,
name: SECURITY_TAG_NAME,
description: SECURITY_TAG_DESCRIPTION,
color: '#2c7b82',
},
];

export const DEFAULT_DASHBOARDS_RESPONSE = [
{
type: 'dashboard',
id: 'c0ac2c00-c1c0-11e7-8995-936807a28b16-ecs',
namespaces: ['default'],
attributes: {
description: 'Summary of Linux kernel audit events.',
title: '[Auditbeat Auditd] Overview ECS',
version: 1,
},
references: [
{
name: 'tag-ref-ba964280-d211-11ed-890b-153ddf1a08e9',
id: 'ba964280-d211-11ed-890b-153ddf1a08e9',
type: 'tag',
},
],
coreMigrationVersion: '8.8.0',
typeMigrationVersion: '8.7.0',
updated_at: '2023-04-03T11:38:00.902Z',
created_at: '2023-04-03T11:20:50.603Z',
version: 'WzE4NzQsMV0=',
score: 0,
},
];

export const getSecuritySolutionTags = jest
.fn()
.mockImplementation(() => Promise.resolve(DEFAULT_TAGS_RESPONSE));

export const getSecuritySolutionDashboards = jest
.fn()
.mockImplementation(() => Promise.resolve(DEFAULT_DASHBOARDS_RESPONSE));
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* 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 type { HttpSetup } from '@kbn/core/public';
import type { Tag } from '@kbn/saved-objects-tagging-plugin/public';
import { INTERNAL_TAGS_URL, INTERNAL_DASHBOARDS_URL } from '../../../../../common/constants';
import type { DashboardTableItem } from '../types';

export const getSecuritySolutionTags = ({ http }: { http: HttpSetup }): Promise<Tag[] | null> =>
http.get(INTERNAL_TAGS_URL);

export const getSecuritySolutionDashboards = ({
http,
}: {
http: HttpSetup;
}): Promise<DashboardTableItem[] | null> => http.get(INTERNAL_DASHBOARDS_URL);
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/*
* 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.
*/

export interface DashboardTableItem {
id: string;
type: string;
attributes: {
title: string;
description: string;
};
references: Array<{ name: string; type: string; id: string }>;
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,26 +9,15 @@ import { renderHook, act } from '@testing-library/react-hooks';
import type { DashboardStart } from '@kbn/dashboard-plugin/public';
import { useKibana } from '../../lib/kibana';
import { TestProviders } from '../../mock/test_providers';
import type { Tag } from '@kbn/saved-objects-tagging-plugin/common';
import { useCreateSecurityDashboardLink } from './use_create_security_dashboard_link';
import { MOCK_TAG_ID } from './api/__mocks__';
import { getSecurityTagIds as mockGetSecurityTagIds } from './utils';

jest.mock('../../lib/kibana');

const TAG_ID = 'securityTagId';
const CREATED_TAG: Tag = {
id: TAG_ID,
name: 'tag title',
description: 'tag description',
color: '#999999',
};
const URL = '/path';

const mockGetSecurityTagId = jest.fn(async (): Promise<string | null> => null);
const mockCreateSecurityTag = jest.fn(async () => CREATED_TAG);
jest.mock('./utils', () => ({
getSecurityTagId: () => mockGetSecurityTagId(),
createSecurityTag: () => mockCreateSecurityTag(),
}));
jest.mock('./utils');

const renderUseCreateSecurityDashboardLink = () =>
renderHook(() => useCreateSecurityDashboardLink(), {
Expand Down Expand Up @@ -57,8 +46,7 @@ describe('useCreateSecurityDashboardLink', () => {
it('should request when renders', async () => {
await asyncRenderUseCreateSecurityDashboard();

expect(mockGetSecurityTagId).toHaveBeenCalledTimes(1);
expect(mockCreateSecurityTag).toHaveBeenCalledTimes(1);
expect(mockGetSecurityTagIds).toHaveBeenCalledTimes(1);
});

it('should return a memoized value when rerendered', async () => {
Expand All @@ -71,28 +59,18 @@ describe('useCreateSecurityDashboardLink', () => {
expect(result1).toBe(result2);
});

it('should not request create tag if already exists', async () => {
mockGetSecurityTagId.mockResolvedValueOnce(TAG_ID);
await asyncRenderUseCreateSecurityDashboard();

expect(mockGetSecurityTagId).toHaveBeenCalledTimes(1);
expect(mockCreateSecurityTag).not.toHaveBeenCalled();
});

it('should generate create url with tag', async () => {
await asyncRenderUseCreateSecurityDashboard();

expect(mockGetRedirectUrl).toHaveBeenCalledWith({ tags: [TAG_ID] });
expect(mockGetRedirectUrl).toHaveBeenCalledWith({ tags: [MOCK_TAG_ID] });
});

it('should not re-request tag id when re-rendered', async () => {
const { rerender } = await asyncRenderUseCreateSecurityDashboard();

expect(mockGetSecurityTagId).toHaveBeenCalledTimes(1);
expect(mockCreateSecurityTag).toHaveBeenCalledTimes(1);
expect(mockGetSecurityTagIds).toHaveBeenCalledTimes(1);
act(() => rerender());
expect(mockGetSecurityTagId).toHaveBeenCalledTimes(1);
expect(mockCreateSecurityTag).toHaveBeenCalledTimes(1);
expect(mockGetSecurityTagIds).toHaveBeenCalledTimes(1);
});

it('should return isLoading while requesting', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,30 +7,24 @@

import { useEffect, useMemo, useState } from 'react';
import { useKibana } from '../../lib/kibana';
import { getSecurityTagId, createSecurityTag } from './utils';
import { getSecurityTagIds } from './utils';

type UseCreateDashboard = () => { isLoading: boolean; url: string };

export const useCreateSecurityDashboardLink: UseCreateDashboard = () => {
const {
dashboard: { locator } = {},
savedObjects: { client: savedObjectsClient },
savedObjectsTagging,
} = useKibana().services;
const { dashboard: { locator } = {}, savedObjectsTagging, http } = useKibana().services;

const [securityTagId, setSecurityTagId] = useState<string | null>(null);
const [securityTagId, setSecurityTagId] = useState<string[] | null>(null);

useEffect(() => {
let ignore = false;
const getOrCreateSecurityTag = async () => {
if (savedObjectsClient && savedObjectsTagging) {
let tagId = await getSecurityTagId(savedObjectsClient);
if (!tagId) {
const newTag = await createSecurityTag(savedObjectsTagging.client);
tagId = newTag.id;
}
if (!ignore) {
setSecurityTagId(tagId);
if (http && savedObjectsTagging) {
// getSecurityTagIds creates a tag if it coundn't find one
const tagIds = await getSecurityTagIds(http);

if (!ignore && tagIds) {
setSecurityTagId(tagIds);
}
}
};
Expand All @@ -40,12 +34,12 @@ export const useCreateSecurityDashboardLink: UseCreateDashboard = () => {
return () => {
ignore = true;
};
}, [savedObjectsClient, savedObjectsTagging]);
}, [http, savedObjectsTagging]);

const result = useMemo(
() => ({
isLoading: securityTagId == null,
url: securityTagId ? locator?.getRedirectUrl({ tags: [securityTagId] }) ?? '' : '',
url: securityTagId ? locator?.getRedirectUrl({ tags: [securityTagId[0]] }) ?? '' : '',
}),
[securityTagId, locator]
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ import type { DashboardStart } from '@kbn/dashboard-plugin/public';
import { EuiBasicTable } from '@elastic/eui';
import { useKibana } from '../../lib/kibana';
import { TestProviders } from '../../mock/test_providers';
import type { DashboardTableItem } from './use_security_dashboards_table';
import {
useSecurityDashboardsTableColumns,
useSecurityDashboardsTableItems,
} from './use_security_dashboards_table';
import * as telemetry from '../../lib/telemetry';
import type { DashboardTableItem } from './types';

jest.mock('../../lib/kibana');
const spyTrack = jest.spyOn(telemetry, 'track');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,37 +8,29 @@
import React, { useEffect, useMemo, useCallback } from 'react';
import type { MouseEventHandler } from 'react';
import type { EuiBasicTableColumn } from '@elastic/eui';
import type { SavedObjectAttributes } from '@kbn/securitysolution-io-ts-alerting-types';
import type { SavedObject } from '@kbn/core/public';
import { getSecurityDashboards } from './utils';
import { LinkAnchor } from '../../components/links';
import { useKibana, useNavigateTo } from '../../lib/kibana';
import * as i18n from './translations';
import { useFetch, REQUEST_NAMES } from '../../hooks/use_fetch';
import { METRIC_TYPE, TELEMETRY_EVENT, track } from '../../lib/telemetry';

export interface DashboardTableItem extends SavedObject<SavedObjectAttributes> {
title?: string;
description?: string;
}
import type { DashboardTableItem } from './types';

const EMPTY_DESCRIPTION = '-' as const;

export const useSecurityDashboardsTableItems = () => {
const {
savedObjects: { client: savedObjectsClient },
} = useKibana().services;
const { http } = useKibana().services;

const { fetch, data, isLoading, error } = useFetch(
REQUEST_NAMES.SECURITY_DASHBOARDS,
getSecurityDashboards
);

useEffect(() => {
if (savedObjectsClient) {
fetch(savedObjectsClient);
if (http) {
fetch(http);
}
}, [fetch, savedObjectsClient]);
}, [fetch, http]);

const items = useMemo(() => {
if (!data) {
Expand Down
Loading