Skip to content

Commit

Permalink
Onboarding security analytics dashboards plugin. (opensearch-project#380
Browse files Browse the repository at this point in the history
)

* Added security analytics dashboards.

Signed-off-by: AWSHurneyt <[email protected]>

* Fixed url is visit() call.

Signed-off-by: AWSHurneyt <[email protected]>

* Enabled github action on PRs to 2.x branch.

Signed-off-by: AWSHurneyt <[email protected]>

* Adjusted workflow to run without security.

Signed-off-by: AWSHurneyt <[email protected]>

* clean up commands

Signed-off-by: Kawika Avilla <[email protected]>

* Enabled security for onboarding experimental plugin.

Signed-off-by: AWSHurneyt <[email protected]>

Signed-off-by: AWSHurneyt <[email protected]>
Signed-off-by: Kawika Avilla <[email protected]>
Co-authored-by: Kawika Avilla <[email protected]>
Signed-off-by: [email protected] <[email protected]>
  • Loading branch information
2 people authored and leanneeliatra committed Sep 15, 2023
1 parent 100e313 commit eb10f43
Show file tree
Hide file tree
Showing 9 changed files with 332 additions and 0 deletions.
27 changes: 27 additions & 0 deletions .github/workflows/security-analytics-release-e2e-workflow.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: Security Analytics Release tests workflow in Bundled OpenSearch Dashboards
on:
pull_request:
branches:
- main
- dev-*
- 2.x
jobs:
changes:
runs-on: ubuntu-latest
outputs:
tests: ${{ steps.filter.outputs.tests }}
steps:
- uses: dorny/paths-filter@v2
id: filter
with:
filters: |
tests:
- 'cypress/**/security-analytics-dashboards-plugin/**'
tests:
needs: changes
if: ${{ needs.changes.outputs.tests == 'true' }}
uses: ./.github/workflows/release-e2e-workflow-template.yml
with:
test-name: Security Analytics
test-command: yarn cypress:run-with-security --browser chromium --spec 'cypress/integration/plugins/security-analytics-dashboards-plugin/*'
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import { PLUGIN_NAME, TWENTY_SECONDS_TIMEOUT } from '../../../utils/plugins/security-analytics-dashboards-plugin/constants'
import {BASE_PATH} from "../../../utils/base_constants";

const SAMPLE_RULE = {
name: 'Cypress test rule',
logType: 'windows',
description: 'This is a rule used to test the rule creation workflow. Not for production use.',
detection:
'selection:\n Provider_Name: Service Control Manager\nEventID: 7045\nServiceName: ZzNetSvc\n{backspace}{backspace}condition: selection',
detectionLine: [
'selection:',
'Provider_Name: Service Control Manager',
'EventID: 7045',
'ServiceName: ZzNetSvc',
'condition: selection',
],
severity: 'critical',
tags: ['attack.persistence', 'attack.privilege_escalation', 'attack.t1543.003'],
references: 'https://nohello.com',
falsePositive: 'unknown',
author: 'Cypress Test Runner',
status: 'experimental',
};

