Skip to content

Commit

Permalink
adds kibana security feature privileges and updates alerts actions to…
Browse files Browse the repository at this point in the history
… include space id in constructor rather than parameter as a part of the get since the spaceId will be available to us in the start phase of the plugin
  • Loading branch information
dhurley14 committed Apr 1, 2021
1 parent 166a879 commit 1bbba1c
Show file tree
Hide file tree
Showing 4 changed files with 269 additions and 8 deletions.
28 changes: 28 additions & 0 deletions x-pack/plugins/features/common/feature_kibana_privileges.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,34 @@ export interface FeatureKibanaPrivileges {
*/
read?: readonly string[];
};

/**
* Solutions should specify owners of alerts here which will provide the solution read / write access to those alerts.
*/
alerts?: {
/**
* List of owners of alerts which users should have full read/write access to when granted this privilege.
* @example
* ```ts
* {
* all: ['securitySolution']
* }
* ```
*/
all?: readonly string[];

/**
* List of owners of alerts which users should have read-only access to when granted this privilege.
* @example
* ```ts
* {
* read: ['securitySolution', 'observability']
* }
* ```
*/
read?: readonly string[];
};

/**
* If your feature requires access to specific saved objects, then specify your access needs here.
*/
Expand Down
12 changes: 4 additions & 8 deletions x-pack/plugins/security/server/authorization/actions/alerts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,11 @@ import { isString } from 'lodash';
export class AlertsActions {
private readonly prefix: string;

constructor(versionNumber: string) {
this.prefix = `alerts:${versionNumber}:`;
constructor(versionNumber: string, spaceId: string) {
this.prefix = `alerts:${spaceId}:${versionNumber}`;
}

public get(spaceId: string, owner: string, operation: string): string {
if (!spaceId || !isString(spaceId)) {
throw new Error('"spaceId" is required and must be a string');
}

public get(owner: string, operation: string): string {
if (!operation || !isString(operation)) {
throw new Error('"operation" is required and must be a string');
}
Expand All @@ -27,6 +23,6 @@ export class AlertsActions {
throw new Error('"owner" is required and must be a string');
}

return `${this.prefix}${spaceId}/${owner}/${operation}`;
return `${this.prefix}:${owner}/${operation}`;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
/*
* 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 type { FeatureKibanaPrivileges } from '../../../../../features/server';
import { KibanaFeature } from '../../../../../features/server';
import { Actions } from '../../actions';
import { FeaturePrivilegeAlertsBuilder } from './alerts';

const version = '1.0.0-zeta1';

describe(`alerts`, () => {
describe(`feature_privilege_builder`, () => {
it('grants no privileges by default', () => {
const actions = new Actions(version);
const alertsFeaturePrivileges = new FeaturePrivilegealertsBuilder(actions);

const privilege: FeatureKibanaPrivileges = {
savedObject: {
all: [],
read: [],
},
ui: [],
};

const feature = new KibanaFeature({
id: 'my-feature',
name: 'my-feature',
app: [],
category: { id: 'foo', label: 'foo' },
privileges: {
all: privilege,
read: privilege,
},
});

expect(alertsFeaturePrivileges.getActions(privilege, feature)).toEqual([]);
});

describe(`within feature`, () => {
it('grants `read` privileges under feature', () => {
const actions = new Actions(version);
const alertsFeaturePrivilege = new FeaturePrivilegealertsBuilder(actions);

const privilege: FeatureKibanaPrivileges = {
alerts: {
read: ['observability'],
},

savedObject: {
all: [],
read: [],
},
ui: [],
};

const feature = new KibanaFeature({
id: 'my-feature',
name: 'my-feature',
app: [],
category: { id: 'foo', label: 'foo' },
privileges: {
all: privilege,
read: privilege,
},
});

expect(alertsFeaturePrivilege.getActions(privilege, feature)).toMatchInlineSnapshot(`
Array [
"alerts:1.0.0-zeta1:observability/get",
"alerts:1.0.0-zeta1:observability/find",
]
`);
});

it('grants `all` privileges under feature', () => {
const actions = new Actions(version);
const alertsFeaturePrivilege = new FeaturePrivilegealertsBuilder(actions);

const privilege: FeatureKibanaPrivileges = {
alerts: {
all: ['security'],
},

savedObject: {
all: [],
read: [],
},
ui: [],
};

const feature = new KibanaFeature({
id: 'my-feature',
name: 'my-feature',
app: [],
category: { id: 'foo', label: 'foo' },
privileges: {
all: privilege,
read: privilege,
},
});

expect(alertsFeaturePrivilege.getActions(privilege, feature)).toMatchInlineSnapshot(`
Array [
"alerts:1.0.0-zeta1:security/get",
"alerts:1.0.0-zeta1:security/find",
"alerts:1.0.0-zeta1:security/create",
"alerts:1.0.0-zeta1:security/delete",
"alerts:1.0.0-zeta1:security/update",
]
`);
});

it('grants both `all` and `read` privileges under feature', () => {
const actions = new Actions(version);
const alertsFeaturePrivilege = new FeaturePrivilegealertsBuilder(actions);

const privilege: FeatureKibanaPrivileges = {
alerts: {
all: ['security'],
read: ['obs'],
},

savedObject: {
all: [],
read: [],
},
ui: [],
};

const feature = new KibanaFeature({
id: 'my-feature',
name: 'my-feature',
app: [],
category: { id: 'foo', label: 'foo' },
privileges: {
all: privilege,
read: privilege,
},
});

expect(alertsFeaturePrivilege.getActions(privilege, feature)).toMatchInlineSnapshot(`
Array [
"alerts:1.0.0-zeta1:security/get",
"alerts:1.0.0-zeta1:security/find",
"alerts:1.0.0-zeta1:security/create",
"alerts:1.0.0-zeta1:security/delete",
"alerts:1.0.0-zeta1:security/update",
"alerts:1.0.0-zeta1:obs/get",
"alerts:1.0.0-zeta1:obs/find",
]
`);
});

it('grants both `all` and `read` privileges under feature with multiple values in alerts array', () => {
const actions = new Actions(version);
const alertsFeaturePrivilege = new FeaturePrivilegealertsBuilder(actions);

const privilege: FeatureKibanaPrivileges = {
alerts: {
all: ['security', 'other-security'],
read: ['obs', 'other-obs'],
},

savedObject: {
all: [],
read: [],
},
ui: [],
};

const feature = new KibanaFeature({
id: 'my-feature',
name: 'my-feature',
app: [],
category: { id: 'foo', label: 'foo' },
privileges: {
all: privilege,
read: privilege,
},
});

expect(alertsFeaturePrivilege.getActions(privilege, feature)).toMatchInlineSnapshot(`
Array [
"alerts:1.0.0-zeta1:security/get",
"alerts:1.0.0-zeta1:security/find",
"alerts:1.0.0-zeta1:security/create",
"alerts:1.0.0-zeta1:security/delete",
"alerts:1.0.0-zeta1:security/update",
"alerts:1.0.0-zeta1:other-security/get",
"alerts:1.0.0-zeta1:other-security/find",
"alerts:1.0.0-zeta1:other-security/create",
"alerts:1.0.0-zeta1:other-security/delete",
"alerts:1.0.0-zeta1:other-security/update",
"alerts:1.0.0-zeta1:obs/get",
"alerts:1.0.0-zeta1:obs/find",
"alerts:1.0.0-zeta1:other-obs/get",
"alerts:1.0.0-zeta1:other-obs/find",
]
`);
});
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* 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 { uniq } from 'lodash';

import type { FeatureKibanaPrivileges } from '../../../../../features/server';
import { BaseFeaturePrivilegeBuilder } from './feature_privilege_builder';

const readOperations: string[] = ['get', 'find'];
const writeOperations: string[] = ['update'];
const allOperations: string[] = [...readOperations, ...writeOperations];

export class FeaturePrivilegeAlertsBuilder extends BaseFeaturePrivilegeBuilder {
public getActions(privilegeDefinition: FeatureKibanaPrivileges): string[] {
const getAlertsPrivilege = (operations: string[], owners: readonly string[]) => {
return owners.flatMap((owner) =>
operations.map((operation) => this.actions.alerts.get(operation, owner))
);
};

return uniq([
...getAlertsPrivilege(allOperations, privilegeDefinition.alerts?.all ?? []),
...getAlertsPrivilege(readOperations, privilegeDefinition.alerts?.read ?? []),
]);
}
}

0 comments on commit 1bbba1c

Please sign in to comment.