Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SIEM] Toggle Column / Code Coverage and Cypress #42766

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion x-pack/legacy/plugins/siem/cypress.json
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
{}
{
"baseUrl": "http://localhost:5601"
}
9 changes: 9 additions & 0 deletions x-pack/legacy/plugins/siem/cypress/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,15 @@ The `username` and `password` from `config/kibana.dev.yml` will be read by the `

See the `Running Tests Interactively` section for details.

### Content Security Policy (CSP) Settings

Your local or cloud Kibana server must have the `csp.strict: false` setting
configured in `kibana.dev.yml`, or `kibana.yml`, as shown in the example below:

```yaml
csp.strict: false
```

## Running Tests Interactively

To run tests in interactively via the Cypress test runner:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import { FIELDS_BROWSER_CONTAINER, FIELDS_BROWSER_FILTER_INPUT, FIELDS_BUTTON } from './selectors';
import {
assertAtLeastOneEventMatchesSearch,
executeKQL,
hostExistsQuery,
toggleTimelineVisibility,
} from '../timeline/helpers';
import { TIMELINE_DATA_PROVIDERS } from '../timeline/selectors';

/** Opens the timeline's Field Browser */
export const openFieldsBrowser = () => {
cy.get(FIELDS_BUTTON).click();

cy.get(FIELDS_BROWSER_CONTAINER).should('exist');
};

/** Populates the timeline with a host from the hosts page */
export const populateTimeline = () => {
toggleTimelineVisibility();

executeKQL(hostExistsQuery);

assertAtLeastOneEventMatchesSearch();
};

/** Clicks an arbitrary UI element that's not part of the fields browser (to dismiss it) */
export const clickOutsideFieldsBrowser = () => {
cy.get(TIMELINE_DATA_PROVIDERS).click();
};

/** Filters the Field Browser by typing `fieldName` in the input */
export const filterFieldsBrowser = (fieldName: string) => {
cy.get(FIELDS_BROWSER_FILTER_INPUT).type(fieldName);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

/** Clicking this button in the timeline opens the Fields browser */
export const FIELDS_BUTTON = '[data-test-subj="show-field-browser"]';

/** The title displayed in the fields browser (i.e. Customize Columns) */
export const FIELDS_BROWSER_TITLE = '[data-test-subj="field-browser-title"]';

/** Contains the body of the fields browser */
export const FIELDS_BROWSER_CONTAINER = '[data-test-subj="fields-browser-container"]';

/** The title of the selected category in the right-hand side of the fields browser */
export const FIELDS_BROWSER_SELECTED_CATEGORY_TITLE = '[data-test-subj="selected-category-title"]';

/** A count of the fields in the selected category in the right-hand side of the fields browser */
export const FIELDS_BROWSER_SELECTED_CATEGORY_COUNT =
'[data-test-subj="selected-category-count-badge"]';

/** Typing in this input filters the Field Browser */
export const FIELDS_BROWSER_FILTER_INPUT = '[data-test-subj="field-search"]';

/**
* This label displays a count of the categories containing (one or more)
* fields that match the filter criteria
*/
export const FIELDS_BROWSER_CATEGORIES_COUNT = '[data-test-subj="categories-count"]';

/**
* This label displays a count of the fields that match the filter criteria
*/
export const FIELDS_BROWSER_FIELDS_COUNT = '[data-test-subj="fields-count"]';
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,9 @@
* you may not use this file except in compliance with the Elastic License.
*/

import { ALL_HOSTS_WIDGET } from './selectors';
import { DEFAULT_TIMEOUT } from '../util/helpers';

/** Wait this long for the for the `All Hosts` widget on the `Hosts` page to load */
const ALL_HOSTS_TIMEOUT = 10 * 1000;
import { ALL_HOSTS_WIDGET } from './selectors';

/** Wait for the for the `All Hosts` widget on the `Hosts` page to load */
export const waitForAllHostsWidget = () => cy.get(ALL_HOSTS_WIDGET, { timeout: ALL_HOSTS_TIMEOUT });
export const waitForAllHostsWidget = () => cy.get(ALL_HOSTS_WIDGET, { timeout: DEFAULT_TIMEOUT });
125 changes: 82 additions & 43 deletions x-pack/legacy/plugins/siem/cypress/integration/lib/login/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,73 +6,112 @@

import * as yaml from 'js-yaml';

import { LOGIN_PAGE } from '../urls';
/**
* 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';

import { DEFAULT_SPACE_BUTTON, PASSWORD, USERNAME } from './selectors';
/**
* 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 `username` and `password` values in the `elasticsearch` section of this
* file will be used to login to Kibana
* The configuration path in `kibana.dev.yml` to the password to be used when
* authenticating with Kibana.
*/
const KIBANA_DEV_YML_PATH = '../../../../config/kibana.dev.yml';
const ELASTICSEARCH_PASSWORD_CONFIG_PATH = 'config.elasticsearch.password';

/** Wait this long for the login page wait (ms) */
const USERNAME_TIMEOUT = 10 * 1000;
/**
* The `CYPRESS_ELASTICSEARCH_USERNAME` environment variable specifies the
* username to be used when authenticating with Kibana
*/
const ELASTICSEARCH_USERNAME = 'ELASTICSEARCH_USERNAME';

/**
* Authenticates with Kibana by POSTing the username and password directly to
* Kibana's `security/v1/login` endpoint, bypassing the login page (for speed).
* 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 = '/api/security/v1/login';

/**
* 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 function over authenticating
* via an interactive login.
* To speed the execution of tests, prefer this non-interactive authentication,
* which is faster than authentication via Kibana's interactive login page.
*/
export const login = () => {
if (credentialsProvidedByEnvironment()) {
loginViaEnvironmentCredentials();
} else {
loginViaConfig();
}
};

/**
* 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 `security/v1/login` endpoint, bypassing the login page (for speed).
*/
const loginViaEnvironmentCredentials = () => {
cy.log(
`NON-interactively logging into Kibana with the \`username\` and \`password\` from the \`elasticsearch\` section of \`${KIBANA_DEV_YML_PATH}\``
`Authenticating via environment credentials from the \`CYPRESS_${ELASTICSEARCH_USERNAME}\` and \`CYPRESS_${ELASTICSEARCH_PASSWORD}\` environment variables`
);