describe('Rules', () => {
before(() => {
// Deleting pre-existing test rules
cy.deleteRule(SAMPLE_RULE.name);
});
beforeEach(() => {
// Visit Rules page
cy.visit(`${BASE_PATH}/app/${PLUGIN_NAME}#/rules`);
});

describe('Can be created', () => {
it('manually using UI', () => {
// Click "create new rule" button
cy.get('[data-test-subj="create_rule_button"]', TWENTY_SECONDS_TIMEOUT).click({
force: true,
});

// Enter the name
cy.get('[data-test-subj="rule_name_field"]', TWENTY_SECONDS_TIMEOUT).type(SAMPLE_RULE.name);

// Enter the log type
cy.get('[data-test-subj="rule_type_dropdown"]', TWENTY_SECONDS_TIMEOUT).select(
SAMPLE_RULE.logType
);

// Enter the description
cy.get('[data-test-subj="rule_description_field"]', TWENTY_SECONDS_TIMEOUT).type(
SAMPLE_RULE.description
);

// Enter the detection
cy.get('[data-test-subj="rule_detection_field"]', TWENTY_SECONDS_TIMEOUT).type(
SAMPLE_RULE.detection
);

// Enter the severity
cy.get('[data-test-subj="rule_severity_dropdown"]', TWENTY_SECONDS_TIMEOUT).select(
SAMPLE_RULE.severity
);

// Enter the tags
SAMPLE_RULE.tags.forEach((tag) =>
cy
.get('[data-test-subj="rule_tags_dropdown"]', TWENTY_SECONDS_TIMEOUT)
.type(`${tag}{enter}{esc}`)
);

// Enter the reference
cy.get('[data-test-subj="rule_references_field_0"]', TWENTY_SECONDS_TIMEOUT).type(
SAMPLE_RULE.references
);

// Enter the false positive cases
cy.get('[data-test-subj="rule_false_positive_cases_field_0"]', TWENTY_SECONDS_TIMEOUT).type(
SAMPLE_RULE.falsePositive
);

// Enter the author
cy.get('[data-test-subj="rule_author_field"]', TWENTY_SECONDS_TIMEOUT).type(
SAMPLE_RULE.author
);

// Enter the log type
cy.get('[data-test-subj="rule_status_dropdown"]', TWENTY_SECONDS_TIMEOUT).select(
SAMPLE_RULE.status
);

// Click "create" button
cy.get('[data-test-subj="create_rule_button"]', TWENTY_SECONDS_TIMEOUT).click({
force: true,
});

// Wait for the page to finish loading
cy.wait(5000);
cy.contains('No items found', TWENTY_SECONDS_TIMEOUT).should('not.exist');

// Search for the rule
cy.get(`input[type="search"]`, TWENTY_SECONDS_TIMEOUT)
// .focus()
.type(`${SAMPLE_RULE.name}{enter}`);

// Click the rule link to open the details flyout
cy.get(`[data-test-subj="rule_link_${SAMPLE_RULE.name}"]`, TWENTY_SECONDS_TIMEOUT).click();

// Confirm the flyout contains the expected values
cy.get(`[data-test-subj="rule_flyout_${SAMPLE_RULE.name}"]`, TWENTY_SECONDS_TIMEOUT)
.click({ force: true })
.within(() => {
// Validate name
cy.get('[data-test-subj="rule_flyout_rule_name"]', TWENTY_SECONDS_TIMEOUT).contains(
SAMPLE_RULE.name,
TWENTY_SECONDS_TIMEOUT
);

// Validate log type
cy.get('[data-test-subj="rule_flyout_rule_log_type"]', TWENTY_SECONDS_TIMEOUT).contains(
SAMPLE_RULE.logType,
TWENTY_SECONDS_TIMEOUT
);

// Validate description
cy.get(
'[data-test-subj="rule_flyout_rule_description"]',
TWENTY_SECONDS_TIMEOUT
).contains(SAMPLE_RULE.description, TWENTY_SECONDS_TIMEOUT);

// Validate author
cy.get('[data-test-subj="rule_flyout_rule_author"]', TWENTY_SECONDS_TIMEOUT).contains(
SAMPLE_RULE.author,
TWENTY_SECONDS_TIMEOUT
);

// Validate source is "custom"
cy.get('[data-test-subj="rule_flyout_rule_source"]', TWENTY_SECONDS_TIMEOUT).contains(
'Custom',
TWENTY_SECONDS_TIMEOUT
);

// Validate severity
cy.get('[data-test-subj="rule_flyout_rule_severity"]', TWENTY_SECONDS_TIMEOUT).contains(
SAMPLE_RULE.severity,
TWENTY_SECONDS_TIMEOUT
);

// Validate tags
SAMPLE_RULE.tags.forEach((tag) =>
cy
.get('[data-test-subj="rule_flyout_rule_tags"]', TWENTY_SECONDS_TIMEOUT)
.contains(tag, TWENTY_SECONDS_TIMEOUT)
);

// Validate references
cy.get('[data-test-subj="rule_flyout_rule_references"]', TWENTY_SECONDS_TIMEOUT).contains(
SAMPLE_RULE.references,
TWENTY_SECONDS_TIMEOUT
);

// Validate false positives
cy.get(
'[data-test-subj="rule_flyout_rule_false_positives"]',
TWENTY_SECONDS_TIMEOUT
).contains(SAMPLE_RULE.falsePositive, TWENTY_SECONDS_TIMEOUT);

// Validate status
cy.get('[data-test-subj="rule_flyout_rule_status"]', TWENTY_SECONDS_TIMEOUT).contains(
SAMPLE_RULE.status,
TWENTY_SECONDS_TIMEOUT
);

// Validate detection
SAMPLE_RULE.detectionLine.forEach((line) =>
cy
.get('[data-test-subj="rule_flyout_rule_detection"]', TWENTY_SECONDS_TIMEOUT)
.contains(line, TWENTY_SECONDS_TIMEOUT)
);

// Close the flyout
cy.get('[data-test-subj="euiFlyoutCloseButton"]', TWENTY_SECONDS_TIMEOUT).click({
force: true,
});
});

// Confirm flyout closed
cy.contains(`[data-test-subj="rule_flyout_${SAMPLE_RULE.name}"]`).should('not.exist');
});
});

after(() => {
// Deleting test rules
cy.deleteRule(SAMPLE_RULE.name);
});
});
1 change: 1 addition & 0 deletions cypress/support/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import '../utils/plugins/anomaly-detection-dashboards-plugin/commands';
import '../utils/plugins/security/commands';
import '../utils/plugins/security-dashboards-plugin/commands';
import '../utils/plugins/alerting-dashboards-plugin/commands';
import '../utils/plugins/security-analytics-dashboards-plugin/commands'

