* Adding feature flag for auth * Hiding SOs and adding consumer field * First pass at adding security changes * Consumer as the app's plugin ID * Create addConsumerToSO migration helper * Fix mapping's SO consumer * Add test for CasesActions * Declare hidden types on SO client * Restructure integration tests * Init spaces_only integration tests * Implementing the cases security string * Adding security plugin tests for cases * Rough concept for authorization class * Adding comments * Fix merge * Get requiredPrivileges for classes * Check privillages * Ensure that all classes are available * Success if hasAllRequested is true * Failure if hasAllRequested is false * Adding schema updates for feature plugin * Seperate basic from trial * Enable SIR on integration tests * Starting the plumbing for authorization in plugin * Unit tests working * Move find route logic to case client * Create integration test helper functions * Adding auth to create call * Create getClassFilter helper * Add class attribute to find request * Create getFindAuthorizationFilter * Ensure savedObject is authorized in find method * Include fields for authorization * Combine authorization filter with cases & subcases filter * Fix isAuthorized flag * Fix merge issue * Create/delete spaces & users before and after tests * Add more user and roles * [Cases] Convert filters from strings to KueryNode (#95288) * [Cases] RBAC: Rename class to scope (#95535) * [Cases][RBAC] Rename scope to owner (#96035) * [Cases] RBAC: Create & Find integration tests (#95511) * [Cases] Cases client enchantment (#95923) * [Cases] Authorization and Client Audit Logger (#95477) * Starting audit logger * Finishing auth audit logger * Fixing tests and types * Adding audit event creator * Renaming class to scope * Adding audit logger messages to create and find * Adding comments and fixing import issue * Fixing type errors * Fixing tests and adding username to message * Addressing PR feedback * Removing unneccessary log and generating id * Fixing module issue and remove expect.anything * [Cases] Migrate sub cases routes to a client (#96461) * Adding sub cases client * Move sub case routes to case client * Throw when attempting to access the sub cases client * Fixing throw and removing user ans soclients * [Cases] RBAC: Migrate routes' unit tests to integration tests (#96374) Co-authored-by: Jonathan Buttner <jonathan.buttner@elastic.co> * [Cases] Move remaining HTTP functionality to client (#96507) * Moving deletes and find for attachments * Moving rest of comment apis * Migrating configuration routes to client * Finished moving routes, starting utils refactor * Refactoring utilites and fixing integration tests * Addressing PR feedback * Fixing mocks and types * Fixing integration tests * Renaming status_stats * Fixing test type errors * Adding plugins to kibana.json * Adding cases to required plugin * [Cases] Refactoring authorization (#97483) * Refactoring authorization * Wrapping auth calls in helper for try catch * Reverting name change * Hardcoding the saved object types * Switching ensure to owner array * [Cases] Add authorization to configuration & cases routes (#97228) * [Cases] Attachments RBAC (#97756) * Starting rbac for comments * Adding authorization to rest of comment apis * Starting the comment rbac tests * Fixing some of the rbac tests * Adding some integration tests * Starting patch tests * Working tests for comments * Working tests * Fixing some tests * Fixing type issues from pulling in master * Fixing connector tests that only work in trial license * Attempting to fix cypress * Mock return of array for configure * Fixing cypress test * Cleaning up * Addressing PR comments * Reducing operations * [Cases] Add RBAC to remaining Cases APIs (#98762) * Starting rbac for comments * Adding authorization to rest of comment apis * Starting the comment rbac tests * Fixing some of the rbac tests * Adding some integration tests * Starting patch tests * Working tests for comments * Working tests * Fixing some tests * Fixing type issues from pulling in master * Fixing connector tests that only work in trial license * Attempting to fix cypress * Mock return of array for configure * Fixing cypress test * Cleaning up * Working case update tests * Addressing PR comments * Reducing operations * Working rbac push case tests * Starting stats apis * Working status tests * User action tests and fixing migration errors * Fixing type errors * including error in message * Addressing pr feedback * Fixing some type errors * [Cases] Add space only tests (#99409) * Starting spaces tests * Finishing space only tests * Refactoring createCaseWithConnector * Fixing spelling * Addressing PR feedback and creating alert tests * Fixing mocks * [Cases] Add security only tests (#99679) * Starting spaces tests * Finishing space only tests * Refactoring createCaseWithConnector * Fixing spelling * Addressing PR feedback and creating alert tests * Fixing mocks * Starting security only tests * Adding remainder security only tests * Using helper objects * Fixing type error for null space * Renaming utility variables * Refactoring users and roles for security only tests * Adding sub feature * [Cases] Cleaning up the services and TODOs (#99723) * Cleaning up the service intialization * Fixing type errors * Adding comments for the api * Working test for cases client * Fix type error * Adding generated docs * Adding more docs and cleaning up types * Cleaning up readme * More clean up and links * Changing some file names * Renaming docs * Integration tests for cases privs and fixes (#100038) * [Cases] RBAC on UI (#99478) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> * Fixing case ids by alert id route call * [Cases] Fixing UI feature permissions and adding UI tests (#100074) * Integration tests for cases privs and fixes * Fixing ui cases permissions and adding tests * Adding test for collection failure and fixing jest * Renaming variables * Fixing type error * Adding some comments * Validate cases features * Fix new schema * Adding owner param for the status stats * Fix get case status tests * Adjusting permissions text and fixing status * Address PR feedback * Adding top level feature back * Fixing feature privileges * Renaming * Removing uneeded else * Fixing tests and adding cases merge tests * [Cases][Security Solution] Basic license security solution API tests (#100925) * Cleaning up the fixture plugins * Adding basic feature test * renaming to unsecuredSavedObjectsClient (#101215) * [Cases] RBAC Refactoring audit logging (#100952) * Refactoring audit logging * Adding unit tests for authorization classes * Addressing feedback and adding util tests * return undefined on empty array * fixing eslint * [Cases] Cleaning up RBAC integration tests (#101324) * Adding tests for space permissions * Adding tests for testing a disable feature Co-authored-by: Christos Nasikas <christos.nasikas@elastic.co> Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
503 lines
16 KiB
TypeScript
503 lines
16 KiB
TypeScript
/*
|
|
* 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 { schema } from '@kbn/config-schema';
|
|
|
|
import { difference } from 'lodash';
|
|
import { Capabilities as UICapabilities } from '../../../../src/core/server';
|
|
import { KibanaFeatureConfig } from '../common';
|
|
import { FeatureKibanaPrivileges, ElasticsearchFeatureConfig } from '.';
|
|
|
|
// Each feature gets its own property on the UICapabilities object,
|
|
// but that object has a few built-in properties which should not be overwritten.
|
|
const prohibitedFeatureIds: Set<keyof UICapabilities> = new Set([
|
|
'catalogue',
|
|
'management',
|
|
'navLinks',
|
|
]);
|
|
|
|
const featurePrivilegePartRegex = /^[a-zA-Z0-9_-]+$/;
|
|
const subFeaturePrivilegePartRegex = /^[a-zA-Z0-9_-]+$/;
|
|
const managementSectionIdRegex = /^[a-zA-Z0-9_-]+$/;
|
|
const reservedFeaturePrrivilegePartRegex = /^(?!reserved_)[a-zA-Z0-9_-]+$/;
|
|
export const uiCapabilitiesRegex = /^[a-zA-Z0-9:_-]+$/;
|
|
|
|
const validLicenseSchema = schema.oneOf([
|
|
schema.literal('basic'),
|
|
schema.literal('standard'),
|
|
schema.literal('gold'),
|
|
schema.literal('platinum'),
|
|
schema.literal('enterprise'),
|
|
schema.literal('trial'),
|
|
]);
|
|
// sub-feature privileges are only available with a `gold` license or better, so restricting sub-feature privileges
|
|
// for `gold` or below doesn't make a whole lot of sense.
|
|
const validSubFeaturePrivilegeLicensesSchema = schema.oneOf([
|
|
schema.literal('platinum'),
|
|
schema.literal('enterprise'),
|
|
schema.literal('trial'),
|
|
]);
|
|
|
|
const listOfCapabilitiesSchema = schema.arrayOf(
|
|
schema.string({
|
|
validate(key: string) {
|
|
if (!uiCapabilitiesRegex.test(key)) {
|
|
return `Does not satisfy regexp ${uiCapabilitiesRegex.toString()}`;
|
|
}
|
|
},
|
|
})
|
|
);
|
|
const managementSchema = schema.recordOf(
|
|
schema.string({
|
|
validate(key: string) {
|
|
if (!managementSectionIdRegex.test(key)) {
|
|
return `Does not satisfy regexp ${managementSectionIdRegex.toString()}`;
|
|
}
|
|
},
|
|
}),
|
|
listOfCapabilitiesSchema
|
|
);
|
|
const catalogueSchema = listOfCapabilitiesSchema;
|
|
const alertingSchema = schema.arrayOf(schema.string());
|
|
const casesSchema = schema.arrayOf(schema.string());
|
|
|
|
const appCategorySchema = schema.object({
|
|
id: schema.string(),
|
|
label: schema.string(),
|
|
ariaLabel: schema.maybe(schema.string()),
|
|
euiIconType: schema.maybe(schema.string()),
|
|
order: schema.maybe(schema.number()),
|
|
});
|
|
|
|
const kibanaPrivilegeSchema = schema.object({
|
|
excludeFromBasePrivileges: schema.maybe(schema.boolean()),
|
|
management: schema.maybe(managementSchema),
|
|
catalogue: schema.maybe(catalogueSchema),
|
|
api: schema.maybe(schema.arrayOf(schema.string())),
|
|
app: schema.maybe(schema.arrayOf(schema.string())),
|
|
alerting: schema.maybe(
|
|
schema.object({
|
|
rule: schema.maybe(
|
|
schema.object({
|
|
all: schema.maybe(alertingSchema),
|
|
read: schema.maybe(alertingSchema),
|
|
})
|
|
),
|
|
alert: schema.maybe(
|
|
schema.object({
|
|
all: schema.maybe(alertingSchema),
|
|
read: schema.maybe(alertingSchema),
|
|
})
|
|
),
|
|
})
|
|
),
|
|
cases: schema.maybe(
|
|
schema.object({
|
|
all: schema.maybe(casesSchema),
|
|
read: schema.maybe(casesSchema),
|
|
})
|
|
),
|
|
savedObject: schema.object({
|
|
all: schema.arrayOf(schema.string()),
|
|
read: schema.arrayOf(schema.string()),
|
|
}),
|
|
ui: listOfCapabilitiesSchema,
|
|
});
|
|
|
|
const kibanaIndependentSubFeaturePrivilegeSchema = schema.object({
|
|
id: schema.string({
|
|
validate(key: string) {
|
|
if (!subFeaturePrivilegePartRegex.test(key)) {
|
|
return `Does not satisfy regexp ${subFeaturePrivilegePartRegex.toString()}`;
|
|
}
|
|
},
|
|
}),
|
|
name: schema.string(),
|
|
includeIn: schema.oneOf([schema.literal('all'), schema.literal('read'), schema.literal('none')]),
|
|
minimumLicense: schema.maybe(validSubFeaturePrivilegeLicensesSchema),
|
|
management: schema.maybe(managementSchema),
|
|
catalogue: schema.maybe(catalogueSchema),
|
|
alerting: schema.maybe(
|
|
schema.object({
|
|
rule: schema.maybe(
|
|
schema.object({
|
|
all: schema.maybe(alertingSchema),
|
|
read: schema.maybe(alertingSchema),
|
|
})
|
|
),
|
|
alert: schema.maybe(
|
|
schema.object({
|
|
all: schema.maybe(alertingSchema),
|
|
read: schema.maybe(alertingSchema),
|
|
})
|
|
),
|
|
})
|
|
),
|
|
cases: schema.maybe(
|
|
schema.object({
|
|
all: schema.maybe(casesSchema),
|
|
read: schema.maybe(casesSchema),
|
|
})
|
|
),
|
|
api: schema.maybe(schema.arrayOf(schema.string())),
|
|
app: schema.maybe(schema.arrayOf(schema.string())),
|
|
savedObject: schema.object({
|
|
all: schema.arrayOf(schema.string()),
|
|
read: schema.arrayOf(schema.string()),
|
|
}),
|
|
ui: listOfCapabilitiesSchema,
|
|
});
|
|
|
|
const kibanaMutuallyExclusiveSubFeaturePrivilegeSchema = kibanaIndependentSubFeaturePrivilegeSchema.extends(
|
|
{
|
|
minimumLicense: schema.never(),
|
|
}
|
|
);
|
|
|
|
const kibanaSubFeatureSchema = schema.object({
|
|
name: schema.string(),
|
|
privilegeGroups: schema.maybe(
|
|
schema.arrayOf(
|
|
schema.oneOf([
|
|
schema.object({
|
|
groupType: schema.literal('mutually_exclusive'),
|
|
privileges: schema.maybe(
|
|
schema.arrayOf(kibanaMutuallyExclusiveSubFeaturePrivilegeSchema, { minSize: 1 })
|
|
),
|
|
}),
|
|
schema.object({
|
|
groupType: schema.literal('independent'),
|
|
privileges: schema.maybe(
|
|
schema.arrayOf(kibanaIndependentSubFeaturePrivilegeSchema, { minSize: 1 })
|
|
),
|
|
}),
|
|
])
|
|
)
|
|
),
|
|
});
|
|
|
|
const kibanaFeatureSchema = schema.object({
|
|
id: schema.string({
|
|
validate(value: string) {
|
|
if (!featurePrivilegePartRegex.test(value)) {
|
|
return `Does not satisfy regexp ${featurePrivilegePartRegex.toString()}`;
|
|
}
|
|
if (prohibitedFeatureIds.has(value)) {
|
|
return `[${value}] is not allowed`;
|
|
}
|
|
},
|
|
}),
|
|
name: schema.string(),
|
|
category: appCategorySchema,
|
|
order: schema.maybe(schema.number()),
|
|
excludeFromBasePrivileges: schema.maybe(schema.boolean()),
|
|
minimumLicense: schema.maybe(validLicenseSchema),
|
|
app: schema.arrayOf(schema.string()),
|
|
management: schema.maybe(managementSchema),
|
|
catalogue: schema.maybe(catalogueSchema),
|
|
alerting: schema.maybe(alertingSchema),
|
|
cases: schema.maybe(casesSchema),
|
|
privileges: schema.oneOf([
|
|
schema.literal(null),
|
|
schema.object({
|
|
all: schema.maybe(kibanaPrivilegeSchema),
|
|
read: schema.maybe(kibanaPrivilegeSchema),
|
|
}),
|
|
]),
|
|
subFeatures: schema.maybe(
|
|
schema.conditional(
|
|
schema.siblingRef('privileges'),
|
|
null,
|
|
// allows an empty array only
|
|
schema.arrayOf(schema.never(), { maxSize: 0 }),
|
|
schema.arrayOf(kibanaSubFeatureSchema)
|
|
)
|
|
),
|
|
privilegesTooltip: schema.maybe(schema.string()),
|
|
reserved: schema.maybe(
|
|
schema.object({
|
|
description: schema.string(),
|
|
privileges: schema.arrayOf(
|
|
schema.object({
|
|
id: schema.string({
|
|
validate(value: string) {
|
|
if (!reservedFeaturePrrivilegePartRegex.test(value)) {
|
|
return `Does not satisfy regexp ${reservedFeaturePrrivilegePartRegex.toString()}`;
|
|
}
|
|
},
|
|
}),
|
|
privilege: kibanaPrivilegeSchema,
|
|
})
|
|
),
|
|
})
|
|
),
|
|
});
|
|
|
|
const elasticsearchPrivilegeSchema = schema.object({
|
|
ui: schema.arrayOf(schema.string()),
|
|
requiredClusterPrivileges: schema.maybe(schema.arrayOf(schema.string())),
|
|
requiredIndexPrivileges: schema.maybe(
|
|
schema.recordOf(schema.string(), schema.arrayOf(schema.string()))
|
|
),
|
|
requiredRoles: schema.maybe(schema.arrayOf(schema.string())),
|
|
});
|
|
|
|
const elasticsearchFeatureSchema = schema.object({
|
|
id: schema.string({
|
|
validate(value: string) {
|
|
if (!featurePrivilegePartRegex.test(value)) {
|
|
return `Does not satisfy regexp ${featurePrivilegePartRegex.toString()}`;
|
|
}
|
|
if (prohibitedFeatureIds.has(value)) {
|
|
return `[${value}] is not allowed`;
|
|
}
|
|
},
|
|
}),
|
|
management: schema.maybe(managementSchema),
|
|
catalogue: schema.maybe(catalogueSchema),
|
|
privileges: schema.arrayOf(elasticsearchPrivilegeSchema),
|
|
});
|
|
|
|
export function validateKibanaFeature(feature: KibanaFeatureConfig) {
|
|
kibanaFeatureSchema.validate(feature);
|
|
|
|
// the following validation can't be enforced by the Joi schema, since it'd require us looking "up" the object graph for the list of valid value, which they explicitly forbid.
|
|
const { app = [], management = {}, catalogue = [], alerting = [], cases = [] } = feature;
|
|
|
|
const unseenApps = new Set(app);
|
|
|
|
const managementSets = Object.entries(management).map((entry) => [
|
|
entry[0],
|
|
new Set(entry[1]),
|
|
]) as Array<[string, Set<string>]>;
|
|
|
|
const unseenManagement = new Map<string, Set<string>>(managementSets);
|
|
|
|
const unseenCatalogue = new Set(catalogue);
|
|
|
|
const unseenAlertTypes = new Set(alerting);
|
|
|
|
const unseenCasesTypes = new Set(cases);
|
|
|
|
function validateAppEntry(privilegeId: string, entry: readonly string[] = []) {
|
|
entry.forEach((privilegeApp) => unseenApps.delete(privilegeApp));
|
|
|
|
const unknownAppEntries = difference(entry, app);
|
|
if (unknownAppEntries.length > 0) {
|
|
throw new Error(
|
|
`Feature privilege ${
|
|
feature.id
|
|
}.${privilegeId} has unknown app entries: ${unknownAppEntries.join(', ')}`
|
|
);
|
|
}
|
|
}
|
|
|
|
function validateCatalogueEntry(privilegeId: string, entry: readonly string[] = []) {
|
|
entry.forEach((privilegeCatalogue) => unseenCatalogue.delete(privilegeCatalogue));
|
|
|
|
const unknownCatalogueEntries = difference(entry || [], catalogue);
|
|
if (unknownCatalogueEntries.length > 0) {
|
|
throw new Error(
|
|
`Feature privilege ${
|
|
feature.id
|
|
}.${privilegeId} has unknown catalogue entries: ${unknownCatalogueEntries.join(', ')}`
|
|
);
|
|
}
|
|
}
|
|
|
|
function validateAlertingEntry(privilegeId: string, entry: FeatureKibanaPrivileges['alerting']) {
|
|
const all: string[] = [...(entry?.rule?.all ?? []), ...(entry?.alert?.all ?? [])];
|
|
const read: string[] = [...(entry?.rule?.read ?? []), ...(entry?.alert?.read ?? [])];
|
|
|
|
all.forEach((privilegeAlertTypes) => unseenAlertTypes.delete(privilegeAlertTypes));
|
|
read.forEach((privilegeAlertTypes) => unseenAlertTypes.delete(privilegeAlertTypes));
|
|
|
|
const unknownAlertingEntries = difference([...all, ...read], alerting);
|
|
if (unknownAlertingEntries.length > 0) {
|
|
throw new Error(
|
|
`Feature privilege ${
|
|
feature.id
|
|
}.${privilegeId} has unknown alerting entries: ${unknownAlertingEntries.join(', ')}`
|
|
);
|
|
}
|
|
}
|
|
|
|
function validateCasesEntry(privilegeId: string, entry: FeatureKibanaPrivileges['cases']) {
|
|
const all = entry?.all ?? [];
|
|
const read = entry?.read ?? [];
|
|
|
|
all.forEach((privilegeCasesTypes) => unseenCasesTypes.delete(privilegeCasesTypes));
|
|
read.forEach((privilegeCasesTypes) => unseenCasesTypes.delete(privilegeCasesTypes));
|
|
|
|
const unknownCasesEntries = difference([...all, ...read], cases);
|
|
if (unknownCasesEntries.length > 0) {
|
|
throw new Error(
|
|
`Feature privilege ${
|
|
feature.id
|
|
}.${privilegeId} has unknown cases entries: ${unknownCasesEntries.join(', ')}`
|
|
);
|
|
}
|
|
}
|
|
|
|
function validateManagementEntry(
|
|
privilegeId: string,
|
|
managementEntry: Record<string, readonly string[]> = {}
|
|
) {
|
|
Object.entries(managementEntry).forEach(([managementSectionId, managementSectionEntry]) => {
|
|
if (unseenManagement.has(managementSectionId)) {
|
|
managementSectionEntry.forEach((entry) => {
|
|
unseenManagement.get(managementSectionId)!.delete(entry);
|
|
if (unseenManagement.get(managementSectionId)?.size === 0) {
|
|
unseenManagement.delete(managementSectionId);
|
|
}
|
|
});
|
|
}
|
|
if (!management[managementSectionId]) {
|
|
throw new Error(
|
|
`Feature privilege ${feature.id}.${privilegeId} has unknown management section: ${managementSectionId}`
|
|
);
|
|
}
|
|
|
|
const unknownSectionEntries = difference(
|
|
managementSectionEntry,
|
|
management[managementSectionId]
|
|
);
|
|
|
|
if (unknownSectionEntries.length > 0) {
|
|
throw new Error(
|
|
`Feature privilege ${
|
|
feature.id
|
|
}.${privilegeId} has unknown management entries for section ${managementSectionId}: ${unknownSectionEntries.join(
|
|
', '
|
|
)}`
|
|
);
|
|
}
|
|
});
|
|
}
|
|
|
|
const privilegeEntries: Array<[string, FeatureKibanaPrivileges]> = [];
|
|
if (feature.privileges) {
|
|
privilegeEntries.push(...Object.entries(feature.privileges));
|
|
}
|
|
if (feature.reserved) {
|
|
feature.reserved.privileges.forEach((reservedPrivilege) => {
|
|
privilegeEntries.push([reservedPrivilege.id, reservedPrivilege.privilege]);
|
|
});
|
|
}
|
|
|
|
if (privilegeEntries.length === 0) {
|
|
return;
|
|
}
|
|
|
|
privilegeEntries.forEach(([privilegeId, privilegeDefinition]) => {
|
|
if (!privilegeDefinition) {
|
|
throw new Error('Privilege definition may not be null or undefined');
|
|
}
|
|
|
|
validateAppEntry(privilegeId, privilegeDefinition.app);
|
|
|
|
validateCatalogueEntry(privilegeId, privilegeDefinition.catalogue);
|
|
|
|
validateManagementEntry(privilegeId, privilegeDefinition.management);
|
|
validateAlertingEntry(privilegeId, privilegeDefinition.alerting);
|
|
validateCasesEntry(privilegeId, privilegeDefinition.cases);
|
|
});
|
|
|
|
const subFeatureEntries = feature.subFeatures ?? [];
|
|
subFeatureEntries.forEach((subFeature) => {
|
|
subFeature.privilegeGroups.forEach((subFeaturePrivilegeGroup) => {
|
|
subFeaturePrivilegeGroup.privileges.forEach((subFeaturePrivilege) => {
|
|
validateAppEntry(subFeaturePrivilege.id, subFeaturePrivilege.app);
|
|
validateCatalogueEntry(subFeaturePrivilege.id, subFeaturePrivilege.catalogue);
|
|
validateManagementEntry(subFeaturePrivilege.id, subFeaturePrivilege.management);
|
|
validateAlertingEntry(subFeaturePrivilege.id, subFeaturePrivilege.alerting);
|
|
validateCasesEntry(subFeaturePrivilege.id, subFeaturePrivilege.cases);
|
|
});
|
|
});
|
|
});
|
|
|
|
if (unseenApps.size > 0) {
|
|
throw new Error(
|
|
`Feature ${
|
|
feature.id
|
|
} specifies app entries which are not granted to any privileges: ${Array.from(
|
|
unseenApps.values()
|
|
).join(',')}`
|
|
);
|
|
}
|
|
|
|
if (unseenCatalogue.size > 0) {
|
|
throw new Error(
|
|
`Feature ${
|
|
feature.id
|
|
} specifies catalogue entries which are not granted to any privileges: ${Array.from(
|
|
unseenCatalogue.values()
|
|
).join(',')}`
|
|
);
|
|
}
|
|
|
|
if (unseenManagement.size > 0) {
|
|
const ungrantedManagement = Array.from(unseenManagement.entries()).reduce((acc, entry) => {
|
|
const values = Array.from(entry[1].values()).map(
|
|
(managementPage) => `${entry[0]}.${managementPage}`
|
|
);
|
|
return [...acc, ...values];
|
|
}, [] as string[]);
|
|
|
|
throw new Error(
|
|
`Feature ${
|
|
feature.id
|
|
} specifies management entries which are not granted to any privileges: ${ungrantedManagement.join(
|
|
','
|
|
)}`
|
|
);
|
|
}
|
|
|
|
if (unseenAlertTypes.size > 0) {
|
|
throw new Error(
|
|
`Feature ${
|
|
feature.id
|
|
} specifies alerting entries which are not granted to any privileges: ${Array.from(
|
|
unseenAlertTypes.values()
|
|
).join(',')}`
|
|
);
|
|
}
|
|
|
|
if (unseenCasesTypes.size > 0) {
|
|
throw new Error(
|
|
`Feature ${
|
|
feature.id
|
|
} specifies cases entries which are not granted to any privileges: ${Array.from(
|
|
unseenCasesTypes.values()
|
|
).join(',')}`
|
|
);
|
|
}
|
|
}
|
|
|
|
export function validateElasticsearchFeature(feature: ElasticsearchFeatureConfig) {
|
|
elasticsearchFeatureSchema.validate(feature);
|
|
// the following validation can't be enforced by the Joi schema without a very convoluted and verbose definition
|
|
const { privileges } = feature;
|
|
privileges.forEach((privilege, index) => {
|
|
const {
|
|
requiredClusterPrivileges = [],
|
|
requiredIndexPrivileges = [],
|
|
requiredRoles = [],
|
|
} = privilege;
|
|
|
|
if (
|
|
requiredClusterPrivileges.length === 0 &&
|
|
requiredIndexPrivileges.length === 0 &&
|
|
requiredRoles.length === 0
|
|
) {
|
|
throw new Error(
|
|
`Feature ${feature.id} has a privilege definition at index ${index} without any privileges defined.`
|
|
);
|
|
}
|
|
});
|
|
}
|