Skip to content

Commit 00a2026

Browse files
Optimize performance of ES privilege response validation (#90074)
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
1 parent 219a86d commit 00a2026

File tree

3 files changed

+57
-48
lines changed

3 files changed

+57
-48
lines changed

x-pack/plugins/security/server/authorization/__snapshots__/validate_es_response.test.ts.snap

Lines changed: 10 additions & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

x-pack/plugins/security/server/authorization/check_privileges.test.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,7 @@ describe('#atSpace', () => {
316316
},
317317
});
318318
expect(result).toMatchInlineSnapshot(
319-
`[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]]]]`
319+
`[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]`
320320
);
321321
});
322322

@@ -338,7 +338,7 @@ describe('#atSpace', () => {
338338
},
339339
});
340340
expect(result).toMatchInlineSnapshot(
341-
`[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]]]]]`
341+
`[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]]`
342342
);
343343
});
344344
});
@@ -1092,7 +1092,7 @@ describe('#atSpaces', () => {
10921092
},
10931093
});
10941094
expect(result).toMatchInlineSnapshot(
1095-
`[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]]]]]`
1095+
`[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]]`
10961096
);
10971097
});
10981098

@@ -1379,7 +1379,7 @@ describe('#atSpaces', () => {
13791379
},
13801380
});
13811381
expect(result).toMatchInlineSnapshot(
1382-
`[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]]]]`
1382+
`[Error: Invalid response received from Elasticsearch has_privilege endpoint. Error: [application.kibana-our_application]: Payload did not match expected resources]`
13831383
);
13841384
});
13851385

@@ -1407,7 +1407,7 @@ describe('#atSpaces', () => {
14071407
},
14081408
});
14091409
expect(result).toMatchInlineSnapshot(
1410-
`[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]]]]`
1410+
`[Error: Invalid response received from Elasticsearch has_privilege endpoint. Error: [application.kibana-our_application]: Payload did not match expected resources]`
14111411
);
14121412
});
14131413

@@ -1440,7 +1440,7 @@ describe('#atSpaces', () => {
14401440
},
14411441
});
14421442
expect(result).toMatchInlineSnapshot(
1443-
`[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]]]`
1443+
`[Error: Invalid response received from Elasticsearch has_privilege endpoint. Error: [application.kibana-our_application]: Payload did not match expected resources]`
14441444
);
14451445
});
14461446

@@ -1463,7 +1463,7 @@ describe('#atSpaces', () => {
14631463
},
14641464
});
14651465
expect(result).toMatchInlineSnapshot(
1466-
`[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]]]]`
1466+
`[Error: Invalid response received from Elasticsearch has_privilege endpoint. Error: [application.kibana-our_application]: Payload did not match expected resources]`
14671467
);
14681468
});
14691469
});
@@ -2266,7 +2266,7 @@ describe('#globally', () => {
22662266
},
22672267
});
22682268
expect(result).toMatchInlineSnapshot(
2269-
`[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]]]]]`
2269+
`[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]]`
22702270
);
22712271
});
22722272

@@ -2384,7 +2384,7 @@ describe('#globally', () => {
23842384
},
23852385
});
23862386
expect(result).toMatchInlineSnapshot(
2387-
`[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]]]]`
2387+
`[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]`
23882388
);
23892389
});
23902390

@@ -2405,7 +2405,7 @@ describe('#globally', () => {
24052405
},
24062406
});
24072407
expect(result).toMatchInlineSnapshot(
2408-
`[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]]]]]`
2408+
`[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]]`
24092409
);
24102410
});
24112411
});

x-pack/plugins/security/server/authorization/validate_es_response.ts

Lines changed: 37 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
* 2.0.
66
*/
77

8-
import Joi from 'joi';
8+
import { schema } from '@kbn/config-schema';
99
import { HasPrivilegesResponse } from './types';
1010

1111
export function validateEsPrivilegeResponse(
@@ -14,48 +14,57 @@ export function validateEsPrivilegeResponse(
1414
actions: string[],
1515
resources: string[]
1616
) {
17-
const schema = buildValidationSchema(application, actions, resources);
18-
const { error, value } = schema.validate(response);
19-
20-
if (error) {
21-
throw new Error(
22-
`Invalid response received from Elasticsearch has_privilege endpoint. ${error}`
23-
);
17+
const validationSchema = buildValidationSchema(application, actions, resources);
18+
try {
19+
validationSchema.validate(response);
20+
} catch (e) {
21+
throw new Error(`Invalid response received from Elasticsearch has_privilege endpoint. ${e}`);
2422
}
2523

26-
return value;
24+
return response;
2725
}
2826

2927
function buildActionsValidationSchema(actions: string[]) {
30-
return Joi.object({
28+
return schema.object({
3129
...actions.reduce<Record<string, any>>((acc, action) => {
3230
return {
3331
...acc,
34-
[action]: Joi.bool().required(),
32+
[action]: schema.boolean(),
3533
};
3634
}, {}),
37-
}).required();
35+
});
3836
}
3937

4038
function buildValidationSchema(application: string, actions: string[], resources: string[]) {
4139
const actionValidationSchema = buildActionsValidationSchema(actions);
4240

43-
const resourceValidationSchema = Joi.object({
44-
...resources.reduce((acc, resource) => {
45-
return {
46-
...acc,
47-
[resource]: actionValidationSchema,
48-
};
49-
}, {}),
50-
}).required();
41+
const resourceValidationSchema = schema.object(
42+
{},
43+
{
44+
unknowns: 'allow',
45+
validate: (value) => {
46+
const actualResources = Object.keys(value).sort();
47+
if (
48+
resources.length !== actualResources.length ||
49+
!resources.sort().every((x, i) => x === actualResources[i])
50+
) {
51+
throw new Error('Payload did not match expected resources');
52+
}
53+
54+
Object.values(value).forEach((actionResult) => {
55+
actionValidationSchema.validate(actionResult);
56+
});
57+
},
58+
}
59+
);
5160

52-
return Joi.object({
53-
username: Joi.string().required(),
54-
has_all_requested: Joi.bool(),
55-
cluster: Joi.object(),
56-
application: Joi.object({
61+
return schema.object({
62+
username: schema.string(),
63+
has_all_requested: schema.boolean(),
64+
cluster: schema.object({}, { unknowns: 'allow' }),
65+
application: schema.object({
5766
[application]: resourceValidationSchema,
58-
}).required(),
59-
index: Joi.object(),
60-
}).required();
67+
}),
68+
index: schema.object({}, { unknowns: 'allow' }),
69+
});
6170
}

0 commit comments

Comments
 (0)