Optimize performance of ES privilege response validation (#90074)

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Larry Gregory 2021-02-04 19:18:10 -05:00 committed by GitHub
parent 219a86dbe5
commit 00a20268b1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 57 additions and 48 deletions

View file

@ -1,21 +1,21 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`validateEsPrivilegeResponse fails validation when an action is malformed in the response 1`] = `"Invalid response received from Elasticsearch has_privilege endpoint. ValidationError: child \\"application\\" fails because [child \\"foo-application\\" fails because [child \\"foo-resource\\" fails because [child \\"action3\\" fails because [\\"action3\\" must be a boolean]]]]"`;
exports[`validateEsPrivilegeResponse fails validation when an action is malformed in the response 1`] = `"Invalid response received from Elasticsearch has_privilege endpoint. Error: [application.foo-application]: [action3]: expected value of type [boolean] but got [string]"`;
exports[`validateEsPrivilegeResponse fails validation when an action is missing in the response 1`] = `"Invalid response received from Elasticsearch has_privilege endpoint. ValidationError: child \\"application\\" fails because [child \\"foo-application\\" fails because [child \\"foo-resource\\" fails because [child \\"action2\\" fails because [\\"action2\\" is required]]]]"`;
exports[`validateEsPrivilegeResponse fails validation when an action is missing in the response 1`] = `"Invalid response received from Elasticsearch has_privilege endpoint. Error: [application.foo-application]: [action2]: expected value of type [boolean] but got [undefined]"`;
exports[`validateEsPrivilegeResponse fails validation when an expected resource property is missing from the response 1`] = `"Invalid response received from Elasticsearch has_privilege endpoint. ValidationError: child \\"application\\" fails because [child \\"foo-application\\" fails because [child \\"bar-resource\\" fails because [\\"bar-resource\\" is required]]]"`;
exports[`validateEsPrivilegeResponse fails validation when an expected resource property is missing from the response 1`] = `"Invalid response received from Elasticsearch has_privilege endpoint. Error: [application.foo-application]: Payload did not match expected resources"`;
exports[`validateEsPrivilegeResponse fails validation when an extra action is present in the response 1`] = `"Invalid response received from Elasticsearch has_privilege endpoint. ValidationError: child \\"application\\" fails because [child \\"foo-application\\" fails because [child \\"foo-resource\\" fails because [\\"action4\\" is not allowed]]]"`;
exports[`validateEsPrivilegeResponse fails validation when an extra action is present in the response 1`] = `"Invalid response received from Elasticsearch has_privilege endpoint. Error: [application.foo-application]: [action4]: definition for this key is missing"`;
exports[`validateEsPrivilegeResponse fails validation when an extra application is present in the response 1`] = `"Invalid response received from Elasticsearch has_privilege endpoint. ValidationError: child \\"application\\" fails because [\\"otherApplication\\" is not allowed]"`;
exports[`validateEsPrivilegeResponse fails validation when an extra application is present in the response 1`] = `"Invalid response received from Elasticsearch has_privilege endpoint. Error: [application.otherApplication]: definition for this key is missing"`;
exports[`validateEsPrivilegeResponse fails validation when an unexpected resource property is present in the response 1`] = `"Invalid response received from Elasticsearch has_privilege endpoint. ValidationError: child \\"application\\" fails because [child \\"foo-application\\" fails because [child \\"bar-resource\\" fails because [\\"bar-resource\\" is required]]]"`;
exports[`validateEsPrivilegeResponse fails validation when an unexpected resource property is present in the response 1`] = `"Invalid response received from Elasticsearch has_privilege endpoint. Error: [application.foo-application]: Payload did not match expected resources"`;
exports[`validateEsPrivilegeResponse fails validation when the "application" property is missing from the response 1`] = `"Invalid response received from Elasticsearch has_privilege endpoint. ValidationError: child \\"application\\" fails because [\\"application\\" is required]"`;
exports[`validateEsPrivilegeResponse fails validation when the "application" property is missing from the response 1`] = `"Invalid response received from Elasticsearch has_privilege endpoint. Error: [application.foo-application]: Payload did not match expected resources"`;
exports[`validateEsPrivilegeResponse fails validation when the requested application is missing from the response 1`] = `"Invalid response received from Elasticsearch has_privilege endpoint. ValidationError: child \\"application\\" fails because [child \\"foo-application\\" fails because [\\"foo-application\\" is required]]"`;
exports[`validateEsPrivilegeResponse fails validation when the requested application is missing from the response 1`] = `"Invalid response received from Elasticsearch has_privilege endpoint. Error: [application.foo-application]: Payload did not match expected resources"`;
exports[`validateEsPrivilegeResponse fails validation when the resource propertry is malformed in the response 1`] = `"Invalid response received from Elasticsearch has_privilege endpoint. ValidationError: child \\"application\\" fails because [child \\"foo-application\\" fails because [child \\"foo-resource\\" fails because [\\"foo-resource\\" must be an object]]]"`;
exports[`validateEsPrivilegeResponse fails validation when the resource propertry is malformed in the response 1`] = `"Invalid response received from Elasticsearch has_privilege endpoint. Error: [application.foo-application]: could not parse object value from json input"`;
exports[`validateEsPrivilegeResponse fails validation when there are no resource properties in the response 1`] = `"Invalid response received from Elasticsearch has_privilege endpoint. ValidationError: child \\"application\\" fails because [child \\"foo-application\\" fails because [child \\"foo-resource\\" fails because [\\"foo-resource\\" is required]]]"`;
exports[`validateEsPrivilegeResponse fails validation when there are no resource properties in the response 1`] = `"Invalid response received from Elasticsearch has_privilege endpoint. Error: [application.foo-application]: Payload did not match expected resources"`;

