Skip to content
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
6 changes: 4 additions & 2 deletions x-pack/test/apm_api_integration/common/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { registry } from './registry';
interface Config {
name: APMFtrConfigName;
license: 'basic' | 'trial';
kibanaConfig?: Record<string, string>;
kibanaConfig?: Record<string, string | string[]>;
}

const supertestAsApmUser = (kibanaServer: UrlObject, apmUser: ApmUser) => async (
Expand Down Expand Up @@ -81,7 +81,9 @@ export function createTestConfig(config: Config) {
serverArgs: [
...xPackAPITestsConfig.get('kbnTestServer.serverArgs'),
...(kibanaConfig
? Object.entries(kibanaConfig).map(([key, value]) => `--${key}=${value}`)
? Object.entries(kibanaConfig).map(([key, value]) =>
Array.isArray(value) ? `--${key}=${JSON.stringify(value)}` : `--${key}=${value}`
)
: []),
],
},
Expand Down
131 changes: 80 additions & 51 deletions x-pack/test/apm_api_integration/tests/alerts/rule_registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,11 @@ export default function ApiTest({ getService }: FtrProviderContext) {
const BULK_INDEX_DELAY = 1000;
const INDEXING_DELAY = 5000;

const ALERTS_INDEX_TARGET = '.alerts-observability.apm.alerts*';
const getAlertsTargetIndicesUrl =
'/api/observability/rules/alerts/dynamic_index_pattern?namespace=default&registrationContexts=observability.apm&registrationContexts=';

const getAlertsTargetIndices = async () =>
supertest.get(getAlertsTargetIndicesUrl).send().set('kbn-xsrf', 'foo');
const APM_METRIC_INDEX_NAME = 'apm-8.0.0-transaction';

const createTransactionMetric = (override: Record<string, any>) => {
Expand Down Expand Up @@ -93,6 +97,13 @@ export default function ApiTest({ getService }: FtrProviderContext) {
.get(`/api/alerts/alert/${alert.id}`)
.set('kbn-xsrf', 'foo');

const { body: targetIndices, status: targetIndicesStatus } = await getAlertsTargetIndices();
if (targetIndices.length === 0) {
const error = new Error('Error getting alert');
Object.assign(error, { response: { body: targetIndices, status: targetIndicesStatus } });
throw error;
}

if (status >= 300) {
const error = new Error('Error getting alert');
Object.assign(error, { response: { body, status } });
Expand All @@ -105,10 +116,22 @@ export default function ApiTest({ getService }: FtrProviderContext) {
await new Promise((resolve) => {
setTimeout(resolve, BULK_INDEX_DELAY);
});
await es.indices.refresh({
index: ALERTS_INDEX_TARGET,
});

/**
* When calling refresh on an index pattern .alerts-observability.apm.alerts* (as was originally the hard-coded string in this test)
* The response from Elasticsearch is a 200, even if no indices which match that index pattern have been created.
* When calling refresh on a concrete index alias .alerts-observability.apm.alerts-default for instance,
* we receive a 404 error index_not_found_exception when no indices have been created which match that alias (obviously).
* Since we are receiving a concrete index alias from the observability api instead of a kibana index pattern
* and we understand / expect that this index does not exist at certain points of the test, we can try-catch at certain points without caring if the call fails.
* There are points in the code where we do want to ensure we get the appropriate error message back
*/
try {
await es.indices.refresh({
index: targetIndices[0],
});
// eslint-disable-next-line no-empty
} catch (exc) {}
return nextAlert;
}

Expand All @@ -121,20 +144,17 @@ export default function ApiTest({ getService }: FtrProviderContext) {

registry.when('Rule registry with write enabled', { config: 'rules', archives: [] }, () => {
it('does not bootstrap indices on plugin startup', async () => {
const { body } = await es.indices.get({
index: ALERTS_INDEX_TARGET,
expand_wildcards: 'open',
allow_no_indices: true,
});

const indices = Object.entries(body).map(([indexName, index]) => {
return {
indexName,
index,
};
});

expect(indices.length).to.be(0);
const { body: targetIndices } = await getAlertsTargetIndices();
try {
const res = await es.indices.get({
index: targetIndices[0],
expand_wildcards: 'open',
allow_no_indices: true,
});
expect(res).to.be.empty();
} catch (exc) {
expect(exc.statusCode).to.eql(404);
}
});

describe('when creating a rule', () => {
Expand Down Expand Up @@ -233,6 +253,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
});

after(async () => {
const { body: targetIndices } = await getAlertsTargetIndices();
if (createResponse.alert) {
const { body, status } = await supertest
.delete(`/api/alerts/alert/${createResponse.alert.id}`)
Expand All @@ -246,7 +267,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
}

await es.deleteByQuery({
index: ALERTS_INDEX_TARGET,
index: targetIndices[0],
body: {
query: {
match_all: {},
Expand All @@ -264,25 +285,29 @@ export default function ApiTest({ getService }: FtrProviderContext) {
expect(createResponse.status).to.be.below(299);

expect(createResponse.alert).not.to.be(undefined);

let alert = await waitUntilNextExecution(createResponse.alert);

const beforeDataResponse = await es.search({
index: ALERTS_INDEX_TARGET,
body: {
query: {
term: {
[EVENT_KIND]: 'signal',
const { body: targetIndices } = await getAlertsTargetIndices();

try {
const res = await es.search({
index: targetIndices[0],
body: {
query: {
term: {
[EVENT_KIND]: 'signal',
},
},
size: 1,
sort: {
'@timestamp': 'desc',
},
},
size: 1,
sort: {
'@timestamp': 'desc',
},
},
});

expect(beforeDataResponse.body.hits.hits.length).to.be(0);
});
expect(res).to.be.empty();
} catch (exc) {
expect(exc.message).contain('index_not_found_exception');
}

await es.index({
index: APM_METRIC_INDEX_NAME,
Expand All @@ -296,22 +321,25 @@ export default function ApiTest({ getService }: FtrProviderContext) {

alert = await waitUntilNextExecution(alert);

const afterInitialDataResponse = await es.search({
index: ALERTS_INDEX_TARGET,
body: {
query: {
term: {
[EVENT_KIND]: 'signal',
try {
const res = await es.search({
index: targetIndices[0],
body: {
query: {
term: {
[EVENT_KIND]: 'signal',
},
},
size: 1,
sort: {
'@timestamp': 'desc',
},
},
size: 1,
sort: {
'@timestamp': 'desc',
},
},
});

expect(afterInitialDataResponse.body.hits.hits.length).to.be(0);
});
expect(res).to.be.empty();
} catch (exc) {
expect(exc.message).contain('index_not_found_exception');
}

await es.index({
index: APM_METRIC_INDEX_NAME,
Expand All @@ -326,7 +354,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
alert = await waitUntilNextExecution(alert);

const afterViolatingDataResponse = await es.search({
index: ALERTS_INDEX_TARGET,
index: targetIndices[0],
body: {
query: {
term: {
Expand Down Expand Up @@ -438,7 +466,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
alert = await waitUntilNextExecution(alert);

const afterRecoveryResponse = await es.search({
index: ALERTS_INDEX_TARGET,
index: targetIndices[0],
body: {
query: {
term: {
Expand Down Expand Up @@ -531,9 +559,10 @@ export default function ApiTest({ getService }: FtrProviderContext) {

registry.when('Rule registry with write not enabled', { config: 'basic', archives: [] }, () => {
it('does not bootstrap the apm rule indices', async () => {
const { body: targetIndices } = await getAlertsTargetIndices();
const errorOrUndefined = await es.indices
.get({
index: ALERTS_INDEX_TARGET,
index: targetIndices[0],
expand_wildcards: 'open',
allow_no_indices: false,
})
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
* 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 expect from '@kbn/expect';

import { superUser, obsOnlySpacesAll, secOnlyRead } from '../../../common/lib/authentication/users';
import type { User } from '../../../common/lib/authentication/types';
import { FtrProviderContext } from '../../../common/ftr_provider_context';
import { getSpaceUrlPrefix } from '../../../common/lib/authentication/spaces';

// eslint-disable-next-line import/no-default-export
export default ({ getService }: FtrProviderContext) => {
const supertestWithoutAuth = getService('supertestWithoutAuth');
const esArchiver = getService('esArchiver');

const TEST_URL = '/internal/rac/alerts';
const ALERTS_INDEX_URL = `${TEST_URL}/index`;
const SPACE1 = 'space1';
const APM_ALERT_INDEX = '.alerts-observability.apm.alerts';
const SECURITY_SOLUTION_ALERT_INDEX = '.alerts-security.alerts';

const getAPMIndexName = async (user: User, space: string, expected: number = 200) => {
const {
body: indexNames,
}: { body: { index_name: string[] | undefined } } = await supertestWithoutAuth
.get(`${getSpaceUrlPrefix(space)}${ALERTS_INDEX_URL}?features=apm`)
.auth(user.username, user.password)
.set('kbn-xsrf', 'true')
.expect(expected);
return indexNames;
};

const getSecuritySolutionIndexName = async (
user: User,
space: string,
expectedStatusCode: number = 200
) => {
const {
body: indexNames,
}: { body: { index_name: string[] | undefined } } = await supertestWithoutAuth
.get(`${getSpaceUrlPrefix(space)}${ALERTS_INDEX_URL}?features=siem`)
.auth(user.username, user.password)
.set('kbn-xsrf', 'true')
.expect(expectedStatusCode);
return indexNames;
};

describe('Alert - Get Index - RBAC - spaces', () => {
before(async () => {
await esArchiver.load('x-pack/test/functional/es_archives/rule_registry/alerts');
});
describe('Users:', () => {
it(`${obsOnlySpacesAll.username} should be able to access the APM alert in ${SPACE1}`, async () => {
const indexNames = await getAPMIndexName(obsOnlySpacesAll, SPACE1);
const observabilityIndex = indexNames?.index_name?.find(
(indexName) => indexName === APM_ALERT_INDEX
);
expect(observabilityIndex).to.eql(APM_ALERT_INDEX); // assert this here so we can use constants in the dynamically-defined test cases below
});

it(`${superUser.username} should be able to access the APM alert in ${SPACE1}`, async () => {
const indexNames = await getAPMIndexName(superUser, SPACE1);
const observabilityIndex = indexNames?.index_name?.find(
(indexName) => indexName === APM_ALERT_INDEX
);
expect(observabilityIndex).to.eql(APM_ALERT_INDEX); // assert this here so we can use constants in the dynamically-defined test cases below
});

it(`${secOnlyRead.username} should NOT be able to access the APM alert in ${SPACE1}`, async () => {
const indexNames = await getAPMIndexName(secOnlyRead, SPACE1);
expect(indexNames?.index_name?.length).to.eql(0);
});

it(`${secOnlyRead.username} should be able to access the security solution alert in ${SPACE1}`, async () => {
const indexNames = await getSecuritySolutionIndexName(secOnlyRead, SPACE1);
const securitySolution = indexNames?.index_name?.find((indexName) =>
indexName.startsWith(SECURITY_SOLUTION_ALERT_INDEX)
);
expect(securitySolution).to.eql(`${SECURITY_SOLUTION_ALERT_INDEX}-${SPACE1}`); // assert this here so we can use constants in the dynamically-defined test cases below
});
});
});
};
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,6 @@ export default ({ loadTestFile, getService }: FtrProviderContext): void => {
loadTestFile(require.resolve('./update_alert'));
loadTestFile(require.resolve('./bulk_update_alerts'));
loadTestFile(require.resolve('./find_alerts'));
loadTestFile(require.resolve('./get_alerts_index'));
});
};