[8.10] [EDR workflows] Osquery serverless tests (#163795) (#164545)

# Backport

This will backport the following commits from `main` to `8.10`:
- [[EDR workflows] Osquery serverless tests
(#163795)](https://github.com/elastic/kibana/pull/163795)

<!--- Backport version: 8.9.7 -->

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

<!--BACKPORT [{"author":{"name":"Tomasz
Ciecierski","email":"tomasz.ciecierski@elastic.co"},"sourceCommit":{"committedDate":"2023-08-23T09:53:14Z","message":"[EDR
workflows] Osquery serverless tests
(#163795)","sha":"054cdbaf1e030f887953681abaa72dccac633480","branchLabelMapping":{"^v8.11.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["chore","release_note:skip","Team:Defend
Workflows","Feature:Osquery","v8.10.0","v8.11.0"],"number":163795,"url":"https://github.com/elastic/kibana/pull/163795","mergeCommit":{"message":"[EDR
workflows] Osquery serverless tests
(#163795)","sha":"054cdbaf1e030f887953681abaa72dccac633480"}},"sourceBranch":"main","suggestedTargetBranches":["8.10"],"targetPullRequestStates":[{"branch":"8.10","label":"v8.10.0","labelRegex":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"main","label":"v8.11.0","labelRegex":"^v8.11.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/163795","number":163795,"mergeCommit":{"message":"[EDR
workflows] Osquery serverless tests
(#163795)","sha":"054cdbaf1e030f887953681abaa72dccac633480"}}]}]
BACKPORT-->

Co-authored-by: Tomasz Ciecierski <tomasz.ciecierski@elastic.co>
This commit is contained in:
Kibana Machine 2023-08-23 07:25:32 -04:00 committed by GitHub
parent 5cefc399aa
commit 79136c28dc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
66 changed files with 1367 additions and 1385 deletions

View file

@ -32,6 +32,7 @@ disabled:
- x-pack/plugins/observability_onboarding/e2e/ftr_config_runner.ts
- x-pack/plugins/observability_onboarding/e2e/ftr_config.ts
- x-pack/test/osquery_cypress/cli_config.ts
- x-pack/test/osquery_cypress/serverless_cli_config.ts
- x-pack/test/osquery_cypress/config.ts
- x-pack/test/osquery_cypress/visual_config.ts
- x-pack/test/security_solution_cypress/cli_config.ts

View file

@ -22,3 +22,17 @@ steps:
soft_fail: true
artifact_paths:
- "target/kibana-osquery/**/*"
- command: .buildkite/scripts/steps/functional/security_serverless_osquery.sh
label: 'Serverless Osquery Cypress Tests'
agents:
queue: n2-4-spot
depends_on: build
timeout_in_minutes: 50
parallelism: 6
retry:
automatic:
- exit_status: '*'
limit: 1
artifact_paths:
- "target/kibana-osquery/**/*"

View file

@ -10,6 +10,7 @@ import { execSync } from 'child_process';
import fs from 'fs';
import prConfigs from '../../../pull_requests.json';
import { areChangesSkippable, doAnyChangesMatch } from '#pipeline-utils';
const prConfig = prConfigs.jobs.find((job) => job.pipelineSlug === 'kibana-pull-request');
if (!prConfig) {

View file

@ -0,0 +1,15 @@
#!/usr/bin/env bash
set -euo pipefail
source .buildkite/scripts/common/util.sh
source .buildkite/scripts/steps/functional/common_cypress.sh
.buildkite/scripts/bootstrap.sh
node scripts/build_kibana_platform_plugins.js
export JOB=kibana-osquery-cypress-serverless
echo "--- Security Osquery Serverless Cypress"
yarn --cwd x-pack/plugins/osquery cypress:serverless:run

View file

@ -7,6 +7,19 @@
import { defineCypressConfig } from '@kbn/cypress-config';
import path from 'path';
import { safeLoad as loadYaml } from 'js-yaml';
import { readFileSync } from 'fs';
import type { YamlRoleDefinitions } from '../../test_serverless/shared/lib';
// eslint-disable-next-line @kbn/imports/no_boundary_crossing
import { setupUserDataLoader } from '../../test_serverless/functional/test_suites/security/cypress/support/setup_data_loader_tasks';
const ROLES_YAML_FILE_PATH = path.join(
`${__dirname}/cypress/support`,
'project_controller_osquery_roles.yml'
);
const roleDefinitions = loadYaml(readFileSync(ROLES_YAML_FILE_PATH, 'utf8')) as YamlRoleDefinitions;
export default defineCypressConfig({
defaultCommandTimeout: 60000,
execTimeout: 120000,
@ -29,6 +42,9 @@ export default defineCypressConfig({
'cypress-react-selector': {
root: '#osquery-app',
},
grepFilterSpecs: true,
grepTags: '@ess',
grepOmitFiltered: true,
},
e2e: {
@ -37,5 +53,10 @@ export default defineCypressConfig({
experimentalRunAllSpecs: true,
experimentalMemoryManagement: true,
numTestsKeptInMemory: 3,
setupNodeEvents(on, config) {
setupUserDataLoader(on, config, { roleDefinitions, additionalRoleName: 'viewer' });
return config;
},
},
});

View file

@ -5,6 +5,7 @@
* 2.0.
*/
import { tag } from '../../tags';
import {
cleanupPack,
cleanupAgentPolicy,
@ -28,11 +29,9 @@ import {
interceptAgentPolicyId,
policyContainsIntegration,
} from '../../tasks/integrations';
import { login } from '../../tasks/login';
import { findAndClickButton, findFormFieldByRowsLabelAndType } from '../../tasks/live_query';
describe('ALL - Add Integration', () => {
describe('ALL - Add Integration', { tags: [tag.ESS, tag.BROKEN_IN_SERVERLESS] }, () => {
let savedQueryId: string;
before(() => {
@ -42,7 +41,7 @@ describe('ALL - Add Integration', () => {
});
beforeEach(() => {
login();
cy.login('elastic');
});
after(() => {
@ -63,7 +62,7 @@ describe('ALL - Add Integration', () => {
cy.get(`[url="${NAV_SEARCH_INPUT_OSQUERY_RESULTS.MANAGER}"]`).should('exist').click();
});
describe('Add and upgrade integration', () => {
describe('Add and upgrade integration', { tags: [tag.ESS] }, () => {
const oldVersion = '0.7.4';
const [integrationName, policyName] = generateRandomStringName(2);
let policyId: string;
@ -78,7 +77,7 @@ describe('ALL - Add Integration', () => {
cleanupAgentPolicy(policyId);
});
it('should add the old integration and be able to upgrade it', () => {
it('should add the old integration and be able to upgrade it', { tags: tag.ESS }, () => {
cy.visit(createOldOsqueryPath(oldVersion));
addCustomIntegration(integrationName, policyName);
policyContainsIntegration(integrationName, policyName);

View file

@ -5,6 +5,7 @@
* 2.0.
*/
import { LIVE_QUERY_EDITOR } from '../../screens/live_query';
import {
cleanupCase,
cleanupPack,
@ -14,17 +15,18 @@ import {
loadRule,
packFixture,
} from '../../tasks/api_fixtures';
import { ROLE, login } from '../../tasks/login';
import {
addToCase,
checkActionItemsInResults,
clickRuleName,
loadRuleAlerts,
submitQuery,
viewRecentCaseAndCheckResults,
} from '../../tasks/live_query';
import { generateRandomStringName, interceptCaseId } from '../../tasks/integrations';
describe('Alert Event Details - Cases', () => {
import { tag } from '../../tags';
import { ServerlessRoleName } from '../../support/roles';
describe('Alert Event Details - Cases', { tags: [tag.ESS, tag.SERVERLESS] }, () => {
let ruleId: string;
let ruleName: string;
let packId: string;
@ -44,9 +46,9 @@ describe('Alert Event Details - Cases', () => {
});
beforeEach(() => {
login(ROLE.soc_manager);
cy.login(ServerlessRoleName.SOC_MANAGER);
cy.visit('/app/security/rules');
cy.contains(ruleName).click();
clickRuleName(ruleName);
});
after(() => {
@ -72,10 +74,10 @@ describe('Alert Event Details - Cases', () => {
cy.getBySel('expand-event').first().click({ force: true });
cy.getBySel('take-action-dropdown-btn').click();
cy.getBySel('osquery-action-item').click();
cy.contains('Run a set of queries in a pack').wait(500).click();
cy.getBySel('select-live-pack').within(() => {
cy.getBySel('comboBoxInput').type(`${packName}{downArrow}{enter}`);
});
cy.contains(/^\d+ agen(t|ts) selected/);
cy.contains('Run a set of queries in a pack').click();
cy.get(LIVE_QUERY_EDITOR).should('not.exist');
cy.getBySel('select-live-pack').click().type(`${packName}{downArrow}{enter}`);
submitQuery();
cy.get('[aria-label="Add to Case"]').first().click();
cy.getBySel('cases-table-add-case-filter-bar').click();
@ -91,7 +93,8 @@ describe('Alert Event Details - Cases', () => {
});
});
describe('Case', () => {
// verify why calling new action doesnt add to response actions list
describe.skip('Case', () => {
let caseId: string;
before(() => {
@ -134,6 +137,7 @@ describe('Alert Event Details - Cases', () => {
cases: true,
timeline: true,
});
addToCase(caseId);
viewRecentCaseAndCheckResults();
});

View file

@ -7,19 +7,21 @@
import { cleanupRule, loadRule } from '../../tasks/api_fixtures';
import { RESPONSE_ACTIONS_ITEM_0, RESPONSE_ACTIONS_ITEM_1 } from '../../tasks/response_actions';
import { ROLE, login } from '../../tasks/login';
import {
checkActionItemsInResults,
clickRuleName,
inputQuery,
loadRuleAlerts,
submitQuery,
} from '../../tasks/live_query';
import { closeModalIfVisible, closeToastIfVisible } from '../../tasks/integrations';
import { RESULTS_TABLE, RESULTS_TABLE_BUTTON } from '../../screens/live_query';
import { tag } from '../../tags';
import { ServerlessRoleName } from '../../support/roles';
const UUID_REGEX = '[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}';
describe('Alert Event Details', { browser: 'electron' }, () => {
describe('Alert Event Details', { browser: 'electron', tags: [tag.ESS, tag.SERVERLESS] }, () => {
let ruleId: string;
let ruleName: string;
@ -36,9 +38,9 @@ describe('Alert Event Details', { browser: 'electron' }, () => {
});
beforeEach(() => {
login(ROLE.soc_manager);
cy.login(ServerlessRoleName.SOC_MANAGER);
cy.visit('/app/security/rules');
cy.contains(ruleName).click();
clickRuleName(ruleName);
});
it('should be able to add investigation guides to response actions', () => {
@ -98,7 +100,7 @@ describe('Alert Event Details', { browser: 'electron' }, () => {
closeModalIfVisible();
});
it('can visit discover from response action results', () => {
it('can visit discover from response action results', { tags: [tag.ESS] }, () => {
const discoverRegex = new RegExp(`action_id: ${UUID_REGEX}`);
cy.getBySel('expand-event').first().click();
cy.getBySel('securitySolutionDocumentDetailsFlyoutResponseSectionHeader').click();
@ -124,7 +126,7 @@ describe('Alert Event Details', { browser: 'electron' }, () => {
});
});
it('can visit lens from response action results', () => {
it('can visit lens from response action results', { tags: [tag.ESS] }, () => {
const lensRegex = new RegExp(`Action ${UUID_REGEX} results`);
cy.getBySel('expand-event').first().click();
cy.getBySel('securitySolutionDocumentDetailsFlyoutResponseSectionHeader').click();
@ -158,7 +160,7 @@ describe('Alert Event Details', { browser: 'electron' }, () => {
cy.getBySel('breadcrumbs').contains(lensRegex);
});
it('can add to timeline from response action results', () => {
it('can add to timeline from response action results', { tags: [tag.ESS] }, () => {
const timelineRegex = new RegExp(`Added ${UUID_REGEX} to timeline`);
const filterRegex = new RegExp(`action_id: "${UUID_REGEX}"`);
cy.getBySel('expand-event').first().click();

View file

@ -5,16 +5,18 @@
* 2.0.
*/
import { tag } from '../../tags';
import { cleanupRule, loadRule } from '../../tasks/api_fixtures';
import { ROLE, login } from '../../tasks/login';
import {
clickRuleName,
inputQuery,
loadRuleAlerts,
submitQuery,
takeOsqueryActionWithParams,
} from '../../tasks/live_query';
import { ServerlessRoleName } from '../../support/roles';
describe('Alert Event Details - dynamic params', () => {
describe('Alert Event Details - dynamic params', { tags: [tag.ESS, tag.SERVERLESS] }, () => {
let ruleId: string;
let ruleName: string;
@ -31,9 +33,9 @@ describe('Alert Event Details - dynamic params', () => {
});
beforeEach(() => {
login(ROLE.soc_manager);
cy.login(ServerlessRoleName.SOC_MANAGER);
cy.visit('/app/security/rules');
cy.contains(ruleName).click();
clickRuleName(ruleName);
});
it('should substitute parameters in investigation guide', () => {

View file

@ -19,170 +19,180 @@ import {
RESPONSE_ACTIONS_ITEM_2,
OSQUERY_RESPONSE_ACTION_ADD_BUTTON,
} from '../../tasks/response_actions';
import { ROLE, login } from '../../tasks/login';
import { checkActionItemsInResults, inputQuery, typeInECSFieldInput } from '../../tasks/live_query';
import {
checkActionItemsInResults,
clickRuleName,
inputQuery,
typeInECSFieldInput,
} from '../../tasks/live_query';
import { closeDateTabIfVisible, closeToastIfVisible } from '../../tasks/integrations';
import { tag } from '../../tags';
import { ServerlessRoleName } from '../../support/roles';
describe('Alert Event Details - Response Actions Form', { browser: 'electron' }, () => {
let multiQueryPackId: string;
let multiQueryPackName: string;
let ruleId: string;
let ruleName: string;
let packId: string;
let packName: string;
const packData = packFixture();
const multiQueryPackData = multiQueryPackFixture();
describe(
'Alert Event Details - Response Actions Form',
{ browser: 'electron', tags: [tag.ESS, tag.SERVERLESS] },
() => {
let multiQueryPackId: string;
let multiQueryPackName: string;
let ruleId: string;
let ruleName: string;
let packId: string;
let packName: string;
const packData = packFixture();
const multiQueryPackData = multiQueryPackFixture();
beforeEach(() => {
loadPack(packData).then((data) => {
packId = data.saved_object_id;
packName = data.name;
beforeEach(() => {
loadPack(packData).then((data) => {
packId = data.saved_object_id;
packName = data.name;
});
loadPack(multiQueryPackData).then((data) => {
multiQueryPackId = data.saved_object_id;
multiQueryPackName = data.name;
});
loadRule().then((data) => {
ruleId = data.id;
ruleName = data.name;
});
cy.login(ServerlessRoleName.SOC_MANAGER);
});
loadPack(multiQueryPackData).then((data) => {
multiQueryPackId = data.saved_object_id;
multiQueryPackName = data.name;
afterEach(() => {
cleanupPack(packId);
cleanupPack(multiQueryPackId);
cleanupRule(ruleId);
});
loadRule().then((data) => {
ruleId = data.id;
ruleName = data.name;
});
login(ROLE.soc_manager);
});
afterEach(() => {
cleanupPack(packId);
cleanupPack(multiQueryPackId);
cleanupRule(ruleId);
});
it('adds response actions with osquery with proper validation and form values', () => {
cy.visit('/app/security/rules');
cy.contains(ruleName).click();
cy.getBySel('editRuleSettingsLink').click();
cy.getBySel('globalLoadingIndicator').should('not.exist');
closeDateTabIfVisible();
cy.getBySel('edit-rule-actions-tab').click();
cy.contains('Response actions are run on each rule execution.');
cy.getBySel(OSQUERY_RESPONSE_ACTION_ADD_BUTTON).click();
cy.getBySel(RESPONSE_ACTIONS_ITEM_0).within(() => {
cy.contains('Query is a required field');
inputQuery('select * from uptime1');
});
cy.getBySel(OSQUERY_RESPONSE_ACTION_ADD_BUTTON).click();
cy.getBySel(RESPONSE_ACTIONS_ITEM_1).within(() => {
cy.contains('Run a set of queries in a pack').click();
});
cy.contains('Save changes').click();
cy.getBySel('response-actions-error')
.within(() => {
it('adds response actions with osquery with proper validation and form values', () => {
cy.visit('/app/security/rules');
clickRuleName(ruleName);
cy.getBySel('editRuleSettingsLink').click();
cy.getBySel('globalLoadingIndicator').should('not.exist');
closeDateTabIfVisible();
cy.getBySel('edit-rule-actions-tab').click();
cy.contains('Response actions are run on each rule execution.');
cy.getBySel(OSQUERY_RESPONSE_ACTION_ADD_BUTTON).click();
cy.getBySel(RESPONSE_ACTIONS_ITEM_0).within(() => {
cy.contains('Query is a required field');
inputQuery('select * from uptime1');
});
cy.getBySel(OSQUERY_RESPONSE_ACTION_ADD_BUTTON).click();
cy.getBySel(RESPONSE_ACTIONS_ITEM_1).within(() => {
cy.contains('Run a set of queries in a pack').click();
});
cy.contains('Save changes').click();
cy.getBySel('response-actions-error')
.within(() => {
cy.contains('Pack is a required field');
})
.should('exist');
cy.getBySel(RESPONSE_ACTIONS_ITEM_1).within(() => {
cy.contains('Pack is a required field');
})
.should('exist');
cy.getBySel(RESPONSE_ACTIONS_ITEM_1).within(() => {
cy.contains('Pack is a required field');
cy.getBySel('comboBoxInput').type(`${packName}{downArrow}{enter}`);
});
cy.getBySel('comboBoxInput').type(`${packName}{downArrow}{enter}`);
});
cy.getBySel(OSQUERY_RESPONSE_ACTION_ADD_BUTTON).click();
cy.getBySel(OSQUERY_RESPONSE_ACTION_ADD_BUTTON).click();
cy.getBySel(RESPONSE_ACTIONS_ITEM_2).within(() => {
cy.contains('Query is a required field');
inputQuery('select * from uptime');
cy.contains('Advanced').click();
typeInECSFieldInput('message{downArrow}{enter}');
cy.getBySel('osqueryColumnValueSelect').type('days{downArrow}{enter}');
cy.wait(1000); // wait for the validation to trigger - cypress is way faster than users ;)
});
cy.getBySel(RESPONSE_ACTIONS_ITEM_2).within(() => {
cy.contains('Query is a required field');
inputQuery('select * from uptime');
cy.contains('Advanced').click();
typeInECSFieldInput('message{downArrow}{enter}');
cy.getBySel('osqueryColumnValueSelect').type('days{downArrow}{enter}');
cy.wait(1000); // wait for the validation to trigger - cypress is way faster than users ;)
});
cy.getBySel('ruleEditSubmitButton').click();
cy.contains(`${ruleName} was saved`).should('exist');
closeToastIfVisible();
cy.getBySel('ruleEditSubmitButton').click();
cy.contains(`${ruleName} was saved`).should('exist');
closeToastIfVisible();
cy.getBySel('editRuleSettingsLink').click();
cy.getBySel('globalLoadingIndicator').should('not.exist');
cy.getBySel('edit-rule-actions-tab').click();
cy.getBySel(RESPONSE_ACTIONS_ITEM_0).within(() => {
cy.contains('select * from uptime1');
});
cy.getBySel(RESPONSE_ACTIONS_ITEM_2).within(() => {
cy.contains('select * from uptime');
cy.contains('Log message optimized for viewing in a log viewer');
cy.contains('Days of uptime');
});
cy.getBySel(RESPONSE_ACTIONS_ITEM_1).within(() => {
cy.contains(packName);
cy.getBySel('comboBoxInput').type('{backspace}{enter}');
});
cy.getBySel(RESPONSE_ACTIONS_ITEM_0).within(() => {
cy.contains('select * from uptime1');
cy.getBySel('remove-response-action').click();
});
cy.getBySel(RESPONSE_ACTIONS_ITEM_0).within(() => {
cy.contains('Search for a pack to run');
cy.contains('Pack is a required field');
cy.getBySel('comboBoxInput').type(`${packName}{downArrow}{enter}`);
});
cy.getBySel(RESPONSE_ACTIONS_ITEM_1).within(() => {
cy.contains('select * from uptime');
cy.contains('Log message optimized for viewing in a log viewer');
cy.contains('Days of uptime');
});
cy.intercept('PUT', '/api/detection_engine/rules').as('saveRuleSingleQuery');
cy.getBySel('ruleEditSubmitButton').click();
cy.wait('@saveRuleSingleQuery').should(({ request }) => {
const oneQuery = [
{
interval: 3600,
query: 'select * from uptime;',
id: Object.keys(packData.queries)[0],
},
];
expect(request.body.response_actions[0].params.queries).to.deep.equal(oneQuery);
});
cy.getBySel('editRuleSettingsLink').click();
cy.getBySel('globalLoadingIndicator').should('not.exist');
cy.getBySel('edit-rule-actions-tab').click();
cy.getBySel(RESPONSE_ACTIONS_ITEM_0).within(() => {
cy.contains('select * from uptime1');
});
cy.getBySel(RESPONSE_ACTIONS_ITEM_2).within(() => {
cy.contains('select * from uptime');
cy.contains('Log message optimized for viewing in a log viewer');
cy.contains('Days of uptime');
});
cy.getBySel(RESPONSE_ACTIONS_ITEM_1).within(() => {
cy.contains(packName);
cy.getBySel('comboBoxInput').type('{backspace}{enter}');
});
cy.getBySel(RESPONSE_ACTIONS_ITEM_0).within(() => {
cy.contains('select * from uptime1');
cy.getBySel('remove-response-action').click();
});
cy.getBySel(RESPONSE_ACTIONS_ITEM_0).within(() => {
cy.contains('Search for a pack to run');
cy.contains('Pack is a required field');
cy.getBySel('comboBoxInput').type(`${packName}{downArrow}{enter}`);
});
cy.getBySel(RESPONSE_ACTIONS_ITEM_1).within(() => {
cy.contains('select * from uptime');
cy.contains('Log message optimized for viewing in a log viewer');
cy.contains('Days of uptime');
});
cy.intercept('PUT', '/api/detection_engine/rules').as('saveRuleSingleQuery');
cy.getBySel('ruleEditSubmitButton').click();
cy.wait('@saveRuleSingleQuery').should(({ request }) => {
const oneQuery = [
{
interval: 3600,
query: 'select * from uptime;',
id: Object.keys(packData.queries)[0],
},
];
expect(request.body.response_actions[0].params.queries).to.deep.equal(oneQuery);
});
cy.contains(`${ruleName} was saved`).should('exist');
closeToastIfVisible();
cy.contains(`${ruleName} was saved`).should('exist');
closeToastIfVisible();
cy.getBySel('editRuleSettingsLink').click();
cy.getBySel('globalLoadingIndicator').should('not.exist');
cy.getBySel('edit-rule-actions-tab').click();
cy.getBySel(RESPONSE_ACTIONS_ITEM_0).within(() => {
cy.contains(packName);
cy.getBySel('comboBoxInput').type(`${multiQueryPackName}{downArrow}{enter}`);
checkActionItemsInResults({
cases: false,
lens: false,
discover: false,
timeline: false,
cy.getBySel('editRuleSettingsLink').click();
cy.getBySel('globalLoadingIndicator').should('not.exist');
cy.getBySel('edit-rule-actions-tab').click();
cy.getBySel(RESPONSE_ACTIONS_ITEM_0).within(() => {
cy.contains(packName);
cy.getBySel('comboBoxInput').type(`${multiQueryPackName}{downArrow}{enter}`);
checkActionItemsInResults({
cases: false,
lens: false,
discover: false,
timeline: false,
});
});
cy.getBySel(RESPONSE_ACTIONS_ITEM_1).within(() => {
cy.contains('select * from uptime');
cy.contains('Log message optimized for viewing in a log viewer');
cy.contains('Days of uptime');
});
cy.intercept('PUT', '/api/detection_engine/rules').as('saveRuleMultiQuery');
cy.contains('Save changes').click();
cy.wait('@saveRuleMultiQuery').should(({ request }) => {
const threeQueries = [
{
interval: 3600,
query: 'SELECT * FROM memory_info;',
platform: 'linux',
id: Object.keys(multiQueryPackData.queries)[0],
},
{
interval: 3600,
query: 'SELECT * FROM system_info;',
id: Object.keys(multiQueryPackData.queries)[1],
},
{
interval: 10,
query: 'select opera_extensions.* from users join opera_extensions using (uid);',
id: Object.keys(multiQueryPackData.queries)[2],
},
];
expect(request.body.response_actions[0].params.queries).to.deep.equal(threeQueries);
});
});
cy.getBySel(RESPONSE_ACTIONS_ITEM_1).within(() => {
cy.contains('select * from uptime');
cy.contains('Log message optimized for viewing in a log viewer');
cy.contains('Days of uptime');
});
cy.intercept('PUT', '/api/detection_engine/rules').as('saveRuleMultiQuery');
cy.contains('Save changes').click();
cy.wait('@saveRuleMultiQuery').should(({ request }) => {
const threeQueries = [
{
interval: 3600,
query: 'SELECT * FROM memory_info;',
platform: 'linux',
id: Object.keys(multiQueryPackData.queries)[0],
},
{
interval: 3600,
query: 'SELECT * FROM system_info;',
id: Object.keys(multiQueryPackData.queries)[1],
},
{
interval: 10,
query: 'select opera_extensions.* from users join opera_extensions using (uid);',
id: Object.keys(multiQueryPackData.queries)[2],
},
];
expect(request.body.response_actions[0].params.queries).to.deep.equal(threeQueries);
});
});
});
}
);

View file

@ -5,19 +5,19 @@
* 2.0.
*/
import { tag } from '../../tags';
import {
addLiveQueryToCase,
checkActionItemsInResults,
viewRecentCaseAndCheckResults,
} from '../../tasks/live_query';
import { navigateTo } from '../../tasks/navigation';
import { ROLE, login } from '../../tasks/login';
import { loadLiveQuery, loadCase, cleanupCase } from '../../tasks/api_fixtures';
import { ServerlessRoleName } from '../../support/roles';
describe('Add to Cases', () => {
let liveQueryId: string;
let liveQueryQuery: string;
before(() => {
loadLiveQuery({
agent_all: true,
@ -28,16 +28,15 @@ describe('Add to Cases', () => {
});
});
describe('observability', () => {
describe('observability', { tags: [tag.ESS] }, () => {
let caseId: string;
let caseTitle: string;
before(() => {
loadCase('observability').then((caseInfo) => {
caseId = caseInfo.id;
caseTitle = caseInfo.title;
});
login(ROLE.soc_manager);
cy.login(ServerlessRoleName.SOC_MANAGER);
navigateTo('/app/osquery');
});
@ -60,7 +59,7 @@ describe('Add to Cases', () => {
});
});
describe('security', () => {
describe('security', { tags: [tag.ESS, tag.SERVERLESS] }, () => {
let caseId: string;
let caseTitle: string;
@ -69,7 +68,7 @@ describe('Add to Cases', () => {
caseId = caseInfo.id;
caseTitle = caseInfo.title;
});
login(ROLE.soc_manager);
cy.login(ServerlessRoleName.SOC_MANAGER);
navigateTo('/app/osquery');
});

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import { ROLE, login } from '../../tasks/login';
import { tag } from '../../tags';
import { navigateTo } from '../../tasks/navigation';
import {
checkActionItemsInResults,
@ -15,10 +15,15 @@ import {
submitQuery,
} from '../../tasks/live_query';
import { loadSpace, loadPack, cleanupPack, cleanupSpace } from '../../tasks/api_fixtures';
import { ServerlessRoleName } from '../../support/roles';
const testSpaces = [
{ name: 'default', tags: [tag.ESS, tag.SERVERLESS] },
{ name: 'custom-spaces', tags: [tag.ESS] },
];
describe('ALL - Custom space', () => {
['default', 'custom-space'].forEach((spaceName) => {
describe(`[${spaceName}]`, () => {
testSpaces.forEach((testSpace) => {
describe(`[${testSpace.name}]`, { tags: testSpace.tags }, () => {
let packName: string;
let packId: string;
let spaceId: string;
@ -26,7 +31,7 @@ describe('ALL - Custom space', () => {
before(() => {
cy.wrap(
new Promise<string>((resolve) => {
if (spaceName !== 'default') {
if (testSpace.name !== 'default') {
loadSpace().then((space) => {
spaceId = space.id;
resolve(spaceId);
@ -56,18 +61,18 @@ describe('ALL - Custom space', () => {
});
beforeEach(() => {
login(ROLE.soc_manager);
cy.login(ServerlessRoleName.SOC_MANAGER);
navigateTo(`/s/${spaceId}/app/osquery`);
});
after(() => {
cleanupPack(packId, spaceId);
if (spaceName !== 'default') {
if (testSpace.name !== 'default') {
cleanupSpace(spaceId);
}
});
it('Discover should be opened in new tab in results table', () => {
it('Discover should be opened in new tab in results table', { tags: [tag.ESS] }, () => {
cy.contains('New live query').click();
selectAllAgents();
inputQuery('select * from uptime;');
@ -85,7 +90,6 @@ describe('ALL - Custom space', () => {
.then(($href) => {
// @ts-expect-error-next-line href string - check types
cy.visit($href);
cy.getBySel('breadcrumbs').contains('Discover').should('exist');
cy.getBySel('discoverDocTable', { timeout: 60000 }).within(() => {
cy.contains('action_data.queryselect * from uptime');
});

View file

@ -5,8 +5,8 @@
* 2.0.
*/
import { tag } from '../../tags';
import { getAdvancedButton } from '../../screens/integrations';
import { ROLE, login } from '../../tasks/login';
import { navigateTo } from '../../tasks/navigation';
import {
checkResults,
@ -17,10 +17,11 @@ import {
typeInECSFieldInput,
typeInOsqueryFieldInput,
} from '../../tasks/live_query';
import { ServerlessRoleName } from '../../support/roles';
describe('EcsMapping', () => {
describe('EcsMapping', { tags: [tag.ESS, tag.SERVERLESS] }, () => {
beforeEach(() => {
login(ROLE.soc_manager);
cy.login(ServerlessRoleName.SOC_MANAGER);
});
it('should properly show static values in form and results', () => {
@ -58,14 +59,17 @@ describe('EcsMapping', () => {
cy.getBySel('savedQuerySelect').within(() => {
cy.getBySel('comboBoxInput').type('processes_elastic{downArrow}{enter}');
});
cy.react('EuiAccordionClass', {
props: { buttonContent: 'Advanced', forceState: 'open' },
}).should('exist');
cy.getBySel('advanced-accordion-content').within(() => {
cy.contains('Advanced').click();
});
cy.react('EuiAccordionClass', {
props: { buttonContent: 'Advanced', forceState: 'closed' },
}).should('exist');
cy.contains('Use the fields below to map results from this query to ECS fields.').should(
'be.visible'
);
cy.contains('Advanced').click();
cy.contains('Use the fields below to map results from this query to ECS fields.').should(
'not.be.visible'
);
cy.contains('Advanced').click();
cy.contains('Use the fields below to map results from this query to ECS fields.').should(
'be.visible'
);
});
});

View file

@ -5,11 +5,12 @@
* 2.0.
*/
import { tag } from '../../tags';
import { navigateTo } from '../../tasks/navigation';
import { ROLE, login } from '../../tasks/login';
import { loadSavedQuery, cleanupSavedQuery } from '../../tasks/api_fixtures';
import { ServerlessRoleName } from '../../support/roles';
describe('ALL - Edit saved query', () => {
describe('ALL - Edit saved query', { tags: [tag.ESS, tag.SERVERLESS] }, () => {
let savedQueryName: string;
let savedQueryId: string;
@ -21,7 +22,7 @@ describe('ALL - Edit saved query', () => {
});
beforeEach(() => {
login(ROLE.soc_manager);
cy.login(ServerlessRoleName.SOC_MANAGER);
navigateTo('/app/osquery/saved_queries');
});

View file

@ -5,91 +5,26 @@
* 2.0.
*/
import { ROLE, login } from '../../tasks/login';
import { tag } from '../../tags';
import { navigateTo } from '../../tasks/navigation';
import {
addToCase,
checkActionItemsInResults,
checkResults,
inputQuery,
selectAllAgents,
submitQuery,
typeInECSFieldInput,
typeInOsqueryFieldInput,
viewRecentCaseAndCheckResults,
} from '../../tasks/live_query';
import {
LIVE_QUERY_EDITOR,
RESULTS_TABLE,
RESULTS_TABLE_BUTTON,
RESULTS_TABLE_CELL_WRRAPER,
} from '../../screens/live_query';
import { LIVE_QUERY_EDITOR } from '../../screens/live_query';
import { getAdvancedButton } from '../../screens/integrations';
import {
loadPack,
loadSavedQuery,
cleanupPack,
cleanupCase,
cleanupSavedQuery,
loadCase,
} from '../../tasks/api_fixtures';
describe('ALL - Live Query', () => {
let packId: string;
let packName: string;
let savedQueryId: string;
let savedQueryName: string;
let caseId: string;
before(() => {
loadPack({
queries: {
system_memory_linux_elastic: {
ecs_mapping: {},
interval: 3600,
platform: 'linux',
query: 'SELECT * FROM memory_info;',
},
system_info_elastic: {
ecs_mapping: {},
interval: 3600,
platform: 'linux,windows,darwin',
query: 'SELECT * FROM system_info;',
},
failingQuery: {
ecs_mapping: {},
interval: 10,
query: 'select opera_extensions.* from users join opera_extensions using (uid);',
},
},
}).then((pack) => {
packId = pack.saved_object_id;
packName = pack.name;
});
loadSavedQuery({
interval: '3600',
query: 'select * from uptime;',
ecs_mapping: {},
}).then((savedQuery) => {
savedQueryId = savedQuery.saved_object_id;
savedQueryName = savedQuery.name;
});
loadCase('securitySolution').then((caseInfo) => {
caseId = caseInfo.id;
});
});
import { ServerlessRoleName } from '../../support/roles';
describe('ALL - Live Query', { tags: [tag.SERVERLESS, tag.ESS] }, () => {
beforeEach(() => {
login(ROLE.soc_manager);
cy.login(ServerlessRoleName.SOC_MANAGER);
navigateTo('/app/osquery');
});
after(() => {
cleanupPack(packId);
cleanupSavedQuery(savedQueryId);
cleanupCase(caseId);
});
it('should validate the form', () => {
cy.contains('New live query').click();
submitQuery();
@ -117,109 +52,6 @@ describe('ALL - Live Query', () => {
cy.url().should('include', 'app/fleet/agents/');
});
it('should run query and enable ecs mapping', () => {
const cmd = Cypress.platform === 'darwin' ? '{meta}{enter}' : '{ctrl}{enter}';
cy.contains('New live query').click();
selectAllAgents();
inputQuery('select * from uptime;');
cy.wait(500);
// checking submit by clicking cmd+enter
inputQuery(cmd);
checkResults();
checkActionItemsInResults({
lens: true,
discover: true,
cases: true,
timeline: false,
});
cy.react(RESULTS_TABLE_CELL_WRRAPER, {
props: { id: 'osquery.days.number', index: 1 },
}).should('exist');
cy.react(RESULTS_TABLE_CELL_WRRAPER, {
props: { id: 'osquery.hours.number', index: 2 },
}).should('exist');
getAdvancedButton().click();
typeInECSFieldInput('message{downArrow}{enter}');
typeInOsqueryFieldInput('days{downArrow}{enter}');
submitQuery();
checkResults();
cy.getBySel(RESULTS_TABLE).within(() => {
cy.getBySel(RESULTS_TABLE_BUTTON).should('exist');
});
cy.react(RESULTS_TABLE_CELL_WRRAPER, {
props: { id: 'message', index: 1 },
}).should('exist');
cy.react(RESULTS_TABLE_CELL_WRRAPER, {
props: { id: 'osquery.days.number', index: 2 },
})
.react('EuiIconTip', { props: { type: 'indexMapping' } })
.should('exist');
});
it('should run customized saved query', () => {
cy.contains('New live query').click();
selectAllAgents();
cy.react('SavedQueriesDropdown').type(`${savedQueryName}{downArrow}{enter}`);
inputQuery('{selectall}{backspace}select * from users;');
cy.wait(1000);
submitQuery();
checkResults();
navigateTo('/app/osquery');
cy.react('EuiButtonIcon', { props: { iconType: 'play' } })
.eq(0)
.should('be.visible')
.click();
cy.get(LIVE_QUERY_EDITOR).contains('select * from users;');
});
it('should open query details by clicking the details icon', () => {
cy.react('EuiButtonIcon', { props: { iconType: 'visTable' } })
.first()
.click();
cy.contains('Live query details');
cy.contains('select * from users;');
});
it('should run live pack', () => {
cy.contains('New live query').click();
cy.contains('Run a set of queries in a pack.').click();
cy.get(LIVE_QUERY_EDITOR).should('not.exist');
cy.getBySel('select-live-pack').click().type(`${packName}{downArrow}{enter}`);
cy.contains('This table contains 3 rows.');
cy.contains('system_memory_linux_elastic');
cy.contains('system_info_elastic');
cy.contains('failingQuery');
selectAllAgents();
submitQuery();
cy.getBySel('live-query-loading').should('exist');
cy.getBySel('live-query-loading', { timeout: 10000 }).should('not.exist');
cy.getBySel('toggleIcon-system_memory_linux_elastic').click();
checkResults();
checkActionItemsInResults({
lens: true,
discover: true,
cases: true,
timeline: false,
});
cy.contains('Status').click();
cy.getBySel('tableHeaderCell_status_0').should('exist');
cy.getBySel('tableHeaderCell_fields.agent_id[0]_1').should('exist');
cy.getBySel('tableHeaderCell__source.action_response.osquery.count_2').should('exist');
cy.getBySel('tableHeaderCell_fields.error[0]_3').should('exist');
cy.getBySel('toggleIcon-system_memory_linux_elastic').click();
cy.getBySel('toggleIcon-failingQuery').click();
cy.contains('Status').click();
cy.contains('query failed, code: 1, message: no such table: opera_extensions');
cy.getBySel('toggleIcon-failingQuery').click();
cy.getBySel('toggleIcon-system_memory_linux_elastic').click();
addToCase(caseId);
viewRecentCaseAndCheckResults();
});
it('should run multiline query', () => {
const multilineQuery =
'select u.username, {shift+enter}' +
@ -255,6 +87,6 @@ describe('ALL - Live Query', () => {
inputQuery('{selectall}{backspace}{selectall}{backspace}');
// not sure if this is how it used to work when I implemented the functionality, but let's leave it like this for now
cy.get(LIVE_QUERY_EDITOR).invoke('height').should('be.gt', 200).and('be.lt', 350);
cy.get(LIVE_QUERY_EDITOR).invoke('height').should('be.gt', 200).and('be.lt', 380);
});
});

View file

@ -0,0 +1,104 @@
/*
* 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 { tag } from '../../tags';
import { navigateTo } from '../../tasks/navigation';
import {
addToCase,
checkActionItemsInResults,
checkResults,
selectAllAgents,
submitQuery,
viewRecentCaseAndCheckResults,
} from '../../tasks/live_query';
import { LIVE_QUERY_EDITOR } from '../../screens/live_query';
import { loadPack, cleanupPack, cleanupCase, loadCase } from '../../tasks/api_fixtures';
import { ServerlessRoleName } from '../../support/roles';
describe('ALL - Live Query Packs', { tags: [tag.SERVERLESS, tag.ESS] }, () => {
let packName: string;
let packId: string;
let caseId: string;
before(() => {
loadPack({
queries: {
system_memory_linux_elastic: {
ecs_mapping: {},
interval: 3600,
platform: 'linux',
query: 'SELECT * FROM memory_info;',
},
system_info_elastic: {
ecs_mapping: {},
interval: 3600,
platform: 'linux,windows,darwin',
query: 'SELECT * FROM system_info;',
},
failingQuery: {
ecs_mapping: {},
interval: 10,
query: 'select opera_extensions.* from users join opera_extensions using (uid);',
},
},
}).then((pack) => {
packId = pack.saved_object_id;
packName = pack.name;
});
loadCase('securitySolution').then((caseInfo) => {
caseId = caseInfo.id;
});
});
beforeEach(() => {
cy.login(ServerlessRoleName.SOC_MANAGER);
navigateTo('/app/osquery');
});
after(() => {
cleanupPack(packId);
cleanupCase(caseId);
});
it('should run live pack', () => {
cy.contains('New live query').click();
cy.contains('Run a set of queries in a pack.').click();
cy.get(LIVE_QUERY_EDITOR).should('not.exist');
cy.getBySel('select-live-pack').click().type(`${packName}{downArrow}{enter}`);
cy.contains('This table contains 3 rows.');
cy.contains('system_memory_linux_elastic');
cy.contains('system_info_elastic');
cy.contains('failingQuery');
selectAllAgents();
submitQuery();
cy.getBySel('live-query-loading').should('exist');
cy.getBySel('live-query-loading', { timeout: 10000 }).should('not.exist');
cy.getBySel('toggleIcon-system_memory_linux_elastic').click();
checkResults();
checkActionItemsInResults({
lens: true,
discover: true,
cases: true,
timeline: false,
});
cy.contains('Status').click();
cy.getBySel('tableHeaderCell_status_0').should('exist');
cy.getBySel('tableHeaderCell_fields.agent_id[0]_1').should('exist');
cy.getBySel('tableHeaderCell__source.action_response.osquery.count_2').should('exist');
cy.getBySel('tableHeaderCell_fields.error[0]_3').should('exist');
cy.getBySel('toggleIcon-system_memory_linux_elastic').click();
cy.getBySel('toggleIcon-failingQuery').click();
cy.contains('Status').click();
cy.contains('query failed, code: 1, message: no such table: opera_extensions');
cy.getBySel('toggleIcon-failingQuery').click();
cy.getBySel('toggleIcon-system_memory_linux_elastic').click();
addToCase(caseId);
viewRecentCaseAndCheckResults();
});
});

View file

@ -0,0 +1,118 @@
/*
* 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 { tag } from '../../tags';
import { navigateTo } from '../../tasks/navigation';
import {
checkActionItemsInResults,
checkResults,
inputQuery,
selectAllAgents,
submitQuery,
typeInECSFieldInput,
typeInOsqueryFieldInput,
} from '../../tasks/live_query';
import {
LIVE_QUERY_EDITOR,
RESULTS_TABLE,
RESULTS_TABLE_BUTTON,
RESULTS_TABLE_CELL_WRRAPER,
} from '../../screens/live_query';
import { getAdvancedButton } from '../../screens/integrations';
import { loadSavedQuery, cleanupSavedQuery } from '../../tasks/api_fixtures';
import { ServerlessRoleName } from '../../support/roles';
describe('ALL - Live Query run custom and saved', { tags: [tag.ESS] }, () => {
let savedQueryId: string;
let savedQueryName: string;
before(() => {
loadSavedQuery({
interval: '3600',
query: 'select * from uptime;',
ecs_mapping: {},
}).then((savedQuery) => {
savedQueryId = savedQuery.saved_object_id;
savedQueryName = savedQuery.name;
});
});
beforeEach(() => {
cy.login(ServerlessRoleName.SOC_MANAGER);
navigateTo('/app/osquery');
});
after(() => {
cleanupSavedQuery(savedQueryId);
});
it('should run query and enable ecs mapping', () => {
const cmd = Cypress.platform === 'darwin' ? '{meta}{enter}' : '{ctrl}{enter}';
cy.contains('New live query').click();
selectAllAgents();
inputQuery('select * from uptime;');
cy.wait(500);
// checking submit by clicking cmd+enter
inputQuery(cmd);
checkResults();
checkActionItemsInResults({
lens: true,
discover: true,
cases: true,
timeline: false,
});
cy.react(RESULTS_TABLE_CELL_WRRAPER, {
props: { id: 'osquery.days.number', index: 1 },
}).should('exist');
cy.react(RESULTS_TABLE_CELL_WRRAPER, {
props: { id: 'osquery.hours.number', index: 2 },
}).should('exist');
getAdvancedButton().click();
typeInECSFieldInput('message{downArrow}{enter}');
typeInOsqueryFieldInput('days{downArrow}{enter}');
submitQuery();
checkResults();
cy.getBySel(RESULTS_TABLE).within(() => {
cy.getBySel(RESULTS_TABLE_BUTTON).should('exist');
});
cy.react(RESULTS_TABLE_CELL_WRRAPER, {
props: { id: 'message', index: 1 },
}).should('exist');
cy.react(RESULTS_TABLE_CELL_WRRAPER, {
props: { id: 'osquery.days.number', index: 2 },
})
.react('EuiIconTip', { props: { type: 'indexMapping' } })
.should('exist');
});
it('should run customized saved query', () => {
cy.contains('New live query').click();
selectAllAgents();
cy.react('SavedQueriesDropdown').type(`${savedQueryName}{downArrow}{enter}`);
inputQuery('{selectall}{backspace}select * from users;');
cy.wait(1000);
submitQuery();
checkResults();
navigateTo('/app/osquery');
cy.react('EuiButtonIcon', { props: { iconType: 'play' } })
.eq(0)
.should('be.visible')
.click();
cy.get(LIVE_QUERY_EDITOR).contains('select * from users;');
});
it('should open query details by clicking the details icon', () => {
cy.react('EuiButtonIcon', { props: { iconType: 'visTable' } })
.first()
.click();
cy.contains('Live query details');
cy.contains('select * from users;');
});
});

View file

@ -5,13 +5,14 @@
* 2.0.
*/
import { tag } from '../../tags';
import { navigateTo } from '../../tasks/navigation';
import { ROLE, login } from '../../tasks/login';
import { checkResults, inputQuery, submitQuery } from '../../tasks/live_query';
import { loadSavedQuery, cleanupSavedQuery } from '../../tasks/api_fixtures';
import { triggerLoadData } from '../../tasks/inventory';
import { ServerlessRoleName } from '../../support/roles';
describe('ALL - Inventory', () => {
describe('ALL - Inventory', { tags: [tag.ESS] }, () => {
let savedQueryName: string;
let savedQueryId: string;
@ -23,7 +24,7 @@ describe('ALL - Inventory', () => {
});
beforeEach(() => {
login(ROLE.soc_manager);
cy.login(ServerlessRoleName.SOC_MANAGER);
navigateTo('/app/osquery');
});

View file

@ -7,6 +7,7 @@
import { recurse } from 'cypress-recurse';
import type { PackagePolicy } from '@kbn/fleet-plugin/common';
import { tag } from '../../tags';
import { API_VERSIONS } from '../../../common/constants';
import { navigateTo } from '../../tasks/navigation';
import {
@ -15,7 +16,6 @@ import {
findFormFieldByRowsLabelAndType,
inputQuery,
} from '../../tasks/live_query';
import { ROLE, login } from '../../tasks/login';
import { activatePack, deactivatePack, preparePack } from '../../tasks/packs';
import {
closeModalIfVisible,
@ -27,6 +27,7 @@ import { DEFAULT_POLICY } from '../../screens/fleet';
import { getIdFormField, getSavedQueriesDropdown } from '../../screens/live_query';
import { loadSavedQuery, cleanupSavedQuery, cleanupPack, loadPack } from '../../tasks/api_fixtures';
import { request } from '../../tasks/common';
import { ServerlessRoleName } from '../../support/roles';
describe('Packs - Create and Edit', () => {
let savedQueryId: string;
@ -86,7 +87,7 @@ describe('Packs - Create and Edit', () => {
});
beforeEach(() => {
login(ROLE.soc_manager);
cy.login(ServerlessRoleName.SOC_MANAGER);
navigateTo('/app/osquery');
});
@ -97,7 +98,7 @@ describe('Packs - Create and Edit', () => {
cleanupSavedQuery(multipleMappingsSavedQueryId);
});
describe('Check if result type is correct', () => {
describe('Check if result type is correct', { tags: [tag.ESS, tag.SERVERLESS] }, () => {
let resultTypePackId: string;
before(() => {
@ -221,7 +222,7 @@ describe('Packs - Create and Edit', () => {
});
});
describe('Check if pack is created', () => {
describe('Check if pack is created', { tags: [tag.ESS, tag.SERVERLESS] }, () => {
const packName = 'Pack-name' + generateRandomStringName(1)[0];
let packId: string;
@ -261,7 +262,7 @@ describe('Packs - Create and Edit', () => {
});
});
describe('to click the edit button and edit pack', () => {
describe('to click the edit button and edit pack', { tags: [tag.ESS, tag.SERVERLESS] }, () => {
const newQueryName = 'new-query-name' + generateRandomStringName(1)[0];
let packId: string;
@ -314,7 +315,57 @@ describe('Packs - Create and Edit', () => {
});
});
describe('should trigger validation when saved query is being chosen', () => {
describe(
'should trigger validation when saved query is being chosen',
{ tags: [tag.ESS, tag.SERVERLESS] },
() => {
let packId: string;
let packName: string;
before(() => {
request<{ items: PackagePolicy[] }>({
url: '/internal/osquery/fleet_wrapper/package_policies',
headers: {
'Elastic-Api-Version': API_VERSIONS.internal.v1,
},
})
.then((response) =>
loadPack({
policy_ids: [response.body.items[0].policy_id],
queries: {
[savedQueryName]: {
ecs_mapping: {},
interval: 3600,
query: 'select * from uptime;',
},
},
})
)
.then((pack) => {
packId = pack.saved_object_id;
packName = pack.name;
});
});
after(() => {
cleanupPack(packId);
});
it('', () => {
preparePack(packName);
findAndClickButton('Edit');
findAndClickButton('Add query');
cy.contains('Attach next query');
cy.contains('ID must be unique').should('not.exist');
getSavedQueriesDropdown().type(`${savedQueryName}{downArrow}{enter}`);
cy.react('EuiFlyoutFooter').react('EuiButton').contains('Save').click();
cy.contains('ID must be unique').should('exist');
cy.react('EuiFlyoutFooter').react('EuiButtonEmpty').contains('Cancel').click();
});
}
);
describe('should open lens in new tab', { tags: [tag.ESS] }, () => {
let packId: string;
let packName: string;
@ -329,49 +380,11 @@ describe('Packs - Create and Edit', () => {
loadPack({
policy_ids: [response.body.items[0].policy_id],
queries: {
[savedQueryName]: { ecs_mapping: {}, interval: 3600, query: 'select * from uptime;' },
},
})
)
.then((pack) => {
packId = pack.saved_object_id;
packName = pack.name;
});
});
after(() => {
cleanupPack(packId);
});
it('', () => {
preparePack(packName);
findAndClickButton('Edit');
findAndClickButton('Add query');
cy.contains('Attach next query');
cy.contains('ID must be unique').should('not.exist');
getSavedQueriesDropdown().type(`${savedQueryName}{downArrow}{enter}`);
cy.react('EuiFlyoutFooter').react('EuiButton').contains('Save').click();
cy.contains('ID must be unique').should('exist');
cy.react('EuiFlyoutFooter').react('EuiButtonEmpty').contains('Cancel').click();
});
});
describe('should open lens in new tab', () => {
let packId: string;
let packName: string;
before(() => {
request<{ items: PackagePolicy[] }>({
url: '/internal/osquery/fleet_wrapper/package_policies',
headers: {
'Elastic-Api-Version': API_VERSIONS.internal.v1,
},
})
.then((response) =>
loadPack({
policy_ids: [response.body.items[0].policy_id],
queries: {
[savedQueryName]: { ecs_mapping: {}, interval: 3600, query: 'select * from uptime;' },
[savedQueryName]: {
ecs_mapping: {},
interval: 3600,
query: 'select * from uptime;',
},
},
})
)
@ -461,7 +474,7 @@ describe('Packs - Create and Edit', () => {
});
});
describe('deactivate and activate pack', () => {
describe('deactivate and activate pack', { tags: [tag.ESS, tag.SERVERLESS] }, () => {
let packId: string;
let packName: string;
@ -497,7 +510,7 @@ describe('Packs - Create and Edit', () => {
});
});
describe('should verify that packs are triggered', () => {
describe('should verify that packs are triggered', { tags: [tag.ESS, tag.SERVERLESS] }, () => {
let packId: string;
let packName: string;
@ -564,7 +577,7 @@ describe('Packs - Create and Edit', () => {
});
});
describe('delete all queries in the pack', () => {
describe('delete all queries in the pack', { tags: [tag.ESS, tag.SERVERLESS] }, () => {
let packId: string;
let packName: string;
@ -610,74 +623,82 @@ describe('Packs - Create and Edit', () => {
});
});
describe('enable changing saved queries and ecs_mappings', () => {
let packId: string;
let packName: string;
describe(
'enable changing saved queries and ecs_mappings',
{ tags: [tag.ESS, tag.SERVERLESS] },
() => {
let packId: string;
let packName: string;
before(() => {
request<{ items: PackagePolicy[] }>({
url: '/internal/osquery/fleet_wrapper/package_policies',
headers: {
'Elastic-Api-Version': API_VERSIONS.internal.v1,
},
})
.then((response) =>
loadPack({
policy_ids: [response.body.items[0].policy_id],
queries: {
[savedQueryName]: { ecs_mapping: {}, interval: 3600, query: 'select * from uptime;' },
},
})
)
.then((pack) => {
packId = pack.saved_object_id;
packName = pack.name;
});
});
before(() => {
request<{ items: PackagePolicy[] }>({
url: '/internal/osquery/fleet_wrapper/package_policies',
headers: {
'Elastic-Api-Version': API_VERSIONS.internal.v1,
},
})
.then((response) =>
loadPack({
policy_ids: [response.body.items[0].policy_id],
queries: {
[savedQueryName]: {
ecs_mapping: {},
interval: 3600,
query: 'select * from uptime;',
},
},
})
)
.then((pack) => {
packId = pack.saved_object_id;
packName = pack.name;
});
});
after(() => {
cleanupPack(packId);
});
after(() => {
cleanupPack(packId);
});
it('', () => {
preparePack(packName);
cy.contains(/^Edit$/).click();
it('', () => {
preparePack(packName);
cy.contains(/^Edit$/).click();
findAndClickButton('Add query');
findAndClickButton('Add query');
getSavedQueriesDropdown().type(`${multipleMappingsSavedQueryName} {downArrow} {enter}`);
cy.contains('Custom key/value pairs').should('exist');
cy.contains('Days of uptime').should('exist');
cy.contains('List of keywords used to tag each').should('exist');
cy.contains('Seconds of uptime').should('exist');
cy.contains('Client network address.').should('exist');
cy.contains('Total uptime seconds').should('exist');
cy.getBySel('ECSMappingEditorForm').should('have.length', 4);
getSavedQueriesDropdown().type(`${multipleMappingsSavedQueryName} {downArrow} {enter}`);
cy.contains('Custom key/value pairs').should('exist');
cy.contains('Days of uptime').should('exist');
cy.contains('List of keywords used to tag each').should('exist');
cy.contains('Seconds of uptime').should('exist');
cy.contains('Client network address.').should('exist');
cy.contains('Total uptime seconds').should('exist');
cy.getBySel('ECSMappingEditorForm').should('have.length', 4);
getSavedQueriesDropdown().type(`${nomappingSavedQueryName} {downArrow} {enter}`);
cy.contains('Custom key/value pairs').should('not.exist');
cy.contains('Days of uptime').should('not.exist');
cy.contains('List of keywords used to tag each').should('not.exist');
cy.contains('Seconds of uptime').should('not.exist');
cy.contains('Client network address.').should('not.exist');
cy.contains('Total uptime seconds').should('not.exist');
cy.getBySel('ECSMappingEditorForm').should('have.length', 1);
getSavedQueriesDropdown().type(`${nomappingSavedQueryName} {downArrow} {enter}`);
cy.contains('Custom key/value pairs').should('not.exist');
cy.contains('Days of uptime').should('not.exist');
cy.contains('List of keywords used to tag each').should('not.exist');
cy.contains('Seconds of uptime').should('not.exist');
cy.contains('Client network address.').should('not.exist');
cy.contains('Total uptime seconds').should('not.exist');
cy.getBySel('ECSMappingEditorForm').should('have.length', 1);
getSavedQueriesDropdown().type(`${oneMappingSavedQueryName} {downArrow} {enter}`);
cy.contains('Name of the continent').should('exist');
cy.contains('Seconds of uptime').should('exist');
cy.getBySel('ECSMappingEditorForm').should('have.length', 2);
getSavedQueriesDropdown().type(`${oneMappingSavedQueryName} {downArrow} {enter}`);
cy.contains('Name of the continent').should('exist');
cy.contains('Seconds of uptime').should('exist');
cy.getBySel('ECSMappingEditorForm').should('have.length', 2);
findAndClickButton('Save');
cy.react('CustomItemAction', {
props: { index: 0, item: { id: oneMappingSavedQueryName } },
}).click();
cy.contains('Name of the continent').should('exist');
cy.contains('Seconds of uptime').should('exist');
});
});
findAndClickButton('Save');
cy.react('CustomItemAction', {
props: { index: 0, item: { id: oneMappingSavedQueryName } },
}).click();
cy.contains('Name of the continent').should('exist');
cy.contains('Seconds of uptime').should('exist');
});
}
);
describe('to click delete button', () => {
describe('to click delete button', { tags: [tag.ESS, tag.SERVERLESS] }, () => {
let packName: string;
before(() => {
@ -700,7 +721,7 @@ describe('Packs - Create and Edit', () => {
});
});
it('', () => {
it('', { tags: [tag.ESS, tag.SERVERLESS] }, () => {
preparePack(packName);
findAndClickButton('Edit');
deleteAndConfirm('pack');

View file

@ -7,6 +7,7 @@
import { find } from 'lodash';
import type { PackagePolicy } from '@kbn/fleet-plugin/common';
import { tag } from '../../tags';
import { API_VERSIONS } from '../../../common/constants';
import { FLEET_AGENT_POLICIES, navigateTo } from '../../tasks/navigation';
import {
@ -18,7 +19,6 @@ import {
selectAllAgents,
submitQuery,
} from '../../tasks/live_query';
import { ROLE, login } from '../../tasks/login';
import { activatePack, cleanupAllPrebuiltPacks, deactivatePack } from '../../tasks/packs';
import {
addIntegration,
@ -32,65 +32,70 @@ import { DEFAULT_POLICY, OSQUERY_POLICY } from '../../screens/fleet';
import { LIVE_QUERY_EDITOR } from '../../screens/live_query';
import { cleanupPack, cleanupAgentPolicy } from '../../tasks/api_fixtures';
import { request } from '../../tasks/common';
import { ServerlessRoleName } from '../../support/roles';
describe('ALL - Packs', () => {
describe('ALL - Packs', { tags: [tag.ESS, tag.SERVERLESS] }, () => {
const integration = 'Osquery Manager';
describe('Validate that agent policy is getting removed from pack if we remove agent policy', () => {
describe(
'Validate that agent policy is getting removed from pack if we remove agent policy',
{ tags: [tag.ESS] },
() => {
beforeEach(() => {
cy.login('elastic');
});
const AGENT_POLICY_NAME = `PackTest` + generateRandomStringName(1)[0];
const REMOVING_PACK = 'removing-pack' + generateRandomStringName(1)[0];
it('add integration', () => {
cy.visit(FLEET_AGENT_POLICIES);
cy.contains('Create agent policy').click();
cy.get('input[placeholder*="Choose a name"]').type(AGENT_POLICY_NAME);
cy.get('.euiFlyoutFooter').contains('Create agent policy').click();
cy.contains(`Agent policy '${AGENT_POLICY_NAME}' created`);
cy.visit(FLEET_AGENT_POLICIES);
cy.contains(AGENT_POLICY_NAME).click();
cy.contains('Add integration').click();
cy.contains(integration).click();
addIntegration(AGENT_POLICY_NAME);
cy.contains('Add Elastic Agent later').click();
navigateTo('app/osquery/packs');
findAndClickButton('Add pack');
findFormFieldByRowsLabelAndType('Name', REMOVING_PACK);
findFormFieldByRowsLabelAndType('Scheduled agent policies (optional)', AGENT_POLICY_NAME);
findAndClickButton('Save pack');
closeToastIfVisible();
cy.getBySel('tablePaginationPopoverButton').click();
cy.getBySel('tablePagination-50-rows').click();
cy.react('ScheduledQueryNameComponent', { props: { name: REMOVING_PACK } }).click();
cy.contains(`${REMOVING_PACK} details`).should('exist');
findAndClickButton('Edit');
cy.react('EuiComboBoxInput', { props: { value: AGENT_POLICY_NAME } }).should('exist');
cy.visit(FLEET_AGENT_POLICIES);
cy.contains(AGENT_POLICY_NAME).click();
cy.get('.euiTableCellContent')
.get('.euiPopover__anchor')
.get(`[aria-label="Open"]`)
.first()
.click();
cy.contains(/^Delete integration$/).click();
closeModalIfVisible();
cy.contains(/^Deleted integration 'osquery_manager-*/);
navigateTo('app/osquery/packs');
cy.contains(REMOVING_PACK).click();
cy.contains(`${REMOVING_PACK} details`).should('exist');
cy.wait(1000);
findAndClickButton('Edit');
cy.react('EuiComboBoxInput', { props: { value: '' } }).should('exist');
});
}
);
describe('Load prebuilt packs', { tags: [tag.ESS, tag.SERVERLESS] }, () => {
beforeEach(() => {
login();
});
const AGENT_POLICY_NAME = `PackTest` + generateRandomStringName(1)[0];
const REMOVING_PACK = 'removing-pack' + generateRandomStringName(1)[0];
it('add integration', () => {
cy.visit(FLEET_AGENT_POLICIES);
cy.contains('Create agent policy').click();
cy.get('input[placeholder*="Choose a name"]').type(AGENT_POLICY_NAME);
cy.get('.euiFlyoutFooter').contains('Create agent policy').click();
cy.contains(`Agent policy '${AGENT_POLICY_NAME}' created`);
cy.visit(FLEET_AGENT_POLICIES);
cy.contains(AGENT_POLICY_NAME).click();
cy.contains('Add integration').click();
cy.contains(integration).click();
addIntegration(AGENT_POLICY_NAME);
cy.contains('Add Elastic Agent later').click();
navigateTo('app/osquery/packs');
findAndClickButton('Add pack');
findFormFieldByRowsLabelAndType('Name', REMOVING_PACK);
findFormFieldByRowsLabelAndType('Scheduled agent policies (optional)', AGENT_POLICY_NAME);
findAndClickButton('Save pack');
closeToastIfVisible();
cy.getBySel('tablePaginationPopoverButton').click();
cy.getBySel('tablePagination-50-rows').click();
cy.react('ScheduledQueryNameComponent', { props: { name: REMOVING_PACK } }).click();
cy.contains(`${REMOVING_PACK} details`).should('exist');
findAndClickButton('Edit');
cy.react('EuiComboBoxInput', { props: { value: AGENT_POLICY_NAME } }).should('exist');
cy.visit(FLEET_AGENT_POLICIES);
cy.contains(AGENT_POLICY_NAME).click();
cy.get('.euiTableCellContent')
.get('.euiPopover__anchor')
.get(`[aria-label="Open"]`)
.first()
.click();
cy.contains(/^Delete integration$/).click();
closeModalIfVisible();
cy.contains(/^Deleted integration 'osquery_manager-*/);
navigateTo('app/osquery/packs');
cy.contains(REMOVING_PACK).click();
cy.contains(`${REMOVING_PACK} details`).should('exist');
cy.wait(1000);
findAndClickButton('Edit');
cy.react('EuiComboBoxInput', { props: { value: '' } }).should('exist');
});
});
describe('Load prebuilt packs', () => {
beforeEach(() => {
login(ROLE.soc_manager);
cy.login(ServerlessRoleName.SOC_MANAGER);
navigateTo('/app/osquery/packs');
});
@ -156,7 +161,6 @@ describe('ALL - Packs', () => {
selectAllAgents();
submitQuery();
cy.getBySel('live-query-loading').should('exist');
cy.getBySel('live-query-loading', { timeout: 10000 }).should('not.exist');
cy.getBySel('toggleIcon-events').click();
checkResults();
checkActionItemsInResults({
@ -170,9 +174,9 @@ describe('ALL - Packs', () => {
});
});
describe('Global packs', () => {
describe('Global packs', { tags: [tag.ESS] }, () => {
beforeEach(() => {
login();
cy.login('elastic');
navigateTo('/app/osquery/packs');
});

View file

@ -5,6 +5,7 @@
* 2.0.
*/
import { tag } from '../../tags';
import { preparePack } from '../../tasks/packs';
import {
addToCase,
@ -18,11 +19,11 @@ import {
viewRecentCaseAndCheckResults,
} from '../../tasks/live_query';
import { navigateTo } from '../../tasks/navigation';
import { ROLE, login } from '../../tasks/login';
import { getSavedQueriesComplexTest } from '../../tasks/saved_queries';
import { loadCase, cleanupCase, loadPack, cleanupPack } from '../../tasks/api_fixtures';
import { ServerlessRoleName } from '../../support/roles';
describe('ALL - Saved queries', () => {
describe('ALL - Saved queries', { tags: [tag.ESS, tag.SERVERLESS] }, () => {
let caseId: string;
before(() => {
@ -32,7 +33,7 @@ describe('ALL - Saved queries', () => {
});
beforeEach(() => {
login(ROLE.soc_manager);
cy.login(ServerlessRoleName.SOC_MANAGER);
navigateTo('/app/osquery');
});

View file

@ -5,12 +5,13 @@
* 2.0.
*/
import { tag } from '../../tags';
import { takeOsqueryActionWithParams } from '../../tasks/live_query';
import { ROLE, login } from '../../tasks/login';
import { ServerlessRoleName } from '../../support/roles';
describe('ALL - Timelines', () => {
describe('ALL - Timelines', { tags: [tag.ESS] }, () => {
beforeEach(() => {
login(ROLE.soc_manager);
cy.login(ServerlessRoleName.SOC_MANAGER);
});
it('should substitute osquery parameter on non-alert event take action', () => {

View file

@ -1,25 +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 { ROLE, login } from '../../tasks/login';
import { navigateTo } from '../../tasks/navigation';
import { checkResults, inputQuery, selectAllAgents, submitQuery } from '../../tasks/live_query';
describe('Admin', () => {
beforeEach(() => {
login(ROLE.admin);
navigateTo('/app/osquery');
});
it('should be able to run live query with BASE All permissions', () => {
cy.contains('New live query').click();
selectAllAgents();
inputQuery('select * from uptime;');
submitQuery();
checkResults();
});
});

View file

@ -5,103 +5,39 @@
* 2.0.
*/
import { ROLE, login } from '../../tasks/login';
import {
checkResults,
findAndClickButton,
findFormFieldByRowsLabelAndType,
submitQuery,
} from '../../tasks/live_query';
import { closeModalIfVisible, closeToastIfVisible } from '../../tasks/integrations';
import { navigateTo } from '../../tasks/navigation';
import { loadPack, loadRule, cleanupRule, cleanupPack } from '../../tasks/api_fixtures';
import { preparePack } from '../../tasks/packs';
import { DEFAULT_POLICY } from '../../screens/fleet';
import { tag } from '../../tags';
import { checkResults, clickRuleName, submitQuery } from '../../tasks/live_query';
import { loadRule, cleanupRule } from '../../tasks/api_fixtures';
import { ServerlessRoleName } from '../../support/roles';
describe('Alert Test', () => {
let packName: string;
let packId: string;
describe('Alert Test', { tags: [tag.ESS] }, () => {
let ruleName: string;
let ruleId: string;
before(() => {
loadPack({
description: '',
enabled: true,
queries: {
packQuery: {
interval: 10,
query: 'select * from uptime;',
ecs_mapping: {},
},
},
}).then((data) => {
packId = data.saved_object_id;
packName = data.name;
});
loadRule().then((data) => {
ruleId = data.id;
ruleName = data.name;
});
});
beforeEach(() => {
login(ROLE.alert_test);
});
after(() => {
cleanupPack(packId);
cleanupRule(ruleId);
});
describe('alert_test role', () => {
beforeEach(() => {
login(ROLE.alert_test);
});
it('should not be able to run live query', () => {
navigateTo('/app/osquery');
preparePack(packName);
findAndClickButton('Edit');
cy.contains(`Edit ${packName}`);
findFormFieldByRowsLabelAndType(
'Scheduled agent policies (optional)',
`${DEFAULT_POLICY} {downArrow}{enter}`
);
findAndClickButton('Update pack');
closeModalIfVisible();
cy.contains(`Successfully updated "${packName}" pack`);
closeToastIfVisible();
cy.visit('/app/security/rules');
cy.contains(ruleName).click();
cy.wait(2000);
cy.getBySel('ruleSwitch').should('have.attr', 'aria-checked', 'true');
cy.getBySel('ruleSwitch').click();
cy.getBySel('ruleSwitch').should('have.attr', 'aria-checked', 'false');
cy.getBySel('ruleSwitch').click();
cy.getBySel('ruleSwitch').should('have.attr', 'aria-checked', 'true');
cy.getBySel('expand-event').first().click();
cy.getBySel('take-action-dropdown-btn').click();
cy.getBySel('osquery-action-item').click();
cy.contains('Run Osquery');
cy.contains('Permission denied');
ruleId = data.id;
});
});
describe('t1_analyst role', () => {
beforeEach(() => {
login(ROLE.t1_analyst);
cy.login(ServerlessRoleName.T1_ANALYST);
cy.visit(`/app/security/rules/id/${ruleId}/alerts`);
cy.getBySel('expand-event').first().click();
cy.visit('/app/security/rules');
clickRuleName(ruleName);
cy.getBySel('expand-event').first().click({ force: true });
cy.wait(500);
cy.getBySel('securitySolutionDocumentDetailsFlyoutInvestigationGuideButton').click();
cy.contains('Get processes').click();
});
after(() => {
cleanupRule(ruleId);
});
it('should be able to run rule investigation guide query', () => {
submitQuery();
checkResults();

View file

@ -1,73 +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 { ROLE, login } from '../../tasks/login';
import { NAV_SEARCH_INPUT_OSQUERY_RESULTS } from '../../tasks/navigation';
import { loadRule, cleanupRule } from '../../tasks/api_fixtures';
describe('None', () => {
beforeEach(() => {
login(ROLE.none);
cy.visit('/app/home');
});
it('should not see osquery in global search', () => {
cy.getBySel('nav-search-input').type('Osquery');
cy.get(`[url="${NAV_SEARCH_INPUT_OSQUERY_RESULTS.MANAGEMENT}"]`).should('not.exist');
cy.get(`[url="${NAV_SEARCH_INPUT_OSQUERY_RESULTS.LOGS}"]`).should('not.exist');
cy.get(`[url="${NAV_SEARCH_INPUT_OSQUERY_RESULTS.MANAGER}"]`).should('not.exist');
});
it('should get 403 forbidden response when trying to GET osquery', () => {
cy.request({
url: '/app/osquery/live_queries',
failOnStatusCode: false,
}).then((resp) => {
expect(resp.status).to.eq(403);
});
cy.request({
url: '/app/osquery/saved_queries',
failOnStatusCode: false,
}).then((resp) => {
expect(resp.status).to.eq(403);
});
cy.request({
url: '/app/osquery/packs',
failOnStatusCode: false,
}).then((resp) => {
expect(resp.status).to.eq(403);
});
});
describe('Detection Engine', () => {
let ruleId: string;
before(() => {
login(ROLE.soc_manager);
loadRule(true).then((data) => {
ruleId = data.id;
});
cy.visit(`/app/security/alerts`);
cy.getBySel('expand-event').should('exist');
login(ROLE.none);
});
after(() => {
cleanupRule(ruleId);
});
it('should not see osquery in alerts', () => {
cy.visit(`/app/security/rules/id/${ruleId}/alerts`);
cy.getBySel('expand-event').first().click();
cy.getBySel('take-action-dropdown-btn').click();
cy.getBySel('securitySolutionDocumentDetailsFlyoutResponseSectionHeader').click();
cy.getBySel('securitySolutionDocumentDetailsFlyoutResponseButton').click();
cy.contains('Permission denied').should('exist');
});
});
});

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import { ROLE, login } from '../../tasks/login';
import { tag } from '../../tags';
import { navigateTo } from '../../tasks/navigation';
import {
cleanupPack,
@ -14,8 +14,9 @@ import {
loadPack,
loadSavedQuery,
} from '../../tasks/api_fixtures';
import { ServerlessRoleName } from '../../support/roles';
describe('Reader - only READ', () => {
describe('Reader - only READ', { tags: [tag.ESS] }, () => {
let savedQueryName: string;
let savedQueryId: string;
let packName: string;
@ -37,7 +38,7 @@ describe('Reader - only READ', () => {
});
beforeEach(() => {
login(ROLE.reader);
cy.login(ServerlessRoleName.READER);
});
after(() => {

View file

@ -1,135 +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 { SAVED_QUERY_ID } from '../../../public/saved_queries/constants';
import { ROLE, login } from '../../tasks/login';
import { navigateTo } from '../../tasks/navigation';
import {
checkActionItemsInResults,
checkResults,
selectAllAgents,
submitQuery,
} from '../../tasks/live_query';
import { getSavedQueriesDropdown, LIVE_QUERY_EDITOR } from '../../screens/live_query';
import {
cleanupPack,
cleanupSavedQuery,
loadLiveQuery,
loadPack,
loadSavedQuery,
} from '../../tasks/api_fixtures';
describe('T1 Analyst - READ + runSavedQueries ', () => {
let savedQueryName: string;
let savedQueryId: string;
let packName: string;
let packId: string;
let liveQueryQuery: string;
before(() => {
loadPack().then((data) => {
packId = data.saved_object_id;
packName = data.name;
});
loadSavedQuery().then((data) => {
savedQueryId = data.saved_object_id;
savedQueryName = data.id;
});
loadLiveQuery().then((data) => {
liveQueryQuery = data.queries?.[0].query;
});
});
beforeEach(() => {
login(ROLE.t1_analyst);
});
after(() => {
cleanupSavedQuery(savedQueryId);
cleanupPack(packId);
});
it('should be able to run saved queries but not add new ones', () => {
navigateTo('/app/osquery/saved_queries');
cy.waitForReact(1000);
cy.contains(savedQueryName);
cy.contains('Add saved query').should('be.disabled');
cy.react('PlayButtonComponent', {
props: { savedQuery: { id: savedQueryName } },
})
.should('not.be.disabled')
.click();
selectAllAgents();
cy.contains('select * from uptime;');
submitQuery();
checkResults();
checkActionItemsInResults({
lens: false,
discover: false,
cases: true,
timeline: false,
});
});
it('should be able to play in live queries history', () => {
navigateTo('/app/osquery/live_queries');
cy.waitForReact(1000);
cy.contains('New live query').should('not.be.disabled');
cy.contains(liveQueryQuery);
cy.wait(1000);
cy.react('EuiTableBody').first().react('CustomItemAction').first().click();
cy.contains(savedQueryName);
submitQuery();
checkResults();
});
it('should be able to use saved query in a new query', () => {
navigateTo('/app/osquery/live_queries');
cy.waitForReact(1000);
cy.contains('New live query').should('not.be.disabled').click();
selectAllAgents();
getSavedQueriesDropdown().type(`${savedQueryName}{downArrow} {enter}`);
cy.contains('select * from uptime');
submitQuery();
checkResults();
});
it('should not be able to add nor edit packs', () => {
navigateTo('/app/osquery/packs');
cy.waitForReact(1000);
cy.getBySel('tablePaginationPopoverButton').click();
cy.getBySel('tablePagination-50-rows').click();
cy.contains('Add pack').should('be.disabled');
cy.react('ActiveStateSwitchComponent', {
props: { item: { name: packName } },
})
.find('button')
.should('be.disabled');
cy.contains(packName).click();
cy.contains(`${packName} details`);
cy.contains('Edit').should('be.disabled');
// TODO: fix it
cy.react('CustomItemAction', {
props: { index: 0, item: { id: SAVED_QUERY_ID } },
options: { timeout: 3000 },
}).should('not.exist');
cy.react('CustomItemAction', {
props: { index: 1, item: { id: SAVED_QUERY_ID } },
options: { timeout: 3000 },
}).should('not.exist');
});
it('should not be able to create new liveQuery from scratch', () => {
navigateTo('/app/osquery');
cy.contains('New live query').click();
selectAllAgents();
cy.get(LIVE_QUERY_EDITOR).should('not.exist');
submitQuery();
cy.contains('Query is a required field');
});
});

View file

@ -0,0 +1,140 @@
/*
* 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 { tag } from '../../tags';
import { SAVED_QUERY_ID } from '../../../public/saved_queries/constants';
import { navigateTo } from '../../tasks/navigation';
import {
checkActionItemsInResults,
checkResults,
selectAllAgents,
submitQuery,
} from '../../tasks/live_query';
import { getSavedQueriesDropdown, LIVE_QUERY_EDITOR } from '../../screens/live_query';
import {
cleanupPack,
cleanupSavedQuery,
loadLiveQuery,
loadPack,
loadSavedQuery,
} from '../../tasks/api_fixtures';
import type { ServerlessRoleName } from '../../support/roles';
describe(`T1 and T2 analysts`, { tags: [tag.ESS, tag.SERVERLESS] }, () => {
['t1_analyst', 't2_analyst'].forEach((role: string) => {
describe(`${role}- READ + runSavedQueries `, { tags: [tag.ESS, tag.SERVERLESS] }, () => {
let savedQueryName: string;
let savedQueryId: string;
let packName: string;
let packId: string;
let liveQueryQuery: string;
before(() => {
loadPack().then((data) => {
packId = data.saved_object_id;
packName = data.name;
});
loadSavedQuery().then((data) => {
savedQueryId = data.saved_object_id;
savedQueryName = data.id;
});
loadLiveQuery().then((data) => {
liveQueryQuery = data.queries?.[0].query;
});
});
beforeEach(() => {
cy.login(role as ServerlessRoleName);
});
after(() => {
cleanupSavedQuery(savedQueryId);
cleanupPack(packId);
});
it('should be able to run saved queries but not add new ones', () => {
navigateTo('/app/osquery/saved_queries');
cy.waitForReact(1000);
cy.contains(savedQueryName);
cy.contains('Add saved query').should('be.disabled');
cy.react('PlayButtonComponent', {
props: { savedQuery: { id: savedQueryName } },
})
.should('not.be.disabled')
.click();
selectAllAgents();
cy.contains('select * from uptime;');
submitQuery();
checkResults();
checkActionItemsInResults({
lens: true,
discover: true,
cases: true,
timeline: false,
});
});
it('should be able to play in live queries history', () => {
navigateTo('/app/osquery/live_queries');
cy.waitForReact(1000);
cy.contains('New live query').should('not.be.disabled');
cy.contains(liveQueryQuery);
cy.wait(1000);
cy.react('EuiTableBody').first().react('CustomItemAction').first().click();
cy.contains(savedQueryName);
submitQuery();
checkResults();
});
it('should be able to use saved query in a new query', () => {
navigateTo('/app/osquery/live_queries');
cy.waitForReact(1000);
cy.contains('New live query').should('not.be.disabled').click();
selectAllAgents();
getSavedQueriesDropdown().type(`${savedQueryName}{downArrow} {enter}`);
cy.contains('select * from uptime');
submitQuery();
checkResults();
});
it('should not be able to add nor edit packs', () => {
navigateTo('/app/osquery/packs');
cy.waitForReact(1000);
cy.getBySel('tablePaginationPopoverButton').click();
cy.getBySel('tablePagination-50-rows').click();
cy.contains('Add pack').should('be.disabled');
cy.react('ActiveStateSwitchComponent', {
props: { item: { name: packName } },
})
.find('button')
.should('be.disabled');
cy.contains(packName).click();
cy.contains(`${packName} details`);
cy.contains('Edit').should('be.disabled');
// TODO: fix it
cy.react('CustomItemAction', {
props: { index: 0, item: { id: SAVED_QUERY_ID } },
options: { timeout: 3000 },
}).should('not.exist');
cy.react('CustomItemAction', {
props: { index: 1, item: { id: SAVED_QUERY_ID } },
options: { timeout: 3000 },
}).should('not.exist');
});
it('should not be able to create new liveQuery from scratch', () => {
navigateTo('/app/osquery');
cy.contains('New live query').click();
selectAllAgents();
cy.get(LIVE_QUERY_EDITOR).should('not.exist');
submitQuery();
cy.contains('Query is a required field');
});
});
});
});

View file

@ -1,142 +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 { ROLE, login } from '../../tasks/login';
import { navigateTo } from '../../tasks/navigation';
import {
checkResults,
selectAllAgents,
submitQuery,
inputQuery,
typeInECSFieldInput,
typeInOsqueryFieldInput,
checkActionItemsInResults,
} from '../../tasks/live_query';
import { getSavedQueriesComplexTest } from '../../tasks/saved_queries';
import { loadPack, loadSavedQuery, cleanupSavedQuery, cleanupPack } from '../../tasks/api_fixtures';
describe('T2 Analyst - READ + Write Live/Saved + runSavedQueries ', () => {
const SAVED_QUERY_ID = 'Saved-Query-Id';
let savedQueryName: string;
let savedQueryId: string;
let packName: string;
let packId: string;
before(() => {
loadPack().then((data) => {
packId = data.saved_object_id;
packName = data.name;
});
loadSavedQuery().then((data) => {
savedQueryId = data.saved_object_id;
savedQueryName = data.id;
});
});
beforeEach(() => {
login(ROLE.t2_analyst);
navigateTo('/app/osquery');
});
after(() => {
cleanupSavedQuery(savedQueryId);
cleanupPack(packId);
});
getSavedQueriesComplexTest();
it('should not be able to add nor edit packs', () => {
navigateTo('/app/osquery/packs');
cy.waitForReact(1000);
cy.getBySel('tablePaginationPopoverButton').click();
cy.getBySel('tablePagination-50-rows').click();
cy.contains('Add pack').should('be.disabled');
cy.react('ActiveStateSwitchComponent', {
props: { item: { name: packName } },
})
.find('button')
.should('be.disabled');
cy.contains(packName).click();
cy.contains(`${packName} details`);
cy.contains('Edit').should('be.disabled');
// TODO: fix
cy.react('CustomItemAction', {
props: { index: 0, item: { id: SAVED_QUERY_ID } },
options: { timeout: 3000 },
}).should('not.exist');
cy.react('CustomItemAction', {
props: { index: 1, item: { id: SAVED_QUERY_ID } },
options: { timeout: 3000 },
}).should('not.exist');
});
it('should run query and enable ecs mapping', () => {
const cmd = Cypress.platform === 'darwin' ? '{meta}{enter}' : '{ctrl}{enter}';
cy.contains('New live query').click();
selectAllAgents();
inputQuery('select * from uptime;');
cy.wait(500);
// checking submit by clicking cmd+enter
inputQuery(cmd);
checkResults();
checkActionItemsInResults({
lens: false,
discover: false,
cases: true,
timeline: false,
});
cy.react('EuiDataGridHeaderCellWrapper', {
props: { id: 'osquery.days.number', index: 1 },
}).should('exist');
cy.react('EuiDataGridHeaderCellWrapper', {
props: { id: 'osquery.hours.number', index: 2 },
}).should('exist');
cy.react('EuiAccordionClass', { props: { buttonContent: 'Advanced' } })
.last()
.click();
typeInECSFieldInput('message{downArrow}{enter}');
typeInOsqueryFieldInput('days{downArrow}{enter}');
submitQuery();
checkResults();
cy.react('EuiDataGridHeaderCellWrapper', {
props: { id: 'message', index: 1 },
}).should('exist');
cy.react('EuiDataGridHeaderCellWrapper', {
props: { id: 'osquery.days.number', index: 2 },
}).within(() => {
cy.get('.euiToolTipAnchor').within(() => {
cy.get('svg').should('exist');
});
});
});
it('to click the edit button and edit pack', () => {
navigateTo('/app/osquery/saved_queries');
cy.react('CustomItemAction', {
props: { index: 1, item: { id: savedQueryName } },
}).click();
cy.contains('Custom key/value pairs.').should('exist');
cy.contains('Hours of uptime').should('exist');
cy.get('[data-test-subj="ECSMappingEditorForm"]')
.first()
.within(() => {
cy.react('EuiButtonIcon', { props: { iconType: 'trash' } }).click();
});
cy.react('EuiButton').contains('Update query').click();
cy.wait(5000);
cy.react('CustomItemAction', {
props: { index: 1, item: { id: savedQueryName } },
}).click();
cy.contains('Custom key/value pairs').should('not.exist');
cy.contains('Hours of uptime').should('not.exist');
});
});

View file

@ -0,0 +1,25 @@
/*
* 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 { tag } from '../../tags';
import { checkOsqueryResponseActionsPermissions } from '../../tasks/response_actions';
describe(
'App Features for Enpoint Complete PLI',
{
tags: [tag.SERVERLESS],
env: {
ftrConfig: {
productTypes: [
{ product_line: 'endpoint', product_tier: 'complete' },
{ product_line: 'security', product_tier: 'complete' },
],
},
},
},
() => checkOsqueryResponseActionsPermissions(true)
);

View file

@ -0,0 +1,25 @@
/*
* 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 { tag } from '../../tags';
import { checkOsqueryResponseActionsPermissions } from '../../tasks/response_actions';
describe(
'App Features for Endpoint Essentials PLI',
{
tags: [tag.SERVERLESS],
env: {
ftrConfig: {
productTypes: [
{ product_line: 'security', product_tier: 'essentials' },
{ product_line: 'endpoint', product_tier: 'essentials' },
],
},
},
},
() => checkOsqueryResponseActionsPermissions(false)
);

View file

@ -0,0 +1,22 @@
/*
* 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 { tag } from '../../tags';
import { checkOsqueryResponseActionsPermissions } from '../../tasks/response_actions';
describe(
'App Features for Security Complete PLI',
{
tags: [tag.SERVERLESS],
env: {
ftrConfig: {
productTypes: [{ product_line: 'security', product_tier: 'complete' }],
},
},
},
() => checkOsqueryResponseActionsPermissions(false)
);

View file

@ -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 { tag } from '../../tags';
import { checkOsqueryResponseActionsPermissions } from '../../tasks/response_actions';
describe(
'App Features for Security Essentials PLI',
{
tags: [tag.SERVERLESS],
env: {
ftrConfig: { productTypes: [{ product_line: 'security', product_tier: 'essentials' }] },
},
},
() => checkOsqueryResponseActionsPermissions(false)
);

View file

@ -29,6 +29,8 @@
module.exports = (on: any, config: any) => {
// eslint-disable-next-line @typescript-eslint/no-var-requires
require('@cypress/code-coverage/task')(on, config);
// eslint-disable-next-line @typescript-eslint/no-var-requires
require('@cypress/grep/src/plugin')(config);
// `on` is used to hook into various events Cypress emits
// `config` is the resolved Cypress config

View file

@ -23,22 +23,37 @@
// ***********************************************************
// force ESM in this module
import type { SecuritySolutionDescribeBlockFtrConfig } from '@kbn/security-solution-plugin/scripts/run_cypress/utils';
export {};
import 'cypress-react-selector';
import registerCypressGrep from '@cypress/grep';
import type { ServerlessRoleName } from './roles';
import { login } from '../tasks/login';
registerCypressGrep();
declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace Cypress {
interface SuiteConfigOverrides {
env?: {
ftrConfig: SecuritySolutionDescribeBlockFtrConfig;
};
}
interface Chainable {
getBySel(...args: Parameters<Cypress.Chainable['get']>): Chainable<JQuery<HTMLElement>>;
getBySelContains(
...args: Parameters<Cypress.Chainable['get']>
): Chainable<JQuery<HTMLElement>>;
clickOutside(): Chainable<JQuery<HTMLBodyElement>>;
login(role?: ServerlessRoleName | 'elastic'): void;
}
}
}
@ -57,6 +72,8 @@ Cypress.Commands.add(
() => cy.get('body').click(0, 0) // 0,0 here are the x and y coordinates
);
Cypress.Commands.add('login', login);
// Alternatively you can use CommonJS syntax:
// require('./commands')
Cypress.on('uncaught:exception', () => false);

View file

@ -0,0 +1,40 @@
soc_manager:
applications:
- application: discover
privileges:
- all
resources: "*"
- application: visualize
privileges:
- read
resources: "*"
- application: observabilityCases
privileges:
- all
resources: "*"
- application: securitySolutionCases
privileges:
- all
resources: "*"
- application: infrastructure
privileges:
- read
resources: "*"
- application: indexPatterns
privileges:
- all
resources: "*"
# custom roles for osquery lack of permission testing
reader:
indices:
- names:
- logs-*
privileges:
- read
- write
applications:
- application: osquery
privileges:
- read
resources: "*"

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 { ServerlessRoleName } from '../../../../test_serverless/shared/lib/security/types';

View file

@ -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.
*/
export const tag = {
SERVERLESS: '@serverless',
ESS: '@ess',
BROKEN_IN_SERVERLESS: '@brokenInServerless',
};

View file

@ -103,6 +103,7 @@ export const loadPack = (payload: Partial<PackItem> = {}, space = 'default') =>
headers: {
'Elastic-Api-Version': API_VERSIONS.public.v1,
},
url: `/s/${space}/api/osquery/packs`,
}).then((response) => response.body.data);
@ -293,4 +294,8 @@ export const loadAgentPolicy = () =>
}).then((response) => response.body.item);
export const cleanupAgentPolicy = (agentPolicyId: string) =>
request({ method: 'POST', body: { agentPolicyId }, url: '/api/fleet/agent_policies/delete' });
request({
method: 'POST',
body: { agentPolicyId },
url: '/api/fleet/agent_policies/delete',
});

View file

@ -6,11 +6,14 @@
*/
export const API_AUTH = {
user: Cypress.env('ELASTICSEARCH_USERNAME'),
pass: Cypress.env('ELASTICSEARCH_PASSWORD'),
user: Cypress.env('KIBANA_USERNAME') ?? Cypress.env('ELASTICSEARCH_USERNAME'),
pass: Cypress.env('KIBANA_PASSWORD') ?? Cypress.env('ELASTICSEARCH_PASSWORD'),
};
export const API_HEADERS = { 'kbn-xsrf': 'cypress' };
export const API_HEADERS = {
'kbn-xsrf': 'cypress',
'x-elastic-internal-origin': 'security-solution',
};
export const request = <T = unknown>(
options: Partial<Cypress.RequestOptions>

View file

@ -6,7 +6,7 @@
*/
import { LIVE_QUERY_EDITOR } from '../screens/live_query';
import { ROLE, login } from './login';
import { ServerlessRoleName } from '../support/roles';
export const DEFAULT_QUERY = 'select * from processes;';
export const BIG_QUERY = 'select * from processes, users limit 110;';
@ -101,9 +101,9 @@ export const toggleRuleOffAndOn = (ruleName: string) => {
};
export const loadRuleAlerts = (ruleName: string) => {
login(ROLE.soc_manager);
cy.login(ServerlessRoleName.SOC_MANAGER);
cy.visit('/app/security/rules');
cy.contains(ruleName).click();
clickRuleName(ruleName);
cy.getBySel('alertsTable').within(() => {
cy.getBySel('expand-event')
.first()
@ -170,3 +170,7 @@ export const takeOsqueryActionWithParams = () => {
submitQuery();
cy.getBySel('dataGridHeader').should('contain', 'tags', { timeout: 6000000 });
};
export const clickRuleName = (ruleName: string) => {
cy.contains('a[data-test-subj="ruleName"]', ruleName).click({ force: true });
};

View file

@ -5,341 +5,82 @@
* 2.0.
*/
import * as yaml from 'js-yaml';
import type { UrlObject } from 'url';
import Url from 'url';
import type { Role } from '@kbn/security-plugin/common';
// import { request } from '@kbn/security-solution-plugin/public/management/cypress/tasks/common';
import { isLocalhost } from '@kbn/security-solution-plugin/scripts/endpoint/common/is_localhost';
import { request } from './common';
import adminRole from '../../scripts/roles_users/admin/role.json';
import alertTestRole from '../../scripts/roles_users/alert_test/role.json';
import noneRole from '../../scripts/roles_users/none/role.json';
import platformEngineerRole from '../../scripts/roles_users/platform_engineer/role.json';
import readerRole from '../../scripts/roles_users/reader/role.json';
import socManagerRole from '../../scripts/roles_users/soc_manager/role.json';
import t1AnalystRole from '../../scripts/roles_users/t1_analyst/role.json';
import t2AnalystRole from '../../scripts/roles_users/t2_analyst/role.json';
export enum ROLE {
soc_manager = 'soc_manager',
reader = 'reader',
t1_analyst = 't1_analyst',
t2_analyst = 't2_analyst',
platform_engineer = 'platform_engineer',
admin = 'admin', // base: ['all']
alert_test = 'alert_test',
none = 'none',
}
export const rolesMapping: { [key in ROLE]: Omit<Role, 'name'> } = {
admin: adminRole,
alert_test: alertTestRole,
none: noneRole,
platform_engineer: platformEngineerRole,
reader: readerRole,
soc_manager: socManagerRole,
t1_analyst: t1AnalystRole,
t2_analyst: t2AnalystRole,
};
import { STANDARD_HTTP_HEADERS } from '../../../../test_serverless/shared/lib/security/default_http_headers';
import { ServerlessRoleName } from '../support/roles';
/**
* Credentials in the `kibana.dev.yml` config file will be used to authenticate
* with Kibana when credentials are not provided via environment variables
*/
const KIBANA_DEV_YML_PATH = '../../../config/kibana.dev.yml';
/**
* The configuration path in `kibana.dev.yml` to the username to be used when
* authenticating with Kibana.
*/
const ELASTICSEARCH_USERNAME_CONFIG_PATH = 'config.elasticsearch.username';
/**
* The configuration path in `kibana.dev.yml` to the password to be used when
* authenticating with Kibana.
*/
const ELASTICSEARCH_PASSWORD_CONFIG_PATH = 'config.elasticsearch.password';
/**
* The `CYPRESS_ELASTICSEARCH_USERNAME` environment variable specifies the
* username to be used when authenticating with Kibana
*/
const ELASTICSEARCH_USERNAME = 'ELASTICSEARCH_USERNAME';
/**
* The `CYPRESS_ELASTICSEARCH_PASSWORD` environment variable specifies the
* username to be used when authenticating with Kibana
*/
const ELASTICSEARCH_PASSWORD = 'ELASTICSEARCH_PASSWORD';
/**
* The Kibana server endpoint used for authentication
*/
const LOGIN_API_ENDPOINT = '/internal/security/login';
/**
* cy.visit will default to the baseUrl which uses the default kibana test user
* This function will override that functionality in cy.visit by building the baseUrl
* directly from the environment variables set up in x-pack/test/security_solution_cypress/runner.ts
* Send login via API
* @param username
* @param password
*
* @param role string role/user to log in with
* @param route string route to visit
* @private
*/
export const getUrlWithRoute = (role: ROLE, route: string) => {
const url = Cypress.config().baseUrl;
const kibana = new URL(String(url));
const theUrl = `${Url.format({
auth: `${role}:changeme`,
username: role,
password: 'changeme',
protocol: kibana.protocol.replace(':', ''),
hostname: kibana.hostname,
port: kibana.port,
} as UrlObject)}${route.startsWith('/') ? '' : '/'}${route}`;
cy.log(`origin: ${theUrl}`);
const sendApiLoginRequest = (
username: string,
password: string
): Cypress.Chainable<{ username: string; password: string }> => {
const url = new URL(Cypress.config().baseUrl ?? '');
url.pathname = '/internal/security/login';
return theUrl;
};
cy.log(`Authenticating [${username}] via ${url.toString()}`);
interface User {
username: string;
password: string;
}
/**
* Builds a URL with basic auth using the passed in user.
*
* @param user the user information to build the basic auth with
* @param route string route to visit
*/
export const constructUrlWithUser = (user: User, route: string) => {
const url = Cypress.config().baseUrl;
const kibana = new URL(String(url));
const hostname = kibana.hostname;
const username = user.username;
const password = user.password;
const protocol = kibana.protocol.replace(':', '');
const port = kibana.port;
const path = `${route.startsWith('/') ? '' : '/'}${route}`;
const strUrl = `${protocol}://${username}:${password}@${hostname}:${port}${path}`;
const builtUrl = new URL(strUrl);
cy.log(`origin: ${builtUrl.href}`);
return builtUrl.href;
};
export const getCurlScriptEnvVars = () => ({
ELASTICSEARCH_URL: Cypress.env('ELASTICSEARCH_URL'),
ELASTICSEARCH_USERNAME: Cypress.env('ELASTICSEARCH_USERNAME'),
ELASTICSEARCH_PASSWORD: Cypress.env('ELASTICSEARCH_PASSWORD'),
KIBANA_URL: Cypress.config().baseUrl,
});
export const postRoleAndUser = (role: ROLE) => {
const rolePrivileges = rolesMapping[role];
// post the role
request({
method: 'PUT',
url: `/api/security/role/${role}`,
body: rolePrivileges,
});
// post the user associated with the role to elasticsearch
request({
return request({
headers: { ...STANDARD_HTTP_HEADERS },
method: 'POST',
url: `/internal/security/users/${role}`,
body: {
username: role,
password: Cypress.env(ELASTICSEARCH_PASSWORD),
roles: [role],
},
});
};
export const deleteRoleAndUser = (role: ROLE) => {
request({
method: 'DELETE',
url: `/internal/security/users/${role}`,
});
request({
method: 'DELETE',
url: `/api/security/role/${role}`,
});
};
export const loginWithUser = (user: User) => {
const url = Cypress.config().baseUrl;
request({
url: url.toString(),
body: {
providerType: 'basic',
providerName: url && !url.includes('localhost') ? 'cloud-basic' : 'basic',
providerName: isLocalhost(url.hostname) ? 'basic' : 'cloud-basic',
currentURL: '/',
params: {
username: user.username,
password: user.password,
username,
password,
},
},
headers: { 'kbn-xsrf': 'cypress-creds-via-config' },
method: 'POST',
url: constructUrlWithUser(user, LOGIN_API_ENDPOINT),
});
}).then(() => ({
username,
password,
}));
};
export const loginWithRole = async (role: ROLE) => {
postRoleAndUser(role);
const theUrl = Url.format({
auth: `${role}:changeme`,
username: role,
password: 'changeme',
protocol: Cypress.env('protocol'),
hostname: Cypress.env('hostname'),
port: Cypress.env('configport'),
} as UrlObject);
cy.log(`origin: ${theUrl}`);
cy.session([role], () => {
cy.request({
body: {
providerType: 'basic',
providerName: 'basic',
currentURL: '/',
params: {
username: role,
password: 'changeme',
},
},
headers: { 'kbn-xsrf': 'cypress-creds-via-config' },
method: 'POST',
url: getUrlWithRoute(role, LOGIN_API_ENDPOINT),
});
});
};
interface CyLoginTask {
(user?: ServerlessRoleName): ReturnType<typeof sendApiLoginRequest>;
/**
* Login using any username/password
* @param username
* @param password
*/
with(username: string, password: string): ReturnType<typeof sendApiLoginRequest>;
}
/**
* Authenticates with Kibana using, if specified, credentials specified by
* environment variables. The credentials in `kibana.dev.yml` will be used
* for authentication when the environment variables are unset.
*
* To speed the execution of tests, prefer this non-interactive authentication,
* which is faster than authentication via Kibana's interactive login page.
* Login to Kibana using API (not login page). By default, user will be logged in using
* the username and password defined via `KIBANA_USERNAME` and `KIBANA_PASSWORD` cypress env
* variables.
* @param user Defaults to `soc_manager`
*/
export const login = (role?: ROLE) => {
if (role != null) {
loginWithRole(role);
} else if (credentialsProvidedByEnvironment()) {
loginViaEnvironmentCredentials();
export const login: CyLoginTask = (
user: ServerlessRoleName | 'elastic' = ServerlessRoleName.SOC_MANAGER
): ReturnType<typeof sendApiLoginRequest> => {
let username = Cypress.env('KIBANA_USERNAME');
let password = Cypress.env('KIBANA_PASSWORD');
if (user && user !== 'elastic') {
// @ts-expect-error update type
return cy.task('loadUserAndRole', { name: user }).then((loadedUser: LoadedRoleAndUser) => {
username = loadedUser.username;
password = loadedUser.password;
return sendApiLoginRequest(username, password);
});
} else {
loginViaConfig();
return sendApiLoginRequest(username, password);
}
};
/**
* Returns `true` if the credentials used to login to Kibana are provided
* via environment variables
*/
const credentialsProvidedByEnvironment = (): boolean =>
Cypress.env(ELASTICSEARCH_USERNAME) != null && Cypress.env(ELASTICSEARCH_PASSWORD) != null;
/**
* Authenticates with Kibana by reading credentials from the
* `CYPRESS_ELASTICSEARCH_USERNAME` and `CYPRESS_ELASTICSEARCH_PASSWORD`
* environment variables, and POSTing the username and password directly to
* Kibana's `/internal/security/login` endpoint, bypassing the login page (for speed).
*/
const loginViaEnvironmentCredentials = () => {
const url = Cypress.config().baseUrl;
cy.log(
`Authenticating via environment credentials from the \`CYPRESS_${ELASTICSEARCH_USERNAME}\` and \`CYPRESS_${ELASTICSEARCH_PASSWORD}\` environment variables`
);
const username = Cypress.env(ELASTICSEARCH_USERNAME);
const password = Cypress.env(ELASTICSEARCH_PASSWORD);
// programmatically authenticate without interacting with the Kibana login page
cy.session([username, password], () => {
cy.request({
body: {
providerType: 'basic',
providerName: url && !url.includes('localhost') ? 'cloud-basic' : 'basic',
currentURL: '/',
params: {
username,
password,
},
},
headers: { 'kbn-xsrf': 'cypress-creds-via-env' },
method: 'POST',
url: `${Cypress.config().baseUrl}${LOGIN_API_ENDPOINT}`,
});
});
};
/**
* Authenticates with Kibana by reading credentials from the
* `kibana.dev.yml` file and POSTing the username and password directly to
* Kibana's `/internal/security/login` endpoint, bypassing the login page (for speed).
*/
const loginViaConfig = () => {
cy.log(
`Authenticating via config credentials \`${ELASTICSEARCH_USERNAME_CONFIG_PATH}\` and \`${ELASTICSEARCH_PASSWORD_CONFIG_PATH}\` from \`${KIBANA_DEV_YML_PATH}\``
);
// read the login details from `kibana.dev.yaml`
cy.readFile(KIBANA_DEV_YML_PATH).then((kibanaDevYml) => {
const config = yaml.safeLoad(kibanaDevYml);
const username = 'elastic';
const password = config.elasticsearch.password;
// programmatically authenticate without interacting with the Kibana login page
cy.session([username, password], () => {
cy.request({
body: {
providerType: 'basic',
providerName: 'basic',
currentURL: '/',
params: {
username,
password,
},
},
headers: { 'kbn-xsrf': 'cypress-creds-via-config' },
method: 'POST',
url: `${Cypress.config().baseUrl}${LOGIN_API_ENDPOINT}`,
});
});
});
};
/**
* Get the configured auth details that were used to spawn cypress
*
* @returns the default Elasticsearch username and password for this environment
*/
export const getEnvAuth = (): User => {
if (credentialsProvidedByEnvironment()) {
return {
username: Cypress.env(ELASTICSEARCH_USERNAME),
password: Cypress.env(ELASTICSEARCH_PASSWORD),
};
} else {
let user: User = { username: '', password: '' };
cy.readFile(KIBANA_DEV_YML_PATH).then((devYml) => {
const config = yaml.safeLoad(devYml);
user = { username: config.elasticsearch.username, password: config.elasticsearch.password };
});
return user;
}
};
/**
* Authenticates with Kibana, visits the specified `url`, and waits for the
* Kibana global nav to be displayed before continuing
*/
export const loginAndWaitForPage = (url: string) => {
login();
cy.visit(url);
};
login.with = (username: string, password: string): ReturnType<typeof sendApiLoginRequest> =>
sendApiLoginRequest(username, password);

View file

@ -5,9 +5,54 @@
* 2.0.
*/
import { clickRuleName } from './live_query';
import { ServerlessRoleName } from '../support/roles';
import { cleanupRule, loadRule } from './api_fixtures';
import { closeDateTabIfVisible } from './integrations';
export const RESPONSE_ACTIONS_ITEM_0 = 'response-actions-list-item-0';
export const RESPONSE_ACTIONS_ITEM_1 = 'response-actions-list-item-1';
export const RESPONSE_ACTIONS_ITEM_2 = 'response-actions-list-item-2';
export const RESPONSE_ACTIONS_ITEM_3 = 'response-actions-list-item-3';
export const OSQUERY_RESPONSE_ACTION_ADD_BUTTON = 'Osquery-response-action-type-selection-option';
export const ENDPOINT_RESPONSE_ACTION_ADD_BUTTON =
'Endpoint Security-response-action-type-selection-option';
export const checkOsqueryResponseActionsPermissions = (enabled: boolean) => {
let ruleId: string;
let ruleName: string;
before(() => {
loadRule().then((data) => {
ruleId = data.id;
ruleName = data.name;
});
});
after(() => {
cleanupRule(ruleId);
});
beforeEach(() => {
cy.login(ServerlessRoleName.SOC_MANAGER);
});
it(`response actions should ${enabled ? 'be available ' : 'not be available'}`, () => {
cy.visit('/app/security/rules');
clickRuleName(ruleName);
cy.getBySel('editRuleSettingsLink').click();
cy.getBySel('globalLoadingIndicator').should('not.exist');
closeDateTabIfVisible();
cy.getBySel('edit-rule-actions-tab').click();
cy.contains('Response actions are run on each rule execution.');
cy.getBySel(OSQUERY_RESPONSE_ACTION_ADD_BUTTON).click();
if (enabled) {
cy.getBySel(ENDPOINT_RESPONSE_ACTION_ADD_BUTTON).click();
cy.contains('Query is a required field');
cy.contains('Select an endpoint response action.');
} else {
cy.contains('Upgrade your license to Endpoint Complete to use Osquery Response Actions.');
cy.getBySel(ENDPOINT_RESPONSE_ACTION_ADD_BUTTON).should('be.disabled');
}
});
};

View file

@ -62,6 +62,7 @@ export const getSavedQueriesComplexTest = () =>
cy.getBySel('pagination-button-next').click().wait(500).click();
cy.contains('columns hidden').should('exist');
// enter fullscreen
cy.getBySel(RESULTS_TABLE_BUTTON).trigger('mouseover');
cy.contains(/Enter fullscreen$/).should('not.exist');
cy.contains('Exit fullscreen').should('exist');

View file

@ -2,7 +2,9 @@
"extends": "../../../../tsconfig.base.json",
"include": [
"**/*",
"../cypress.config.ts"
"../cypress.config.ts",
"../serverless_cypress.config.ts",
"../../../test_serverless/shared/lib"
],
"exclude": [
"target/**/*"
@ -25,7 +27,6 @@
"path": "../tsconfig.json",
"force": true
},
"@kbn/security-plugin",
"@kbn/security-solution-plugin",
"@kbn/fleet-plugin",
"@kbn/cases-plugin"

View file

@ -9,6 +9,8 @@
"cypress:changed-specs-only": "yarn cypress:run --changed-specs-only --env burn=2",
"cypress:open": "node ../security_solution/scripts/start_cypress_parallel open --config-file ../osquery/cypress.config.ts --ftr-config-file ../../../x-pack/test/osquery_cypress/cli_config",
"cypress:run": "node ../security_solution/scripts/start_cypress_parallel run --config-file ../osquery/cypress.config.ts --ftr-config-file ../../../x-pack/test/osquery_cypress/cli_config --concurrency 1",
"cypress:serverless:open": "node ../security_solution/scripts/start_cypress_parallel open --config-file ../osquery/serverless_cypress.config.ts --ftr-config-file ../../../x-pack/test/osquery_cypress/serverless_cli_config",
"cypress:serverless:run": "node ../security_solution/scripts/start_cypress_parallel run --config-file ../osquery/serverless_cypress.config.ts --ftr-config-file ../../../x-pack/test/osquery_cypress/serverless_cli_config --concurrency 1",
"nyc": "../../../node_modules/.bin/nyc report --reporter=text-summary",
"junit:merge": "../../../node_modules/.bin/mochawesome-merge ../../../target/kibana-osquery/cypress/results/mochawesome*.json > ../../../target/kibana-osquery/cypress/results/output.json && ../../../node_modules/.bin/marge ../../../target/kibana-osquery/cypress/results/output.json --reportDir ../../../target/kibana-osquery/cypress/results && yarn junit:transform && mkdir -p ../../../target/junit && cp ../../../target/kibana-osquery/cypress/results/*.xml ../../../target/junit/",
"junit:transform": "node ../security_solution/scripts/junit_transformer --pathPattern '../../../target/kibana-osquery/cypress/results/*.xml' --rootDirectory ../../../ --reportName 'Osquery Cypress' --writeInPlace"

View file

@ -0,0 +1,43 @@
/*
* 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 { defineCypressConfig } from '@kbn/cypress-config';
// eslint-disable-next-line @kbn/imports/no_boundary_crossing
import { setupUserDataLoader } from '../../test_serverless/functional/test_suites/security/cypress/support/setup_data_loader_tasks';
// eslint-disable-next-line import/no-default-export
export default defineCypressConfig({
defaultCommandTimeout: 60000,
execTimeout: 60000,
pageLoadTimeout: 60000,
responseTimeout: 60000,
screenshotsFolder: '../../../target/kibana-osquery/cypress/screenshots',
trashAssetsBeforeRuns: false,
video: false,
viewportHeight: 946,
viewportWidth: 1680,
env: {
'cypress-react-selector': {
root: '#osquery-app',
},
grepFilterSpecs: true,
grepTags: '@serverless --@brokenInServerless',
grepOmitFiltered: true,
},
e2e: {
specPattern: './cypress/e2e/**/*.cy.ts',
experimentalRunAllSpecs: true,
experimentalMemoryManagement: true,
numTestsKeptInMemory: 3,
setupNodeEvents: (on, config) => {
setupUserDataLoader(on, config, { additionalRoleName: 'viewer' });
return config;
},
},
});

View file

@ -5,6 +5,7 @@
},
"exclude": [
"cypress.config.ts",
"serverless_cypress.config.ts",
"target/**/*",
],
"include": [
@ -15,6 +16,7 @@
"scripts/**/**.json",
"server/**/*",
"cypress.config.ts",
"serverless_cypress.config.ts",
"../../../typings/**/*",
// ECS and Osquery schema files
"public/common/schemas/*/**.json",
@ -74,6 +76,6 @@
"@kbn/core-lifecycle-browser",
"@kbn/core-saved-objects-server",
"@kbn/monaco",
"@kbn/io-ts-utils"
"@kbn/io-ts-utils",
]
}

View file

@ -36,7 +36,7 @@ import type {
declare global {
namespace Cypress {
interface SuiteConfigOverrides {
env: {
env?: {
ftrConfig: SecuritySolutionDescribeBlockFtrConfig;
};
}

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import { generateRandomStringName } from '@kbn/osquery-plugin/cypress/tasks/integrations';
import { generateRandomStringName } from '../../../tasks/utils';
import { indexEndpointHosts } from '../../../tasks/index_endpoint_hosts';
import type { ReturnTypeFromChainable } from '../../../types';
import { indexEndpointRuleAlerts } from '../../../tasks/index_endpoint_rule_alerts';

View file

@ -5,12 +5,12 @@
* 2.0.
*/
import { generateRandomStringName } from '@kbn/osquery-plugin/cypress/tasks/integrations';
import { disableExpandableFlyoutAdvancedSettings } from '../../../tasks/common';
import { APP_ALERTS_PATH } from '../../../../../../common/constants';
import { closeAllToasts } from '../../../tasks/toasts';
import { fillUpNewRule } from '../../../tasks/response_actions';
import { login, loginWithRole, ROLE } from '../../../tasks/login';
import { generateRandomStringName } from '../../../tasks/utils';
import type { ReturnTypeFromChainable } from '../../../types';
import { indexEndpointHosts } from '../../../tasks/index_endpoint_hosts';
import { indexEndpointRuleAlerts } from '../../../tasks/index_endpoint_rule_alerts';

View file

@ -5,8 +5,8 @@
* 2.0.
*/
import { generateRandomStringName } from '@kbn/osquery-plugin/cypress/tasks/integrations';
import { disableExpandableFlyoutAdvancedSettings } from '../../../tasks/common';
import { generateRandomStringName } from '../../../tasks/utils';
import { APP_ALERTS_PATH } from '../../../../../../common/constants';
import { closeAllToasts } from '../../../tasks/toasts';
import { indexEndpointHosts } from '../../../tasks/index_endpoint_hosts';

View file

@ -0,0 +1,9 @@
/*
* 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 generateRandomStringName = (length: number) =>
Array.from({ length }, () => Math.random().toString(36).substring(2));

View file

@ -35,6 +35,5 @@
"@kbn/test",
"@kbn/repo-info",
"@kbn/data-views-plugin",
"@kbn/osquery-plugin/cypress",
]
}

View file

@ -266,9 +266,18 @@ export const cli = () => {
}
if (hasFleetServerArgs) {
vars.kbnTestServer.serverArgs.push(
`--xpack.fleet.agents.fleet_server.hosts=["https://${hostRealIp}:${fleetServerPort}"]`
);
vars.kbnTestServer.serverArgs.push(
`--xpack.fleet.agents.elasticsearch.host=http://${hostRealIp}:${esPort}`
);
if (vars.serverless) {
vars.kbnTestServer.serverArgs.push(
`--xpack.fleet.internal.fleetServerStandalone=false`
);
}
}
// Serverless Specific

View file

@ -6,5 +6,5 @@
*/
export async function getLatestVersion(): Promise<string> {
return '8.9.0-SNAPSHOT';
return '8.10.0-SNAPSHOT';
}

View file

@ -0,0 +1,46 @@
/*
* 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 { FtrConfigProviderContext } from '@kbn/test';
import { startOsqueryCypress } from './runner';
export default async function ({ readConfigFile }: FtrConfigProviderContext) {
const securitySolutionCypressConfig = await readConfigFile(
require.resolve(
'../../test_serverless/functional/test_suites/security/cypress/security_config.base.ts'
)
);
return {
...securitySolutionCypressConfig.getAll(),
esTestCluster: {
...securitySolutionCypressConfig.get('esTestCluster'),
serverArgs: [
...securitySolutionCypressConfig.get('esTestCluster.serverArgs'),
'http.host=0.0.0.0',
],
},
kbnTestServer: {
...securitySolutionCypressConfig.get('kbnTestServer'),
serverArgs: [
...securitySolutionCypressConfig.get('kbnTestServer.serverArgs'),
`--xpack.fleet.agents.fleet_server.hosts=["https://host.docker.internal:8220"]`,
`--xpack.fleet.agents.elasticsearch.host=http://host.docker.internal:${securitySolutionCypressConfig.get(
'servers.elasticsearch.port'
)}`,
`--xpack.fleet.packages.0.name=osquery_manager`,
`--xpack.fleet.packages.0.version=latest`,
`--xpack.fleet.internal.fleetServerStandalone=false`,
],
},
testRunner: startOsqueryCypress,
};
}

View file

@ -16,12 +16,17 @@ import {
} from '@kbn/fleet-plugin/common/types';
import { ToolingLog } from '@kbn/tooling-log';
export const DEFAULT_HEADERS = Object.freeze({
'x-elastic-internal-product': 'security-solution',
});
export const getInstalledIntegration = async (kbnClient: KbnClient, integrationName: string) => {
const {
data: { item },
} = await kbnClient.request<{ item: PackagePolicy }>({
method: 'GET',
path: `/api/fleet/epm/packages/${integrationName}`,
headers: DEFAULT_HEADERS,
});
return item;

View file

@ -6,7 +6,8 @@
*/
import { defineCypressConfig } from '@kbn/cypress-config';
import { setupDataLoaderTasks } from './support/setup_data_loader_tasks';
import { dataLoaders as setupEndpointDataLoaders } from '@kbn/security-solution-plugin/public/management/cypress/support/data_loaders';
import { setupUserDataLoader } from './support/setup_data_loader_tasks';
export default defineCypressConfig({
defaultCommandTimeout: 60000,
@ -25,7 +26,9 @@ export default defineCypressConfig({
supportFile: './support/e2e.js',
specPattern: './e2e/**/*.cy.ts',
setupNodeEvents: (on, config) => {
setupDataLoaderTasks(on, config);
// Reuse data loaders from endpoint management cypress setup
setupEndpointDataLoaders(on, config);
setupUserDataLoader(on, config, {});
},
},
});

View file

@ -42,7 +42,7 @@ export interface LoadUserAndRoleCyTaskOptions {
declare global {
namespace Cypress {
interface SuiteConfigOverrides {
env: {
env?: {
ftrConfig: SecuritySolutionDescribeBlockFtrConfig;
};
}

View file

@ -6,17 +6,22 @@
*/
import { createRuntimeServices } from '@kbn/security-solution-plugin/scripts/endpoint/common/stack_services';
import { dataLoaders } from '@kbn/security-solution-plugin/public/management/cypress/support/data_loaders';
import { LoadUserAndRoleCyTaskOptions } from '../cypress';
import { LoadedRoleAndUser, SecurityRoleAndUserLoader } from '../../../../../shared/lib';
import {
LoadedRoleAndUser,
SecurityRoleAndUserLoader,
YamlRoleDefinitions,
} from '../../../../../shared/lib';
export const setupDataLoaderTasks = (
interface AdditionalDefinitions {
roleDefinitions?: YamlRoleDefinitions;
additionalRoleName?: string;
}
export const setupUserDataLoader = (
on: Cypress.PluginEvents,
config: Cypress.PluginConfigOptions
config: Cypress.PluginConfigOptions,
{ roleDefinitions, additionalRoleName }: AdditionalDefinitions
) => {
// Reuse data loaders from endpoint management cypress setup
dataLoaders(on, config);
const stackServicesPromise = createRuntimeServices({
kibanaUrl: config.env.KIBANA_URL,
elasticsearchUrl: config.env.ELASTICSEARCH_URL,
@ -29,7 +34,7 @@ export const setupDataLoaderTasks = (
const roleAndUserLoaderPromise: Promise<SecurityRoleAndUserLoader> = stackServicesPromise.then(
({ kbnClient, log }) => {
return new SecurityRoleAndUserLoader(kbnClient, log);
return new SecurityRoleAndUserLoader(kbnClient, log, roleDefinitions);
}
);
@ -39,7 +44,7 @@ export const setupDataLoaderTasks = (
* @param name
*/
loadUserAndRole: async ({ name }: LoadUserAndRoleCyTaskOptions): Promise<LoadedRoleAndUser> => {
return (await roleAndUserLoaderPromise).load(name);
return (await roleAndUserLoaderPromise).load(name, additionalRoleName);
},
});
};

View file

@ -6,3 +6,4 @@
*/
export * from './kibana_roles';
export * from './types';

View file

@ -8,27 +8,15 @@
import { safeLoad as loadYaml } from 'js-yaml';
import { readFileSync } from 'fs';
import * as path from 'path';
import { cloneDeep } from 'lodash';
import { cloneDeep, merge } from 'lodash';
import { FeaturesPrivileges, Role, RoleIndexPrivilege } from '@kbn/security-plugin/common';
import { ServerlessRoleName } from '../types';
const ROLES_YAML_FILE_PATH = path.join(__dirname, 'project_controller_security_roles.yml');
const ROLE_NAMES = [
't1_analyst',
't2_analyst',
't3_analyst',
'threat_intelligence_analyst',
'rule_author',
'soc_manager',
'detections_admin',
'platform_engineer',
'endpoint_operations_analyst',
'endpoint_policy_manager',
] as const;
const ROLE_NAMES = Object.values(ServerlessRoleName);
export type ServerlessRoleName = typeof ROLE_NAMES[number];
type YamlRoleDefinitions = Record<
export type YamlRoleDefinitions = Record<
ServerlessRoleName,
{
cluster: string[] | null;
@ -45,10 +33,16 @@ const roleDefinitions = loadYaml(readFileSync(ROLES_YAML_FILE_PATH, 'utf8')) as
export type ServerlessSecurityRoles = Record<ServerlessRoleName, Role>;
export const getServerlessSecurityKibanaRoleDefinitions = (): ServerlessSecurityRoles => {
export const getServerlessSecurityKibanaRoleDefinitions = (
additionalRoleDefinitions?: YamlRoleDefinitions
): ServerlessSecurityRoles => {
const definitions = cloneDeep(roleDefinitions);
const mergedDefinitions: YamlRoleDefinitions = merge(
definitions,
additionalRoleDefinitions || {}
);
return Object.entries(definitions).reduce((roles, [roleName, definition]) => {
return Object.entries(mergedDefinitions).reduce((roles, [roleName, definition]) => {
if (!ROLE_NAMES.includes(roleName as ServerlessRoleName)) {
throw new Error(
`Un-expected role [${roleName}] found in YAML file [${ROLES_YAML_FILE_PATH}]`

View file

@ -404,6 +404,10 @@ soc_manager:
privileges:
- all
resources: "*"
- application: savedObjectsManagement
privileges:
- all
resources: "*"
detections_admin:
cluster:

View file

@ -15,6 +15,7 @@ import { AxiosError } from 'axios';
import {
getServerlessSecurityKibanaRoleDefinitions,
ServerlessSecurityRoles,
YamlRoleDefinitions,
} from './kibana_roles';
import { STANDARD_HTTP_HEADERS } from '../default_http_headers';
@ -46,7 +47,7 @@ export class RoleAndUserLoader<R extends Record<string, Role> = Record<string, R
};
}
async load(name: keyof R): Promise<LoadedRoleAndUser> {
async load(name: keyof R, additionalRoleName?: string): Promise<LoadedRoleAndUser> {
const role = this.roles[name];
if (!role) {
@ -56,9 +57,12 @@ export class RoleAndUserLoader<R extends Record<string, Role> = Record<string, R
}
const roleName = role.name;
const roleNames = [roleName];
if (additionalRoleName) {
roleNames.push(additionalRoleName);
}
await this.createRole(role);
await this.createUser(roleName, 'changeme', [roleName]);
await this.createUser(roleName, 'changeme', roleNames);
return {
role: roleName,
@ -123,7 +127,11 @@ export class RoleAndUserLoader<R extends Record<string, Role> = Record<string, R
}
export class SecurityRoleAndUserLoader extends RoleAndUserLoader<ServerlessSecurityRoles> {
constructor(kbnClient: KbnClient, logger: ToolingLog) {
super(kbnClient, logger, getServerlessSecurityKibanaRoleDefinitions());
constructor(
kbnClient: KbnClient,
logger: ToolingLog,
additionalRoleDefinitions?: YamlRoleDefinitions
) {
super(kbnClient, logger, getServerlessSecurityKibanaRoleDefinitions(additionalRoleDefinitions));
}
}

View file

@ -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.
*/
export enum ServerlessRoleName {
T1_ANALYST = 't1_analyst',
T2_ANALYST = 't2_analyst',
T3_ANALYST = 't3_analyst',
THREAT_INTELLIGENCE_ANALYST = 'threat_intelligence_analyst',
RULE_AUTHOR = 'rule_author',
SOC_MANAGER = 'soc_manager',
DETECTIONS_ADMIN = 'detections_admin',
PLATFORM_ENGINEER = 'platform_engineer',
ENDPOINT_OPERATIONS_ANALYST = 'endpoint_operations_analyst',
ENDPOINT_POLICY_MANAGER = 'endpoint_policy_manager',
READER = 'reader', // custom role to test lack of permissions
}