UNPKG

@hclsoftware/secagent

Version:

IAST agent

304 lines (268 loc) 10.5 kB
//IASTIGNORE /* * **************************************************** * Licensed Materials - Property of HCL. * (c) Copyright HCL Technologies Ltd. 2017, 2025. * Note to U.S. Government Users *Restricted Rights. * **************************************************** */ 'use strict' const fs = require('fs') const path = require('path') const os = require('os') const EventEmitter = require('events') const AdmZip = require('adm-zip') const ConfigFileManager = require('../ConfigFile/ConfigFileManager') const loggerConstants = require('./LoggerConstants') const Globals = require("../Globals") class IastLog extends EventEmitter { constructor (prefix, isActive, logType) { super() this.logSymbol = Symbol('logger-task') this.compressSymbol = Symbol('compress-task') this.logType = logType this.initLogger(prefix, isActive) this.logFileLevel = loggerConstants.logLevel.strToLevel[this.logType === loggerConstants.logTypes.EVENTS ? loggerConstants.defaultEventsLogLevel : loggerConstants.defaultFindingsLogLevel] this.stdOutLevel = loggerConstants.logLevel.strToLevel[this.logType === loggerConstants.logTypes.EVENTS ? loggerConstants.defaultEventsStdLogLevel : loggerConstants.defaultFindingsStdLogLevel] this.start() this.currentLogSize = 0 } initLogger (prefix, isActive) { this.secagentDir = path.join(os.tmpdir(), 'Secagent') if (IastLog.isFirstInit) { try { fs.mkdirSync(this.secagentDir, {recursive: true}, () => { }) // may get an error if directory already exists, ignore. } catch (error) { } console.origLog(`Using log directory: ${this.secagentDir}`) IastLog.isFirstInit = false; } this.prefix = prefix const name = this.getCurrentLogFile() this.filename = path.join(this.secagentDir, name) console.origLog(`${loggerConstants.logTypeName[this.logType]} log: ${this.filename}`) this.isActive = isActive this.compressEvent() this.flushOnWrite = Globals.FlushOnEveryWriteMode this.maxSizeFileInBytes = loggerConstants.maxSizeFileInBytes this.maxTotalSizeBytes = 10 * this.maxSizeFileInBytes } SetConfigurationEvent () { ConfigFileManager.configFileEventEmitter.on(ConfigFileManager.FILE_UPLOADED, (configInfo) => this.onFileUploaded(configInfo)) } start () { this.addListener(this.logSymbol, (logLevel, txt) => { this.logEvent(logLevel, txt) }) this.addListener(this.compressSymbol, () => { this.compressEvent() if (this.zippedFilesSize() >= IastLog.maxTotalSizeBytes) { this.deleteZipFiles() } }) } setMaxSizeFile (newSize) { this.maxSizeFileInBytes = newSize this.maxTotalSizeBytes = 10 * this.maxSizeFileInBytes this.info("setting max file size to " + this.maxTotalSizeBytes) } onFileUploaded (configInfo) { const logObj = configInfo.logging if (this.logType === loggerConstants.logTypes.EVENTS) { this.logFileLevel = logObj.logLevel != null ? loggerConstants.logLevel.strToLevel[logObj.logLevel] : this.defaultEventsLogLevel this.stdOutLevel = logObj.stdoutLogLevel != null ? loggerConstants.logLevel.strToLevel[logObj.stdoutLogLevel] : this.defaultEventsStdLogLevel } else if (this.logType === loggerConstants.logTypes.FINDINGS) { this.logFileLevel = logObj.findingsLogLevel != null ? loggerConstants.logLevel.strToLevel[logObj.findingsLogLevel] : this.defaultFindingsLogLevel } if (logObj.maxSizeLogFileMB != null) { const fileSize = parseInt(logObj.maxSizeLogFileMB) this.setMaxSizeFile(!isNaN(fileSize) ? fileSize * loggerConstants.mbSize : loggerConstants.defaultMaxSizeFileInMbB) console.origLog(`${this.logType}: maxSizeLogFileMB set to ${fileSize}`) } } deleteZipFiles () { try { let latestTime = 0 let chosenFile let fullFileName const filenames = fs.readdirSync(this.secagentDir).filter(file => file.startsWith(this.prefix) && file.endsWith('.zip')) filenames.forEach(file => { fullFileName = path.join(this.secagentDir, file) const { birthtime } = fs.statSync(fullFileName) if (birthtime != null && (chosenFile == null || Date.parse(birthtime) < latestTime)) { chosenFile = fullFileName latestTime = Date.parse(birthtime) } }) if (chosenFile != null) { fs.unlink(chosenFile, (err) => { if (err) throw err }) } } catch (e) { this.error('Caught Exception while compressing log') this.error(e) } } zippedFilesSize () { let zipsSize = 0 try { const filenames = fs.readdirSync(this.secagentDir).filter(file => file.startsWith(this.prefix) && file.endsWith('.zip')) filenames.forEach(file => { const fullFileName = path.join(this.secagentDir, file) const { size } = fs.statSync(fullFileName) zipsSize += size }) } catch (e) { this.error('Caught Exception while compressing log') this.error(e) } return zipsSize } forceGettingNewLog () { try { const filenames = fs.readdirSync(this.secagentDir) filenames.forEach(file => { const fullFileName = path.join(this.secagentDir, file) fs.unlink(fullFileName, (err) => { if (err) throw err }) }) } catch (e) { this.error('Caught Exception while compressing log') this.error(e) } } compressEvent () { try { const filenames = fs.readdirSync(this.secagentDir).filter(file => file.startsWith(this.prefix) && file.endsWith('.log')) filenames.forEach(file => { const fullFileName = path.join(this.secagentDir, file) if (fullFileName.toString() !== this.filename) { const zipName = fullFileName.origReplace('.log', '.zip') const zip = new AdmZip() zip.addLocalFile(fullFileName) zip.writeZip(zipName) fs.unlink(fullFileName, (err) => { if (err) throw err }) } }) } catch (e) { this.error('Caught Exception while compressing log') this.error(e) } } logEvent (logLevel, txt) { try { if (this.stdOutLevel >= logLevel || (this.isActive && this.logFileLevel >= logLevel)) { const parsedText = this.parseLog(logLevel, txt) if (this.stdOutLevel >= logLevel) { console.origLog(parsedText) // stdout } if (this.isActive && this.logFileLevel >= logLevel) this.writeToLog(parsedText) } } catch (e) { // don't log in this catch block - it might trigger circular calls, e.g: this.error() => logEvent => an error occurs => this.error() console.origError('Caught Exception while writing to log') console.origError(e) } } getCurrentLogFile () { let chosenFile let latestTime = 0 const filenames = fs.readdirSync(this.secagentDir).filter(file => file.startsWith(this.prefix) && file.endsWith('.log')) filenames.forEach(file => { const { birthtime } = fs.statSync(path.join(this.secagentDir, file)) if (file.startsWith(this.prefix) && file.endsWith('.log') && birthtime != null && (chosenFile == null || Date.parse(birthtime) > latestTime)) { chosenFile = file.toString() latestTime = Date.parse(birthtime) } }) if (chosenFile != null) return chosenFile else return this.getNewLogFileName() } getNewLogFileName () { const time = new Date().toISOString().replace('T', '-').replace('Z', '').replace(':', '-').replace(':', '-').replace('.', '-') return this.prefix + time + '.log' } logIfEnabled (logLevel, txt) { if (this.isActive && (this.logFileLevel >= logLevel || this.stdOutLevel >= logLevel)) this.emit(this.logSymbol, logLevel, txt) } writeToLog (parsedText) { this.currentLogSize += parsedText.length // Asynchronously append data to a file, creating the file if it does not yet exist if (this.flushOnWrite) { fs.appendFileSync(this.filename, parsedText.toString(), function (err) { if (err) console.origError('error', err) }) } else { fs.appendFile(this.filename, parsedText.toString(), function (err) { if (err) console.origError('error', err) }) } if (this.isCurrentFileOverWeighted()) { console.origLog(`Compressing log file ${this.filename}`) this.changeLogFile() this.emit(this.compressSymbol) } } changeLogFile () { this.filename = path.join(this.secagentDir, this.getNewLogFileName()) this.currentLogSize = 0 } isCurrentFileOverWeighted () { return this.currentLogSize > this.maxSizeFileInBytes } parseLog (logLevel, txt) { const date = new Date() // current date // adjust 0 before single digit date let day = ("0" + date.getDate()).slice(-2); // current month let month = ("0" + (date.getMonth() + 1)).slice(-2); // current year let year = date.getFullYear(); // current hours let hours = ("0" + date.getHours()).slice(-2); // current minutes let minutes = ("0" + date.getMinutes()).slice(-2); // current seconds let seconds = ("0" + date.getSeconds()).slice(-2); // current milliseconds let milliseconds = date.getMilliseconds(); // add date & time in YYYY-MM-DD HH:MM:SS.SSS format const timestamp = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}.${milliseconds}` let line line = `${timestamp} [] ${loggerConstants.logLevel.levelToStr[logLevel]} ${txt}\n` return line } trace (txt) { this.logIfEnabled(loggerConstants.logLevel.TRACE, txt) } info (txt) { this.logIfEnabled(loggerConstants.logLevel.INFO, txt) } debug (txt) { this.logIfEnabled(loggerConstants.logLevel.DEBUG, txt) } warning (txt) { this.logIfEnabled(loggerConstants.logLevel.WARNING, txt) } error (err) { if (err.message !== undefined && err.stack !== undefined) { this.logIfEnabled(loggerConstants.logLevel.ERROR, err.message) this.logIfEnabled(loggerConstants.logLevel.ERROR, err.stack) } else { this.logIfEnabled(loggerConstants.logLevel.ERROR, err) } } shutDown () { this.removeAllListeners() } } IastLog.isFirstInit = true module.exports.eventLog = new IastLog('secagent-', true, loggerConstants.logTypes.EVENTS) module.exports.findingsLog = new IastLog('vlogs-', true, loggerConstants.logTypes.FINDINGS) module.exports.IastLog = IastLog