From 41ee71f0538196fd3c6ee53d2b77ddaf06b1d9d1 Mon Sep 17 00:00:00 2001 From: modeco80 Date: Sat, 21 Sep 2024 21:14:27 -0400 Subject: [PATCH] cvmts: Add staff audit logging support Basically what it says on the tin. More staff operations should probably be audited, but for now this provides a good starting point. --- .gitignore | 5 ++- cvmts/src/AuditLog.ts | 62 +++++++++++++++++++++++++++++++++++++ cvmts/src/CollabVMServer.ts | 8 ++++- 3 files changed, 73 insertions(+), 2 deletions(-) create mode 100644 cvmts/src/AuditLog.ts diff --git a/.gitignore b/.gitignore index 6fbbf6a..ddd9804 100644 --- a/.gitignore +++ b/.gitignore @@ -14,4 +14,7 @@ cvm-rs/target cvm-rs/index.node # geolite shit -**/geoip/ \ No newline at end of file +**/geoip/ + +# staff audit log +audit.log diff --git a/cvmts/src/AuditLog.ts b/cvmts/src/AuditLog.ts new file mode 100644 index 0000000..315e8ae --- /dev/null +++ b/cvmts/src/AuditLog.ts @@ -0,0 +1,62 @@ +import pino from 'pino'; +import { Rank, User } from './User.js'; + +// Staff audit log. +// TODO: +// - Hook this up to a db or something instead of misusing pino +export class AuditLog { + private auditLogger = pino({ + name: 'AuditLog', + transport: { + target: 'pino/file', + options: { + destination: './audit.log' + } + } + }); + + private static StaffHonorFromRank(user: User, uppercase: boolean) { + switch (user.rank) { + case Rank.Moderator: + if (uppercase) return 'Moderator'; + else return 'moderator'; + + case Rank.Admin: + if (uppercase) return 'Administrator'; + else return 'administrator'; + + default: + throw new Error("input user is not staff.. how'd you even get here?"); + } + } + + onReset(callingUser: User) { + this.auditLogger.info({ staffUsername: callingUser.username }, `${AuditLog.StaffHonorFromRank(callingUser, true)} reset the virtual machine.`); + } + + onReboot(callingUser: User) { + this.auditLogger.info({ staffUsername: callingUser.username }, `${AuditLog.StaffHonorFromRank(callingUser, true)} rebooted the virtual machine.`); + } + + onMute(callingUser: User, target: User, perm: boolean) { + this.auditLogger.info({ staffUsername: callingUser.username, targetUsername: target.username, perm: perm }, `${AuditLog.StaffHonorFromRank(callingUser, true)} muted user.`); + } + + onUnmute(callingUser: User, target: User) { + this.auditLogger.info({ staffUsername: callingUser.username, targetUsername: target.username }, `${AuditLog.StaffHonorFromRank(callingUser, true)} unmuted user.`); + } + + onKick(callingUser: User, target: User) { + this.auditLogger.info({ staffUsername: callingUser.username, targetUsername: target.username }, `${AuditLog.StaffHonorFromRank(callingUser, true)} kicked user.`); + } + + onBan(callingUser: User, target: User) { + this.auditLogger.info({ staffUsername: callingUser.username, targetUsername: target.username }, `${AuditLog.StaffHonorFromRank(callingUser, true)} banned user.`); + } + + onMonitorCommand(callingUser: User, command: string) { + this.auditLogger.info({ staffUsername: callingUser.username, commandLine: command }, `${AuditLog.StaffHonorFromRank(callingUser, true)} executed monitor command.`); + } +} + +export let TheAuditLog = new AuditLog(); diff --git a/cvmts/src/CollabVMServer.ts b/cvmts/src/CollabVMServer.ts index 3dccc75..cd8d14d 100644 --- a/cvmts/src/CollabVMServer.ts +++ b/cvmts/src/CollabVMServer.ts @@ -20,6 +20,7 @@ import { CollabVMProtocolMessage, CollabVMProtocolMessageType } from '@cvmts/col import { Size, Rect } from './Utilities.js'; import pino from 'pino'; import { BanManager } from './BanManager.js'; +import { TheAuditLog } from './AuditLog.js'; // Instead of strange hacks we can just use nodejs provided // import.meta properties, which have existed since LTS if not before @@ -526,18 +527,21 @@ export default class CollabVMServer { // QEMU Monitor if (client.rank !== Rank.Admin) return; if (msgArr.length !== 4 || msgArr[2] !== this.Config.collabvm.node) return; + TheAuditLog.onMonitorCommand(client, msgArr[3]); let output = await this.VM.MonitorCommand(msgArr[3]); client.sendMsg(cvm.guacEncode('admin', '2', String(output))); break; case '8': // Restore if (client.rank !== Rank.Admin && (client.rank !== Rank.Moderator || !this.Config.collabvm.moderatorPermissions.restore)) return; + TheAuditLog.onReset(client); this.VM.Reset(); break; case '10': // Reboot if (client.rank !== Rank.Admin && (client.rank !== Rank.Moderator || !this.Config.collabvm.moderatorPermissions.reboot)) return; if (msgArr.length !== 3 || msgArr[2] !== this.Config.collabvm.node) return; + TheAuditLog.onReboot(client); await this.VM.Reboot(); break; case '12': @@ -545,7 +549,7 @@ export default class CollabVMServer { if (client.rank !== Rank.Admin && (client.rank !== Rank.Moderator || !this.Config.collabvm.moderatorPermissions.ban)) return; var user = this.clients.find((c) => c.username === msgArr[2]); if (!user) return; - this.logger.info(`Banning ${user.username!} (${user.IP.address}) by request of ${client.username!}`); + TheAuditLog.onBan(client, user); user.ban(this.banmgr); case '13': // Force Vote @@ -578,6 +582,7 @@ export default class CollabVMServer { default: return; } + //TheAdminLogger.onMute(client, user, permamute); user.mute(permamute); break; case '15': @@ -585,6 +590,7 @@ export default class CollabVMServer { if (client.rank !== Rank.Admin && (client.rank !== Rank.Moderator || !this.Config.collabvm.moderatorPermissions.kick)) return; var user = this.clients.find((c) => c.username === msgArr[2]); if (!user) return; + TheAuditLog.onKick(client, user); user.kick(); break; case '16':