Skip to content

Commit

Permalink
[ResponseOps][Alerting] Fix stackAlerts plugin missing rac API auth s…
Browse files Browse the repository at this point in the history
…cope (#193948)

## Summary

Adds the `['rac']` API access scope to the Stack Alerts feature to
correctly authenticate alerts API endpoints with the `stackAlerts`
permission.
Also adds a dedicated API integration test for the impacted endpoint and
permission set.

## Release note

Fix Stack Alerts feature API access control

## To verify

1. Create rules that fire alerts in Stack management
2. Wait for alerts to be created
3. Create a role with only `Stack Management > Rules : Read` privilege
4. Create a user with that role
5. In another window, open Kibana with the newly created user
6. Check that the Stack Management > Alerts page renders correctly, not
showing any missing 403 error toasts
  • Loading branch information
umbopepato authored Oct 7, 2024
1 parent 71c8d6f commit 17fcaa5
Show file tree
Hide file tree
Showing 6 changed files with 66 additions and 26 deletions.
4 changes: 2 additions & 2 deletions x-pack/plugins/stack_alerts/server/feature.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ export const BUILT_IN_ALERTS_FEATURE: KibanaFeatureConfig = {
all: [],
read: [],
},
api: [],
api: ['rac'],
ui: [],
},
read: {
Expand Down Expand Up @@ -108,7 +108,7 @@ export const BUILT_IN_ALERTS_FEATURE: KibanaFeatureConfig = {
all: [],
read: [],
},
api: [],
api: ['rac'],
ui: [],
},
},
Expand Down
8 changes: 8 additions & 0 deletions x-pack/test/functional_with_es_ssl/config.base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,14 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) {
spaces: ['*'],
},
],
elasticsearch: {
indices: [
{
names: ['.alerts-*'],
privileges: ['read'],
},
],
},
},
only_actions_role: {
kibana: [
Expand Down
18 changes: 18 additions & 0 deletions x-pack/test/rule_registry/common/lib/authentication/roles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,23 @@ export const logsOnlyAllSpacesAll: Role = {
},
};

export const stackAlertsOnlyReadSpacesAll: Role = {
name: 'stack_alerts_only_read_spaces_all',
privileges: {
elasticsearch: {
indices: [],
},
kibana: [
{
feature: {
stackAlerts: ['read'],
},
spaces: ['*'],
},
],
},
};

export const stackAlertsOnlyAllSpacesAll: Role = {
name: 'stack_alerts_only_all_spaces_all',
privileges: {
Expand Down Expand Up @@ -511,6 +528,7 @@ export const allRoles = [
securitySolutionOnlyReadSpacesAll,
observabilityOnlyAllSpacesAll,
logsOnlyAllSpacesAll,
stackAlertsOnlyReadSpacesAll,
stackAlertsOnlyAllSpacesAll,
observabilityOnlyReadSpacesAll,
observabilityOnlyAllSpacesAllWithReadESIndices,
Expand Down
16 changes: 12 additions & 4 deletions x-pack/test/rule_registry/common/lib/authentication/users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ import {
observabilityMinReadAlertsAllSpacesAll,
observabilityOnlyAllSpacesAllWithReadESIndices,
securitySolutionOnlyAllSpacesAllWithReadESIndices,
stackAlertsOnlyAllSpacesAll,
stackAlertsOnlyReadSpacesAll as stackAlertsOnlyReadSpacesAllRole,
stackAlertsOnlyAllSpacesAll as stackAlertsOnlyAllSpacesAllRole,
} from './roles';
import { User } from './types';

Expand Down Expand Up @@ -130,6 +131,12 @@ export const obsOnlyReadSpacesAll: User = {
roles: [observabilityOnlyReadSpacesAll.name],
};

export const stackAlertsOnlyReadSpacesAll: User = {
username: 'stack_alerts_only_read_spaces_all',
password: 'stack_alerts_only_read_spaces_all',
roles: [stackAlertsOnlyReadSpacesAllRole.name],
};

export const users = [
superUser,
secOnly,
Expand Down Expand Up @@ -177,10 +184,10 @@ export const logsOnlySpacesAll: User = {
roles: [logsOnlyAllSpacesAll.name],
};

export const stackAlertsOnlySpacesAll: User = {
export const stackAlertsOnlyAllSpacesAll: User = {
username: 'stack_alerts_only_all_spaces_all',
password: 'stack_alerts_only_all_spaces_all',
roles: [stackAlertsOnlyAllSpacesAll.name],
roles: [stackAlertsOnlyAllSpacesAllRole.name],
};

export const obsOnlySpacesAllEsRead: User = {
Expand Down Expand Up @@ -297,7 +304,8 @@ export const allUsers = [
secOnlyReadSpacesAll,
obsOnlySpacesAll,
logsOnlySpacesAll,
stackAlertsOnlySpacesAll,
stackAlertsOnlyReadSpacesAll,
stackAlertsOnlyAllSpacesAll,
obsSecSpacesAll,
obsSecReadSpacesAll,
obsMinReadAlertsRead,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@

import expect from '@kbn/expect';

import { superUser, obsOnlySpacesAll, secOnlyRead } from '../../../common/lib/authentication/users';
import {
superUser,
obsOnlySpacesAll,
secOnlyRead,
stackAlertsOnlyReadSpacesAll,
} 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';
Expand All @@ -22,27 +27,19 @@ export default ({ getService }: FtrProviderContext) => {
const SPACE1 = 'space1';
const APM_ALERT_INDEX = '.alerts-observability.apm.alerts-default';
const SECURITY_SOLUTION_ALERT_INDEX = '.alerts-security.alerts';
const STACK_ALERT_INDEX = '.alerts-stack.alerts-default';

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

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

return resp.body.index_name as string[];
};

Expand All @@ -52,24 +49,33 @@ export default ({ getService }: FtrProviderContext) => {
});
describe('Users:', () => {
it(`${obsOnlySpacesAll.username} should be able to access the APM alert in ${SPACE1}`, async () => {
const indexNames = await getAPMIndexName(obsOnlySpacesAll, SPACE1);
const indexNames = await getIndexName(['apm'], obsOnlySpacesAll, SPACE1);
expect(indexNames.includes(APM_ALERT_INDEX)).to.eql(true); // 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 indexNames = await getIndexName(['apm'], superUser, SPACE1);
expect(indexNames.includes(APM_ALERT_INDEX)).to.eql(true); // 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);
const indexNames = await getIndexName(['apm'], secOnlyRead, SPACE1);
expect(indexNames?.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 indexNames = await getIndexName(['siem'], secOnlyRead, SPACE1);
expect(indexNames.includes(`${SECURITY_SOLUTION_ALERT_INDEX}-${SPACE1}`)).to.eql(true); // assert this here so we can use constants in the dynamically-defined test cases below
});

it(`${stackAlertsOnlyReadSpacesAll.username} should be able to access the stack alert in ${SPACE1}`, async () => {
const indexNames = await getIndexName(
['stackAlerts'],
stackAlertsOnlyReadSpacesAll,
SPACE1
);
expect(indexNames.includes(STACK_ALERT_INDEX)).to.eql(true);
});
});
});
};
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
obsOnlySpacesAll,
logsOnlySpacesAll,
secOnlySpacesAllEsReadAll,
stackAlertsOnlySpacesAll,
stackAlertsOnlyAllSpacesAll,
superUser,
} from '../../../common/lib/authentication/users';

Expand Down Expand Up @@ -360,8 +360,8 @@ export default ({ getService }: FtrProviderContext) => {
const result = await secureBsearch.send<RuleRegistrySearchResponse>({
supertestWithoutAuth,
auth: {
username: stackAlertsOnlySpacesAll.username,
password: stackAlertsOnlySpacesAll.password,
username: stackAlertsOnlyAllSpacesAll.username,
password: stackAlertsOnlyAllSpacesAll.password,
},
referer: 'test',
kibanaVersion,
Expand Down

0 comments on commit 17fcaa5

Please sign in to comment.