[8.0] [Osquery] Add more e2e tests (#121487) (#122651)

* [Osquery] Add more e2e tests (#121487)

(cherry picked from commit 75aadc6081)

* fix formatting

* version

Co-authored-by: Tomasz Ciecierski <ciecierskitomek@gmail.com>
Co-authored-by: Patryk Kopycinski <patryk.kopycinski@elastic.co>
This commit is contained in:
Kibana Machine 2022-01-14 14:43:45 -05:00 committed by GitHub
parent 7487be7ef5
commit 8a3c998920
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
44 changed files with 651 additions and 108 deletions

View file

@ -9,3 +9,5 @@ steps:
automatic:
- exit_status: '*'
limit: 1
artifact_paths:
- "target/kibana-osquery/**/*"

View file

@ -4,8 +4,9 @@ set -euo pipefail
source .buildkite/scripts/common/util.sh
export BUILD_TS_REFS_DISABLE=false
.buildkite/scripts/bootstrap.sh
.buildkite/scripts/download_build_artifacts.sh
node scripts/build_kibana_platform_plugins.js
export JOB=kibana-osquery-cypress
@ -16,5 +17,5 @@ cd "$XPACK_DIR"
checks-reporter-with-killswitch "Osquery Cypress Tests" \
node scripts/functional_tests \
--debug --bail \
--kibana-install-dir "$KIBANA_BUILD_LOCATION" \
--config test/osquery_cypress/cli_config.ts

View file

@ -1,10 +1,10 @@
{
"baseUrl": "http://localhost:5620",
"defaultCommandTimeout": 6000,
"execTimeout": 12000,
"defaultCommandTimeout": 60000,
"execTimeout": 120000,
"pageLoadTimeout": 12000,
"retries": {
"runMode": 2
"runMode": 0
},
"screenshotsFolder": "../../../target/kibana-osquery/cypress/screenshots",
"trashAssetsBeforeRuns": false,
@ -12,5 +12,10 @@
"videosFolder": "../../../target/kibana-osquery/cypress/videos",
"viewportHeight": 900,
"viewportWidth": 1440,
"experimentalStudio": true
"experimentalStudio": true,
"env": {
"cypress-react-selector": {
"root": "#osquery-app"
}
}
}

View file

@ -0,0 +1,26 @@
{
"attributes": {
"created_at": "2021-12-21T08:54:07.802Z",
"created_by": "elastic",
"description": "Test saved query description",
"ecs_mapping": [
{
"key": "labels",
"value": {
"field": "hours"
}
}
],
"id": "Saved-Query-Id",
"interval": "3600",
"query": "select * from uptime;",
"updated_at": "2021-12-21T08:54:38.648Z",
"updated_by": "elastic"
},
"coreMigrationVersion": "8.0.0",
"id": "8eae68b0-623b-11ec-8b00-d5db3ac3cda1",
"references": [],
"type": "osquery-saved-query",
"updated_at": "2021-12-21T08:54:38.653Z",
"version": "Wzg5MywxXQ=="
}

View file

@ -1,60 +0,0 @@
/*
* 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 { FLEET_AGENT_POLICIES, navigateTo } from '../tasks/navigation';
import { addIntegration } from '../tasks/integrations';
import { checkResults, inputQuery, selectAllAgents, submitQuery } from '../tasks/live_query';
import { login } from '../tasks/login';
describe('Add Integration', () => {
const integration = 'Osquery Manager';
before(() => {
login();
});
it.skip('should open Osquery app', () => {
cy.visit('/app/osquery/live_queries');
cy.wait(3000);
cy.contains('Live queries history', { timeout: 60000 });
cy.contains('New live query').click();
cy.wait(3000);
cy.contains('Saved queries').click();
cy.wait(3000);
cy.contains('Saved queries', { timeout: 60000 });
cy.contains('Add saved query').click();
cy.wait(3000);
cy.contains('Packs').click();
cy.wait(3000);
cy.contains('Packs', { timeout: 60000 });
cy.contains('Add pack').click();
cy.wait(3000);
});
it('should display Osquery integration in the Policies list once installed ', () => {
addAndVerifyIntegration();
});
it.skip('should run live query', () => {
navigateTo('/app/osquery/live_queries/new');
cy.wait(1000);
selectAllAgents();
inputQuery();
submitQuery();
checkResults();
});
function addAndVerifyIntegration() {
navigateTo(FLEET_AGENT_POLICIES);
cy.contains('Default Fleet Server policy').click();
cy.contains('Add integration').click();
cy.contains(integration).click();
addIntegration();
cy.contains('osquery_manager-');
}
});

View file

@ -0,0 +1,27 @@
/*
* 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 { FLEET_AGENT_POLICIES } from '../../tasks/navigation';
import { addIntegration } from '../../tasks/integrations';
import { login } from '../../tasks/login';
describe('Super User - Add Integration', () => {
const integration = 'Osquery Manager';
beforeEach(() => {
login();
});
it('should display Osquery integration in the Policies list once installed ', () => {
cy.visit(FLEET_AGENT_POLICIES);
cy.contains('Default Fleet Server policy').click();
cy.contains('Add integration').click();
cy.contains(integration).click();
addIntegration();
cy.contains('osquery_manager-');
});
});

View file

@ -0,0 +1,52 @@
/*
* 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 { login } from '../../tasks/login';
import { navigateTo } from '../../tasks/navigation';
import {
checkResults,
inputQuery,
selectAllAgents,
submitQuery,
typeInECSFieldInput,
typeInOsqueryFieldInput,
} from '../../tasks/live_query';
describe('Super User - Live Query', () => {
beforeEach(() => {
login();
navigateTo('/app/osquery');
});
it('should run query and enable ecs mapping', () => {
cy.contains('New live query').click();
selectAllAgents();
inputQuery('select * from uptime;');
submitQuery();
checkResults();
cy.react('EuiDataGridHeaderCellWrapper', {
props: { id: 'osquery.days', index: 1 },
});
cy.react('EuiDataGridHeaderCellWrapper', {
props: { id: 'osquery.hours', index: 2 },
});
cy.react('EuiAccordion', { props: { buttonContent: 'Advanced' } }).click();
typeInECSFieldInput('message{downArrow}{enter}');
typeInOsqueryFieldInput('days{downArrow}{enter}');
submitQuery();
checkResults();
cy.react('EuiDataGridHeaderCellWrapper', {
props: { id: 'message', index: 1 },
});
cy.react('EuiDataGridHeaderCellWrapper', {
props: { id: 'osquery.days', index: 2 },
}).react('EuiIconIndexMapping');
});
});

View file

@ -0,0 +1,54 @@
/*
* 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 { navigateTo } from '../../tasks/navigation';
import { login } from '../../tasks/login';
import { checkResults, inputQuery, submitQuery } from '../../tasks/live_query';
import { ArchiverMethod, runKbnArchiverScript } from '../../tasks/archiver';
describe('Super User - Metrics', () => {
beforeEach(() => {
login();
navigateTo('/app/osquery');
});
before(() => {
runKbnArchiverScript(ArchiverMethod.LOAD, 'saved_query');
});
after(() => {
runKbnArchiverScript(ArchiverMethod.UNLOAD, 'saved_query');
});
it('should be able to run the query', () => {
cy.get('[data-test-subj="toggleNavButton"]').click();
cy.contains('Metrics').click();
cy.wait(1000);
cy.get('[data-test-subj="nodeContainer"]').click();
cy.contains('Osquery').click();
inputQuery('select * from uptime;');
submitQuery();
checkResults();
});
it('should be able to run the previously saved query', () => {
cy.get('[data-test-subj="toggleNavButton"]').click();
cy.get('[data-test-subj="collapsibleNavAppLink"').contains('Metrics').click();
cy.wait(500);
cy.get('[data-test-subj="nodeContainer"]').click();
cy.contains('Osquery').click();
cy.get('[data-test-subj="comboBoxInput"]').first().click();
cy.wait(500);
cy.get('div[role=listBox]').should('have.lengthOf.above', 0);
cy.get('[data-test-subj="comboBoxInput"]').first().type('{downArrow}{enter}');
submitQuery();
checkResults();
});
});

View file

@ -0,0 +1,107 @@
/*
* 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 { navigateTo } from '../../tasks/navigation';
import {
deleteAndConfirm,
findAndClickButton,
findFormFieldByRowsLabelAndType,
inputQuery,
} from '../../tasks/live_query';
import { login } from '../../tasks/login';
import { ArchiverMethod, runKbnArchiverScript } from '../../tasks/archiver';
import { preparePack } from '../../tasks/packs';
describe('SuperUser - Packs', () => {
const SAVED_QUERY_ID = 'Saved-Query-Id';
const PACK_NAME = 'Pack-name';
const NEW_QUERY_NAME = 'new-query-name';
before(() => {
runKbnArchiverScript(ArchiverMethod.LOAD, 'saved_query');
});
beforeEach(() => {
login();
navigateTo('/app/osquery');
});
after(() => {
runKbnArchiverScript(ArchiverMethod.UNLOAD, 'saved_query');
});
it('should add a pack from a saved query', () => {
cy.contains('Packs').click();
findAndClickButton('Add pack');
findFormFieldByRowsLabelAndType('Name', PACK_NAME);
findFormFieldByRowsLabelAndType('Description (optional)', 'Pack description');
findFormFieldByRowsLabelAndType(
'Scheduled agent policies (optional)',
'Default Fleet Server policy'
);
cy.react('List').first().click();
findAndClickButton('Add query');
cy.contains('Attach next query');
cy.react('EuiComboBox', { props: { placeholder: 'Search for saved queries' } })
.click()
.type(SAVED_QUERY_ID);
cy.react('List').first().click();
cy.react('EuiFormRow', { props: { label: 'Interval (s)' } })
.click()
.clear()
.type('10');
cy.react('EuiFlyoutFooter').react('EuiButton').contains('Save').click();
cy.react('EuiTableRow').contains(SAVED_QUERY_ID);
findAndClickButton('Save pack');
cy.contains('Save and deploy changes');
findAndClickButton('Save and deploy changes');
cy.contains(PACK_NAME);
});
it('to click the edit button and edit pack', () => {
preparePack(PACK_NAME, SAVED_QUERY_ID);
findAndClickButton('Edit');
cy.contains(`Edit ${PACK_NAME}`);
findAndClickButton('Add query');
cy.contains('Attach next query');
inputQuery('select * from uptime');
findFormFieldByRowsLabelAndType('ID', NEW_QUERY_NAME);
cy.react('EuiFlyoutFooter').react('EuiButton').contains('Save').click();
cy.react('EuiTableRow').contains(NEW_QUERY_NAME);
findAndClickButton('Update pack');
cy.contains('Save and deploy changes');
findAndClickButton('Save and deploy changes');
});
// THIS TESTS TAKES TOO LONG FOR NOW - LET ME THINK IT THROUGH
// it('to click the icon and visit discover', () => {
// preparePack(PACK_NAME, SAVED_QUERY_ID);
// cy.react('CustomItemAction', {
// props: { index: 0, item: { id: SAVED_QUERY_ID } },
// }).click();
// cy.get('[data-test-subj="superDatePickerToggleQuickMenuButton"').click();
// cy.get('[data-test-subj="superDatePickerToggleRefreshButton"').click();
// cy.get('[data-test-subj="superDatePickerRefreshIntervalInput"').clear().type('10');
// cy.get('button').contains('Apply').click();
// cy.get('[data-test-subj="discoverDocTable"]', { timeout: 60000 }).contains(
// `pack_${PACK_NAME}_${SAVED_QUERY_ID}`
// );
// });
// it('by clicking in Lens button', () => {
// preparePack(PACK_NAME, SAVED_QUERY_ID);
// cy.react('CustomItemAction', {
// props: { index: 1, item: { id: SAVED_QUERY_ID } },
// }).click();
// cy.get('[data-test-subj="lnsWorkspace"]');
// cy.get('[data-test-subj="breadcrumbs"]').contains(
// `Action pack_${PACK_NAME}_${SAVED_QUERY_ID} results`
// );
// });
it('to click delete button', () => {
preparePack(PACK_NAME, SAVED_QUERY_ID);
findAndClickButton('Edit');
deleteAndConfirm('pack');
});
});

View file

@ -0,0 +1,84 @@
/*
* 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 { navigateTo } from '../../tasks/navigation';
import {
checkResults,
DEFAULT_QUERY,
deleteAndConfirm,
findFormFieldByRowsLabelAndType,
inputQuery,
selectAllAgents,
submitQuery,
} from '../../tasks/live_query';
import { login } from '../../tasks/login';
describe('Super User - Saved queries', () => {
const SAVED_QUERY_ID = 'Saved-Query-Id';
const SAVED_QUERY_DESCRIPTION = 'Saved Query Description';
beforeEach(() => {
login();
navigateTo('/app/osquery');
});
it('should save the query', () => {
cy.contains('New live query').click();
selectAllAgents();
inputQuery(DEFAULT_QUERY);
submitQuery();
checkResults();
cy.contains('Save for later').click();
cy.contains('Save query');
findFormFieldByRowsLabelAndType('ID', SAVED_QUERY_ID);
findFormFieldByRowsLabelAndType('Description', SAVED_QUERY_DESCRIPTION);
cy.react('EuiButtonDisplay').contains('Save').click();
});
it('should view query details in status', () => {
cy.contains('New live query');
cy.react('ActionTableResultsButton').first().click();
cy.wait(1000);
cy.contains(DEFAULT_QUERY);
checkResults();
cy.react('EuiTab', { props: { id: 'status' } }).click();
cy.wait(1000);
cy.react('EuiTableRow').should('have.lengthOf', 1);
cy.contains('Successful').siblings().contains(1);
});
it('should display a previously saved query and run it', () => {
cy.contains('Saved queries').click();
cy.contains(SAVED_QUERY_ID);
cy.react('PlayButtonComponent', {
props: { savedQuery: { attributes: { id: SAVED_QUERY_ID } } },
}).click();
selectAllAgents();
submitQuery();
});
it('should edit the saved query', () => {
cy.contains('Saved queries').click();
cy.contains(SAVED_QUERY_ID);
cy.react('CustomItemAction', {
props: { index: 1, item: { attributes: { id: SAVED_QUERY_ID } } },
}).click();
findFormFieldByRowsLabelAndType('Description', ' Edited');
cy.react('EuiButton').contains('Update query').click();
cy.contains(`${SAVED_QUERY_DESCRIPTION} Edited`);
});
it('should delete the saved query', () => {
cy.contains('Saved queries').click();
cy.contains(SAVED_QUERY_ID);
cy.react('CustomItemAction', {
props: { index: 1, item: { attributes: { id: SAVED_QUERY_ID } } },
}).click();
deleteAndConfirm('query');
cy.contains(SAVED_QUERY_ID);
});
});

View file

@ -0,0 +1,30 @@
/*
* 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 { login } from '../../tasks/login';
import { navigateTo } from '../../tasks/navigation';
import { ROLES } from '../../test';
import { ArchiverMethod, runKbnArchiverScript } from '../../tasks/archiver';
describe('T1 Analyst - Live Query', () => {
beforeEach(() => {
login(ROLES.t1_analyst);
});
describe('should run a live query', () => {
before(() => {
runKbnArchiverScript(ArchiverMethod.LOAD, 'saved_query');
});
after(() => {
runKbnArchiverScript(ArchiverMethod.UNLOAD, 'saved_query');
});
it('when passed as a saved query ', () => {
navigateTo('/app/osquery/saved_queries');
cy.waitForReact(1000);
});
});
});

View file

@ -22,6 +22,8 @@
// https://on.cypress.io/configuration
// ***********************************************************
// eslint-disable-next-line import/no-extraneous-dependencies
import 'cypress-react-selector';
// Import commands.js using ES2015 syntax:
import './commands';
// import './coverage';

View file

@ -0,0 +1,24 @@
/*
* 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 enum ArchiverMethod {
SAVE = 'save',
LOAD = 'load',
UNLOAD = 'unload',
}
export const runKbnArchiverScript = (method: ArchiverMethod, fileName: string) => {
const {
ELASTICSEARCH_USERNAME,
ELASTICSEARCH_PASSWORD,
hostname: HOSTNAME,
configport: PORT,
} = Cypress.env();
const script = `node ../../../scripts/kbn_archiver.js --kibana-url http://${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD}@${HOSTNAME}:${PORT} ${method} ./cypress/fixtures/saved_objects/${fileName}.ndjson`;
cy.exec(script, { env: { NODE_TLS_REJECT_UNAUTHORIZED: 1 } });
};

View file

@ -5,16 +5,44 @@
* 2.0.
*/
import { AGENT_FIELD, ALL_AGENTS_OPTION, LIVE_QUERY_EDITOR } from '../screens/live_query';
import { LIVE_QUERY_EDITOR } from '../screens/live_query';
export const DEFAULT_QUERY = 'select * from processes;';
export const selectAllAgents = () => {
cy.get(AGENT_FIELD).first().click();
return cy.get(ALL_AGENTS_OPTION).contains('All agents').click();
cy.react('EuiComboBox', { props: { placeholder: 'Select agents or groups' } }).type('All agents');
cy.react('EuiFilterSelectItem').contains('All agents').should('exist');
cy.react('EuiComboBox', { props: { placeholder: 'Select agents or groups' } }).type(
'{downArrow}{enter}'
);
};
export const inputQuery = () => cy.get(LIVE_QUERY_EDITOR).type('select * from processes;');
export const inputQuery = (query: string) => cy.get(LIVE_QUERY_EDITOR).type(query);
export const submitQuery = () => cy.contains('Submit').click();
export const checkResults = () =>
cy.get('[data-test-subj="dataGridRowCell"]', { timeout: 60000 }).should('have.lengthOf.above', 0);
export const typeInECSFieldInput = (text: string) =>
cy.get('[data-test-subj="ECS-field-input"]').click().type(text);
export const typeInOsqueryFieldInput = (text: string) =>
cy.react('OsqueryColumnFieldComponent').first().react('ResultComboBox').click().type(text);
export const findFormFieldByRowsLabelAndType = (label: string, text: string) => {
cy.react('EuiFormRow', { props: { label } }).type(text);
};
export const deleteAndConfirm = (type: string) => {
cy.react('EuiButton').contains(`Delete ${type}`).click();
cy.contains(`Are you sure you want to delete this ${type}?`);
cy.react('EuiButton').contains('Confirm').click();
cy.get('[data-test-subj="globalToastList"]')
.first()
.contains('Successfully deleted')
.contains(type);
};
export const findAndClickButton = (text: string) => {
cy.react('EuiButton').contains(text).click();
};

View file

@ -104,10 +104,10 @@ export const getCurlScriptEnvVars = () => ({
export const postRoleAndUser = (role: ROLES) => {
const env = getCurlScriptEnvVars();
const detectionsRoleScriptPath = `./server/lib/detection_engine/scripts/roles_users/${role}/post_detections_role.sh`;
const detectionsRoleJsonPath = `./server/lib/detection_engine/scripts/roles_users/${role}/detections_role.json`;
const detectionsUserScriptPath = `./server/lib/detection_engine/scripts/roles_users/${role}/post_detections_user.sh`;
const detectionsUserJsonPath = `./server/lib/detection_engine/scripts/roles_users/${role}/detections_user.json`;
const detectionsRoleScriptPath = `./scripts/roles_users/${role}/post_role.sh`;
const detectionsRoleJsonPath = `./scripts/roles_users/${role}/role.json`;
const detectionsUserScriptPath = `./scripts/roles_users/${role}/post_user.sh`;
const detectionsUserJsonPath = `./scripts/roles_users/${role}/user.json`;
// post the role
cy.exec(`bash ${detectionsRoleScriptPath} ${detectionsRoleJsonPath}`, {
@ -122,7 +122,7 @@ export const postRoleAndUser = (role: ROLES) => {
export const deleteRoleAndUser = (role: ROLES) => {
const env = getCurlScriptEnvVars();
const detectionsUserDeleteScriptPath = `./server/lib/detection_engine/scripts/roles_users/${role}/delete_detections_user.sh`;
const detectionsUserDeleteScriptPath = `./scripts/roles_users/${role}/delete_user.sh`;
// delete the role
cy.exec(`bash ${detectionsUserDeleteScriptPath}`, {

View file

@ -15,8 +15,12 @@ export const NEW_LIVE_QUERY = 'app/osquery/live_queries/new';
export const OSQUERY_INTEGRATION_PAGE = '/app/fleet/integrations/osquery_manager/add-integration';
export const navigateTo = (page: string, opts?: Partial<Cypress.VisitOptions>) => {
cy.visit(page, opts);
cy.contains('Loading Elastic').should('exist');
cy.contains('Loading Elastic').should('not.exist');
// There's a security warning toast that seemingly makes ui elements in the bottom right unavailable, so we close it
return cy.get('[data-test-subj="toastCloseButton"]').click();
cy.get('[data-test-subj="toastCloseButton"]', { timeout: 30000 }).click();
cy.waitForReact();
};
export const openNavigationFlyout = () => {

View file

@ -0,0 +1,14 @@
/*
* 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 const preparePack = (packName: string, savedQueryId: string) => {
cy.contains('Packs').click();
const createdPack = cy.contains(packName);
createdPack.click();
cy.waitForReact(1000);
cy.react('EuiTableRow').contains(savedQueryId);
};

View file

@ -10,7 +10,8 @@
"outDir": "target/types",
"types": [
"cypress",
"node"
"node",
"cypress-react-selector"
],
"resolveJsonModule": true,
},

View file

@ -19,11 +19,11 @@ interface ActionTableResultsButtonProps {
actionId: string;
}
const ActionTableResultsButton = React.memo<ActionTableResultsButtonProps>(({ actionId }) => {
const ActionTableResultsButton: React.FC<ActionTableResultsButtonProps> = ({ actionId }) => {
const navProps = useRouterNavigate(`live_queries/${actionId}`);
return <EuiButtonIcon iconType="visTable" {...navProps} />;
});
};
ActionTableResultsButton.displayName = 'ActionTableResultsButton';

View file

@ -21,6 +21,7 @@ export const useAgentPolicy = ({ policyId, skip, silent }: UseAgentPolicy) => {
const { http } = useKibana().services;
const setErrorToast = useErrorToast();
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return useQuery<any, Error>(
['agentPolicy', { policyId }],
() => http.get(`/internal/osquery/fleet_wrapper/agent_policies/${policyId}`),

View file

@ -57,7 +57,7 @@ const OsqueryAppComponent = () => {
}
return (
<Container>
<Container id="osquery-app">
<Wrapper>
<Nav>
<EuiFlexGroup gutterSize="l" alignItems="center">

View file

@ -264,6 +264,7 @@ const ECSComboboxFieldComponent: React.FC<ECSComboboxFieldProps> = ({
options={ECSSchemaOptions}
selectedOptions={selectedOptions}
onChange={handleChange}
data-test-subj="ECS-field-input"
renderOption={renderOption}
rowHeight={32}
isClearable
@ -1103,7 +1104,7 @@ export const ECSMappingEditorField = React.memo(
{Object.entries(value).map(([ecsKey, ecsValue]) => (
<ECSMappingEditorForm
// eslint-disable-next-line
ref={(formRef) => {
ref={(formRef) => {
if (formRef) {
formRefs.current[ecsKey] = formRef;
}
@ -1123,7 +1124,7 @@ export const ECSMappingEditorField = React.memo(
{!euiFieldProps?.isDisabled && (
<ECSMappingEditorForm
// eslint-disable-next-line
ref={(formRef) => {
ref={(formRef) => {
if (formRef) {
formRefs.current.new = formRef;
}

View file

@ -0,0 +1,13 @@
/*
* 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 IQueryPayload {
attributes?: {
name: string;
id: string;
};
}

View file

@ -13,6 +13,7 @@ import { PLUGIN_ID } from '../../common';
import { pagePathGetters } from '../common/page_paths';
import { PACKS_ID } from './constants';
import { useErrorToast } from '../common/hooks/use_error_toast';
import { IQueryPayload } from './types';
interface UseCreatePackProps {
withRedirect?: boolean;
@ -29,7 +30,7 @@ export const useCreatePack = ({ withRedirect }: UseCreatePackProps) => {
return useMutation(
(payload) =>
http.post<any>('/internal/osquery/packs', {
http.post<IQueryPayload>('/internal/osquery/packs', {
body: JSON.stringify(payload),
}),
{

View file

@ -22,6 +22,7 @@ export const usePacks = ({
return useQuery(
[PACKS_ID, { pageIndex, pageSize, sortField, sortDirection }],
async () =>
// eslint-disable-next-line @typescript-eslint/no-explicit-any
http.get<any>('/internal/osquery/packs', {
query: { pageIndex, pageSize, sortField, sortDirection },
}),

View file

@ -13,6 +13,7 @@ import { PLUGIN_ID } from '../../common';
import { pagePathGetters } from '../common/page_paths';
import { PACKS_ID } from './constants';
import { useErrorToast } from '../common/hooks/use_error_toast';
import { IQueryPayload } from './types';
interface UseUpdatePackProps {
withRedirect?: boolean;
@ -32,7 +33,7 @@ export const useUpdatePack = ({ withRedirect, options }: UseUpdatePackProps) =>
return useMutation(
// @ts-expect-error update types
({ id, ...payload }) =>
http.put<any>(`/internal/osquery/packs/${id}`, {
http.put<IQueryPayload>(`/internal/osquery/packs/${id}`, {
body: JSON.stringify(payload),
}),
{

View file

@ -232,7 +232,7 @@ const ResultsTableComponent: React.FC<ResultsTableComponentProps> = ({
);
useEffect(() => {
if (!allResultsData?.edges) {
if (!allResultsData?.edges?.length) {
return;
}

View file

@ -72,6 +72,10 @@ export const useAllResults = ({
)
.toPromise();
if (!responseData?.edges?.length && responseData.totalCount) {
throw new Error('Empty edges while positive totalCount');
}
return {
...responseData,
inspect: getInspectResponse(responseData, {} as InspectResponse),

View file

@ -15,9 +15,7 @@ import { WithHeaderLayout } from '../../../components/layouts';
import { useBreadcrumbs } from '../../../common/hooks/use_breadcrumbs';
const LiveQueriesPageComponent = () => {
const permissions = useKibana().services.application.capabilities.osquery;
useBreadcrumbs('live_queries');
const newQueryLinkProps = useRouterNavigate('live_queries/new');
const LeftColumn = useMemo(
() => (
@ -37,28 +35,35 @@ const LiveQueriesPageComponent = () => {
[]
);
const RightColumn = useMemo(
() => (
<EuiButton
fill
{...newQueryLinkProps}
iconType="plusInCircle"
isDisabled={!(permissions.writeLiveQueries || permissions.runSavedQueries)}
>
<FormattedMessage
id="xpack.osquery.liveQueriesHistory.newLiveQueryButtonLabel"
defaultMessage="New live query"
/>
</EuiButton>
),
[permissions.writeLiveQueries, permissions.runSavedQueries, newQueryLinkProps]
);
return (
<WithHeaderLayout leftColumn={LeftColumn} rightColumn={RightColumn} rightColumnGrow={false}>
<WithHeaderLayout
leftColumn={LeftColumn}
rightColumn={<NewLiveQueryButton />}
rightColumnGrow={false}
>
<ActionsTable />
</WithHeaderLayout>
);
};
export const LiveQueriesPage = React.memo(LiveQueriesPageComponent);
const NewLiveQueryButton = React.memo(() => {
const permissions = useKibana().services.application.capabilities.osquery;
const newQueryLinkProps = useRouterNavigate('live_queries/new');
return (
<EuiButton
fill
{...newQueryLinkProps}
iconType="plusInCircle"
isDisabled={!(permissions.writeLiveQueries || permissions.runSavedQueries)}
>
<FormattedMessage
id="xpack.osquery.liveQueriesHistory.newLiveQueryButtonLabel"
defaultMessage="New live query"
/>
</EuiButton>
);
});
NewLiveQueryButton.displayName = 'NewLiveQueryButton';

View file

@ -29,6 +29,7 @@ export const useCreateSavedQuery = ({ withRedirect }: UseCreateSavedQueryProps)
return useMutation(
(payload) =>
// eslint-disable-next-line @typescript-eslint/no-explicit-any
http.post<any>('/internal/osquery/saved_query', {
body: JSON.stringify(payload),
}),

View file

@ -29,6 +29,7 @@ export const useUpdateSavedQuery = ({ savedQueryId }: UseUpdateSavedQueryProps)
return useMutation(
(payload) =>
// eslint-disable-next-line @typescript-eslint/no-explicit-any
http.put<any>(`/internal/osquery/saved_query/${savedQueryId}`, {
body: JSON.stringify(payload),
}),

View file

@ -0,0 +1,11 @@
Initial version of roles support for Osquery
| Role | Data Sources<br/> (logs-osquery_manager* index) | Live Queries | Saved Queries | Packs | Osquery Integration | Cases | Discovery/Lens | Metrics UI |
|:--------------------------------------------:|:-----------------------------------------------:|:-------------------------------:|:-------------:|:-----:|:-------------------:|:-----:|:-------------:|:----------:|
| NO Data Source access user | none | none | none | none | none | none | none | none |
| Reader (read-only user) | read | read | read | read | none | none | none | none |
| T1 Analyst | read | read, write (run saved queries) | read | read | none | none | none | none |
| T2 Analyst | read | read, write (tbc) | all | read | none | read | none | none |
| Hunter / T3 Analyst | read | all | all | all | none | all | read | all |
| SOC Manager | read | all | all | all | none | all | read | all |
| Platform Engineer (data ingest, cluster ops) | read | all | all | all | all | all | read | none |

View file

@ -0,0 +1,8 @@
/*
* 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 * from './t1_analyst';

View file

@ -0,0 +1,11 @@
#
# 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.
#
curl -v -H 'Content-Type: application/json' -H 'kbn-xsrf: 123'\
-u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \
-XDELETE ${ELASTICSEARCH_URL}/_security/user/t1_analyst

View file

@ -0,0 +1,11 @@
#
# 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.
#
curl -H 'Content-Type: application/json' -H 'kbn-xsrf: 123'\
-u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \
-XGET ${KIBANA_URL}/api/security/role/t1_analyst | jq -S .

View file

@ -0,0 +1,11 @@
/*
* 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 * as t1AnalystUser from './user.json';
import * as t1AnalystRole from './role.json';
export { t1AnalystUser, t1AnalystRole };

View file

@ -0,0 +1,14 @@
#
# 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.
#
ROLE_CONFIG=(${@:-./detections_role.json})
curl -H 'Content-Type: application/json' -H 'kbn-xsrf: 123'\
-u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \
-XPUT ${KIBANA_URL}/api/security/role/t1_analyst \
-d @${ROLE_CONFIG}

View file

@ -0,0 +1,14 @@
#
# 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.
#
USER=(${@:-./detections_user.json})
curl -H 'Content-Type: application/json' -H 'kbn-xsrf: 123'\
-u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \
${ELASTICSEARCH_URL}/_security/user/t1_analyst \
-d @${USER}

View file

@ -0,0 +1,19 @@
{
"elasticsearch": {
"indices": [
{
"names": ["logs-osquery_manager*"],
"privileges": ["read"]
}
]
},
"kibana": [
{
"feature": {
"osquery": ["read"]
},
"spaces": ["*"]
}
]
}

View file

@ -0,0 +1,6 @@
{
"password": "changeme",
"roles": ["t1_analyst"],
"full_name": "T1 Analyst",
"email": "detections-reader@example.com"
}

View file

@ -58,7 +58,7 @@ export const savedQueryType: SavedObjectsType = {
getTitle: (savedObject) => savedObject.attributes.id,
getEditUrl: (savedObject) => `/saved_queries/${savedObject.id}/edit`,
getInAppUrl: (savedObject) => ({
path: `/app/saved_queries/${savedObject.id}`,
path: `/app/osquery/saved_queries/${savedObject.id}`,
uiCapabilitiesPath: 'osquery.read',
}),
},
@ -124,7 +124,7 @@ export const packType: SavedObjectsType = {
getTitle: (savedObject) => `Pack: ${savedObject.attributes.name}`,
getEditUrl: (savedObject) => `/packs/${savedObject.id}/edit`,
getInAppUrl: (savedObject) => ({
path: `/app/packs/${savedObject.id}`,
path: `/app/osquery/packs/${savedObject.id}`,
uiCapabilitiesPath: 'osquery.read',
}),
onExport: (context, objects) =>

View file

@ -11,6 +11,7 @@
"common/**/*",
"public/**/*",
"scripts/**/*",
"scripts/**/**.json",
"server/**/*",
"../../../typings/**/*",
// ECS and Osquery schema files

View file

@ -38,6 +38,9 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) {
'--csp.strict=false',
// define custom kibana server args here
`--elasticsearch.ssl.certificateAuthorities=${CA_CERT_PATH}`,
`--xpack.fleet.agents.elasticsearch.host=http://host.docker.internal:${kibanaCommonTestsConfig.get(
'servers.elasticsearch.port'
)}`,
],
},
};

View file

@ -80,7 +80,11 @@ function startOsqueryCypress(context: FtrProviderContext, cypressCommand: string
env: {
FORCE_COLOR: '1',
// eslint-disable-next-line @typescript-eslint/naming-convention
CYPRESS_baseUrl: Url.format(config.get('servers.kibana')),
CYPRESS_baseUrl: Url.format({
protocol: config.get('servers.kibana.protocol'),
hostname: config.get('servers.kibana.hostname'),
port: config.get('servers.kibana.port'),
}),
// eslint-disable-next-line @typescript-eslint/naming-convention
CYPRESS_protocol: config.get('servers.kibana.protocol'),
// eslint-disable-next-line @typescript-eslint/naming-convention