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.
This commit is contained in:
modeco80 2024-09-21 21:14:27 -04:00
parent 072fd06918
commit 41ee71f053
3 changed files with 73 additions and 2 deletions

5
.gitignore vendored
View file

@ -14,4 +14,7 @@ cvm-rs/target
cvm-rs/index.node cvm-rs/index.node
# geolite shit # geolite shit
**/geoip/ **/geoip/
# staff audit log
audit.log

62
cvmts/src/AuditLog.ts Normal file
View file

@ -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();

View file

@ -20,6 +20,7 @@ import { CollabVMProtocolMessage, CollabVMProtocolMessageType } from '@cvmts/col
import { Size, Rect } from './Utilities.js'; import { Size, Rect } from './Utilities.js';
import pino from 'pino'; import pino from 'pino';
import { BanManager } from './BanManager.js'; import { BanManager } from './BanManager.js';
import { TheAuditLog } from './AuditLog.js';
// Instead of strange hacks we can just use nodejs provided // Instead of strange hacks we can just use nodejs provided
// import.meta properties, which have existed since LTS if not before // import.meta properties, which have existed since LTS if not before
@ -526,18 +527,21 @@ export default class CollabVMServer {
// QEMU Monitor // QEMU Monitor
if (client.rank !== Rank.Admin) return; if (client.rank !== Rank.Admin) return;
if (msgArr.length !== 4 || msgArr[2] !== this.Config.collabvm.node) 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]); let output = await this.VM.MonitorCommand(msgArr[3]);
client.sendMsg(cvm.guacEncode('admin', '2', String(output))); client.sendMsg(cvm.guacEncode('admin', '2', String(output)));
break; break;
case '8': case '8':
// Restore // Restore
if (client.rank !== Rank.Admin && (client.rank !== Rank.Moderator || !this.Config.collabvm.moderatorPermissions.restore)) return; if (client.rank !== Rank.Admin && (client.rank !== Rank.Moderator || !this.Config.collabvm.moderatorPermissions.restore)) return;
TheAuditLog.onReset(client);
this.VM.Reset(); this.VM.Reset();
break; break;
case '10': case '10':
// Reboot // Reboot
if (client.rank !== Rank.Admin && (client.rank !== Rank.Moderator || !this.Config.collabvm.moderatorPermissions.reboot)) return; 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; if (msgArr.length !== 3 || msgArr[2] !== this.Config.collabvm.node) return;
TheAuditLog.onReboot(client);
await this.VM.Reboot(); await this.VM.Reboot();
break; break;
case '12': 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; 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]); var user = this.clients.find((c) => c.username === msgArr[2]);
if (!user) return; 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); user.ban(this.banmgr);
case '13': case '13':
// Force Vote // Force Vote
@ -578,6 +582,7 @@ export default class CollabVMServer {
default: default:
return; return;
} }
//TheAdminLogger.onMute(client, user, permamute);
user.mute(permamute); user.mute(permamute);
break; break;
case '15': 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; 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]); var user = this.clients.find((c) => c.username === msgArr[2]);
if (!user) return; if (!user) return;
TheAuditLog.onKick(client, user);
user.kick(); user.kick();
break; break;
case '16': case '16':