// Alternatively you can use CommonJS syntax:
// require('./commands')
Expand Down
1 change: 1 addition & 0 deletions cypress/utils/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ export * from './plugins/query-workbench-dashboards/constants';
export * from './plugins/security/constants';
export * from './plugins/notifications-dashboards/constants';
export * from './plugins/security-dashboards-plugin/constants';
export * from './plugins/security-analytics-dashboards-plugin/constants'
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

const { NODE_API } = require('./constants');

// ***********************************************
// This example commands.js shows you how to
// create various custom commands and overwrite
// existing commands.
//
// For more comprehensive examples of custom
// commands please read more here:
// https://on.cypress.io/custom-commands
// ***********************************************
//
//
// -- This is a parent command --


Cypress.Commands.add('createRule', (ruleJSON) => {
cy.request('POST', `${Cypress.env('opensearch')}${NODE_API.RULES_BASE}`, ruleJSON);
});

Cypress.Commands.add('updateRule', (ruleId, ruleJSON) => {
cy.request('PUT', `${Cypress.env('opensearch')}/${NODE_API.RULES_BASE}/${ruleId}`, ruleJSON);
});

Cypress.Commands.add('deleteRule', (ruleName) => {
const body = {
from: 0,
size: 5000,
query: {
nested: {
path: 'rule',
query: {
bool: {
must: [{ match: { 'rule.title': 'Cypress test rule' } }],
},
},
},
},
};
cy.request({
method: 'POST',
url: `${Cypress.env('opensearch')}${NODE_API.RULES_BASE}/_search?pre_packaged=false`,
failOnStatusCode: false,
body,
}).then((response) => {
if (response.status === 200) {
for (let hit of response.body.hits.hits) {
if (hit._source.title === ruleName)
cy.request(
'DELETE',
`${Cypress.env('opensearch')}${NODE_API.RULES_BASE}/${hit._id}?forced=true`
);
}
}
});
});

Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

export const TWENTY_SECONDS_TIMEOUT = { timeout: 20000 };

export const INDICES = {
DETECTORS_INDEX: '.opensearch-detectors-config',
PRE_PACKAGED_RULES_INDEX: '.opensearch-pre-packaged-rules-config',
CUSTOM_RULES_INDEX: '.opensearch-custom-rules-config',
};

export const PLUGIN_NAME = 'opensearch_security_analytics_dashboards';
export const BASE_API_PATH = '/_plugins/_security_analytics';

export const NODE_API = {
DETECTORS_BASE: `${BASE_API_PATH}/detectors`,
SEARCH_DETECTORS: `${BASE_API_PATH}/detectors/_search`,
INDICES_BASE: `${BASE_API_PATH}/indices`,
GET_FINDINGS: `${BASE_API_PATH}/findings/_search`,
DOCUMENT_IDS_QUERY: `${BASE_API_PATH}/document_ids_query`,
TIME_RANGE_QUERY: `${BASE_API_PATH}/time_range_query`,
MAPPINGS_BASE: `${BASE_API_PATH}/mappings`,
MAPPINGS_VIEW: `${BASE_API_PATH}/mappings/view`,
GET_ALERTS: `${BASE_API_PATH}/alerts`,
RULES_BASE: `${BASE_API_PATH}/rules`,
CHANNELS: `${BASE_API_PATH}/_notifications/channels`,
ACKNOWLEDGE_ALERTS: `${BASE_API_PATH}/detectors/{detector_id}/_acknowledge/alerts`,
INDEX_TEMPLATE_BASE: '/_index_template',
};
Empty file added site/index.html
Empty file.
8 changes: 8 additions & 0 deletions site/js/dashboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,14 @@ const plugins = {
],
},
},
'security-analytics-dashboards-plugin': {
name: 'securityAnalyticsDashboards',
default: {
videos: [
'rules_spec.js',
],
},
},
};

// eslint-disable-next-line no-unused-vars
Expand Down
1 change: 1 addition & 0 deletions test_finder.sh
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ OSD_COMPONENT_TEST_MAP=( "OpenSearch-Dashboards:opensearch-dashboards"
"notificationsDashboards:notifications-dashboards"
"customImportMapDashboards:custom-import-map-dashboards"
"searchRelevanceDashboards:search-relevance-dashboards"
"securityAnalyticsDashboards:security-analytics-dashboards-plugin"
)

[ -f $OSD_BUILD_MANIFEST ] && TEST_TYPE="manifest" || TEST_TYPE="default"
Expand Down

0 comments on commit eb10f43

Please sign in to comment.