mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
# Backport This will backport the following commits from `main` to `8.8`: - [Fixes per-object audit logging for saved object resolve actions (#160014)](https://github.com/elastic/kibana/pull/160014) <!--- Backport version: 8.9.7 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sqren/backport) <!--BACKPORT [{"author":{"name":"Jeramy Soucy","email":"jeramy.soucy@elastic.co"},"sourceCommit":{"committedDate":"2023-06-21T08:03:31Z","message":"Fixes per-object audit logging for saved object resolve actions (#160014)\n\n## Summary\r\nIt was noticed that audit logs for Saved Object 'resolve' actions were\r\nnot getting generated. On investigation we found that this regression\r\nwas introduced in #148165. During work on migrating saved object\r\nauthorization and auditing logic into the saved object security\r\nextension, several abstracted functions were created for reuse. One of\r\nthese is responsible for performing audit logging (`auditHelper`). In\r\nmigration of the unit tests, inputs to the `auditHelper`, via\r\n`authorize` and `enforceAuthoriztion`, were not correctly validated for\r\n`authorizeAndRedactInternalBulkResolve`.\r\n\r\nThis PR corrects the regression by passing the saved object information\r\nthrough the call chain to `auditHelper`, and addresses the error in the\r\nunit tests by implementing checks to validate the input parameters at\r\neach level of the call chain.\r\n\r\nThis PR also corrects the unit test for auditObjectsForSpaceDeletion.\r\n\r\n### Tests\r\nSee\r\n`x-pack/plugins/security/server/saved_objects/saved_objects_security_extension.test.ts`\r\n'#authorizeAndRedactInternalBulkResolve'\r\n\r\n### Manual testing:\r\n- Enable audit logging, example:\r\n\r\n```yaml\r\nxpack.security.audit.enabled: true\r\nxpack.security.audit.appender:\r\n type: rolling-file\r\n fileName: ./logs/audit.log\r\n policy:\r\n type: time-interval\r\n interval: 24h\r\n strategy:\r\n type: numeric\r\n max: 10\r\n layout:\r\n type: json\r\n```\r\n\r\n- Create several dashboards (or add all sample data sets)\r\n- Open each dashboard in the Kibana UI\r\n- Check the audit logs and verify that there is a \"saved_object_resolve\"\r\nentry to describe each dashboard access event, and that each log\r\ncontains the dashboard type and id, example:\r\n\r\n>\r\n{\"event\":{\"action\":\"saved_object_resolve\",\"category\":[\"database\"],\"type\":[\"access\"],\"outcome\":\"success\"},\"kibana\":{\"space_id\":\"default\",\"session_id\":\"HrDKOgZ6EiEV2bf8Io9bc/enfGsS+fTtbQ5g2ap21CU=\",\"saved_object\":{\"type\":\"dashboard\",\"id\":\"7adfa750-4c81-11e8-b3d7-01146121b73d\"}},\"user\":{\"id\":\"u_mGBROF_q5bmFCATbLXAcCwKa0k8JvONAwSruelyKA5E_0\",\"name\":\"elastic\",\"roles\":[\"superuser\"]},\"trace\":{\"id\":\"b11f0414-ca68-4e28-854a-249b19f88c36\"},\"client\":{\"ip\":\"127.0.0.1\"},\"http\":{\"request\":{\"headers\":{\"x-forwarded-for\":\"127.0.0.1\"}}},\"service\":{\"node\":{\"roles\":[\"background_tasks\",\"ui\"]}},\"ecs\":{\"version\":\"8.6.1\"},\"@timestamp\":\"2023-06-20T18:30:20.943+02:00\",\"message\":\"User\r\nhas resolved dashboard\r\n[id=7adfa750-4c81-11e8-b3d7-01146121b73d]\",\"log\":{\"level\":\"INFO\",\"logger\":\"plugins.security.audit.ecs\"},\"process\":{\"pid\":15004},\"transaction\":{\"id\":\"4917a352c3f68cd6\"}}\r\n\r\n## Release Note:\r\nThis PR fixes a regression where the \"saved_object_resolve\" audit action\r\nwas not being logged per object.","sha":"2ec067f7474118d089f46c0e2a9e726dc4c6c852","branchLabelMapping":{"^v8.9.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["blocker","release_note:fix","Feature:Saved Objects","Feature:Security/Audit","backport:prev-minor","v8.9.0","v8.8.2"],"number":160014,"url":"https://github.com/elastic/kibana/pull/160014","mergeCommit":{"message":"Fixes per-object audit logging for saved object resolve actions (#160014)\n\n## Summary\r\nIt was noticed that audit logs for Saved Object 'resolve' actions were\r\nnot getting generated. On investigation we found that this regression\r\nwas introduced in #148165. During work on migrating saved object\r\nauthorization and auditing logic into the saved object security\r\nextension, several abstracted functions were created for reuse. One of\r\nthese is responsible for performing audit logging (`auditHelper`). In\r\nmigration of the unit tests, inputs to the `auditHelper`, via\r\n`authorize` and `enforceAuthoriztion`, were not correctly validated for\r\n`authorizeAndRedactInternalBulkResolve`.\r\n\r\nThis PR corrects the regression by passing the saved object information\r\nthrough the call chain to `auditHelper`, and addresses the error in the\r\nunit tests by implementing checks to validate the input parameters at\r\neach level of the call chain.\r\n\r\nThis PR also corrects the unit test for auditObjectsForSpaceDeletion.\r\n\r\n### Tests\r\nSee\r\n`x-pack/plugins/security/server/saved_objects/saved_objects_security_extension.test.ts`\r\n'#authorizeAndRedactInternalBulkResolve'\r\n\r\n### Manual testing:\r\n- Enable audit logging, example:\r\n\r\n```yaml\r\nxpack.security.audit.enabled: true\r\nxpack.security.audit.appender:\r\n type: rolling-file\r\n fileName: ./logs/audit.log\r\n policy:\r\n type: time-interval\r\n interval: 24h\r\n strategy:\r\n type: numeric\r\n max: 10\r\n layout:\r\n type: json\r\n```\r\n\r\n- Create several dashboards (or add all sample data sets)\r\n- Open each dashboard in the Kibana UI\r\n- Check the audit logs and verify that there is a \"saved_object_resolve\"\r\nentry to describe each dashboard access event, and that each log\r\ncontains the dashboard type and id, example:\r\n\r\n>\r\n{\"event\":{\"action\":\"saved_object_resolve\",\"category\":[\"database\"],\"type\":[\"access\"],\"outcome\":\"success\"},\"kibana\":{\"space_id\":\"default\",\"session_id\":\"HrDKOgZ6EiEV2bf8Io9bc/enfGsS+fTtbQ5g2ap21CU=\",\"saved_object\":{\"type\":\"dashboard\",\"id\":\"7adfa750-4c81-11e8-b3d7-01146121b73d\"}},\"user\":{\"id\":\"u_mGBROF_q5bmFCATbLXAcCwKa0k8JvONAwSruelyKA5E_0\",\"name\":\"elastic\",\"roles\":[\"superuser\"]},\"trace\":{\"id\":\"b11f0414-ca68-4e28-854a-249b19f88c36\"},\"client\":{\"ip\":\"127.0.0.1\"},\"http\":{\"request\":{\"headers\":{\"x-forwarded-for\":\"127.0.0.1\"}}},\"service\":{\"node\":{\"roles\":[\"background_tasks\",\"ui\"]}},\"ecs\":{\"version\":\"8.6.1\"},\"@timestamp\":\"2023-06-20T18:30:20.943+02:00\",\"message\":\"User\r\nhas resolved dashboard\r\n[id=7adfa750-4c81-11e8-b3d7-01146121b73d]\",\"log\":{\"level\":\"INFO\",\"logger\":\"plugins.security.audit.ecs\"},\"process\":{\"pid\":15004},\"transaction\":{\"id\":\"4917a352c3f68cd6\"}}\r\n\r\n## Release Note:\r\nThis PR fixes a regression where the \"saved_object_resolve\" audit action\r\nwas not being logged per object.","sha":"2ec067f7474118d089f46c0e2a9e726dc4c6c852"}},"sourceBranch":"main","suggestedTargetBranches":["8.8"],"targetPullRequestStates":[{"branch":"main","label":"v8.9.0","labelRegex":"^v8.9.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/160014","number":160014,"mergeCommit":{"message":"Fixes per-object audit logging for saved object resolve actions (#160014)\n\n## Summary\r\nIt was noticed that audit logs for Saved Object 'resolve' actions were\r\nnot getting generated. On investigation we found that this regression\r\nwas introduced in #148165. During work on migrating saved object\r\nauthorization and auditing logic into the saved object security\r\nextension, several abstracted functions were created for reuse. One of\r\nthese is responsible for performing audit logging (`auditHelper`). In\r\nmigration of the unit tests, inputs to the `auditHelper`, via\r\n`authorize` and `enforceAuthoriztion`, were not correctly validated for\r\n`authorizeAndRedactInternalBulkResolve`.\r\n\r\nThis PR corrects the regression by passing the saved object information\r\nthrough the call chain to `auditHelper`, and addresses the error in the\r\nunit tests by implementing checks to validate the input parameters at\r\neach level of the call chain.\r\n\r\nThis PR also corrects the unit test for auditObjectsForSpaceDeletion.\r\n\r\n### Tests\r\nSee\r\n`x-pack/plugins/security/server/saved_objects/saved_objects_security_extension.test.ts`\r\n'#authorizeAndRedactInternalBulkResolve'\r\n\r\n### Manual testing:\r\n- Enable audit logging, example:\r\n\r\n```yaml\r\nxpack.security.audit.enabled: true\r\nxpack.security.audit.appender:\r\n type: rolling-file\r\n fileName: ./logs/audit.log\r\n policy:\r\n type: time-interval\r\n interval: 24h\r\n strategy:\r\n type: numeric\r\n max: 10\r\n layout:\r\n type: json\r\n```\r\n\r\n- Create several dashboards (or add all sample data sets)\r\n- Open each dashboard in the Kibana UI\r\n- Check the audit logs and verify that there is a \"saved_object_resolve\"\r\nentry to describe each dashboard access event, and that each log\r\ncontains the dashboard type and id, example:\r\n\r\n>\r\n{\"event\":{\"action\":\"saved_object_resolve\",\"category\":[\"database\"],\"type\":[\"access\"],\"outcome\":\"success\"},\"kibana\":{\"space_id\":\"default\",\"session_id\":\"HrDKOgZ6EiEV2bf8Io9bc/enfGsS+fTtbQ5g2ap21CU=\",\"saved_object\":{\"type\":\"dashboard\",\"id\":\"7adfa750-4c81-11e8-b3d7-01146121b73d\"}},\"user\":{\"id\":\"u_mGBROF_q5bmFCATbLXAcCwKa0k8JvONAwSruelyKA5E_0\",\"name\":\"elastic\",\"roles\":[\"superuser\"]},\"trace\":{\"id\":\"b11f0414-ca68-4e28-854a-249b19f88c36\"},\"client\":{\"ip\":\"127.0.0.1\"},\"http\":{\"request\":{\"headers\":{\"x-forwarded-for\":\"127.0.0.1\"}}},\"service\":{\"node\":{\"roles\":[\"background_tasks\",\"ui\"]}},\"ecs\":{\"version\":\"8.6.1\"},\"@timestamp\":\"2023-06-20T18:30:20.943+02:00\",\"message\":\"User\r\nhas resolved dashboard\r\n[id=7adfa750-4c81-11e8-b3d7-01146121b73d]\",\"log\":{\"level\":\"INFO\",\"logger\":\"plugins.security.audit.ecs\"},\"process\":{\"pid\":15004},\"transaction\":{\"id\":\"4917a352c3f68cd6\"}}\r\n\r\n## Release Note:\r\nThis PR fixes a regression where the \"saved_object_resolve\" audit action\r\nwas not being logged per object.","sha":"2ec067f7474118d089f46c0e2a9e726dc4c6c852"}},{"branch":"8.8","label":"v8.8.2","labelRegex":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"}]}] BACKPORT--> Co-authored-by: Jeramy Soucy <jeramy.soucy@elastic.co>
This commit is contained in:
parent
78d76d8481
commit
e2c3c9a301
2 changed files with 104 additions and 72 deletions
|
@ -4751,6 +4751,13 @@ describe('#authorizeAndRedactInternalBulkResolve', () => {
|
|||
['login:']: { authorizedSpaces: ['x', 'foo'] },
|
||||
});
|
||||
|
||||
const auditObjects = objects.map((obj) => {
|
||||
return {
|
||||
type: obj.saved_object.type,
|
||||
id: obj.saved_object.id,
|
||||
};
|
||||
});
|
||||
|
||||
test('returns empty array when no objects are provided`', async () => {
|
||||
const { securityExtension } = setup();
|
||||
const emptyObjects: Array<SavedObjectsResolveResponse<unknown> | BulkResolveError> = [];
|
||||
|
@ -4790,6 +4797,7 @@ describe('#authorizeAndRedactInternalBulkResolve', () => {
|
|||
spaces: expectedSpaces,
|
||||
enforceMap: expectedEnforceMap,
|
||||
auditOptions: {
|
||||
objects: auditObjects,
|
||||
useSuccessOutcome: true,
|
||||
},
|
||||
});
|
||||
|
@ -4817,7 +4825,7 @@ describe('#authorizeAndRedactInternalBulkResolve', () => {
|
|||
action: SecurityAction.INTERNAL_BULK_RESOLVE,
|
||||
typesAndSpaces: expectedEnforceMap,
|
||||
typeMap: partiallyAuthorizedTypeMap,
|
||||
auditOptions: { useSuccessOutcome: true },
|
||||
auditOptions: { objects: auditObjects, useSuccessOutcome: true },
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -4835,7 +4843,7 @@ describe('#authorizeAndRedactInternalBulkResolve', () => {
|
|||
action: SecurityAction.INTERNAL_BULK_RESOLVE,
|
||||
typesAndSpaces: expectedEnforceMap,
|
||||
typeMap: fullyAuthorizedTypeMap,
|
||||
auditOptions: { useSuccessOutcome: true },
|
||||
auditOptions: { objects: auditObjects, useSuccessOutcome: true },
|
||||
});
|
||||
expect(result).toEqual(objects);
|
||||
});
|
||||
|
@ -4866,37 +4874,44 @@ describe('#authorizeAndRedactInternalBulkResolve', () => {
|
|||
action: 'saved_object_resolve',
|
||||
addToSpaces: undefined,
|
||||
deleteFromSpaces: undefined,
|
||||
objects: undefined,
|
||||
objects: auditObjects,
|
||||
useSuccessOutcome: true,
|
||||
});
|
||||
expect(addAuditEventSpy).toHaveBeenCalledTimes(1);
|
||||
expect(addAuditEventSpy).toHaveBeenCalledWith({
|
||||
action: 'saved_object_resolve',
|
||||
addToSpaces: undefined,
|
||||
deleteFromSpaces: undefined,
|
||||
unauthorizedSpaces: undefined,
|
||||
unauthorizedTypes: undefined,
|
||||
error: undefined,
|
||||
outcome: 'success',
|
||||
});
|
||||
expect(auditLogger.log).toHaveBeenCalledTimes(1);
|
||||
expect(auditLogger.log).toHaveBeenCalledWith({
|
||||
error: undefined,
|
||||
event: {
|
||||
action: AuditAction.RESOLVE,
|
||||
category: ['database'],
|
||||
expect(addAuditEventSpy).toHaveBeenCalledTimes(auditObjects.length);
|
||||
let i = 1;
|
||||
for (const auditObj of auditObjects) {
|
||||
expect(addAuditEventSpy).toHaveBeenNthCalledWith(i++, {
|
||||
action: 'saved_object_resolve',
|
||||
addToSpaces: undefined,
|
||||
deleteFromSpaces: undefined,
|
||||
unauthorizedSpaces: undefined,
|
||||
unauthorizedTypes: undefined,
|
||||
error: undefined,
|
||||
outcome: 'success',
|
||||
type: ['access'],
|
||||
},
|
||||
kibana: {
|
||||
add_to_spaces: undefined,
|
||||
delete_from_spaces: undefined,
|
||||
unauthorized_spaces: undefined,
|
||||
unauthorized_types: undefined,
|
||||
saved_object: undefined,
|
||||
},
|
||||
message: `User has resolved saved objects`,
|
||||
});
|
||||
savedObject: auditObj,
|
||||
});
|
||||
}
|
||||
expect(auditLogger.log).toHaveBeenCalledTimes(auditObjects.length);
|
||||
i = 1;
|
||||
for (const auditObj of auditObjects) {
|
||||
expect(auditLogger.log).toHaveBeenNthCalledWith(i++, {
|
||||
error: undefined,
|
||||
event: {
|
||||
action: AuditAction.RESOLVE,
|
||||
category: ['database'],
|
||||
outcome: 'success',
|
||||
type: ['access'],
|
||||
},
|
||||
kibana: {
|
||||
add_to_spaces: undefined,
|
||||
delete_from_spaces: undefined,
|
||||
unauthorized_spaces: undefined,
|
||||
unauthorized_types: undefined,
|
||||
saved_object: auditObj,
|
||||
},
|
||||
message: `User has resolved ${auditObj.type} [id=${auditObj.id}]`,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
test(`throws when unauthorized`, async () => {
|
||||
|
@ -4939,28 +4954,31 @@ describe('#authorizeAndRedactInternalBulkResolve', () => {
|
|||
);
|
||||
expect(enforceAuthorizationSpy).toHaveBeenCalledTimes(1);
|
||||
|
||||
expect(addAuditEventSpy).toHaveBeenCalledTimes(1);
|
||||
expect(auditLogger.log).toHaveBeenCalledTimes(1);
|
||||
expect(auditLogger.log).toHaveBeenCalledWith({
|
||||
error: {
|
||||
code: 'Error',
|
||||
message: `Unable to bulk_get ${resolveObj1.saved_object.type},${resolveObj2.saved_object.type}`,
|
||||
},
|
||||
event: {
|
||||
action: AuditAction.RESOLVE,
|
||||
category: ['database'],
|
||||
outcome: 'failure',
|
||||
type: ['access'],
|
||||
},
|
||||
kibana: {
|
||||
add_to_spaces: undefined,
|
||||
delete_from_spaces: undefined,
|
||||
unauthorized_spaces: undefined,
|
||||
unauthorized_types: undefined,
|
||||
saved_object: undefined,
|
||||
},
|
||||
message: `Failed attempt to resolve saved objects`,
|
||||
});
|
||||
expect(addAuditEventSpy).toHaveBeenCalledTimes(objects.length);
|
||||
expect(auditLogger.log).toHaveBeenCalledTimes(objects.length);
|
||||
let i = 1;
|
||||
for (const auditObj of auditObjects) {
|
||||
expect(auditLogger.log).toHaveBeenNthCalledWith(i++, {
|
||||
error: {
|
||||
code: 'Error',
|
||||
message: `Unable to bulk_get ${resolveObj1.saved_object.type},${resolveObj2.saved_object.type}`,
|
||||
},
|
||||
event: {
|
||||
action: AuditAction.RESOLVE,
|
||||
category: ['database'],
|
||||
outcome: 'failure',
|
||||
type: ['access'],
|
||||
},
|
||||
kibana: {
|
||||
add_to_spaces: undefined,
|
||||
delete_from_spaces: undefined,
|
||||
unauthorized_spaces: undefined,
|
||||
unauthorized_types: undefined,
|
||||
saved_object: auditObj,
|
||||
},
|
||||
message: `Failed attempt to resolve ${auditObj.type} [id=${auditObj.id}]`,
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -6196,25 +6214,39 @@ describe(`#auditObjectsForSpaceDeletion`, () => {
|
|||
expect(auditHelperSpy).not.toHaveBeenCalled(); // The helper is not called, the addAudit method is called directly
|
||||
expect(addAuditEventSpy).toHaveBeenCalledTimes(objects.length - 1);
|
||||
expect(auditLogger.log).toHaveBeenCalledTimes(objects.length - 1);
|
||||
const i = 0;
|
||||
for (const obj of objects) {
|
||||
if (i === 0) continue; // The first object namespaces includes '*', so there will not be an audit for it
|
||||
|
||||
expect(auditLogger.log).toHaveBeenNthCalledWith(i, {
|
||||
error: undefined,
|
||||
event: {
|
||||
action: AuditAction.UPDATE_OBJECTS_SPACES,
|
||||
category: ['database'],
|
||||
outcome: 'unknown',
|
||||
type: ['change'],
|
||||
},
|
||||
kibana: {
|
||||
add_to_spaces: undefined,
|
||||
delete_from_spaces: obj.namespaces!.length > 1 ? obj.namespaces : undefined,
|
||||
saved_object: undefined,
|
||||
},
|
||||
message: `User is updating spaces of dashboard [id=${obj.id}]`,
|
||||
});
|
||||
}
|
||||
// The first object's namespaces includes '*', so there will not be an audit for it
|
||||
|
||||
// The second object only exists in the space we're deleting, so it is audited as a delete
|
||||
expect(auditLogger.log).toHaveBeenNthCalledWith(1, {
|
||||
error: undefined,
|
||||
event: {
|
||||
action: AuditAction.DELETE,
|
||||
category: ['database'],
|
||||
outcome: 'unknown',
|
||||
type: ['deletion'],
|
||||
},
|
||||
kibana: {
|
||||
delete_from_spaces: undefined,
|
||||
saved_object: { type: objects[1].type, id: objects[1].id },
|
||||
},
|
||||
message: `User is deleting dashboard [id=${objects[1].id}]`,
|
||||
});
|
||||
|
||||
// The third object exists in spaces other than what we're deleting, so it is audited as a change
|
||||
expect(auditLogger.log).toHaveBeenNthCalledWith(2, {
|
||||
error: undefined,
|
||||
event: {
|
||||
action: AuditAction.UPDATE_OBJECTS_SPACES,
|
||||
category: ['database'],
|
||||
outcome: 'unknown',
|
||||
type: ['change'],
|
||||
},
|
||||
kibana: {
|
||||
delete_from_spaces: [spaceId],
|
||||
saved_object: { type: objects[2].type, id: objects[2].id },
|
||||
},
|
||||
message: `User is updating spaces of dashboard [id=${objects[2].id}]`,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1212,7 +1212,7 @@ export class SavedObjectsSecurityExtension implements ISavedObjectsSecurityExten
|
|||
types: new Set(typesAndSpaces.keys()),
|
||||
spaces: spacesToAuthorize,
|
||||
enforceMap: typesAndSpaces,
|
||||
auditOptions: { useSuccessOutcome: true },
|
||||
auditOptions: { objects: auditableObjects, useSuccessOutcome: true },
|
||||
});
|
||||
|
||||
return objects.map((result) => {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue