feat(xo-server-audit): backup last hash (#5077)

This commit is contained in:
badrAZ
2020-08-20 12:32:22 +02:00
committed by GitHub
parent c4d96fbc49
commit 38de5048bc
3 changed files with 62 additions and 2 deletions

View File

@@ -11,6 +11,7 @@
- [VM/Network] Ability to set VIF TX checksumming [#5095](https://github.com/vatesfr/xen-orchestra/issues/5095) (PR [#5182](https://github.com/vatesfr/xen-orchestra/pull/5182))
- [Proxy] Improve health check error messages [#5161](https://github.com/vatesfr/xen-orchestra/issues/5161) (PR [#5191](https://github.com/vatesfr/xen-orchestra/pull/5191))
- [VM/network] Ability to change a VIF's locking mode [#4713](https://github.com/vatesfr/xen-orchestra/issues/4713) (PR [#5188](https://github.com/vatesfr/xen-orchestra/pull/5188))
- [[Audit] Regularly save fingerprints on remote server for better tempering detection](https://xen-orchestra.com/blog/xo-audit/) [#4844](https://github.com/vatesfr/xen-orchestra/issues/4844) (PR [#5077](https://github.com/vatesfr/xen-orchestra/pull/5077))
### Bug fixes
@@ -39,6 +40,7 @@
>
> In case of conflict, the highest (lowest in previous list) `$version` wins.
- xo-server-audit minor
- xo-server patch
- xo-server-sdn-controller patch
- xo-server minor

View File

@@ -36,6 +36,7 @@
"devDependencies": {
"@babel/cli": "^7.7.0",
"@babel/core": "^7.7.2",
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.0.0",
"@babel/preset-env": "^7.7.1",
"cross-env": "^7.0.2",
"rimraf": "^3.0.0"
@@ -49,8 +50,10 @@
},
"dependencies": {
"@xen-orchestra/audit-core": "^0.1.1",
"@xen-orchestra/cron": "^1.0.6",
"@xen-orchestra/log": "^0.2.0",
"async-iterator-to-stream": "^1.1.0",
"lodash": "^4.17.19",
"promise-toolbox": "^0.15.0",
"readable-stream": "^3.5.0",
"xo-common": "^0.5.0"

View File

@@ -2,6 +2,7 @@ import asyncIteratorToStream from 'async-iterator-to-stream'
import createLogger from '@xen-orchestra/log'
import { alteredAuditRecord, missingAuditRecord } from 'xo-common/api-errors'
import { createGzip } from 'zlib'
import { createSchedule } from '@xen-orchestra/cron'
import { fromCallback } from 'promise-toolbox'
import { pipeline } from 'readable-stream'
import {
@@ -135,6 +136,15 @@ class AuditXoPlugin {
this._cleaners = []
this._xo = xo
const { enabled = true, schedule: { cron = '0 6 * * *', timezone } = {} } =
staticConfig.lastHashUpload ?? {}
if (enabled) {
this._uploadLastHashJob = createSchedule(cron, timezone).createJob(() =>
this._uploadLastHash().catch(log.error)
)
}
this._auditCore = undefined
this._storage = undefined
@@ -193,6 +203,12 @@ class AuditXoPlugin {
oldest: { type: 'string', optional: true },
}
const uploadLastHashJob = this._uploadLastHashJob
if (uploadLastHashJob !== undefined) {
uploadLastHashJob.start()
cleaners.push(() => uploadLastHashJob.stop())
}
cleaners.push(
this._xo.addApiMethods({
audit: {
@@ -293,6 +309,41 @@ class AuditXoPlugin {
}))
}
// See www-xo#344
async _uploadLastHash() {
const xo = this._xo
const chain = await xo.audit.getLastChain()
let lastHash, integrityCheckSuccess
if (chain !== null) {
const hashes = chain.hashes
lastHash = hashes[hashes.length - 1]
// check the integrity of all stored hashes
integrityCheckSuccess = await Promise.all(
hashes.map((oldest, key) =>
oldest !== lastHash
? this._checkIntegrity({ oldest, newest: hashes[key + 1] })
: true
)
).then(
() => true,
() => false
)
}
// generate a valid fingerprint of all stored records in case of a failure integrity check
const { oldest, newest, error } = await this._generateFingerprint({
oldest: integrityCheckSuccess ? lastHash : undefined,
})
if (chain === null || !integrityCheckSuccess || error !== undefined) {
await xo.audit.startNewChain({ oldest, newest })
} else {
await xo.audit.extendLastChain({ oldest, newest })
}
}
async _checkIntegrity(props) {
const { oldest = NULL_ID, newest = await this._storage.getLastId() } = props
return this._auditCore.checkIntegrity(oldest, newest).catch(error => {
@@ -311,14 +362,18 @@ class AuditXoPlugin {
try {
return {
fingerprint: `${oldest}|${newest}`,
newest,
nValid: await this._checkIntegrity({ oldest, newest }),
oldest,
}
} catch (error) {
if (missingAuditRecord.is(error) || alteredAuditRecord.is(error)) {
return {
fingerprint: `${error.data.id}|${newest}`,
nValid: error.data.nValid,
error,
fingerprint: `${error.data.id}|${newest}`,
newest,
nValid: error.data.nValid,
oldest: error.data.id,
}
}
throw error