mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[ResponseOps][Alerting] Fix stackAlerts plugin missing rac API auth scope (#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
This commit is contained in:
parent
71c8d6fddc
commit
17fcaa5c8e
6 changed files with 66 additions and 26 deletions
|
@ -73,7 +73,7 @@ export const BUILT_IN_ALERTS_FEATURE: KibanaFeatureConfig = {
|
|||
all: [],
|
||||
read: [],
|
||||
},
|
||||
api: [],
|
||||
api: ['rac'],
|
||||
ui: [],
|
||||
},
|
||||
read: {
|
||||
|
@ -108,7 +108,7 @@ export const BUILT_IN_ALERTS_FEATURE: KibanaFeatureConfig = {
|
|||
all: [],
|
||||
read: [],
|
||||
},
|
||||
api: [],
|
||||
api: ['rac'],
|
||||
ui: [],
|
||||
},
|
||||
},
|
||||
|
|
|
@ -126,6 +126,14 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) {
|
|||
spaces: ['*'],
|
||||
},
|
||||
],
|
||||
elasticsearch: {
|
||||
indices: [
|
||||
{
|
||||
names: ['.alerts-*'],
|
||||
privileges: ['read'],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
only_actions_role: {
|
||||
kibana: [
|
||||
|
|
|
@ -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: {
|
||||
|
@ -511,6 +528,7 @@ export const allRoles = [
|
|||
securitySolutionOnlyReadSpacesAll,
|
||||
observabilityOnlyAllSpacesAll,
|
||||
logsOnlyAllSpacesAll,
|
||||
stackAlertsOnlyReadSpacesAll,
|
||||
stackAlertsOnlyAllSpacesAll,
|
||||
observabilityOnlyReadSpacesAll,
|
||||
observabilityOnlyAllSpacesAllWithReadESIndices,
|
||||
|
|
|
@ -30,7 +30,8 @@ import {
|
|||
observabilityMinReadAlertsAllSpacesAll,
|
||||
observabilityOnlyAllSpacesAllWithReadESIndices,
|
||||
securitySolutionOnlyAllSpacesAllWithReadESIndices,
|
||||
stackAlertsOnlyAllSpacesAll,
|
||||
stackAlertsOnlyReadSpacesAll as stackAlertsOnlyReadSpacesAllRole,
|
||||
stackAlertsOnlyAllSpacesAll as stackAlertsOnlyAllSpacesAllRole,
|
||||
} from './roles';
|
||||
import { User } from './types';
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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 = {
|
||||
|
@ -297,7 +304,8 @@ export const allUsers = [
|
|||
secOnlyReadSpacesAll,
|
||||
obsOnlySpacesAll,
|
||||
logsOnlySpacesAll,
|
||||
stackAlertsOnlySpacesAll,
|
||||
stackAlertsOnlyReadSpacesAll,
|
||||
stackAlertsOnlyAllSpacesAll,
|
||||
obsSecSpacesAll,
|
||||
obsSecReadSpacesAll,
|
||||
obsMinReadAlertsRead,
|
||||
|
|
|
@ -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';
|
||||
|
@ -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[];
|
||||
};
|
||||
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
|
|
@ -13,7 +13,7 @@ import {
|
|||
obsOnlySpacesAll,
|
||||
logsOnlySpacesAll,
|
||||
secOnlySpacesAllEsReadAll,
|
||||
stackAlertsOnlySpacesAll,
|
||||
stackAlertsOnlyAllSpacesAll,
|
||||
superUser,
|
||||
} from '../../../common/lib/authentication/users';
|
||||
|
||||
|
@ -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,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue