[ML] Moving privileges checks to server (#39585)

* [ML] Moving privileges checks to server

* removing unused include

* adding tests

* adding general kibana type

* adding coments and extra test

* fixing type issue
This commit is contained in:
James Gowdy 2019-06-26 17:33:51 +01:00 committed by GitHub
parent 3e48e62a47
commit 4f28b25daf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 842 additions and 246 deletions

View file

@ -4,5 +4,4 @@
* you may not use this file except in compliance with the Elastic License.
*/
export type Cluster = Record<string, boolean>;
export type Privileges = Record<string, boolean>;
export type callWithRequestType = (action: string, params?: any) => Promise<any>;

View file

@ -0,0 +1,70 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
//
export interface Privileges {
// Anomaly Detection
canGetJobs: boolean;
canCreateJob: boolean;
canDeleteJob: boolean;
canOpenJob: boolean;
canCloseJob: boolean;
canForecastJob: boolean;
canGetDatafeeds: boolean;
canStartStopDatafeed: boolean;
canUpdateJob: boolean;
canUpdateDatafeed: boolean;
canPreviewDatafeed: boolean;
// Calendars
canGetCalendars: boolean;
canCreateCalendar: boolean;
canDeleteCalendar: boolean;
// Filters
canGetFilters: boolean;
canCreateFilter: boolean;
canDeleteFilter: boolean;
// File Data Visualizer
canFindFileStructure: boolean;
// Data Frames
canGetDataFrameJobs: boolean;
canDeleteDataFrameJob: boolean;
canPreviewDataFrameJob: boolean;
canCreateDataFrameJob: boolean;
canStartStopDataFrameJob: boolean;
}
export function getDefaultPrivileges(): Privileges {
return {
// Anomaly Detection
canGetJobs: false,
canCreateJob: false,
canDeleteJob: false,
canOpenJob: false,
canCloseJob: false,
canForecastJob: false,
canGetDatafeeds: false,
canStartStopDatafeed: false,
canUpdateJob: false,
canUpdateDatafeed: false,
canPreviewDatafeed: false,
// Calendars
canGetCalendars: false,
canCreateCalendar: false,
canDeleteCalendar: false,
// Filters
canGetFilters: false,
canCreateFilter: false,
canDeleteFilter: false,
// File Data Visualizer
canFindFileStructure: false,
// Data Frames
canGetDataFrameJobs: false,
canDeleteDataFrameJob: false,
canPreviewDataFrameJob: false,
canCreateDataFrameJob: false,
canStartStopDataFrameJob: false,
};
}

View file

@ -139,6 +139,6 @@ export async function hasImportPermission(indexName) {
];
}
const resp = await ml.checkPrivilege(priv);
const resp = await ml.hasPrivileges(priv);
return (resp.securityDisabled === true || resp.has_all_requested === true);
}

View file

@ -9,10 +9,10 @@ import { i18n } from '@kbn/i18n';
// @ts-ignore
import { hasLicenseExpired } from '../license/check_license';
import { Privileges } from './common';
import { Privileges, getDefaultPrivileges } from '../../common/types/privileges';
import { getPrivileges } from './get_privileges';
let privileges: Privileges = {};
let privileges: Privileges = getDefaultPrivileges();
export function checkGetJobsPrivilege(kbnUrl: any): Promise<Privileges> {
return new Promise((resolve, reject) => {
@ -100,7 +100,7 @@ export function checkCreateDataFrameJobsPrivilege(kbnUrl: any): Promise<Privileg
// check the privilege type and the license to see whether a user has permission to access a feature.
// takes the name of the privilege variable as specified in get_privileges.js
export function checkPermission(privilegeType: string) {
export function checkPermission(privilegeType: keyof Privileges) {
const licenseHasExpired = hasLicenseExpired();
return privileges[privilegeType] === true && licenseHasExpired !== true;
}

View file

@ -5,249 +5,21 @@
*/
import { ml } from '../services/ml_api_service';
// @ts-ignore
import { setUpgradeInProgress } from '../services/upgrade_service';
import { Cluster, Privileges } from './common';
import { setUpgradeInProgress } from '../services/upgrade_service';
import { Privileges, getDefaultPrivileges } from '../../common/types/privileges';
export function getPrivileges(): Promise<Privileges> {
const privileges: Privileges = {
// Anomaly Detection
canGetJobs: false,
canCreateJob: false,
canDeleteJob: false,
canOpenJob: false,
canCloseJob: false,
canForecastJob: false,
canGetDatafeeds: false,
canStartStopDatafeed: false,
canUpdateJob: false,
canUpdateDatafeed: false,
canPreviewDatafeed: false,
// Calendars
canGetCalendars: false,
canCreateCalendar: false,
canDeleteCalendar: false,
// Filters
canGetFilters: false,
canCreateFilter: false,
canDeleteFilter: false,
// File Data Visualizer
canFindFileStructure: false,
// Data Frames
canGetDataFrameJobs: false,
canDeleteDataFrameJob: false,
canPreviewDataFrameJob: false,
canCreateDataFrameJob: false,
canStartStopDataFrameJob: false,
};
return new Promise((resolve, reject) => {
const priv = {
cluster: [
'cluster:monitor/data_frame/get',
'cluster:monitor/data_frame/stats/get',
'cluster:monitor/xpack/ml/job/get',
'cluster:monitor/xpack/ml/job/stats/get',
'cluster:monitor/xpack/ml/datafeeds/get',
'cluster:monitor/xpack/ml/datafeeds/stats/get',
'cluster:monitor/xpack/ml/calendars/get',
'cluster:admin/data_frame/delete',
'cluster:admin/data_frame/preview',
'cluster:admin/data_frame/put',
'cluster:admin/data_frame/start',
'cluster:admin/data_frame/start_task',
'cluster:admin/data_frame/stop',
'cluster:admin/xpack/ml/job/put',
'cluster:admin/xpack/ml/job/delete',
'cluster:admin/xpack/ml/job/update',
'cluster:admin/xpack/ml/job/open',
'cluster:admin/xpack/ml/job/close',
'cluster:admin/xpack/ml/job/forecast',
'cluster:admin/xpack/ml/datafeeds/put',
'cluster:admin/xpack/ml/datafeeds/delete',
'cluster:admin/xpack/ml/datafeeds/start',
'cluster:admin/xpack/ml/datafeeds/stop',
'cluster:admin/xpack/ml/datafeeds/update',
'cluster:admin/xpack/ml/datafeeds/preview',
'cluster:admin/xpack/ml/calendars/put',
'cluster:admin/xpack/ml/calendars/delete',
'cluster:admin/xpack/ml/calendars/jobs/update',
'cluster:admin/xpack/ml/calendars/events/post',
'cluster:admin/xpack/ml/calendars/events/delete',
'cluster:admin/xpack/ml/filters/put',
'cluster:admin/xpack/ml/filters/get',
'cluster:admin/xpack/ml/filters/update',
'cluster:admin/xpack/ml/filters/delete',
'cluster:monitor/xpack/ml/findfilestructure',
],
};
ml.checkPrivilege(priv)
.then(resp => {
if (resp.upgradeInProgress === true) {
ml.checkMlPrivileges()
.then(({ privileges, upgradeInProgress }) => {
if (upgradeInProgress === true) {
setUpgradeInProgress(true);
// only check for getting endpoints
// force all to be true if security is disabled
setGettingPrivileges(resp.cluster, privileges, resp.securityDisabled === true);
} else if (resp.securityDisabled) {
// if security has been disabled, securityDisabled is returned from the endpoint
// therefore set all privileges to true
Object.keys(privileges).forEach(k => (privileges[k] = true));
} else {
setGettingPrivileges(resp.cluster, privileges);
setActionPrivileges(resp.cluster, privileges);
}
resolve(privileges);
})
.catch(() => {
reject(privileges);
reject(getDefaultPrivileges());
});
});
}
function setGettingPrivileges(
cluster: Cluster = {},
privileges: Privileges = {},
forceTrue = false
) {
// Anomaly Detection
if (
forceTrue ||
(cluster['cluster:monitor/xpack/ml/job/get'] &&
cluster['cluster:monitor/xpack/ml/job/stats/get'])
) {
privileges.canGetJobs = true;
}
if (
forceTrue ||
(cluster['cluster:monitor/xpack/ml/datafeeds/get'] &&
cluster['cluster:monitor/xpack/ml/datafeeds/stats/get'])
) {
privileges.canGetDatafeeds = true;
}
// Calendars
if (forceTrue || cluster['cluster:monitor/xpack/ml/calendars/get']) {
privileges.canGetCalendars = true;
}
// Filters
if (forceTrue || cluster['cluster:admin/xpack/ml/filters/get']) {
privileges.canGetFilters = true;
}
// File Data Visualizer
if (forceTrue || cluster['cluster:monitor/xpack/ml/findfilestructure']) {
privileges.canFindFileStructure = true;
}
// Data Frames
if (
forceTrue ||
(cluster['cluster:monitor/data_frame/get'] && cluster['cluster:monitor/data_frame/stats/get'])
) {
privileges.canGetDataFrameJobs = true;
}
}
function setActionPrivileges(cluster: Cluster = {}, privileges: Privileges = {}) {
// Anomaly Detection
if (
cluster['cluster:admin/xpack/ml/job/put'] &&
cluster['cluster:admin/xpack/ml/job/open'] &&
cluster['cluster:admin/xpack/ml/datafeeds/put']
) {
privileges.canCreateJob = true;
}
if (cluster['cluster:admin/xpack/ml/job/update']) {
privileges.canUpdateJob = true;
}
if (cluster['cluster:admin/xpack/ml/job/open']) {
privileges.canOpenJob = true;
}
if (cluster['cluster:admin/xpack/ml/job/close']) {
privileges.canCloseJob = true;
}
if (cluster['cluster:admin/xpack/ml/job/forecast']) {
privileges.canForecastJob = true;
}
if (
cluster['cluster:admin/xpack/ml/job/delete'] &&
cluster['cluster:admin/xpack/ml/datafeeds/delete']
) {
privileges.canDeleteJob = true;
}
if (
cluster['cluster:admin/xpack/ml/job/open'] &&
cluster['cluster:admin/xpack/ml/datafeeds/start'] &&
cluster['cluster:admin/xpack/ml/datafeeds/stop']
) {
privileges.canStartStopDatafeed = true;
}
if (cluster['cluster:admin/xpack/ml/datafeeds/update']) {
privileges.canUpdateDatafeed = true;
}
if (cluster['cluster:admin/xpack/ml/datafeeds/preview']) {
privileges.canPreviewDatafeed = true;
}
// Calendars
if (
cluster['cluster:admin/xpack/ml/calendars/put'] &&
cluster['cluster:admin/xpack/ml/calendars/jobs/update'] &&
cluster['cluster:admin/xpack/ml/calendars/events/post']
) {
privileges.canCreateCalendar = true;
}
if (
cluster['cluster:admin/xpack/ml/calendars/delete'] &&
cluster['cluster:admin/xpack/ml/calendars/events/delete']
) {
privileges.canDeleteCalendar = true;
}
// Filters
if (
cluster['cluster:admin/xpack/ml/filters/put'] &&
cluster['cluster:admin/xpack/ml/filters/update']
) {
privileges.canCreateFilter = true;
}
if (cluster['cluster:admin/xpack/ml/filters/delete']) {
privileges.canDeleteFilter = true;
}
// Data Frames
if (cluster['cluster:admin/data_frame/put']) {
privileges.canCreateDataFrameJob = true;
}
if (cluster['cluster:admin/data_frame/delete']) {
privileges.canDeleteDataFrameJob = true;
}
if (cluster['cluster:admin/data_frame/preview']) {
privileges.canPreviewDataFrameJob = true;
}
if (
cluster['cluster:admin/data_frame/start'] &&
cluster['cluster:admin/data_frame/start_task'] &&
cluster['cluster:admin/data_frame/stop']
) {
privileges.canStartStopDataFrameJob = true;
}
}

View file

@ -5,6 +5,7 @@
*/
import { Annotation } from '../../../common/types/annotations';
import { Privileges } from '../../../common/types/privileges';
// TODO This is not a complete representation of all methods of `ml.*`.
// It just satisfies needs for other parts of the code area which use
@ -30,7 +31,8 @@ declare interface Ml {
stopDataFrameTransformsJob(jobId: string): Promise<any>;
};
checkPrivilege(obj: object): Promise<any>;
hasPrivileges(obj: object): Promise<any>;
checkMlPrivileges(): Promise<{ privileges: Privileges; upgradeInProgress: boolean }>;
esSearch: any;
getIndices(): Promise<EsIndex[]>;

View file

@ -211,7 +211,7 @@ export const ml = {
});
},
checkPrivilege(obj) {
hasPrivileges(obj) {
return http({
url: `${basePath}/_has_privileges`,
method: 'POST',
@ -219,6 +219,13 @@ export const ml = {
});
},
checkMlPrivileges() {
return http({
url: `${basePath}/ml_privileges`,
method: 'GET',
});
},
getNotificationSettings() {
return http({
url: `${basePath}/notification_settings`,

View file

@ -4,13 +4,12 @@
* you may not use this file except in compliance with the Elastic License.
*/
let upgradeInProgress: boolean = false;
let upgradeInProgress = false;
export function setUpgradeInProgress(show) {
export function setUpgradeInProgress(show: boolean) {
upgradeInProgress = show;
}
export function isUpgradeInProgress() {
export function isUpgradeInProgress(): boolean {
return upgradeInProgress;
}

View file

@ -0,0 +1,170 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
export function callWithRequestProvider(testType: string) {
switch (testType) {
case 'partialPrivileges':
return partialPrivileges;
case 'fullPrivileges':
return fullPrivileges;
case 'upgradeWithFullPrivileges':
return upgradeWithFullPrivileges;
case 'upgradeWithPartialPrivileges':
return upgradeWithPartialPrivileges;
default:
return fullPrivileges;
}
}
const fullPrivileges = async (action: string, params?: any) => {
switch (action) {
case 'ml.privilegeCheck':
return {
username: 'test2',
has_all_requested: false,
cluster: fullClusterPrivileges,
index: {},
application: {},
};
case 'ml.info':
return { upgrade_mode: false };
default:
break;
}
};
const partialPrivileges = async (action: string, params?: any) => {
switch (action) {
case 'ml.privilegeCheck':
return {
username: 'test2',
has_all_requested: false,
cluster: partialClusterPrivileges,
index: {},
application: {},
};
case 'ml.info':
return { upgrade_mode: false };
default:
break;
}
};
const upgradeWithFullPrivileges = async (action: string, params?: any) => {
switch (action) {
case 'ml.privilegeCheck':
return {
username: 'elastic',
has_all_requested: true,
cluster: fullClusterPrivileges,
index: {},
application: {},
};
case 'ml.info':
return { upgrade_mode: true };
default:
break;
}
};
const upgradeWithPartialPrivileges = async (action: string, params?: any) => {
switch (action) {
case 'ml.privilegeCheck':
return {
username: 'test2',
has_all_requested: false,
cluster: partialClusterPrivileges,
index: {},
application: {},
};
case 'ml.info':
return { upgrade_mode: true };
default:
break;
}
};
const fullClusterPrivileges = {
'cluster:admin/xpack/ml/datafeeds/delete': true,
'cluster:admin/xpack/ml/datafeeds/update': true,
'cluster:admin/xpack/ml/job/forecast': true,
'cluster:monitor/xpack/ml/job/stats/get': true,
'cluster:admin/xpack/ml/filters/delete': true,
'cluster:admin/xpack/ml/datafeeds/preview': true,
'cluster:admin/xpack/ml/datafeeds/start': true,
'cluster:admin/xpack/ml/filters/put': true,
'cluster:admin/xpack/ml/datafeeds/stop': true,
'cluster:monitor/xpack/ml/calendars/get': true,
'cluster:admin/xpack/ml/filters/get': true,
'cluster:monitor/xpack/ml/datafeeds/get': true,
'cluster:admin/data_frame/start_task': true,
'cluster:admin/xpack/ml/filters/update': true,
'cluster:admin/xpack/ml/calendars/events/post': true,
'cluster:monitor/data_frame/get': true,
'cluster:admin/xpack/ml/job/close': true,
'cluster:monitor/xpack/ml/datafeeds/stats/get': true,
'cluster:admin/data_frame/preview': true,
'cluster:admin/xpack/ml/calendars/jobs/update': true,
'cluster:admin/data_frame/put': true,
'cluster:admin/xpack/ml/calendars/put': true,
'cluster:monitor/data_frame/stats/get': true,
'cluster:admin/data_frame/stop': true,
'cluster:admin/xpack/ml/calendars/events/delete': true,
'cluster:admin/xpack/ml/datafeeds/put': true,
'cluster:admin/xpack/ml/job/open': true,
'cluster:admin/xpack/ml/job/delete': true,
'cluster:monitor/xpack/ml/job/get': true,
'cluster:admin/data_frame/delete': true,
'cluster:admin/xpack/ml/job/put': true,
'cluster:admin/xpack/ml/job/update': true,
'cluster:admin/data_frame/start': true,
'cluster:admin/xpack/ml/calendars/delete': true,
'cluster:monitor/xpack/ml/findfilestructure': true,
};
// the same as ml_user role
const partialClusterPrivileges = {
'cluster:admin/xpack/ml/datafeeds/delete': false,
'cluster:admin/xpack/ml/datafeeds/update': false,
'cluster:admin/xpack/ml/job/forecast': false,
'cluster:monitor/xpack/ml/job/stats/get': true,
'cluster:admin/xpack/ml/filters/delete': false,
'cluster:admin/xpack/ml/datafeeds/preview': false,
'cluster:admin/xpack/ml/datafeeds/start': false,
'cluster:admin/xpack/ml/filters/put': false,
'cluster:admin/xpack/ml/datafeeds/stop': false,
'cluster:monitor/xpack/ml/calendars/get': true,
'cluster:admin/xpack/ml/filters/get': false,
'cluster:monitor/xpack/ml/datafeeds/get': true,
'cluster:admin/data_frame/start_task': false,
'cluster:admin/xpack/ml/filters/update': false,
'cluster:admin/xpack/ml/calendars/events/post': false,
'cluster:monitor/data_frame/get': false,
'cluster:admin/xpack/ml/job/close': false,
'cluster:monitor/xpack/ml/datafeeds/stats/get': true,
'cluster:admin/data_frame/preview': false,
'cluster:admin/xpack/ml/calendars/jobs/update': false,
'cluster:admin/data_frame/put': false,
'cluster:admin/xpack/ml/calendars/put': false,
'cluster:monitor/data_frame/stats/get': false,
'cluster:admin/data_frame/stop': false,
'cluster:admin/xpack/ml/calendars/events/delete': false,
'cluster:admin/xpack/ml/datafeeds/put': false,
'cluster:admin/xpack/ml/job/open': false,
'cluster:admin/xpack/ml/job/delete': false,
'cluster:monitor/xpack/ml/job/get': true,
'cluster:admin/data_frame/delete': false,
'cluster:admin/xpack/ml/job/put': false,
'cluster:admin/xpack/ml/job/update': false,
'cluster:admin/data_frame/start': false,
'cluster:admin/xpack/ml/calendars/delete': false,
'cluster:monitor/xpack/ml/findfilestructure': true,
};

View file

@ -0,0 +1,267 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { callWithRequestProvider } from './__mocks__/call_with_request';
import { privilegesProvider } from './check_privileges';
import { mlPrivileges } from './privileges';
const xpackMainPluginWithSecurity = {
info: {
isAvailable: () => true,
feature: () => ({
isEnabled: () => true,
}),
},
};
const xpackMainPluginWithOutSecurity = {
info: {
isAvailable: () => true,
feature: () => ({
isEnabled: () => false,
}),
},
};
describe('check_privileges', () => {
describe('getPrivileges() - right number of privileges', () => {
test('es privileges count', async done => {
const count = mlPrivileges.cluster.length;
expect(count).toBe(35);
done();
});
test('kibana privileges count', async done => {
const callWithRequest = callWithRequestProvider('partialPrivileges');
const { getPrivileges } = privilegesProvider(callWithRequest, xpackMainPluginWithSecurity);
const { privileges } = await getPrivileges();
const count = Object.keys(privileges).length;
expect(count).toBe(23);
done();
});
});
describe('getPrivileges() with security', () => {
test('ml_user privileges only', async done => {
const callWithRequest = callWithRequestProvider('partialPrivileges');
const { getPrivileges } = privilegesProvider(callWithRequest, xpackMainPluginWithSecurity);
const { privileges, upgradeInProgress } = await getPrivileges();
expect(upgradeInProgress).toBe(false);
expect(privileges.canGetJobs).toBe(true);
expect(privileges.canCreateJob).toBe(false);
expect(privileges.canDeleteJob).toBe(false);
expect(privileges.canOpenJob).toBe(false);
expect(privileges.canCloseJob).toBe(false);
expect(privileges.canForecastJob).toBe(false);
expect(privileges.canGetDatafeeds).toBe(true);
expect(privileges.canStartStopDatafeed).toBe(false);
expect(privileges.canUpdateJob).toBe(false);
expect(privileges.canUpdateDatafeed).toBe(false);
expect(privileges.canPreviewDatafeed).toBe(false);
expect(privileges.canGetCalendars).toBe(true);
expect(privileges.canCreateCalendar).toBe(false);
expect(privileges.canDeleteCalendar).toBe(false);
expect(privileges.canGetFilters).toBe(false);
expect(privileges.canCreateFilter).toBe(false);
expect(privileges.canDeleteFilter).toBe(false);
expect(privileges.canFindFileStructure).toBe(true);
expect(privileges.canGetDataFrameJobs).toBe(false);
expect(privileges.canDeleteDataFrameJob).toBe(false);
expect(privileges.canPreviewDataFrameJob).toBe(false);
expect(privileges.canCreateDataFrameJob).toBe(false);
expect(privileges.canStartStopDataFrameJob).toBe(false);
done();
});
test('full privileges', async done => {
const callWithRequest = callWithRequestProvider('fullPrivileges');
const { getPrivileges } = privilegesProvider(callWithRequest, xpackMainPluginWithSecurity);
const { privileges, upgradeInProgress } = await getPrivileges();
expect(upgradeInProgress).toBe(false);
expect(privileges.canGetJobs).toBe(true);
expect(privileges.canCreateJob).toBe(true);
expect(privileges.canDeleteJob).toBe(true);
expect(privileges.canOpenJob).toBe(true);
expect(privileges.canCloseJob).toBe(true);
expect(privileges.canForecastJob).toBe(true);
expect(privileges.canGetDatafeeds).toBe(true);
expect(privileges.canStartStopDatafeed).toBe(true);
expect(privileges.canUpdateJob).toBe(true);
expect(privileges.canUpdateDatafeed).toBe(true);
expect(privileges.canPreviewDatafeed).toBe(true);
expect(privileges.canGetCalendars).toBe(true);
expect(privileges.canCreateCalendar).toBe(true);
expect(privileges.canDeleteCalendar).toBe(true);
expect(privileges.canGetFilters).toBe(true);
expect(privileges.canCreateFilter).toBe(true);
expect(privileges.canDeleteFilter).toBe(true);
expect(privileges.canFindFileStructure).toBe(true);
expect(privileges.canGetDataFrameJobs).toBe(true);
expect(privileges.canDeleteDataFrameJob).toBe(true);
expect(privileges.canPreviewDataFrameJob).toBe(true);
expect(privileges.canCreateDataFrameJob).toBe(true);
expect(privileges.canStartStopDataFrameJob).toBe(true);
done();
});
test('upgrade in progress with full privileges', async done => {
const callWithRequest = callWithRequestProvider('upgradeWithFullPrivileges');
const { getPrivileges } = privilegesProvider(callWithRequest, xpackMainPluginWithSecurity);
const { privileges, upgradeInProgress } = await getPrivileges();
expect(upgradeInProgress).toBe(true);
expect(privileges.canGetJobs).toBe(true);
expect(privileges.canCreateJob).toBe(false);
expect(privileges.canDeleteJob).toBe(false);
expect(privileges.canOpenJob).toBe(false);
expect(privileges.canCloseJob).toBe(false);
expect(privileges.canForecastJob).toBe(false);
expect(privileges.canGetDatafeeds).toBe(true);
expect(privileges.canStartStopDatafeed).toBe(false);
expect(privileges.canUpdateJob).toBe(false);
expect(privileges.canUpdateDatafeed).toBe(false);
expect(privileges.canPreviewDatafeed).toBe(false);
expect(privileges.canGetCalendars).toBe(true);
expect(privileges.canCreateCalendar).toBe(false);
expect(privileges.canDeleteCalendar).toBe(false);
expect(privileges.canGetFilters).toBe(true);
expect(privileges.canCreateFilter).toBe(false);
expect(privileges.canDeleteFilter).toBe(false);
expect(privileges.canFindFileStructure).toBe(true);
expect(privileges.canGetDataFrameJobs).toBe(true);
expect(privileges.canDeleteDataFrameJob).toBe(false);
expect(privileges.canPreviewDataFrameJob).toBe(false);
expect(privileges.canCreateDataFrameJob).toBe(false);
expect(privileges.canStartStopDataFrameJob).toBe(false);
done();
});
test('upgrade in progress with partial privileges', async done => {
const callWithRequest = callWithRequestProvider('upgradeWithPartialPrivileges');
const { getPrivileges } = privilegesProvider(callWithRequest, xpackMainPluginWithSecurity);
const { privileges, upgradeInProgress } = await getPrivileges();
expect(upgradeInProgress).toBe(true);
expect(privileges.canGetJobs).toBe(true);
expect(privileges.canCreateJob).toBe(false);
expect(privileges.canDeleteJob).toBe(false);
expect(privileges.canOpenJob).toBe(false);
expect(privileges.canCloseJob).toBe(false);
expect(privileges.canForecastJob).toBe(false);
expect(privileges.canGetDatafeeds).toBe(true);
expect(privileges.canStartStopDatafeed).toBe(false);
expect(privileges.canUpdateJob).toBe(false);
expect(privileges.canUpdateDatafeed).toBe(false);
expect(privileges.canPreviewDatafeed).toBe(false);
expect(privileges.canGetCalendars).toBe(true);
expect(privileges.canCreateCalendar).toBe(false);
expect(privileges.canDeleteCalendar).toBe(false);
expect(privileges.canGetFilters).toBe(false);
expect(privileges.canCreateFilter).toBe(false);
expect(privileges.canDeleteFilter).toBe(false);
expect(privileges.canFindFileStructure).toBe(true);
expect(privileges.canGetDataFrameJobs).toBe(false);
expect(privileges.canDeleteDataFrameJob).toBe(false);
expect(privileges.canPreviewDataFrameJob).toBe(false);
expect(privileges.canCreateDataFrameJob).toBe(false);
expect(privileges.canStartStopDataFrameJob).toBe(false);
done();
});
});
describe('getPrivileges() without security', () => {
test('ml_user privileges only', async done => {
const callWithRequest = callWithRequestProvider('partialPrivileges');
const { getPrivileges } = privilegesProvider(callWithRequest, xpackMainPluginWithOutSecurity);
const { privileges, upgradeInProgress } = await getPrivileges();
expect(upgradeInProgress).toBe(false);
expect(privileges.canGetJobs).toBe(true);
expect(privileges.canCreateJob).toBe(true);
expect(privileges.canDeleteJob).toBe(true);
expect(privileges.canOpenJob).toBe(true);
expect(privileges.canCloseJob).toBe(true);
expect(privileges.canForecastJob).toBe(true);
expect(privileges.canGetDatafeeds).toBe(true);
expect(privileges.canStartStopDatafeed).toBe(true);
expect(privileges.canUpdateJob).toBe(true);
expect(privileges.canUpdateDatafeed).toBe(true);
expect(privileges.canPreviewDatafeed).toBe(true);
expect(privileges.canGetCalendars).toBe(true);
expect(privileges.canCreateCalendar).toBe(true);
expect(privileges.canDeleteCalendar).toBe(true);
expect(privileges.canGetFilters).toBe(true);
expect(privileges.canCreateFilter).toBe(true);
expect(privileges.canDeleteFilter).toBe(true);
expect(privileges.canFindFileStructure).toBe(true);
expect(privileges.canGetDataFrameJobs).toBe(true);
expect(privileges.canDeleteDataFrameJob).toBe(true);
expect(privileges.canPreviewDataFrameJob).toBe(true);
expect(privileges.canCreateDataFrameJob).toBe(true);
expect(privileges.canStartStopDataFrameJob).toBe(true);
done();
});
test('upgrade in progress with full privileges', async done => {
const callWithRequest = callWithRequestProvider('upgradeWithFullPrivileges');
const { getPrivileges } = privilegesProvider(callWithRequest, xpackMainPluginWithOutSecurity);
const { privileges, upgradeInProgress } = await getPrivileges();
expect(upgradeInProgress).toBe(true);
expect(privileges.canGetJobs).toBe(true);
expect(privileges.canCreateJob).toBe(false);
expect(privileges.canDeleteJob).toBe(false);
expect(privileges.canOpenJob).toBe(false);
expect(privileges.canCloseJob).toBe(false);
expect(privileges.canForecastJob).toBe(false);
expect(privileges.canGetDatafeeds).toBe(true);
expect(privileges.canStartStopDatafeed).toBe(false);
expect(privileges.canUpdateJob).toBe(false);
expect(privileges.canUpdateDatafeed).toBe(false);
expect(privileges.canPreviewDatafeed).toBe(false);
expect(privileges.canGetCalendars).toBe(true);
expect(privileges.canCreateCalendar).toBe(false);
expect(privileges.canDeleteCalendar).toBe(false);
expect(privileges.canGetFilters).toBe(true);
expect(privileges.canCreateFilter).toBe(false);
expect(privileges.canDeleteFilter).toBe(false);
expect(privileges.canFindFileStructure).toBe(true);
expect(privileges.canGetDataFrameJobs).toBe(true);
expect(privileges.canDeleteDataFrameJob).toBe(false);
expect(privileges.canPreviewDataFrameJob).toBe(false);
expect(privileges.canCreateDataFrameJob).toBe(false);
expect(privileges.canStartStopDataFrameJob).toBe(false);
done();
});
test('upgrade in progress with partial privileges', async done => {
const callWithRequest = callWithRequestProvider('upgradeWithPartialPrivileges');
const { getPrivileges } = privilegesProvider(callWithRequest, xpackMainPluginWithOutSecurity);
const { privileges, upgradeInProgress } = await getPrivileges();
expect(upgradeInProgress).toBe(true);
expect(privileges.canGetJobs).toBe(true);
expect(privileges.canCreateJob).toBe(false);
expect(privileges.canDeleteJob).toBe(false);
expect(privileges.canOpenJob).toBe(false);
expect(privileges.canCloseJob).toBe(false);
expect(privileges.canForecastJob).toBe(false);
expect(privileges.canGetDatafeeds).toBe(true);
expect(privileges.canStartStopDatafeed).toBe(false);
expect(privileges.canUpdateJob).toBe(false);
expect(privileges.canUpdateDatafeed).toBe(false);
expect(privileges.canPreviewDatafeed).toBe(false);
expect(privileges.canGetCalendars).toBe(true);
expect(privileges.canCreateCalendar).toBe(false);
expect(privileges.canDeleteCalendar).toBe(false);
expect(privileges.canGetFilters).toBe(true);
expect(privileges.canCreateFilter).toBe(false);
expect(privileges.canDeleteFilter).toBe(false);
expect(privileges.canFindFileStructure).toBe(true);
expect(privileges.canGetDataFrameJobs).toBe(true);
expect(privileges.canDeleteDataFrameJob).toBe(false);
expect(privileges.canPreviewDataFrameJob).toBe(false);
expect(privileges.canCreateDataFrameJob).toBe(false);
expect(privileges.canStartStopDataFrameJob).toBe(false);
done();
});
});
});

View file

@ -0,0 +1,200 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { Privileges, getDefaultPrivileges } from '../../../common/types/privileges';
import { callWithRequestType } from '../../../common/types/kibana';
import { isSecurityDisabled } from '../../lib/security_utils';
import { upgradeCheckProvider } from './upgrade';
import { mlPrivileges } from './privileges';
type ClusterPrivilege = Record<string, boolean>;
interface Response {
privileges: Privileges;
upgradeInProgress: boolean;
}
export function privilegesProvider(callWithRequest: callWithRequestType, xpackMainPlugin: any) {
const { isUpgradeInProgress } = upgradeCheckProvider(callWithRequest);
async function getPrivileges(): Promise<Response> {
// get the default privileges, forced to be false.
const privileges = getDefaultPrivileges();
const upgradeInProgress = await isUpgradeInProgress();
const securityDisabled = isSecurityDisabled(xpackMainPlugin);
if (securityDisabled === true) {
if (upgradeInProgress === true) {
// if security is disabled and an upgrade in is progress,
// force all "getting" privileges to be true
// leaving all "setting" privileges to be the default false
setGettingPrivileges({}, privileges, true);
} else {
// if no upgrade is in progress,
// get all privileges forced to true
Object.keys(privileges).forEach(k => (privileges[k as keyof Privileges] = true));
}
} else {
// security enabled
// load all ml privileges for this user.
const { cluster } = await callWithRequest('ml.privilegeCheck', { body: mlPrivileges });
setGettingPrivileges(cluster, privileges);
if (upgradeInProgress === false) {
// if an upgrade is in progress, don't apply the "setting"
// privileges. leave them to be the default false.
setActionPrivileges(cluster, privileges);
}
}
return { privileges, upgradeInProgress };
}
return { getPrivileges };
}
function setGettingPrivileges(
cluster: ClusterPrivilege = {},
privileges: Privileges,
forceTrue = false
) {
// Anomaly Detection
if (
forceTrue ||
(cluster['cluster:monitor/xpack/ml/job/get'] &&
cluster['cluster:monitor/xpack/ml/job/stats/get'])
) {
privileges.canGetJobs = true;
}
if (
forceTrue ||
(cluster['cluster:monitor/xpack/ml/datafeeds/get'] &&
cluster['cluster:monitor/xpack/ml/datafeeds/stats/get'])
) {
privileges.canGetDatafeeds = true;
}
// Calendars
if (forceTrue || cluster['cluster:monitor/xpack/ml/calendars/get']) {
privileges.canGetCalendars = true;
}
// Filters
if (forceTrue || cluster['cluster:admin/xpack/ml/filters/get']) {
privileges.canGetFilters = true;
}
// File Data Visualizer
if (forceTrue || cluster['cluster:monitor/xpack/ml/findfilestructure']) {
privileges.canFindFileStructure = true;
}
// Data Frames
if (
forceTrue ||
(cluster['cluster:monitor/data_frame/get'] && cluster['cluster:monitor/data_frame/stats/get'])
) {
privileges.canGetDataFrameJobs = true;
}
}
function setActionPrivileges(cluster: ClusterPrivilege = {}, privileges: Privileges) {
// Anomaly Detection
if (
cluster['cluster:admin/xpack/ml/job/put'] &&
cluster['cluster:admin/xpack/ml/job/open'] &&
cluster['cluster:admin/xpack/ml/datafeeds/put']
) {
privileges.canCreateJob = true;
}
if (cluster['cluster:admin/xpack/ml/job/update']) {
privileges.canUpdateJob = true;
}
if (cluster['cluster:admin/xpack/ml/job/open']) {
privileges.canOpenJob = true;
}
if (cluster['cluster:admin/xpack/ml/job/close']) {
privileges.canCloseJob = true;
}
if (cluster['cluster:admin/xpack/ml/job/forecast']) {
privileges.canForecastJob = true;
}
if (
cluster['cluster:admin/xpack/ml/job/delete'] &&
cluster['cluster:admin/xpack/ml/datafeeds/delete']
) {
privileges.canDeleteJob = true;
}
if (
cluster['cluster:admin/xpack/ml/job/open'] &&
cluster['cluster:admin/xpack/ml/datafeeds/start'] &&
cluster['cluster:admin/xpack/ml/datafeeds/stop']
) {
privileges.canStartStopDatafeed = true;
}
if (cluster['cluster:admin/xpack/ml/datafeeds/update']) {
privileges.canUpdateDatafeed = true;
}
if (cluster['cluster:admin/xpack/ml/datafeeds/preview']) {
privileges.canPreviewDatafeed = true;
}
// Calendars
if (
cluster['cluster:admin/xpack/ml/calendars/put'] &&
cluster['cluster:admin/xpack/ml/calendars/jobs/update'] &&
cluster['cluster:admin/xpack/ml/calendars/events/post']
) {
privileges.canCreateCalendar = true;
}
if (
cluster['cluster:admin/xpack/ml/calendars/delete'] &&
cluster['cluster:admin/xpack/ml/calendars/events/delete']
) {
privileges.canDeleteCalendar = true;
}
// Filters
if (
cluster['cluster:admin/xpack/ml/filters/put'] &&
cluster['cluster:admin/xpack/ml/filters/update']
) {
privileges.canCreateFilter = true;
}
if (cluster['cluster:admin/xpack/ml/filters/delete']) {
privileges.canDeleteFilter = true;
}
// Data Frames
if (cluster['cluster:admin/data_frame/put']) {
privileges.canCreateDataFrameJob = true;
}
if (cluster['cluster:admin/data_frame/delete']) {
privileges.canDeleteDataFrameJob = true;
}
if (cluster['cluster:admin/data_frame/preview']) {
privileges.canPreviewDataFrameJob = true;
}
if (
cluster['cluster:admin/data_frame/start'] &&
cluster['cluster:admin/data_frame/start_task'] &&
cluster['cluster:admin/data_frame/stop']
) {
privileges.canStartStopDataFrameJob = true;
}
}

View file

@ -0,0 +1,7 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
export { privilegesProvider } from './check_privileges';

View file

@ -0,0 +1,45 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
export const mlPrivileges = {
cluster: [
'cluster:monitor/data_frame/get',
'cluster:monitor/data_frame/stats/get',
'cluster:monitor/xpack/ml/job/get',
'cluster:monitor/xpack/ml/job/stats/get',
'cluster:monitor/xpack/ml/datafeeds/get',
'cluster:monitor/xpack/ml/datafeeds/stats/get',
'cluster:monitor/xpack/ml/calendars/get',
'cluster:admin/data_frame/delete',
'cluster:admin/data_frame/preview',
'cluster:admin/data_frame/put',
'cluster:admin/data_frame/start',
'cluster:admin/data_frame/start_task',
'cluster:admin/data_frame/stop',
'cluster:admin/xpack/ml/job/put',
'cluster:admin/xpack/ml/job/delete',
'cluster:admin/xpack/ml/job/update',
'cluster:admin/xpack/ml/job/open',
'cluster:admin/xpack/ml/job/close',
'cluster:admin/xpack/ml/job/forecast',
'cluster:admin/xpack/ml/datafeeds/put',
'cluster:admin/xpack/ml/datafeeds/delete',
'cluster:admin/xpack/ml/datafeeds/start',
'cluster:admin/xpack/ml/datafeeds/stop',
'cluster:admin/xpack/ml/datafeeds/update',
'cluster:admin/xpack/ml/datafeeds/preview',
'cluster:admin/xpack/ml/calendars/put',
'cluster:admin/xpack/ml/calendars/delete',
'cluster:admin/xpack/ml/calendars/jobs/update',
'cluster:admin/xpack/ml/calendars/events/post',
'cluster:admin/xpack/ml/calendars/events/delete',
'cluster:admin/xpack/ml/filters/put',
'cluster:admin/xpack/ml/filters/get',
'cluster:admin/xpack/ml/filters/update',
'cluster:admin/xpack/ml/filters/delete',
'cluster:monitor/xpack/ml/findfilestructure',
],
};

View file

@ -0,0 +1,33 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { mlLog } from '../../client/log';
import { callWithRequestType } from '../../../common/types/kibana';
export function upgradeCheckProvider(callWithRequest: callWithRequestType) {
async function isUpgradeInProgress(): Promise<boolean> {
let upgradeInProgress = false;
try {
const info = await callWithRequest('ml.info');
// if ml indices are currently being migrated, upgrade_mode will be set to true
// pass this back with the privileges to allow for the disabling of UI controls.
upgradeInProgress = info.upgrade_mode === true;
} catch (error) {
// if the ml.info check fails, it could be due to the user having insufficient privileges
// most likely they do not have the ml_user role and therefore will be blocked from using
// ML at all. However, we need to catch this error so the privilege check doesn't fail.
if (error.status === 403) {
mlLog.info(
'Unable to determine whether upgrade is being performed due to insufficient user privileges'
);
} else {
mlLog.warn('Unable to determine whether upgrade is being performed');
}
}
return upgradeInProgress;
}
return { isUpgradeInProgress };
}

View file

@ -0,0 +1,7 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
export function isSecurityDisabled(xpackMainPlugin: any): boolean;

View file

@ -8,6 +8,7 @@
import { callWithRequestFactory } from '../client/call_with_request_factory';
import { callWithInternalUserFactory } from '../client/call_with_internal_user_factory';
import { privilegesProvider } from '../lib/check_privileges';
import { mlLog } from '../client/log';
import { wrapError } from '../client/errors';
@ -82,6 +83,23 @@ export function systemRoutes({ commonRouteConfig, elasticsearchPlugin, route, xp
}
});
route({
method: 'GET',
path: '/api/ml/ml_privileges',
async handler(request) {
const callWithRequest = callWithRequestFactory(elasticsearchPlugin, request);
try {
const { getPrivileges } = privilegesProvider(callWithRequest, xpackMainPlugin);
return await getPrivileges();
} catch (error) {
return wrapError(error);
}
},
config: {
...commonRouteConfig
}
});
route({
method: 'GET',
path: '/api/ml/ml_node_count',