[8.8] Fixes per-object audit logging for saved object resolve actions (#160014) (#160101)

# 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:
Kibana Machine 2023-06-21 05:36:32 -04:00 committed by GitHub
parent 78d76d8481
commit e2c3c9a301
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 104 additions and 72 deletions

View file

@ -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}]`,
});
});
});

View file

@ -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) => {