@@ -0,0 +1,91 @@
|
||||
/**
|
||||
* Append-only local audit log for privileged RPC actions.
|
||||
*
|
||||
* Path: PEARDOCK_AUDIT_LOG (default: ./peardock-audit.log)
|
||||
* Set PEARDOCK_AUDIT=0 to disable.
|
||||
*/
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import logger from '../utils/logger.js'
|
||||
|
||||
const ENABLED = process.env.PEARDOCK_AUDIT !== '0'
|
||||
const LOG_PATH = process.env.PEARDOCK_AUDIT_LOG || path.join(process.cwd(), 'peardock-audit.log')
|
||||
|
||||
/** Methods that should always be audited when successful/attempted. */
|
||||
const AUDIT_METHODS = new Set([
|
||||
'removeContainer',
|
||||
'killContainer',
|
||||
'pruneContainers',
|
||||
'pruneImages',
|
||||
'pruneNetworks',
|
||||
'pruneVolumes',
|
||||
'removeImage',
|
||||
'removeStack',
|
||||
'deployStack',
|
||||
'deployContainer',
|
||||
'buildImage',
|
||||
'registryLogin',
|
||||
'archiveContainerPut',
|
||||
'removeNetwork',
|
||||
'removeVolume',
|
||||
'updateContainer',
|
||||
'duplicateContainer',
|
||||
'commitContainer',
|
||||
])
|
||||
|
||||
/**
|
||||
* @param {object} entry
|
||||
* @param {string} entry.method
|
||||
* @param {string} [entry.peerId]
|
||||
* @param {string} [entry.role]
|
||||
* @param {boolean} [entry.ok]
|
||||
* @param {string} [entry.error]
|
||||
* @param {object} [entry.args]
|
||||
*/
|
||||
export function audit(entry) {
|
||||
if (!ENABLED) return
|
||||
if (!entry?.method) return
|
||||
if (!AUDIT_METHODS.has(entry.method) && entry.force !== true) return
|
||||
|
||||
const line = JSON.stringify({
|
||||
ts: new Date().toISOString(),
|
||||
method: entry.method,
|
||||
peerId: entry.peerId ? String(entry.peerId).slice(0, 16) : null,
|
||||
role: entry.role || null,
|
||||
ok: entry.ok !== false,
|
||||
error: entry.error || null,
|
||||
// Never log secrets; only allowlisted arg keys
|
||||
args: sanitizeArgs(entry.args),
|
||||
})
|
||||
|
||||
try {
|
||||
fs.appendFileSync(LOG_PATH, line + '\n', { encoding: 'utf8' })
|
||||
} catch (err) {
|
||||
logger.warn('audit log write failed', { error: err.message })
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {unknown} args
|
||||
*/
|
||||
function sanitizeArgs(args) {
|
||||
if (!args || typeof args !== 'object') return null
|
||||
const out = {}
|
||||
const keys = ['id', 'name', 'stackName', 'image', 'operation', 'path', 'term', 'containerIds']
|
||||
for (const k of keys) {
|
||||
if (args[k] != null) {
|
||||
if (k === 'containerIds' && Array.isArray(args[k])) {
|
||||
out[k] = args[k].slice(0, 20).map((x) => String(x).slice(0, 12))
|
||||
} else {
|
||||
out[k] = String(args[k]).slice(0, 128)
|
||||
}
|
||||
}
|
||||
}
|
||||
return Object.keys(out).length ? out : null
|
||||
}
|
||||
|
||||
export function shouldAudit(method) {
|
||||
return ENABLED && AUDIT_METHODS.has(method)
|
||||
}
|
||||
|
||||
export { AUDIT_METHODS, LOG_PATH }
|
||||
Reference in New Issue
Block a user