View file

@ -316,7 +316,7 @@ describe('#atSpace', () => {
},
});
expect(result).toMatchInlineSnapshot(
`[Error: Invalid response received from Elasticsearch has_privilege endpoint. ValidationError: child "application" fails because [child "kibana-our_application" fails because [child "space:space_1" fails because ["saved_object:bar-type/get" is not allowed]]]]`
`[Error: Invalid response received from Elasticsearch has_privilege endpoint. Error: [application.kibana-our_application]: [saved_object:bar-type/get]: definition for this key is missing]`
);
});
@ -338,7 +338,7 @@ describe('#atSpace', () => {
},
});
expect(result).toMatchInlineSnapshot(
`[Error: Invalid response received from Elasticsearch has_privilege endpoint. ValidationError: child "application" fails because [child "kibana-our_application" fails because [child "space:space_1" fails because [child "saved_object:foo-type/get" fails because ["saved_object:foo-type/get" is required]]]]]`
`[Error: Invalid response received from Elasticsearch has_privilege endpoint. Error: [application.kibana-our_application]: [saved_object:foo-type/get]: expected value of type [boolean] but got [undefined]]`
);
});
});
@ -1092,7 +1092,7 @@ describe('#atSpaces', () => {
},
});
expect(result).toMatchInlineSnapshot(
`[Error: Invalid response received from Elasticsearch has_privilege endpoint. ValidationError: child "application" fails because [child "kibana-our_application" fails because [child "space:space_1" fails because [child "mock-action:version" fails because ["mock-action:version" is required]]]]]`
`[Error: Invalid response received from Elasticsearch has_privilege endpoint. Error: [application.kibana-our_application]: [mock-action:version]: expected value of type [boolean] but got [undefined]]`
);
});
@ -1379,7 +1379,7 @@ describe('#atSpaces', () => {
},
});
expect(result).toMatchInlineSnapshot(
`[Error: Invalid response received from Elasticsearch has_privilege endpoint. ValidationError: child "application" fails because [child "kibana-our_application" fails because [child "space:space_2" fails because ["space:space_2" is required]]]]`
`[Error: Invalid response received from Elasticsearch has_privilege endpoint. Error: [application.kibana-our_application]: Payload did not match expected resources]`
);
});
@ -1407,7 +1407,7 @@ describe('#atSpaces', () => {
},
});
expect(result).toMatchInlineSnapshot(
`[Error: Invalid response received from Elasticsearch has_privilege endpoint. ValidationError: child "application" fails because [child "kibana-our_application" fails because [child "space:space_2" fails because ["space:space_2" is required]]]]`
`[Error: Invalid response received from Elasticsearch has_privilege endpoint. Error: [application.kibana-our_application]: Payload did not match expected resources]`
);
});
@ -1440,7 +1440,7 @@ describe('#atSpaces', () => {
},
});
expect(result).toMatchInlineSnapshot(
`[Error: Invalid response received from Elasticsearch has_privilege endpoint. ValidationError: child "application" fails because [child "kibana-our_application" fails because ["space:space_3" is not allowed]]]`
`[Error: Invalid response received from Elasticsearch has_privilege endpoint. Error: [application.kibana-our_application]: Payload did not match expected resources]`
);
});
@ -1463,7 +1463,7 @@ describe('#atSpaces', () => {
},
});
expect(result).toMatchInlineSnapshot(
`[Error: Invalid response received from Elasticsearch has_privilege endpoint. ValidationError: child "application" fails because [child "kibana-our_application" fails because [child "space:space_2" fails because ["space:space_2" is required]]]]`
`[Error: Invalid response received from Elasticsearch has_privilege endpoint. Error: [application.kibana-our_application]: Payload did not match expected resources]`
);
});
});
@ -2266,7 +2266,7 @@ describe('#globally', () => {
},
});
expect(result).toMatchInlineSnapshot(
`[Error: Invalid response received from Elasticsearch has_privilege endpoint. ValidationError: child "application" fails because [child "kibana-our_application" fails because [child "*" fails because [child "mock-action:version" fails because ["mock-action:version" is required]]]]]`
`[Error: Invalid response received from Elasticsearch has_privilege endpoint. Error: [application.kibana-our_application]: [mock-action:version]: expected value of type [boolean] but got [undefined]]`
);
});
@ -2384,7 +2384,7 @@ describe('#globally', () => {
},
});
expect(result).toMatchInlineSnapshot(
`[Error: Invalid response received from Elasticsearch has_privilege endpoint. ValidationError: child "application" fails because [child "kibana-our_application" fails because [child "*" fails because ["saved_object:bar-type/get" is not allowed]]]]`
`[Error: Invalid response received from Elasticsearch has_privilege endpoint. Error: [application.kibana-our_application]: [saved_object:bar-type/get]: definition for this key is missing]`
);
});
@ -2405,7 +2405,7 @@ describe('#globally', () => {
},
});
expect(result).toMatchInlineSnapshot(
`[Error: Invalid response received from Elasticsearch has_privilege endpoint. ValidationError: child "application" fails because [child "kibana-our_application" fails because [child "*" fails because [child "saved_object:foo-type/get" fails because ["saved_object:foo-type/get" is required]]]]]`
`[Error: Invalid response received from Elasticsearch has_privilege endpoint. Error: [application.kibana-our_application]: [saved_object:foo-type/get]: expected value of type [boolean] but got [undefined]]`
);
});
});

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import Joi from 'joi';
import { schema } from '@kbn/config-schema';
import { HasPrivilegesResponse } from './types';
export function validateEsPrivilegeResponse(
@ -14,48 +14,57 @@ export function validateEsPrivilegeResponse(
actions: string[],
resources: string[]
) {
const schema = buildValidationSchema(application, actions, resources);
const { error, value } = schema.validate(response);
if (error) {
throw new Error(
`Invalid response received from Elasticsearch has_privilege endpoint. ${error}`
);
const validationSchema = buildValidationSchema(application, actions, resources);
try {
validationSchema.validate(response);
} catch (e) {
throw new Error(`Invalid response received from Elasticsearch has_privilege endpoint. ${e}`);
}
return value;
return response;
}
function buildActionsValidationSchema(actions: string[]) {
return Joi.object({
return schema.object({
...actions.reduce<Record<string, any>>((acc, action) => {
return {
...acc,
[action]: Joi.bool().required(),
[action]: schema.boolean(),
};
}, {}),
}).required();
});
}
function buildValidationSchema(application: string, actions: string[], resources: string[]) {
const actionValidationSchema = buildActionsValidationSchema(actions);
const resourceValidationSchema = Joi.object({
...resources.reduce((acc, resource) => {
return {
...acc,
[resource]: actionValidationSchema,
};
}, {}),
}).required();
const resourceValidationSchema = schema.object(
{},
{
unknowns: 'allow',
validate: (value) => {
const actualResources = Object.keys(value).sort();
if (
resources.length !== actualResources.length ||
!resources.sort().every((x, i) => x === actualResources[i])
) {
throw new Error('Payload did not match expected resources');
}
return Joi.object({
username: Joi.string().required(),
has_all_requested: Joi.bool(),
cluster: Joi.object(),
application: Joi.object({
Object.values(value).forEach((actionResult) => {
actionValidationSchema.validate(actionResult);
});
},
}
);
return schema.object({
username: schema.string(),
has_all_requested: schema.boolean(),
cluster: schema.object({}, { unknowns: 'allow' }),
application: schema.object({
[application]: resourceValidationSchema,
}).required(),
index: Joi.object(),
}).required();
}),
index: schema.object({}, { unknowns: 'allow' }),
});
}