@hclsoftware/secagent
Version:
IAST agent
304 lines (268 loc) • 10.5 kB
JavaScript
//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