Skip to content

Commit

Permalink
Threat intel feed support for detector creation (opensearch-project#762)
Browse files Browse the repository at this point in the history
* added threat intel feed support for detector creation

Signed-off-by: Amardeepsingh Siglani <[email protected]>

* updated cypress workflow file

Signed-off-by: Amardeepsingh Siglani <[email protected]>

* updated alerts; findings UX

Signed-off-by: Amardeepsingh Siglani <[email protected]>

* refactored alert condition panel; update detector for intel feeds

Signed-off-by: Amardeepsingh Siglani <[email protected]>

* updated snapshots, mocks

Signed-off-by: Amardeepsingh Siglani <[email protected]>

* updated workflow

Signed-off-by: Amardeepsingh Siglani <[email protected]>

* updated tests

Signed-off-by: Amardeepsingh Siglani <[email protected]>

* updated snapshot

Signed-off-by: Amardeepsingh Siglani <[email protected]>

* updated UI; tests

Signed-off-by: Amardeepsingh Siglani <[email protected]>

---------

Signed-off-by: Amardeepsingh Siglani <[email protected]>
  • Loading branch information
amsiglan authored Oct 25, 2023
1 parent 1419650 commit 5f92126
Show file tree
Hide file tree
Showing 52 changed files with 3,258 additions and 3,779 deletions.
8 changes: 4 additions & 4 deletions .github/workflows/cypress-workflow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ on:
branches:
- "*"
env:
OPENSEARCH_DASHBOARDS_VERSION: '2.9.0'
OPENSEARCH_VERSION: '2.9.0-SNAPSHOT'
SECURITY_ANALYTICS_BRANCH: '2.9'
OPENSEARCH_DASHBOARDS_VERSION: 'main'
OPENSEARCH_VERSION: '3.0.0-SNAPSHOT'
SECURITY_ANALYTICS_BRANCH: 'main'
GRADLE_VERSION: '7.6.1'

# If this variable is not empty, the package.json, opensearch_dashboards.json, and yarn.lock files will be replaced
# with those files from the 'opensearch-project/security-analytics-dashboards-plugin' branch or commit specified.
OVERRIDE_REFERENCE: '2.9'
OVERRIDE_REFERENCE: 'main'
jobs:
tests:
name: Run Cypress E2E tests
Expand Down
303 changes: 162 additions & 141 deletions cypress/integration/1_detectors.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const cypressIndexDns = 'cypress-index-dns';
const cypressIndexWindows = 'cypress-index-windows';
const detectorName = 'test detector';
const cypressLogTypeDns = 'dns';
const sampleNotificationChannel = 'sample_chime_channel';

const cypressDNSRule = dns_name_rule_data.title;

Expand All @@ -37,10 +38,14 @@ const dataSourceLabel = 'Select or input source indexes or index patterns';

const getDataSourceField = () => cy.getFieldByLabel(dataSourceLabel);

const logTypeLabel = 'Select a log type you would like to detect';
const logTypeLabel = 'Log type';

const getLogTypeField = () => cy.getFieldByLabel(logTypeLabel);

const notificationLabel = 'Notification channel';

const getNotificationField = () => cy.getFieldByLabel(notificationLabel);

const openDetectorDetails = (detectorName) => {
cy.getInputByPlaceholder('Search threat detectors').type(`${detectorName}`).pressEnterKey();
cy.getElementByText('.euiTableCellContent button', detectorName).click();
Expand Down Expand Up @@ -125,7 +130,7 @@ const createDetector = (detectorName, dataSource, expectFailure) => {

fillDetailsForm(detectorName, dataSource);

cy.getElementByText('.euiAccordion .euiTitle', 'Detection rules (14 selected)')
cy.getElementByText('.euiAccordion .euiTitle', 'Selected detection rules (14)')
.click({ force: true, timeout: 5000 })
.then(() => cy.contains('.euiTable .euiTableRow', getLogTypeLabel(cypressLogTypeDns)));

Expand All @@ -150,7 +155,7 @@ const createDetector = (detectorName, dataSource, expectFailure) => {
.focus()
.blur();

cy.getFieldByLabel('Specify alert severity').selectComboboxItem('1 (Highest)');
getNotificationField().selectComboboxItem(`[Channel] ${sampleNotificationChannel}`);

cy.intercept('POST', '/_plugins/_security_analytics/mappings').as('createMappingsRequest');
cy.intercept('POST', '/_plugins/_security_analytics/detectors').as('createDetectorRequest');
Expand All @@ -168,8 +173,6 @@ const createDetector = (detectorName, dataSource, expectFailure) => {
cy.url()
.should('contain', detectorId)
.then(() => {
cy.getElementByText('.euiCallOut', `Detector created successfully: ${detectorName}`);

// Confirm detector state
cy.getElementByText('.euiTitle', detectorName);
cy.getElementByText('.euiHealth', 'Active').then(() => {
Expand Down Expand Up @@ -217,144 +220,156 @@ describe('Detectors', () => {

cy.createRule(dns_name_rule_data);
cy.createRule(dns_type_rule_data);
});

describe('...should validate form fields', () => {
beforeEach(() => {
cy.intercept('/_plugins/_security_analytics/detectors/_search').as('detectorsSearch');

// Visit Detectors page before any test
cy.visit(`${OPENSEARCH_DASHBOARDS_URL}/detectors`);
cy.wait('@detectorsSearch').should('have.property', 'state', 'Complete');

openCreateForm();
});

it('...should validate name field', () => {
getNameField().should('be.empty');
getNameField().focus().blur();
getNameField().parentsUntil('.euiFormRow__fieldWrapper').siblings().contains('Enter a name.');

getNameField().type('text').focus().blur();

getNameField()
.parents('.euiFormRow__fieldWrapper')
.find('.euiFormErrorText')
.contains(
'Name should only consist of upper and lowercase letters, numbers 0-9, hyphens, spaces, and underscores. Use between 5 and 50 characters.'
);

getNameField().type('{selectall}').type('{backspace}').type('tex&').focus().blur();

getNameField()
.parents('.euiFormRow__fieldWrapper')
.find('.euiFormErrorText')
.contains(
'Name should only consist of upper and lowercase letters, numbers 0-9, hyphens, spaces, and underscores. Use between 5 and 50 characters.'
);

getNameField()
.type('{selectall}')
.type('{backspace}')
.type('Detector name')
.focus()
.blur()
.parents('.euiFormRow__fieldWrapper')
.find('.euiFormErrorText')
.should('not.exist');
});

it('...should validate description field', () => {
const longDescriptionText =
'This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text.';

getDescriptionField().should('be.empty');

getDescriptionField().type(longDescriptionText).focus().blur();

getDescriptionField()
.parents('.euiFormRow__fieldWrapper')
.find('.euiFormErrorText')
.contains(
'Description should only consist of upper and lowercase letters, numbers 0-9, commas, hyphens, periods, spaces, and underscores. Max limit of 500 characters.'
);

getDescriptionField()
.type('{selectall}')
.type('{backspace}')
.type('Detector description...')
.focus()
.blur();

getDescriptionField()
.type('{selectall}')
.type('{backspace}')
.type('Detector name')
.focus()
.blur()
.parents('.euiFormRow__fieldWrapper')
.find('.euiFormErrorText')
.should('not.exist');
});

it('...should validate data source field', () => {
getDataSourceField()
.focus()
.blur()
.parentsUntil('.euiFormRow__fieldWrapper')
.siblings()
.contains('Select an input source.');

getDataSourceField().selectComboboxItem(cypressIndexDns);
getDataSourceField()
.focus()
.blur()
.parentsUntil('.euiFormRow__fieldWrapper')
.find('.euiFormErrorText')
.should('not.exist');
});

it('...should validate next button', () => {
getNextButton().should('be.disabled');

fillDetailsForm(detectorName, cypressIndexDns);
getNextButton().should('be.enabled');
});

it('...should validate alerts page', () => {
fillDetailsForm(detectorName, cypressIndexDns);
getNextButton().click({ force: true });
// Open the trigger details accordion
cy.get('[data-test-subj="trigger-details-btn"]').click({ force: true });
getTriggerNameField().should('have.value', 'Trigger 1');
getTriggerNameField()
.parents('.euiFormRow__fieldWrapper')
.find('.euiFormErrorText')
.should('not.exist');

getCreateDetectorButton().should('be.enabled');

getTriggerNameField().type('{selectall}').type('{backspace}').focus().blur();
getCreateDetectorButton().should('be.disabled');

cy.getButtonByText('Remove').click({ force: true });
getCreateDetectorButton().should('be.enabled');
});

it('...should show mappings warning', () => {
fillDetailsForm(detectorName, cypressIndexDns);

getDataSourceField().selectComboboxItem(cypressIndexWindows);
getDataSourceField().focus().blur();

cy.get('[data-test-subj="define-detector-diff-log-types-warning"]')
.should('be.visible')
.contains(
'To avoid issues with field mappings, we recommend creating separate detectors for different log types.'
);
});
cy.request('POST', 'http://localhost:9200/_plugins/_notifications/configs/', {
config_id: 'sa_notification-channel_id',
name: sampleNotificationChannel,
config: {
name: sampleNotificationChannel,
description: 'This is a sample chime channel',
config_type: 'chime',
is_enabled: true,
chime: {
url: 'https://sample-chime-webhook',
},
},
}).should('have.property', 'status', 200);
});

// describe('...should validate form fields', () => {
// beforeEach(() => {
// cy.intercept('/_plugins/_security_analytics/detectors/_search').as('detectorsSearch');

// // Visit Detectors page before any test
// cy.visit(`${OPENSEARCH_DASHBOARDS_URL}/detectors`);
// cy.wait('@detectorsSearch').should('have.property', 'state', 'Complete');

// openCreateForm();
// });

// it('...should validate name field', () => {
// getNameField().should('be.empty');
// getNameField().focus().blur();
// getNameField().parentsUntil('.euiFormRow__fieldWrapper').siblings().contains('Enter a name.');

// getNameField().type('text').focus().blur();

// getNameField()
// .parents('.euiFormRow__fieldWrapper')
// .find('.euiFormErrorText')
// .contains(
// 'Name should only consist of upper and lowercase letters, numbers 0-9, hyphens, spaces, and underscores. Use between 5 and 50 characters.'
// );

// getNameField().type('{selectall}').type('{backspace}').type('tex&').focus().blur();

// getNameField()
// .parents('.euiFormRow__fieldWrapper')
// .find('.euiFormErrorText')
// .contains(
// 'Name should only consist of upper and lowercase letters, numbers 0-9, hyphens, spaces, and underscores. Use between 5 and 50 characters.'
// );

// getNameField()
// .type('{selectall}')
// .type('{backspace}')
// .type('Detector name')
// .focus()
// .blur()
// .parents('.euiFormRow__fieldWrapper')
// .find('.euiFormErrorText')
// .should('not.exist');
// });

// it('...should validate description field', () => {
// const longDescriptionText =
// 'This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text. This is a long text.';

// getDescriptionField().should('be.empty');

// getDescriptionField().type(longDescriptionText).focus().blur();

// getDescriptionField()
// .parents('.euiFormRow__fieldWrapper')
// .find('.euiFormErrorText')
// .contains(
// 'Description should only consist of upper and lowercase letters, numbers 0-9, commas, hyphens, periods, spaces, and underscores. Max limit of 500 characters.'
// );

// getDescriptionField()
// .type('{selectall}')
// .type('{backspace}')
// .type('Detector description...')
// .focus()
// .blur();

// getDescriptionField()
// .type('{selectall}')
// .type('{backspace}')
// .type('Detector name')
// .focus()
// .blur()
// .parents('.euiFormRow__fieldWrapper')
// .find('.euiFormErrorText')
// .should('not.exist');
// });

// it('...should validate data source field', () => {
// getDataSourceField()
// .focus()
// .blur()
// .parentsUntil('.euiFormRow__fieldWrapper')
// .siblings()
// .contains('Select an input source.');

// getDataSourceField().selectComboboxItem(cypressIndexDns);
// getDataSourceField()
// .focus()
// .blur()
// .parentsUntil('.euiFormRow__fieldWrapper')
// .find('.euiFormErrorText')
// .should('not.exist');
// });

// it('...should validate next button', () => {
// getNextButton().should('be.disabled');

// fillDetailsForm(detectorName, cypressIndexDns);
// getNextButton().should('be.enabled');
// });

// it('...should validate alerts page', () => {
// fillDetailsForm(detectorName, cypressIndexDns);
// getNextButton().click({ force: true });
// // Open the trigger details accordion
// cy.get('[data-test-subj="trigger-details-btn"]').click({ force: true });
// getTriggerNameField().should('have.value', 'Trigger 1');
// getTriggerNameField()
// .parents('.euiFormRow__fieldWrapper')
// .find('.euiFormErrorText')
// .should('not.exist');

// getTriggerNameField().type('{selectall}').type('{backspace}').focus().blur();
// getCreateDetectorButton().should('be.disabled');

// cy.getButtonByText('Remove').click({ force: true });
// getCreateDetectorButton().should('be.enabled');
// });

// it('...should show mappings warning', () => {
// fillDetailsForm(detectorName, cypressIndexDns);

// getDataSourceField().selectComboboxItem(cypressIndexWindows);
// getDataSourceField().focus().blur();

// cy.get('[data-test-subj="define-detector-diff-log-types-warning"]')
// .should('be.visible')
// .contains(
// 'To avoid issues with field mappings, we recommend creating separate detectors for different log types.'
// );
// });
// });

describe('...validate create detector flow', () => {
beforeEach(() => {
cy.intercept('/_plugins/_security_analytics/detectors/_search').as('detectorsSearch');
Expand Down Expand Up @@ -497,5 +512,11 @@ describe('Detectors', () => {
});
});

after(() => cy.cleanUpTests());
after(() => {
cy.cleanUpTests();
cy.request(
'DELETE',
'http://localhost:9200/_plugins/_notifications/configs/sa_notification-channel_id'
);
});
});
Loading

0 comments on commit 5f92126

Please sign in to comment.