Release rolling / release (push) Successful in 7m49s
Squash a container into a single-layer image from Manage → Flatten, with repo/tag, pause, config re-apply, vault credentials, and optional registry push.
146 lines
3.3 KiB
JavaScript
146 lines
3.3 KiB
JavaScript
/**
|
|
* 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',
|
|
'pruneBuilder',
|
|
'systemPrune',
|
|
'recreateContainer',
|
|
'upsertSchedule',
|
|
'deleteSchedule',
|
|
'updateAlertsConfig',
|
|
'exportAlertsConfig',
|
|
'upsertAlertChannel',
|
|
'deleteAlertChannel',
|
|
'upsertAlertRule',
|
|
'deleteAlertRule',
|
|
'testAlertChannel',
|
|
'fireManualAlert',
|
|
'removeImage',
|
|
'removeStack',
|
|
'deployStack',
|
|
'syncStackFromGit',
|
|
'deployContainer',
|
|
'createTunnel',
|
|
'closeTunnel',
|
|
'createContainer',
|
|
'buildImage',
|
|
'pushImage',
|
|
'loadImageFinish',
|
|
'importImage',
|
|
'registryLogin',
|
|
'registryLogout',
|
|
'registryDeleteTag',
|
|
'registryDeleteTags',
|
|
'registryDeleteRepository',
|
|
'vaultStoreCredential',
|
|
'vaultDeleteCredential',
|
|
'vaultUseCredential',
|
|
'vaultTestCredential',
|
|
'invitePeer',
|
|
'deleteInvite',
|
|
'revokePeer',
|
|
'unrevokePeer',
|
|
'clearRevokedPeers',
|
|
'setPeerRole',
|
|
'archiveContainerPut',
|
|
'removeNetwork',
|
|
'removeVolume',
|
|
'updateContainer',
|
|
'duplicateContainer',
|
|
'commitContainer',
|
|
'flattenContainer',
|
|
'swarmInit',
|
|
'swarmLeave',
|
|
'createService',
|
|
'removeService',
|
|
'createSecret',
|
|
'removeSecret',
|
|
'installPlugin',
|
|
'removePlugin',
|
|
])
|
|
|
|
/**
|
|
* @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',
|
|
'authMode',
|
|
'role',
|
|
]
|
|
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 }
|