Skip to content

Commit

Permalink
[Detection Engine][MKI] Audit alerts FTRs in prep for MKI (#179211)
Browse files Browse the repository at this point in the history
## Summary

Begins work on #169185 and
#151877 .

Related to #151877 this PR:
- Moves FTR tests under the `/alerts` folder that do not require
Platinum license into the basics folder. Tests in
`/alerts/trial_license_complete_tier` folder should now relate to
functionality that requires the higher license tier.
- Rearranged some of the folder structure so that it was clear what the
intent of the tests is
- Makes note of any issues in tickets that we will need to follow up on

Related to #169185 this PR:
- Ensures that tests are properly tagged for ESS & serverless
- Ensures none of the tests that are critical contain the `@skipInQA`
tag
  • Loading branch information
yctercero authored Mar 28, 2024
1 parent 8a5d327 commit 7698d9c
Show file tree
Hide file tree
Showing 20 changed files with 187 additions and 250 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,19 @@
*/

import expect from '@kbn/expect';
import { parse as parseCookie } from 'tough-cookie';
import { adminTestUser } from '@kbn/test';
import { ALERT_WORKFLOW_STATUS } from '@kbn/rule-data-utils';
import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';

import {
DETECTION_ENGINE_SIGNALS_STATUS_URL,
DETECTION_ENGINE_QUERY_SIGNALS_URL,
} from '@kbn/security-solution-plugin/common/constants';
import { ROLES } from '@kbn/security-solution-plugin/common/test';
import { DetectionAlert } from '@kbn/security-solution-plugin/common/api/detection_engine';
import { setAlertStatus, getAlertUpdateByQueryEmptyResponse, refreshIndex } from '../../../utils';
import {
setAlertStatus,
getAlertUpdateByQueryEmptyResponse,
refreshIndex,
} from '../../../../utils';
import {
createAlertsIndex,
deleteAllAlerts,
Expand All @@ -28,28 +29,21 @@ import {
getAlertsByIds,
waitForRuleSuccess,
getRuleForAlertTesting,
} from '../../../../../../common/utils/security_solution';
import {
createUserAndRole,
deleteUserAndRole,
} from '../../../../../../common/services/security_solution';
import { FtrProviderContext } from '../../../../../ftr_provider_context';
import { EsArchivePathBuilder } from '../../../../../es_archive_path_builder';
} from '../../../../../../../common/utils/security_solution';
import { FtrProviderContext } from '../../../../../../ftr_provider_context';
import { EsArchivePathBuilder } from '../../../../../../es_archive_path_builder';

export default ({ getService }: FtrProviderContext) => {
const supertest = getService('supertest');
const esArchiver = getService('esArchiver');
const supertestWithoutAuth = getService('supertestWithoutAuth');
const log = getService('log');
const es = getService('es');
// TODO: add a new service for loading archiver files similar to "getService('es')"
const config = getService('config');
const isServerless = config.get('serverless');
const dataPathBuilder = new EsArchivePathBuilder(isServerless);
const path = dataPathBuilder.getPath('auditbeat/hosts');

// Failing: See https://github.com/elastic/kibana/issues/170753
describe.skip('@ess @serverless open_close_alerts', () => {
describe('@ess @serverless change alert status endpoints', () => {
describe('validation checks', () => {
describe('update by ids', () => {
it('should not give errors when querying and the alerts index does not exist yet', async () => {
Expand Down Expand Up @@ -142,41 +136,13 @@ export default ({ getService }: FtrProviderContext) => {
await waitForAlertsToBePresent(supertest, log, 10, [id]);
const alertsOpen = await getAlertsByIds(supertest, log, [id]);
expect(alertsOpen.hits.hits.length).equal(10);
});

it('should be have set the alerts in an open state initially', async () => {
const rule = {
...getRuleForAlertTesting(['auditbeat-*']),
query: 'process.executable: "/usr/bin/sudo"',
};
const { id } = await createRule(supertest, log, rule);
await waitForRuleSuccess({ supertest, log, id });
await waitForAlertsToBePresent(supertest, log, 10, [id]);
const alertsOpen = await getAlertsByIds(supertest, log, [id]);
const everyAlertOpen = alertsOpen.hits.hits.every(
(hit) => hit._source?.[ALERT_WORKFLOW_STATUS] === 'open'
);
expect(everyAlertOpen).to.eql(true);
});

it('should be able to close alerts while logged in and populate workflow_user', async () => {
// Login so we can test changing alert status within an interactive session
// We write `profile_uid` to `kibana.alert.workflow_user` if it's available,
// but `profile_uid` is only available in interactive sessions
const response = await supertestWithoutAuth
.post('/internal/security/login')
.set('kbn-xsrf', 'xxx')
.send({
providerType: 'basic',
providerName: 'basic',
currentURL: '/',
params: { username: adminTestUser.username, password: adminTestUser.password },
})
.expect(200);

const cookies = response.header['set-cookie'];
expect(cookies).to.have.length(1);

it('should set alerts to acknowledged', async () => {
const rule = {
...getRuleForAlertTesting(['auditbeat-*']),
query: 'process.executable: "/usr/bin/sudo"',
Expand All @@ -187,37 +153,23 @@ export default ({ getService }: FtrProviderContext) => {
const alertsOpen = await getAlertsByIds(supertest, log, [id]);
const alertIds = alertsOpen.hits.hits.map((alert) => alert._id);

// set all of the alerts to the state of closed. There is no reason to use a waitUntil here
// as this route intentionally has a waitFor within it and should only return when the query has
// the data.
await supertestWithoutAuth
await supertest
.post(DETECTION_ENGINE_SIGNALS_STATUS_URL)
.set('kbn-xsrf', 'true')
.set('Cookie', parseCookie(cookies[0])!.cookieString())
.send(setAlertStatus({ alertIds, status: 'closed' }))
.send(setAlertStatus({ alertIds, status: 'acknowledged' }))
.expect(200);

await refreshIndex(es, '.alerts-security.alerts-default*');

const { body: alertsClosed }: { body: estypes.SearchResponse<DetectionAlert> } =
const { body: alertsAcknowledged }: { body: estypes.SearchResponse<DetectionAlert> } =
await supertest
.post(DETECTION_ENGINE_QUERY_SIGNALS_URL)
.set('kbn-xsrf', 'true')
.send(getQueryAlertIds(alertIds))
.expect(200);
expect(alertsClosed.hits.hits.length).to.equal(10);
const everyAlertClosed = alertsClosed.hits.hits.every(
(hit) => hit._source?.['kibana.alert.workflow_status'] === 'closed'
);
expect(everyAlertClosed).to.eql(true);
const everyAlertWorkflowUserExists = alertsClosed.hits.hits.every(
(hit) => hit._source?.['kibana.alert.workflow_user'] !== null
);
expect(everyAlertWorkflowUserExists).to.eql(true);
const everyAlertWorkflowStatusUpdatedAtExists = alertsClosed.hits.hits.every(
(hit) => hit._source?.['kibana.alert.workflow_status_updated_at'] !== null

const everyAlertAcknowledged = alertsAcknowledged.hits.hits.every(
(hit) => hit._source?.['kibana.alert.workflow_status'] === 'acknowledged'
);
expect(everyAlertWorkflowStatusUpdatedAtExists).to.eql(true);
expect(everyAlertAcknowledged).to.eql(true);
});

it('should be able close alerts without logging in and workflow_user is set to null', async () => {
Expand Down Expand Up @@ -262,75 +214,6 @@ export default ({ getService }: FtrProviderContext) => {
);
expect(everyAlertWorkflowStatusUpdatedAtExists).to.eql(true);
});
// This fails and should be investigated or removed if it no longer applies
it.skip('should be able to close alerts with t1 analyst user', async () => {
const rule = getRuleForAlertTesting(['auditbeat-*']);
const { id } = await createRule(supertest, log, rule);
await waitForRuleSuccess({ supertest, log, id });
await waitForAlertsToBePresent(supertest, log, 1, [id]);
await createUserAndRole(getService, ROLES.t1_analyst);
const alertsOpen = await getAlertsByIds(supertest, log, [id]);
const alertIds = alertsOpen.hits.hits.map((alert) => alert._id);

// Try to set all of the alerts to the state of closed.
// This should not be possible with the given user.
await supertestWithoutAuth
.post(DETECTION_ENGINE_SIGNALS_STATUS_URL)
.set('kbn-xsrf', 'true')
.auth(ROLES.t1_analyst, 'changeme')
.send(setAlertStatus({ alertIds, status: 'closed' }))
.expect(200);

// query for the alerts with the superuser
// to allow a check that the alerts were NOT closed with t1 analyst
const { body: alertsClosed }: { body: estypes.SearchResponse<DetectionAlert> } =
await supertest
.post(DETECTION_ENGINE_QUERY_SIGNALS_URL)
.set('kbn-xsrf', 'true')
.send(getQueryAlertIds(alertIds))
.expect(200);

const everyAlertClosed = alertsClosed.hits.hits.every(
(hit) => hit._source?.['kibana.alert.workflow_status'] === 'closed'
);
expect(everyAlertClosed).to.eql(true);

await deleteUserAndRole(getService, ROLES.t1_analyst);
});
// This fails and should be investigated or removed if it no longer applies
it.skip('should be able to close alerts with soc_manager user', async () => {
const rule = getRuleForAlertTesting(['auditbeat-*']);
const { id } = await createRule(supertest, log, rule);
await waitForRuleSuccess({ supertest, log, id });
await waitForAlertsToBePresent(supertest, log, 1, [id]);
const userAndRole = ROLES.soc_manager;
await createUserAndRole(getService, userAndRole);
const alertsOpen = await getAlertsByIds(supertest, log, [id]);
const alertIds = alertsOpen.hits.hits.map((alert) => alert._id);

// Try to set all of the alerts to the state of closed.
// This should not be possible with the given user.
await supertestWithoutAuth
.post(DETECTION_ENGINE_SIGNALS_STATUS_URL)
.set('kbn-xsrf', 'true')
.auth(userAndRole, 'changeme') // each user has the same password
.send(setAlertStatus({ alertIds, status: 'closed' }))
.expect(200);

const { body: alertsClosed }: { body: estypes.SearchResponse<DetectionAlert> } =
await supertest
.post(DETECTION_ENGINE_QUERY_SIGNALS_URL)
.set('kbn-xsrf', 'true')
.send(getQueryAlertIds(alertIds))
.expect(200);

const everyAlertClosed = alertsClosed.hits.hits.every(
(hit) => hit._source?.['kibana.alert.workflow_status'] === 'closed'
);
expect(everyAlertClosed).to.eql(true);

await deleteUserAndRole(getService, userAndRole);
});
});
});
});
Expand Down
Loading

0 comments on commit 7698d9c

Please sign in to comment.