// read the login details
cy.readFile(KIBANA_DEV_YML_PATH).then(kibanaDevYml => {
const config = yaml.safeLoad(kibanaDevYml);

// programmatically log us in without needing the UI
cy.request({
body: {
username: config.elasticsearch.username,
password: config.elasticsearch.password,
},
followRedirect: false,
headers: { 'kbn-xsrf': 'cypress' },
method: 'POST',
url: 'http://localhost:5601/api/security/v1/login',
});
// programmatically authenticate without interacting with the Kibana login page
cy.request({
body: {
username: Cypress.env(ELASTICSEARCH_USERNAME),
password: Cypress.env(ELASTICSEARCH_PASSWORD),
},
followRedirect: false,
headers: { 'kbn-xsrf': 'cypress-creds-via-env' },
method: 'POST',
url: `${Cypress.config().baseUrl}${LOGIN_API_ENDPOINT}`,
});
};

/**
* This (slower) login function authenticates with Kibana via the login page.
* To speed the execution of tests, is generally preferable to use the
* NON-interactive `login` function (in this file) instead, because in
* addition to having to wait for the interactive Kibana login page to load,
* this function also waits for the "spaces" page to load after
* authenticating so it can click on the default space.
* Authenticates with Kibana by reading credentials from the
* `kibana.dev.yml` file and POSTing the username and password directly to
* Kibana's `security/v1/login` endpoint, bypassing the login page (for speed).
*/
export const interactiveLogin = () => {
const loginViaConfig = () => {
cy.log(
`Interactively logging into Kibana with the \`username\` and \`password\` from the \`elasticsearch\` section of \`${KIBANA_DEV_YML_PATH}\``
`Authenticating via config credentials \`${ELASTICSEARCH_USERNAME_CONFIG_PATH}\` and \`${ELASTICSEARCH_PASSWORD_CONFIG_PATH}\` from \`${KIBANA_DEV_YML_PATH}\``
);

// read the login details
// read the login details from `kibana.dev.yaml`
cy.readFile(KIBANA_DEV_YML_PATH).then(kibanaDevYml => {
const config = yaml.safeLoad(kibanaDevYml);

cy.visit(LOGIN_PAGE);

cy.get(USERNAME, { timeout: USERNAME_TIMEOUT }).type(config.elasticsearch.username);
cy.get(PASSWORD).type(`${config.elasticsearch.password}{enter}`, {
log: false,
// programmatically authenticate without interacting with the Kibana login page
cy.request({
body: {
username: config.elasticsearch.username,
password: config.elasticsearch.password,
},
followRedirect: false,
headers: { 'kbn-xsrf': 'cypress-creds-via-config' },
method: 'POST',
url: `${Cypress.config().baseUrl}${LOGIN_API_ENDPOINT}`,
});

cy.get(DEFAULT_SPACE_BUTTON).click(); // click the `Default` space in the `Select Your Space` page
});
};
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/

import { LOGOUT_LINK, USER_MENU } from './selectors';
import { LOGOUT } from '../urls';

export const logout = () => {
cy.get(USER_MENU).click();

cy.get(LOGOUT_LINK).click();
cy.visit(`${Cypress.config().baseUrl}${LOGOUT}`);
};
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@
*/

/** Top-level (global) navigation link to the `Hosts` page */
export const NAVIGATION_HOSTS = '[data-test-subj="navigation-hosts"]';
export const NAVIGATION_HOSTS = '[data-test-subj="navigation-link-hosts"]';

/** Top-level (global) navigation link to the `Network` page */
export const NAVIGATION_NETWORK = '[data-test-subj="navigation-network"]';
export const NAVIGATION_NETWORK = '[data-test-subj="navigation-link-network"]';

/** Top-level (global) navigation link to the `Overview` page */
export const NAVIGATION_OVERVIEW = '[data-test-subj="navigation-overview"]';
export const NAVIGATION_OVERVIEW = '[data-test-subj="navigation-link-overview"]';

/** Top-level (global) navigation link to the `Timelines` page */
export const NAVIGATION_TIMELINES = '[data-test-subj="navigation-timelines"]';
export const NAVIGATION_TIMELINES = '[data-test-subj="navigation-link-timelines"]';
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,46 @@

import { drag, drop } from '../drag_n_drop/helpers';
import { ALL_HOSTS_WIDGET_DRAGGABLE_HOSTS } from '../hosts/selectors';

import { TIMELINE_DATA_PROVIDERS, TIMELINE_TOGGLE_BUTTON } from './selectors';

/** Wait up to this many ms for the timeline to render data providers that were dropped in the timeline */
export const TIMELINE_RENDER_DATA_PROVIDERS_TIMEOUT = 10 * 1000;
import {
SEARCH_OR_FILTER_CONTAINER,
SERVER_SIDE_EVENT_COUNT,
TIMELINE_DATA_PROVIDERS,
TIMELINE_TOGGLE_BUTTON,
TOGGLE_EXPAND_EVENT,
} from './selectors';
import { DEFAULT_TIMEOUT } from '../util/helpers';

/** Toggles the timeline's open / closed state by clicking the `T I M E L I N E` button */
export const toggleTimelineVisibility = () =>
cy
.get(TIMELINE_TOGGLE_BUTTON)
.first()
.click();
cy.get(TIMELINE_TOGGLE_BUTTON, { timeout: DEFAULT_TIMEOUT }).click();

/** Drags and drops a host from the `All Hosts` widget on the `Hosts` page to the timeline */
export const dragFromAllHostsToTimeline = () => {
cy.get(ALL_HOSTS_WIDGET_DRAGGABLE_HOSTS)
.first()
.then(host => drag(host));

cy.get(TIMELINE_DATA_PROVIDERS)
cy.get(TIMELINE_DATA_PROVIDERS).then(dataProvidersDropArea => drop(dataProvidersDropArea));
};

/** Executes the specified KQL query in the timeline */
export const executeKQL = (query: string) => {
cy.get(`${SEARCH_OR_FILTER_CONTAINER} input`).type(`${query} {enter}`);
};

/** A sample KQL query that finds any documents where the `host.name` field exists */
export const hostExistsQuery = 'host.name: *';

/** Asserts that at least one event matches the timeline's search criteria */
export const assertAtLeastOneEventMatchesSearch = () =>
cy
.get(SERVER_SIDE_EVENT_COUNT, { timeout: DEFAULT_TIMEOUT })
.invoke('text')
.should('be.above', 0);

/** Toggles open or closed the first event in the timeline */
export const toggleFirstEventDetails = () => {
cy.get(TOGGLE_EXPAND_EVENT, { timeout: DEFAULT_TIMEOUT })
.first()
.then(dataProvidersDropArea => drop(dataProvidersDropArea));
.click();
};
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,13 @@ export const TIMELINE_DROPPED_DATA_PROVIDERS = `${TIMELINE_DATA_PROVIDERS} ${DAT

/** The `T I M E L I N E` button that toggles visibility of the Timeline */
export const TIMELINE_TOGGLE_BUTTON = '[data-test-subj="flyoutOverlay"]';

/** Contains the KQL bar for searching or filtering in the timeline */
export const SEARCH_OR_FILTER_CONTAINER =
'[data-test-subj="timeline-search-or-filter-search-container"]';

/** The total server-side count of the events matching the timeline's search criteria */
export const SERVER_SIDE_EVENT_COUNT = '[data-test-subj="server-side-event-count"]';

/** Expands or collapses an event in the timeline */
export const TOGGLE_EXPAND_EVENT = '[data-test-subj="expand-event"]';
13 changes: 8 additions & 5 deletions x-pack/legacy/plugins/siem/cypress/integration/lib/urls/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,19 @@
*/

/** The SIEM app's Hosts page */
export const HOSTS_PAGE = 'http://localhost:5601/app/siem#/hosts';
export const HOSTS_PAGE = '/app/siem#/hosts';

/** Kibana's login page */
export const LOGIN_PAGE = 'http://localhost:5601/login';
export const LOGIN_PAGE = '/login';

/** The SIEM app's Network page */
export const NETWORK_PAGE = 'http://localhost:5601/app/siem#/network';
export const NETWORK_PAGE = '/app/siem#/network';

/** The SIEM app's Overview page */
export const OVERVIEW_PAGE = 'http://localhost:5601/app/siem#/overview';
export const OVERVIEW_PAGE = '/app/siem#/overview';

/** The SIEM app's Timelines page */
export const TIMELINES_PAGE = 'http://localhost:5601/app/siem#/timelines';
export const TIMELINES_PAGE = '/app/siem#/timelines';

/** Visit this URL to logout of Kibana */
export const LOGOUT = '/logout';
Loading