feat(audit-core, xo-server-audit): ability to delete a range of records (#5277)
This commit is contained in:
@@ -30,6 +30,7 @@
|
|||||||
"rimraf": "^3.0.0"
|
"rimraf": "^3.0.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@xen-orchestra/log": "^0.2.0",
|
||||||
"core-js": "^3.6.4",
|
"core-js": "^3.6.4",
|
||||||
"golike-defer": "^0.4.1",
|
"golike-defer": "^0.4.1",
|
||||||
"lodash": "^4.17.15",
|
"lodash": "^4.17.15",
|
||||||
|
|||||||
@@ -2,9 +2,12 @@
|
|||||||
import 'core-js/features/symbol/async-iterator'
|
import 'core-js/features/symbol/async-iterator'
|
||||||
|
|
||||||
import assert from 'assert'
|
import assert from 'assert'
|
||||||
|
import createLogger from '@xen-orchestra/log'
|
||||||
import defer from 'golike-defer'
|
import defer from 'golike-defer'
|
||||||
import hash from 'object-hash'
|
import hash from 'object-hash'
|
||||||
|
|
||||||
|
const log = createLogger('xo:audit-core')
|
||||||
|
|
||||||
export class Storage {
|
export class Storage {
|
||||||
constructor() {
|
constructor() {
|
||||||
this._lock = Promise.resolve()
|
this._lock = Promise.resolve()
|
||||||
@@ -65,8 +68,17 @@ export class AuditCore {
|
|||||||
@defer
|
@defer
|
||||||
async add($defer, subject, event, data) {
|
async add($defer, subject, event, data) {
|
||||||
const time = Date.now()
|
const time = Date.now()
|
||||||
|
$defer(await this._storage.acquireLock())
|
||||||
|
return this._addUnsafe({
|
||||||
|
data,
|
||||||
|
event,
|
||||||
|
subject,
|
||||||
|
time,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async _addUnsafe({ data, event, subject, time }) {
|
||||||
const storage = this._storage
|
const storage = this._storage
|
||||||
$defer(await storage.acquireLock())
|
|
||||||
|
|
||||||
// delete "undefined" properties and normalize data with JSON.stringify
|
// delete "undefined" properties and normalize data with JSON.stringify
|
||||||
const record = JSON.parse(
|
const record = JSON.parse(
|
||||||
@@ -139,4 +151,45 @@ export class AuditCore {
|
|||||||
await this._storage.del(id)
|
await this._storage.del(id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@defer
|
||||||
|
async deleteRangeAndRewrite($defer, newest, oldest) {
|
||||||
|
assert.notStrictEqual(newest, undefined)
|
||||||
|
assert.notStrictEqual(oldest, undefined)
|
||||||
|
|
||||||
|
const storage = this._storage
|
||||||
|
$defer(await storage.acquireLock())
|
||||||
|
|
||||||
|
assert.notStrictEqual(await storage.get(newest), undefined)
|
||||||
|
const oldestRecord = await storage.get(oldest)
|
||||||
|
assert.notStrictEqual(oldestRecord, undefined)
|
||||||
|
|
||||||
|
const lastId = await storage.getLastId()
|
||||||
|
const recentRecords = []
|
||||||
|
for await (const record of this.getFrom(lastId)) {
|
||||||
|
if (record.id === newest) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
recentRecords.push(record)
|
||||||
|
}
|
||||||
|
|
||||||
|
for await (const record of this.getFrom(newest)) {
|
||||||
|
await storage.del(record.id)
|
||||||
|
if (record.id === oldest) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await storage.setLastId(oldestRecord.previousId)
|
||||||
|
|
||||||
|
for (const record of recentRecords) {
|
||||||
|
try {
|
||||||
|
await this._addUnsafe(record)
|
||||||
|
await storage.del(record.id)
|
||||||
|
} catch (error) {
|
||||||
|
log.error(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,9 +17,10 @@ interface Record {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class AuditCore {
|
export class AuditCore {
|
||||||
constructor(storage: Storage) {}
|
constructor(storage: Storage) { }
|
||||||
public add(subject: any, event: string, data: any): Promise<Record> {}
|
public add(subject: any, event: string, data: any): Promise<Record> { }
|
||||||
public checkIntegrity(oldest: string, newest: string): Promise<number> {}
|
public checkIntegrity(oldest: string, newest: string): Promise<number> { }
|
||||||
public getFrom(newest?: string): AsyncIterator {}
|
public getFrom(newest?: string): AsyncIterator { }
|
||||||
public deleteFrom(newest: string): Promise<void> {}
|
public deleteFrom(newest: string): Promise<void> { }
|
||||||
|
public deleteRangeAndRewrite(newest: string, oldest: string): Promise<void> { }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,3 +27,6 @@
|
|||||||
> - major: if the change breaks compatibility
|
> - major: if the change breaks compatibility
|
||||||
>
|
>
|
||||||
> In case of conflict, the highest (lowest in previous list) `$version` wins.
|
> In case of conflict, the highest (lowest in previous list) `$version` wins.
|
||||||
|
|
||||||
|
- @xen-orchestra/audit-core minor
|
||||||
|
- xo-server-audit minor
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ const DEFAULT_BLOCKED_LIST = {
|
|||||||
'acl.getCurrentPermissions': true,
|
'acl.getCurrentPermissions': true,
|
||||||
'audit.checkIntegrity': true,
|
'audit.checkIntegrity': true,
|
||||||
'audit.clean': true,
|
'audit.clean': true,
|
||||||
|
'audit.deleteRange': true,
|
||||||
'audit.generateFingerprint': true,
|
'audit.generateFingerprint': true,
|
||||||
'audit.getRecords': true,
|
'audit.getRecords': true,
|
||||||
'backup.list': true,
|
'backup.list': true,
|
||||||
@@ -238,11 +239,21 @@ class AuditXoPlugin {
|
|||||||
clean.permission = 'admin'
|
clean.permission = 'admin'
|
||||||
clean.description = 'Clean audit database'
|
clean.description = 'Clean audit database'
|
||||||
|
|
||||||
|
const deleteRange = this._deleteRangeAndRewrite.bind(this)
|
||||||
|
deleteRange.description =
|
||||||
|
'Delete a range of records and rewrite the records chain'
|
||||||
|
deleteRange.permission = 'admin'
|
||||||
|
deleteRange.params = {
|
||||||
|
newest: { type: 'string' },
|
||||||
|
oldest: { type: 'string', optional: true },
|
||||||
|
}
|
||||||
|
|
||||||
cleaners.push(
|
cleaners.push(
|
||||||
this._xo.addApiMethods({
|
this._xo.addApiMethods({
|
||||||
audit: {
|
audit: {
|
||||||
checkIntegrity,
|
checkIntegrity,
|
||||||
clean,
|
clean,
|
||||||
|
deleteRange,
|
||||||
exportRecords,
|
exportRecords,
|
||||||
generateFingerprint,
|
generateFingerprint,
|
||||||
getRecords,
|
getRecords,
|
||||||
@@ -428,6 +439,13 @@ class AuditXoPlugin {
|
|||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async _deleteRangeAndRewrite({ newest, oldest = newest }) {
|
||||||
|
await this._auditCore.deleteRangeAndRewrite(newest, oldest)
|
||||||
|
if (this._uploadLastHashJob !== undefined) {
|
||||||
|
await this._uploadLastHash()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AuditXoPlugin.prototype._getRecordsStream = asyncIteratorToStream(
|
AuditXoPlugin.prototype._getRecordsStream = asyncIteratorToStream(
|
||||||
|
|||||||
Reference in New Issue
Block a user