INDEX.JS ``` /** Copyright (c) 2018 Craig Yamato */ /** * @fileoverview The SyslogPro module for sending syslog messages * Most APIs will return a promise. These APIs can be used using * `then(...)/catch(...)` * * Syslog formatting classes can be used as input into a Syslog class to be used * simultaneously to the same Syslog server. The Syslog Class with a configured * Syslog server target can also be used as the input into each of the * formatting classes so that they may run independently. * @author Craig Yamato * @copyright (c) 2018 - Craig Yamato * @version 0.1.0 * @exports Syslog * @exports LEEF * @exports CEF * @module SyslogPro */ 'use strict'; const moment = require('moment'); const os = require('os'); const dns = require('dns'); let dnsPromises = dns.promises; const fs = require('fs'); /** * Format the ANSI foreground color code from a RGB hex code or ANSI color code * @private * @param {string} hex - The color hex code in the form of #FFFFFF or Number of * the ANSI color code (30-37 Standard & 0-255 Extended) * @returns {Promise} - The formatted ANSI color code * @throws {Error} - A Format Error */ function rgbToAnsi(hex, extendedColor) { return new Promise((resolve, reject) => { let colorCode = 0; // Var to hold color code ``` Break HEX Code up into RGB ``` const hexParts = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); if (hexParts || typeof hex === 'number') { if (typeof hex === 'number') { if (extendedColor && hex < 256) { resolve(hex); } else if ((hex > 29 && hex < 38) || (hex > 89 && hex < 98)) { resolve(hex); } else { reject(new Error('FORMAT ERROR: Color code not in range')); } } else { const r = parseInt(hexParts[1], 16); const g = parseInt(hexParts[2], 16); const b = parseInt(hexParts[3], 16); if (extendedColor) { if (r === g && g === b) { ``` Gray Scale Color ``` if (r < 8) { colorCode = 16; } else if (r > 248) { colorCode = 231; } else { colorCode = Math.round(((r - 8) / 247) * 24) + 232; } } else { colorCode = 16 + (36 * Math.round(r / 255 * 5)) + (6 * Math.round(g / 255 * 5)) + Math.round(b / 255 * 5); } } else { colorCode = 30; const red = r / 255; const green = g / 255; const blue = b / 255; let v = Math.max(red, green, blue) * 100; v = Math.round(v / 50); if (v === 1) { colorCode += ((Math.round(b / 255) << 2) | (Math.round(g / 255) << 1) | Math.round(r / 255)); } if (v === 2) { colorCode += 60; } } } resolve(colorCode); return; } else { reject(new Error('TYPE ERROR: Not in RGB color hex or color code')); return; } }); } /** * A class to work with syslog messages using UDP, TCP, or TLS transport. * There is support for Syslog message formatting RFC-3164, RFC-5424 including * Structured Data, IBM LEEF (Log Event Extended Format), and HP CEF (Common * Event Format). * Syslog formatting classes can be used as input into a Syslog class to be used * simultaneously to the same Syslog server. * * @requires moment * @version 0.0.0 * @since 0.0.0 */ class Syslog { /** * Construct a new Syslog transport object with user options * @public * @version 0.0.0 * @since 0.0.0 * @this Syslog * @param {object} [options] - Options object * >>>Transport Configuration * @param {string} [options.target='localhost'] - The IP Address|FQDN of the * Syslog Server, this option if set will take presidents over any target * set in a formatting object * @param {string} [options.protocol='udp'] - L4 transport protocol * (udp|tcp|tls), this option if set will take presidents over any * transport set in a formatting object * @param {number} [options.port=514] - IP port, this option if set will take * presidents over any IP Port set in a formatting object * @param {number} [options.tcpTimeout=10000] - Ignored for all other * transports, this option if set will take presidents over any timeout * set in a formatting object * @param {string[]} [options.tlsServerCerts] - Array of authorized TLS server * certificates file locations, this option if set will take presidents * over any certificates set in a formatting object * @param {string} [options.tlsClientCert] - Client TLS certificate file * location that this client should use, this option if set will take * presidents over any certificates set in a formatting object * @param {string} [options.tlsClientKey] - Client TLS key file * location that this client should use, this option if set will take * presidents over any certificates set in a formatting object * >>>Syslog Format Settings * @param {string} [options.format='none'] - Valid syslog format options for * this module are 'none', 'rfc3164', 'rfc5424', 'leef', 'cef' * @param {RFC3164} [options.rfc5424] - {@link module:SyslogPro~RFC5424| * RFC5424 related settings} * @param {RFC5424} [options.rfc5424] - {@link module:SyslogPro~RFC5424| * RFC5424 related settings} * @param {LEEF} [options.leef] - {@link module:SyslogPro~LEEF|IBM LEEF * (Log Event Extended Format) object} * @param {CEF} [options.cef] - {@link module:SyslogPro~CEF|HP CEF * (Common Event Format) formatting object} */ constructor(options) { this.constructor__ = true; if (!options) { options = {}; } ``` Basic transport setup ``` /** @type {string} */ this.target = options.target || 'localhost'; /** @type {string} */ this.protocol = options.protocol || 'udp'; this.protocol = this.protocol.toLowerCase(); /** @type {number} */ this.port = options.port || 514; /** @type {number} */ this.tcpTimeout = options.tcpTimeout || 10000; if ((typeof options.tlsServerCerts === 'object' && Array.isArray(options.tlsServerCerts)) || typeof options.tlsServerCerts === 'string') { this.addTlsServerCerts(options.tlsServerCerts); } else { /** @type {string[]} */ this.tlsServerCerts = []; } if (options.tlsClientCert) { /** @type {string} */ this.tlsClientCert = options.tlsClientCert; } if (options.tlsClientKey) { /** @type {string} */ this.tlsClientKey = options.tlsClientKey; } ``` Syslog Format ``` if (typeof options.format === 'string') { /** @type {string} */ this.format = options.format.toLowerCase(); } else { this.format = options.format || 'none'; } if (options.rfc3164) { if (options.rfc3164.constructor__) { /** @type {RFC3164} */ this.rfc3164 = options.rfc3164; } else { this.rfc3164 = new RFC3164(options); } } if (options.rfc5424) { if (options.rfc5424.constructor__) { /** @type {RFC5424} */ this.rfc5424 = options.rfc5424; } else { this.rfc5424 = new RFC5424(options); } } if (options.leef) { if (options.leef.constructor__) { /** @type {LEEF} */ this.leef = options.leef; } else { this.leef = new LEEF(options); } } if (options.cef) { if (options.cef.constructor__) { /** @type {CEF} */ this.cef = options.cef; } else { this.cef = new CEF(options); } } if (this.format === 'rfc3164' && !this.rfc3164) { this.rfc3164 = new RFC3164(); } if (this.format === 'rfc5424' && !this.rfc5424) { this.rfc5424 = new RFC5424(); } if (this.format === 'leef' && !this.leef) { this.leef = new LEEF(); } if (this.format === 'cef' && !this.cef) { this.cef = new CEF(); } } /** * Add a TLS server certificate which can be used to authenticate the server * this syslog client is connecting too. This function will validate the * input as a file location string and add it to an array of certificates * @private * @version 0.0.0 * @since 0.0.0 * @param {string|string[]} certs - File location of the certificate(s) * @returns {Promise} - True * @throws {Error} - A Type Error */ addTlsServerCerts(certs) { return new Promise((resolve, reject) => { if (typeof certs === 'object' && Array.isArray(certs)) { /** @private @type {string[]} */ this.tlsServerCerts = certs; } else if (typeof certs === 'string') { this.tlsServerCerts = [certs]; } else { let errMsg = 'TYPE ERROR: Server Cert file locations should be a string'; errMsg += ' or array of strings'; reject(new Error(errMsg)); } resolve(true); }); } /** * Send the Syslog message over UDP * @private * @param {string} msg - The formatted Syslog Message * @returns {Promise} - The Syslog formatted string sent * @throws {Error} - Network Error */ udpMessage(msg) { return new Promise((resolve, reject) => { ``` Test for target DNS and Address Family (IPv4/6) by looking up the DNS ``` const dgram = require('dgram'); const dnsOptions = { verbatim: true, }; dnsPromises.lookup(this.target, dnsOptions) .then((result) => { const udpType = result.family === 4 ? 'udp4' : 'udp6'; let client = dgram.createSocket(udpType); ``` Turn msg in to a UTF8 buffer ``` let msgBuffer = Buffer.from(msg, 'utf8'); client.send(msgBuffer, this.port, this.target, () => { client.close(); resolve(msg); }); }) .catch((error) => { reject(error); // Reject out of the sendMessage function promise }); }); } /** * Send the Syslog message over TCP * @private * @param {string} msg - The formatted Syslog Message * @returns {Promise} - The Syslog formatted string sent * @throws {Error} - Timeout error for TCP and TLS connections * @throws {Error} - Network Error */ tcpMessage(msg) { return new Promise((resolve, reject) => { const net = require('net'); const dnsOptions = { verbatim: true, }; dnsPromises.lookup(this.target, dnsOptions) .then((result) => { const tcpOptions = { host: this.target, port: this.port, family: result.family, }; const client = net.createConnection(tcpOptions, () => { ``` Turn msg in to a UTF8 buffer ``` let msgBuffer = Buffer.from(msg, 'utf8'); client.write(msgBuffer, () => { client.end(); }); }); client.setTimeout(this.tcpTimeout); client.on('end', () => { resolve(msg); }); client.on('timeout', () => { client.end(); reject(new Error('TIMEOUT ERROR: Syslog server TCP timeout')); }); client.on('error', (error) => { client.destroy(); reject(error); }); }) .catch((error) => { reject(error); }); }); } /** * Send the Syslog message over TLS * @private * @param {string} msg - The formatted Syslog Message * @returns {Promise} - The Syslog formatted string sent * @throws {Error} - Timeout error for TCP and TLS connections * @throws {Error} - Network Error */ tlsMessage(msg) { return new Promise((resolve, reject) => { const tls = require('tls'); const tlsOptions = { host: this.target, port: this.port, }; ``` Load client cert and key if requested ``` if (typeof this.tlsClientKey === 'string' && typeof this.tlsClientCert === 'string') { tlsOptions.key = fs.readFileSync(this.tlsClientKey); tlsOptions.cert = fs.readFileSync(this.tlsClientCert); } else if (typeof this.tlsClientKey !== 'string' && typeof this.tlsClientKey !== 'undefined') { let errMsg = 'TYPE ERROR: TLS Client Key is not a file'; errMsg += 'location string'; reject(new Error(errMsg)); return; } else if (typeof this.tlsClientCert !== 'string' && typeof this.tlsClientCert !== 'undefined') { let errMsg = 'TYPE ERROR: TLS Client Cert is not a file'; errMsg += 'location string'; reject(new Error(errMsg)); return; } ``` Load any server certs if provided ``` let tlsCerts = this.tlsServerCerts.length; if (tlsCerts > 0) { let tlsOptionsCerts = []; for (let certIndex = 0; certIndex < tlsCerts; certIndex++) { if (typeof this.tlsServerCerts[certIndex] !== 'string') { let errMsg = 'TYPE ERROR: TLS Server Cert is not a file'; errMsg += 'location string'; reject(new Error(errMsg)); } let cert = fs.readFileSync(this.tlsServerCerts[certIndex]); tlsOptionsCerts.push(cert); } tlsOptions.ca = tlsOptionsCerts; tlsOptions.rejectUnauthorized = true; } const client = tls.connect(tlsOptions, () => { ``` Turn msg in to a UTF8 buffer ``` let msgBuffer = Buffer.from(msg, 'utf8'); client.write(msgBuffer, () => { client.end(); }); }); client.setTimeout(this.tcpTimeout); client.on('end', () => { resolve(msg); }); client.on('timeout', () => { client.end(); reject(new Error('TIMEOUT ERROR: Syslog server TLS timeout')); }); client.on('error', (error) => { client.destroy(); reject(error); }); }); } /** * Send the Syslog message to the selected target Syslog server using the * selected transport. * @private * @param {string} msg - The formatted Syslog Message * @returns {Promise} - The Syslog formatted string sent * @throws {Error} - Timeout error for TCP and TLS connections * @throws {Error} - Network Error */ send(msg) { return new Promise((resolve, reject) => { if (typeof msg !== 'string') { reject(new Error('TYPE ERROR: Syslog message must be a string')); return; } this.protocol = this.protocol.toLowerCase(); if (this.protocol === 'udp') { this.udpMessage(msg) .then((result) => { resolve(result); }) .catch((reson) => { reject(reson); }); } else if (this.protocol === 'tcp') { this.tcpMessage(msg) .then((result) => { resolve(result); }) .catch((reson) => { reject(reson); }); } else if (this.protocol === 'tls') { this.tlsMessage(msg) .then((result) => { resolve(result); }) .catch((reson) => { reject(reson); }); } else { let errorMsg = 'FORMAT ERROR: Protocol not recognized, should be '; errorMsg += 'udp|tcp|tls'; reject(new Error(errorMsg)); } }); } } /** * A class to work with RFC3164 formatted syslog messages. The messaging is * fully configurable and ANSI foreground colors can be added. Both ANSI 8 and * ANSI 256 color are fully supported. Most APIs will return a promise. These * APIs can be used using `then(...)/catch(...)` * * A Syslog class with a configured * Syslog server target can also be used as the input into the formatting * classes so that it may run independently. * * The RFC3164 Syslog logging format is meant to be used as a stream of log data * from a service or application. This class is designed to be used in this * fashion where new messages are written to the class as needed. * @requires moment * @version 0.0.0 * @since 0.0.0 */ class RFC3164 { /** * Construct a new RFC3164 formatted Syslog object with user options * @public * @this RFC3164 * @param {object} [options] - Options object * @param {string} [options.applicationName='NodeJSLogger'] - Application * @param {string} [options.hostname=os.hostname] - The name of this server * @param {number} [options.facility=23] - Facility code to use sending this * message * @param {boolean} [options.color=false] - Apply color coding encoding tag * with syslog message text * @param {boolean} [options.extendedColor=false] - Use the extended ANSI * color set encoding tag with syslog message text * @param {object} [options.colors] - User defended colors for * severities * @param {string} [options.colors.emergencyColor] - A RGB Hex coded color in * the form of #FFFFFF or as or the ANSI color code number (30-37 Standard * & 0-255 Extended) * @param {string} [options.colors.alertColor] - A RGB Hex coded color in the * form of #FFFFFF or as or the ANSI color code number (30-37 Standard & * 0-255 Extended) * @param {string} [options.colors.criticalColor] - A RGB Hex coded color in * the form of #FFFFFF or as or the ANSI color code number (30-37 Standard * & 0-255 Extended) * @param {string} [options.colors.errorColor] - A RGB Hex coded color in the * form of #FFFFFF or as or the ANSI color code number (30-37 Standard & * 0-255 Extended) * @param {string} [options.colors.warningColor] - A RGB Hex coded color in * the form of #FFFFFF or as or the ANSI color code number (30-37 Standard * & 0-255 Extended) * @param {string} [options.colors.noticeColor] - A RGB Hex coded color in the * form of #FFFFFF or as or the ANSI color code number (30-37 Standard & * 0-255 Extended) * @param {string} [options.colors.informationalColor] - A RGB Hex coded color * in the form of #FFFFFF or as or the ANSI color code number (30-37 * Standard & 0-255 Extended) * @param {string} [options.colors.debugColor] - A RGB Hex coded color in the * form of #FFFFFF or as or the ANSI color code number (30-37 Standard & * 0-255 Extended) * @param {Syslog} [options.server=false] - A {@link module:SyslogPro~Syslog| * Syslog server connection} that should be used to send messages directly * from this class. @see SyslogPro~Syslog */ constructor(options) { /** @private @type {boolean} */ this.constructor__ = true; options = options || {}; this.hostname = options.hostname || os.hostname(); this.applicationName = options.applicationName || ''; this.facility = options.facility || 23; if (options.color) { /** @type {boolean} */ this.color = true; } else { this.color = false; } if (options.extendedColor) { /** @type {boolean} */ this.extendedColor = true; } else { this.extendedColor = false; } if (options.server) { if (!options.server.constructor__) { /** @private @type {Syslog} */ this.server = new Syslog(options.server); } else { this.server = options.server; } } if (this.extendedColor) { /** @private @type {number} */ this.emergencyColor = 1; // Red foreground color /** @private @type {number} */ this.alertColor = 202; // Dark Orange foreground color /** @private @type {number} */ this.criticalColor = 208; // Orange foreground color /** @private @type {number} */ this.errorColor = 178; // Light Orange foreground color /** @private @type {number} */ this.warningColor = 226; // Yellow foreground color /** @private @type {number} */ this.noticeColor = 117; // Light Blue foreground color /** @private @type {number} */ this.informationalColor = 45; // Blue foreground color /** @private @type {number} */ this.debugColor = 27; // Dark Blue foreground color } else { this.emergencyColor = 31; // Red foreground color this.alertColor = 31; // Red foreground color this.criticalColor = 31; // Red foreground color this.errorColor = 33; // Yellow foreground color this.warningColor = 33; // Yellow foreground color this.noticeColor = 36; // Blue foreground color this.informationalColor = 36; // Blue foreground color this.debugColor = 34; // Dark Blue foreground color } if (typeof options.colors === 'object') { this.setColor(options.colors, this.extendedColor); } } /** * Sets the color to be used for messages at a set priority * @public * @param {string} [colors.emergencyColor] - A RGB Hex coded color in the form * of #FFFFFF or as or the ANSI color code number (30-37 Standard & 0-255 * Extended) * @param {string} [colors.alertColor] - A RGB Hex coded color in the form * of #FFFFFF or as or the ANSI color code number (30-37 Standard & 0-255 * Extended) * @param {string} [colors.criticalColor] - A RGB Hex coded color in the form * of #FFFFFF or as or the ANSI color code number (30-37 Standard & 0-255 * Extended) * @param {string} [colors.errorColor] - A RGB Hex coded color in the form * of #FFFFFF or as or the ANSI color code number (30-37 Standard & 0-255 * Extended) * @param {string} [colors.warningColor] - A RGB Hex coded color in the form * of #FFFFFF or as or the ANSI color code number (30-37 Standard & 0-255 * Extended) * @param {string} [colors.noticeColor] - A RGB Hex coded color in the form * of #FFFFFF or as or the ANSI color code number (30-37 Standard & 0-255 * Extended) * @param {string} [colors.informationalColor] - A RGB Hex coded color in the * form of #FFFFFF or as or the ANSI color code number (30-37 Standard & * 0-255 Extended) * @param {string} [colors.debugColor] - A RGB Hex coded color in the form * of #FFFFFF or as or the ANSI color code number (30-37 Standard & 0-255 * Extended) * @throws {Error} A standard error object */ setColor(colors, extendedColor) { return new Promise((resolve, reject) => { let colorPromises = []; if (colors.emergencyColor) { colorPromises.push( new Promise((resolve, reject) => { rgbToAnsi(colors.emergencyColor, this.extendedColor) .then((result) => { this.emergencyColor = result; resolve(true); }) .catch((reson) => { reson.message = 'TYPE ERROR: '; reson.message += 'emergencyColor'; reson.message += ' Not in RGB color hex or color code'; reject(reson); }); })); } if (colors.alertColor) { colorPromises.push( new Promise((resolve, reject) => { rgbToAnsi(colors.alertColor, this.extendedColor) .then((result) => { this.alertColor = result; resolve(true); }) .catch((reson) => { reson.message = 'TYPE ERROR: '; reson.message += 'alertColor'; reson.message += ' Not in RGB color hex or color code'; reject(reson); }); })); } if (colors.criticalColor) { colorPromises.push( new Promise((resolve, reject) => { rgbToAnsi(colors.criticalColor, this.extendedColor) .then((result) => { this.criticalColor = result; resolve(true); }) .catch((reson) => { reson.message = 'TYPE ERROR: '; reson.message += 'criticalColor'; reson.message += ' Not in RGB color hex or color code'; reject(reson); }); })); } if (colors.errorColor) { colorPromises.push( new Promise((resolve, reject) => { rgbToAnsi(colors.errorColor, this.extendedColor) .then((result) => { this.errorColor = result; resolve(true); }) .catch((reson) => { reson.message = 'TYPE ERROR: '; reson.message += 'errorColor'; reson.message += ' Not in RGB color hex or color code'; reject(reson); }); })); } if (colors.warningColor) { colorPromises.push( new Promise((resolve, reject) => { rgbToAnsi(colors.warningColor, this.extendedColor) .then((result) => { this.warningColor = result; resolve(true); }) .catch((reson) => { reson.message = 'TYPE ERROR: '; reson.message += 'warningColor'; reson.message += ' Not in RGB color hex or color code'; reject(reson); }); })); } if (colors.noticeColor) { colorPromises.push( new Promise((resolve, reject) => { rgbToAnsi(colors.noticeColor, this.extendedColor) .then((result) => { this.noticeColor = result; resolve(true); }) .catch((reson) => { reson.message = 'TYPE ERROR: '; reson.message += 'noticeColor'; reson.message += ' Not in RGB color hex or color code'; reject(reson); }); })); } if (colors.informationalColor) { colorPromises.push( new Promise((resolve, reject) => { rgbToAnsi(colors.informationalColor, this.extendedColor) .then((result) => { this.informationalColor = result; resolve(true); }) .catch((reson) => { reson.message = 'TYPE ERROR: '; reson.message += 'informationalColor'; reson.message += ' Not in RGB color hex or color code'; reject(reson); }); })); } if (colors.debugColor) { colorPromises.push( new Promise((resolve, reject) => { rgbToAnsi(colors.debugColor, this.extendedColor) .then((result) => { this.debugColor = result; resolve(true); }) .catch((reson) => { reson.message = 'TYPE ERROR: '; reson.message += 'debugColor'; reson.message += ' Not in RGB color hex or color code'; reject(reson); }); })); } Promise.all(colorPromises) .then((results) => { resolve(true); }) .catch((reson) => { reject(reson); }); }); } /** * Building a formatted message. Returns a promise with a formatted message * @public * @param {string} msg - The Syslog Message * @param {object} [options] - Options object * @param {number} [options.severity=7] - An array of structure * @param {number} [options.colorCode=36] - The ANSI color code to use if * message coloration is selected * @returns {Promise} A Syslog formatted string according to the selected RFC * @throws {Error} A standard error object */ buildMessage(msg, options) { return new Promise((resolve, reject) => { options = options || {}; let severity = typeof options.severity === 'number' ? options.severity : 6; if (typeof msg !== 'string' || options.msgSeverity > 7) { let errMsg = 'FORMAT ERROR: Syslog message must be a string'; errMsg += ' msgSeverity must be a number between 0 and 7'; reject(new Error(errMsg)); return; } let fmtMsg = ''; // Formatted Syslog message string var const newLine = '\n'; const newLineRegEx = /(\r|\n|(\r\n))/; const escapeCode = '\u001B'; const resetColor = '\u001B[0m'; ``` The PRI is common to both RFC formats ``` const pri = (this.facility * 8) + severity; ``` Remove any newline character ``` msg = msg.replace(newLineRegEx, ''); ``` Add requested color ``` if (this.color) { options.msgColor = options.msgColor || 36; let colorCode = '['; if (this.extendedColor) { colorCode += '38;5;'; // Extended 256 Colors ANSI Code } if (typeof options.msgColor === 'number') { colorCode += options.msgColor; colorCode += 'm'; // ANSI Color Closer } else { colorCode = '[39m'; // Use terminal's default color } msg = escapeCode + colorCode + msg + resetColor; } ``` RegEx to find a leading 0 in the day of a DateTime for RFC3164 RFC3164 uses BSD timeformat ``` const rfc3164DateRegEx = /((A|D|F|J|M|N|O|S)(a|c|e|p|o|u)(b|c|g|l|n|p|r|t|v|y)\s)0(\d\s\d\d:\d\d:\d\d)/; const timestamp = moment() .format('MMM DD hh:mm:ss') .replace(rfc3164DateRegEx, '$1 $5'); ``` Build message ``` fmtMsg = '<' + pri + '>'; fmtMsg += timestamp; fmtMsg += ' ' + this.hostname; fmtMsg += ' ' + this.applicationName; fmtMsg += ' ' + msg; fmtMsg += newLine; resolve(fmtMsg); }); } /** * send a RFC5424 formatted message. Returns a promise with the formatted * message that was sent. If no server connection was defined when the * class was created a default Syslog connector will be used. * @see SyslogPro~Syslog * @public * @param {string} msg - The unformatted Syslog message to send * @param {object} [options] - Options object * @param {number} [options.severity=7] - An array of structure * @param {number} [options.colorCode=36] - The ANSI color code to use if * @returns {Promise} A Syslog formatted string according to the selected RFC * @throws {Error} A standard error object */ send(msg, options) { return new Promise((resolve, reject) => { if (!this.server) { this.server = new Syslog(); } this.buildMessage(msg, options) .then((result) => { this.server.send(result) .then((sendResult) => { resolve(sendResult); }) .catch((error) => { reject(error); }); }) .catch((error) => { reject(error); }); }); } /** * Send a syslog message with a security level of 0 (Emergency) * @public * @param {string} msg - The emergency message to send to the Syslog server * @returns {Promise} - The formatted syslog message sent to the Syslog server * @throws {Error} - Any bubbled-up error */ emergency(msg) { return this.send(msg, { severity: 0, colorCode: this.emergencyColor, }); } /** * Send a syslog message with a security level of 0 (Emergency) * @public * @param {string} msg - The emergency message to send to the Syslog server * @returns {Promise} - The formatted syslog message sent to the Syslog server * @throws {Error} - Any bubbled-up error */ emer(msg) { return this.emergency(msg); } /** * Send a syslog message with a severity level of 1 (Alert) * @public * @param {string} msg - The alert message to send to the Syslog server * @returns {Promise} - The formatted syslog message sent to the Syslog server * @throws {Error} - Any bubbled-up error */ alert(msg) { return this.send(msg, { severity: 1, colorCode: this.alertColor, }); } /** * Send a syslog message with a severity level of 2 (Critical) * @public * @param {string} msg - The critical message to send to the Syslog server * @returns {Promise} - The formatted syslog message sent to the Syslog server * @throws {Error} - Any bubbled-up error */ critical(msg) { return this.send(msg, { severity: 2, colorCode: this.criticalColor, }); } /** * Send a syslog message with a severity level of 2 (Critical) * @public * @param {string} msg - The critical message to send to the Syslog server * @returns {Promise} - The formatted syslog message sent to the Syslog server * @throws {Error} - Any bubbled-up error */ crit(msg) { return this.critical(msg); } /** * Send a syslog message with a severity level of 3 (Error) * @public * @param {string} msg - The error message to send to the Syslog server * @returns {Promise} - The formatted syslog message sent to the Syslog server * @throws {Error} - Any bubbled-up error */ error(msg) { return this.send(msg, { severity: 3, colorCode: this.errorColor, }); } /** * Send a syslog message with a severity level of 3 (Error) * @public * @param {string} msg - The error message to send to the Syslog server * @returns {Promise} - The formatted syslog message sent to the Syslog server * @throws {Error} - Any bubbled-up error */ err(msg) { return this.error(msg); } /** * Send a syslog message with a severity level of 4 (Warning) * @public * @param {string} msg - The warning message to send to the Syslog server * @returns {Promise} - The formatted syslog message sent to the Syslog server * @throws {Error} - Any bubbled-up error */ warning(msg) { return this.send(msg, { severity: 4, colorCode: this.warningColor, }); } /** * Send a syslog message with a severity level of 4 (Warning) * @public * @param {string} msg - The warning message to send to the Syslog server * @returns {Promise} - The formatted syslog message sent to the Syslog server * @throws {Error} - Any bubbled-up error */ warn(msg) { return this.warning(msg); } /** * Send a syslog message with a severity level of 5 (Notice) * @public * @param {string} msg - The notice message to send to the Syslog server * @returns {Promise} - The formatted syslog message sent to the Syslog server * @throws {Error} - Any bubbled-up error */ notice(msg) { return this.send(msg, { severity: 5, colorCode: this.noticeColor, }); } /** * Send a syslog message with a severity level of 5 (Notice) * @public * @param {string} msg - The notice message to send to the Syslog server * @returns {Promise} - The formatted syslog message sent to the Syslog server * @throws {Error} - Any bubbled-up error */ note(msg) { return this.notice(msg); } /** * Send a syslog message with a severity level of 6 (Informational) * @public * @param {string} msg - The informational message to send to the Syslog * server * @returns {Promise} - The formatted syslog message sent to the Syslog server * @throws {Error} - Any bubbled-up error */ informational(msg) { return this.send(msg, { severity: 6, colorCode: this.informationalColor, }); } /** * Send a syslog message with a severity level of 6 (Informational) * @public * @param {string} msg - The informational message to send to the Syslog * server * @returns {Promise} - The formatted syslog message sent to the Syslog server * @throws {Error} - Any bubbled-up error */ info(msg) { return this.informational(msg); } /** * Send a syslog message with a severity level of 6 (Informational) * @public * @param {string} msg - The informational message to send to the Syslog * server * @returns {Promise} - The formatted syslog message sent to the Syslog server * @throws {Error} - Any bubbled-up error */ log(msg) { return this.informational(msg); } /** * Send a syslog message with a severity level of 7 (Debug) * @public * @param {string} msg - The debug message to send to the Syslog server * @returns {Promise} - The formatted syslog message sent to the Syslog server * @throws {Error} - Any bubbled-up error */ debug(msg) { return this.send(msg, { severity: 7, colorCode: this.debugColor, }); } } /** * A class to work with RFC5424 formatted syslog messages. The messaging is * fully configurable and ANSI foreground * colors can be added. Both ANSI 8 * and ANSI 256 color are fully supported. *Most APIs will return a promise. These APIs can be used using * `then(...)/catch(...)` * * A Syslog class with a configured * Syslog server target can also be used as the input into the formatting * classes so that it may run independently. * * The RFC5424 Syslog logging format is meant to be used as a stream of log data * from a service or application. This class is designed to be used in this * fashion where new messages are written to the class as needed. * @requires moment * @version 0.0.0 * @since 0.0.0 */ class RFC5424 { /** * Construct a new RFC5424 formatted Syslog object with user options * @public * @this RFC5424 * @param {object} [options] - Options object * @param {string} [options.applicationName='NodeJSLogger'] - Application * @param {string} [options.hostname=os.hostname] - The name of this server * @param {boolean} [options.timestamp=false] - Included a Timestamp * @param {boolean} [options.timestampUTC=false] - RFC standard is for * local time * @param {boolean} [options.timestampMS=false] - Timestamp with ms * resolution * @param {boolean} [options.timestampTZ=true] - Should the timestamp * included time zone * @param {boolean} [options.includeStructuredData=false] - Included * any provided structured data * @param {boolean} [options.utf8BOM=true] - Included the UTF8 * @param {boolean} [options.color=false] - Included the UTF8 * @param {boolean} [options.extendedColor=false] - Included the UTF8 * encoding tag with syslog message text * @param {object} [options.colors] - User defended colors for * severities * @param {string} [options.colors.emergencyColor] - A RGB Hex coded color in * the form of #FFFFFF or as or the ANSI color code number (30-37 Standard * & 0-255 Extended) * @param {string} [options.colors.alertColor] - A RGB Hex coded color in the * form of #FFFFFF or as or the ANSI color code number (30-37 Standard & * 0-255 Extended) * @param {string} [options.colors.criticalColor] - A RGB Hex coded color in * the form of #FFFFFF or as or the ANSI color code number (30-37 Standard * & 0-255 Extended) * @param {string} [options.colors.errorColor] - A RGB Hex coded color in the * form of #FFFFFF or as or the ANSI color code number (30-37 Standard & * 0-255 Extended) * @param {string} [options.colors.warningColor] - A RGB Hex coded color in * the form of #FFFFFF or as or the ANSI color code number (30-37 Standard * & 0-255 Extended) * @param {string} [options.colors.noticeColor] - A RGB Hex coded color in the * form of #FFFFFF or as or the ANSI color code number (30-37 Standard & * 0-255 Extended) * @param {string} [options.colors.informationalColor] - A RGB Hex coded color * in the form of #FFFFFF or as or the ANSI color code number (30-37 * Standard & 0-255 Extended) * @param {string} [options.colors.debugColor] - A RGB Hex coded color in the * form of #FFFFFF or as or the ANSI color code number (30-37 Standard & * 0-255 Extended) * @param {Syslog} [options.server=false] - A {@link module:SyslogPro~Syslog| * Syslog server connection} that should be used to send messages directly * from this class. @see SyslogPro~Syslog */ constructor(options) { /** @private @type {boolean} */ this.constructor__ = true; options = options || {}; this.hostname = options.hostname || os.hostname(); this.applicationName = options.applicationName || ''; if (typeof options.timestamp === 'undefined' || options.timestamp) { /** @type {boolean} */ this.timestamp = true; } else { this.timestamp = false; } if (options.timestampUTC) { /** @type {boolean} */ this.timestampUTC = true; } else { this.timestampUTC = false; } if (typeof options.timestampTZ === 'undefined' || options.timestampTZ) { /** @type {boolean} */ this.timestampTZ = true; } else { this.timestampTZ = false; } if (options.timestampMS) { /** @type {boolean} */ this.timestampMS = true; } else { this.timestampMS = false; } if (options.includeStructuredData) { /** @type {boolean} */ this.includeStructuredData = true; } else { this.includeStructuredData = false; } if (typeof options.utf8BOM === 'undefined' || options.utf8BOM) { /** @type {boolean} */ this.utf8BOM = true; } else { this.utf8BOM = false; } if (options.color) { /** @type {boolean} */ this.color = true; } else { this.color = false; } if (options.extendedColor) { /** @type {boolean} */ this.extendedColor = true; } else { this.extendedColor = false; } if (options.server) { if (!options.server.constructor__) { /** @private @type {Syslog} */ this.server = new Syslog(options.server); } else { this.server = options.server; } } if (this.extendedColor) { /** @private @type {number} */ this.emergencyColor = 1; // Red foreground color /** @private @type {number} */ this.alertColor = 202; // Dark Orange foreground color /** @private @type {number} */ this.criticalColor = 208; // Orange foreground color /** @private @type {number} */ this.errorColor = 178; // Light Orange foreground color /** @private @type {number} */ this.warningColor = 226; // Yellow foreground color /** @private @type {number} */ this.noticeColor = 117; // Light Blue foreground color /** @private @type {number} */ this.informationalColor = 45; // Blue foreground color /** @private @type {number} */ this.debugColor = 27; // Dark Blue foreground color } else { this.emergencyColor = 31; // Red foreground color this.alertColor = 31; // Red foreground color this.criticalColor = 31; // Red foreground color this.errorColor = 33; // Yellow foreground color this.warningColor = 33; // Yellow foreground color this.noticeColor = 36; // Blue foreground color this.informationalColor = 36; // Blue foreground color this.debugColor = 34; // Dark Blue foreground color } if (typeof options.colors === 'object') { this.setColor(options.colors, this.extendedColor); } } /** * Sets the color to be used for messages at a set priority * @public * @param {string} [colors.emergencyColor] - A RGB Hex coded color in the form * of #FFFFFF or as or the ANSI color code number (30-37 Standard & 0-255 * Extended) * @param {string} [colors.alertColor] - A RGB Hex coded color in the form * of #FFFFFF or as or the ANSI color code number (30-37 Standard & 0-255 * Extended) * @param {string} [colors.criticalColor] - A RGB Hex coded color in the form * of #FFFFFF or as or the ANSI color code number (30-37 Standard & 0-255 * Extended) * @param {string} [colors.errorColor] - A RGB Hex coded color in the form * of #FFFFFF or as or the ANSI color code number (30-37 Standard & 0-255 * Extended) * @param {string} [colors.warningColor] - A RGB Hex coded color in the form * of #FFFFFF or as or the ANSI color code number (30-37 Standard & 0-255 * Extended) * @param {string} [colors.noticeColor] - A RGB Hex coded color in the form * of #FFFFFF or as or the ANSI color code number (30-37 Standard & 0-255 * Extended) * @param {string} [colors.informationalColor] - A RGB Hex coded color in the * form of #FFFFFF or as or the ANSI color code number (30-37 Standard & * 0-255 Extended) * @param {string} [colors.debugColor] - A RGB Hex coded color in the form * of #FFFFFF or as or the ANSI color code number (30-37 Standard & 0-255 * Extended) * @throws {Error} A standard error object */ setColor(colors, extendedColor) { return new Promise((resolve, reject) => { let colorPromises = []; if (colors.emergencyColor) { colorPromises.push( new Promise((resolve, reject) => { rgbToAnsi(colors.emergencyColor, this.extendedColor) .then((result) => { this.emergencyColor = result; resolve(true); }) .catch((reson) => { reson.message = 'TYPE ERROR: '; reson.message += 'emergencyColor'; reson.message += ' Not in RGB color hex or color code'; reject(reson); }); })); } if (colors.alertColor) { colorPromises.push( new Promise((resolve, reject) => { rgbToAnsi(colors.alertColor, this.extendedColor) .then((result) => { this.alertColor = result; resolve(true); }) .catch((reson) => { reson.message = 'TYPE ERROR: '; reson.message += 'alertColor'; reson.message += ' Not in RGB color hex or color code'; reject(reson); }); })); } if (colors.criticalColor) { colorPromises.push( new Promise((resolve, reject) => { rgbToAnsi(colors.criticalColor, this.extendedColor) .then((result) => { this.criticalColor = result; resolve(true); }) .catch((reson) => { reson.message = 'TYPE ERROR: '; reson.message += 'criticalColor'; reson.message += ' Not in RGB color hex or color code'; reject(reson); }); })); } if (colors.errorColor) { colorPromises.push( new Promise((resolve, reject) => { rgbToAnsi(colors.errorColor, this.extendedColor) .then((result) => { this.errorColor = result; resolve(true); }) .catch((reson) => { reson.message = 'TYPE ERROR: '; reson.message += 'errorColor'; reson.message += ' Not in RGB color hex or color code'; reject(reson); }); })); } if (colors.warningColor) { colorPromises.push( new Promise((resolve, reject) => { rgbToAnsi(colors.warningColor, this.extendedColor) .then((result) => { this.warningColor = result; resolve(true); }) .catch((reson) => { reson.message = 'TYPE ERROR: '; reson.message += 'warningColor'; reson.message += ' Not in RGB color hex or color code'; reject(reson); }); })); } if (colors.noticeColor) { colorPromises.push( new Promise((resolve, reject) => { rgbToAnsi(colors.noticeColor, this.extendedColor) .then((result) => { this.noticeColor = result; resolve(true); }) .catch((reson) => { reson.message = 'TYPE ERROR: '; reson.message += 'noticeColor'; reson.message += ' Not in RGB color hex or color code'; reject(reson); }); })); } if (colors.informationalColor) { colorPromises.push( new Promise((resolve, reject) => { rgbToAnsi(colors.informationalColor, this.extendedColor) .then((result) => { this.informationalColor = result; resolve(true); }) .catch((reson) => { reson.message = 'TYPE ERROR: '; reson.message += 'informationalColor'; reson.message += ' Not in RGB color hex or color code'; reject(reson); }); })); } if (colors.debugColor) { colorPromises.push( new Promise((resolve, reject) => { rgbToAnsi(colors.debugColor, this.extendedColor) .then((result) => { this.debugColor = result; resolve(true); }) .catch((reson) => { reson.message = 'TYPE ERROR: '; reson.message += 'debugColor'; reson.message += ' Not in RGB color hex or color code'; reject(reson); }); })); } Promise.all(colorPromises) .then((results) => { resolve(true); }) .catch((reson) => { reject(reson); }); }); } /** * Building a formatted message. Returns a promise with a formatted message * @public * @param {string} msg - The Syslog Message * @param {object} [options] - Options object * @param {number} [options.severity=7] - An array of structure * @param {number} [options.facility=23] - Facility code to use sending this * message * @param {string} [options.pid='-'] - The process id of the service sending * this message * @param {string[]} [options.structuredData] - An array of structure * data strings conforming to the IETF/IANA defined SD-IDs or IANA * registered SMI Network Management Private Enterprise Code SD-ID * conforming to the format * [name@ parameter=value] * @param {number} [options.colorCode=36] - The ANSI color code to use if * message coloration is selected * @returns {Promise} A Syslog formatted string according to the selected RFC * @throws {Error} A standard error object */ buildMessage(msg, options) { return new Promise((resolve, reject) => { options = options || {}; let severity = typeof options.severity === 'number' ? options.severity : 6; if (typeof msg !== 'string' || options.severity > 7) { let errMsg = 'FORMAT ERROR: Syslog message must be a string'; errMsg += ' msgSeverity must be a number between 0 and 7'; reject(new Error(errMsg)); return; } let facility = options.facility || 23; let pid = options.pid || '-'; let id = options.id || '-'; let msgStructuredData = options.msgStructuredData || []; let fmtMsg = ''; // Formated Syslog message string var const newLine = '\n'; const newLineRegEx = /(\r|\n|(\r\n))/; const escapeCode = '\u001B'; const resetColor = '\u001B[0m'; ``` The PRI is common to both RFC formats ``` const pri = (facility * 8) + severity; ``` Remove any newline character ``` msg = msg.replace(newLineRegEx, ''); ``` Add requested color ``` if (this.color) { options.msgColor = options.msgColor || 36; let colorCode = '['; if (this.extendedColor) { colorCode += '38;5;'; // Extended 256 Colors ANSI Code } if (typeof options.msgColor === 'number') { colorCode += options.msgColor; colorCode += 'm'; // ANSI Color Closer } else { colorCode = '[39m'; // Use terminal's default color } msg = escapeCode + colorCode + msg + resetColor; } ``` RFC5424 timestamp formating ``` let timestamp = '-'; if (this.timestamp) { let timeQuality = '[timeQuality'; if (this.timestampUTC) { timeQuality += ' tzKnown=1'; if (this.timestampMS) { if (this.timestampTZ) { timestamp = moment().utc().format('YYYY-MM-DDThh:mm:ss.SSSSSSZ'); } else { timestamp = moment().utc().format('YYYY-MM-DDThh:mm:ss.SSSSSS'); } } else { if (this.timestampTZ) { timestamp = moment().utc().format('YYYY-MM-DDThh:mm:ssZ'); } else { timestamp = moment().utc().format('YYYY-MM-DDThh:mm:ss'); } } } else { if (this.timestampTZ) { timeQuality += ' tzKnown=1'; if (this.timestampMS) { timeQuality += ' isSynced=1'; timeQuality += ' syncAccuracy=0'; timestamp = moment().format('YYYY-MM-DDThh:mm:ss.SSSSSSZ'); } else { timestamp = moment().format('YYYY-MM-DDThh:mm:ssZ'); } } else { timeQuality += ' tzKnown=0'; if (this.timestampMS) { timeQuality += ' isSynced=1'; timeQuality += ' syncAccuracy=0'; timestamp = moment().format('YYYY-MM-DDThh:mm:ss.SSSSSS'); } else { timestamp = moment().format('YYYY-MM-DDThh:mm:ss'); } } } timeQuality += ']'; msgStructuredData.push(timeQuality); } ``` Build Structured Data string ``` let structuredData = '-'; const sdElementCount = msgStructuredData.length; if (this.includeStructuredData && sdElementCount > 0) { let sdElementNames = []; let sdElements = []; const sdElementNameRegEx = /(\[)(\S*)(\s|\])/; ``` Loop to drop duplicates of the same SD Element name ``` for (let elementIndex = 0; elementIndex < sdElementCount; elementIndex++) { let elementName = msgStructuredData[elementIndex] .match(sdElementNameRegEx)[2]; if (!sdElementNames.includes(elementName)) { sdElementNames.push(elementName); sdElements.push(msgStructuredData[elementIndex]); } } structuredData = sdElements.join(''); } ``` Build the message ``` fmtMsg = '<' + pri + '>'; fmtMsg += '1'; // Version number fmtMsg += ' ' + timestamp; fmtMsg += ' ' + this.hostname; fmtMsg += ' ' + this.applicationName; fmtMsg += ' ' + pid; fmtMsg += ' ' + id; fmtMsg += ' ' + structuredData; if (this.utf8BOM) { fmtMsg += ' BOM' + msg; } else { fmtMsg += ' ' + msg; } fmtMsg += newLine; resolve(fmtMsg); }); } /** * send a RFC5424 formatted message. Returns a promise with the formatted * message that was sent. If no server connection was defined when the * class was created a default Syslog connector will be used. * @see SyslogPro~Syslog * @public * @param {string} msg - The unformatted Syslog message to send * @returns {Promise} A Syslog formatted string according to the selected RFC * @throws {Error} A standard error object */ send(msg, options) { return new Promise((resolve, reject) => { if (!this.server) { this.server = new Syslog(); } this.buildMessage(msg, options) .then((result) => { this.server.send(result) .then((sendResult) => { resolve(sendResult); }) .catch((error) => { reject(error); }); }) .catch((error) => { reject(error); }); }); } /** * Send a syslog message with a severity level of 0 (Emergency) * @public * @param {string} msg - The emergency message to send to the Syslog server * @returns {Promise} - The formatted syslog message sent to the Syslog server * @throws {Error} - Any bubbled-up error */ emergency(msg) { return this.send(msg, { severity: 0, colorCode: this.emergencyColor, }); } /** * Send a syslog message with a severity level of 0 (Emergency) * @public * @param {string} msg - The emergency message to send to the Syslog server * @returns {Promise} - The formatted syslog message sent to the Syslog server * @throws {Error} - Any bubbled-up error */ emer(msg) { return this.emergency(msg); } /** * Send a syslog message with a severity level of 1 (Alert) * @public * @param {string} msg - The alert message to send to the Syslog server * @returns {Promise} - The formatted syslog message sent to the Syslog server * @throws {Error} - Any bubbled-up error */ alert(msg) { return this.send(msg, { severity: 1, colorCode: this.alertColor, }); } /** * Send a syslog message with a severity level of 2 (Critical) * @public * @param {string} msg - The critical message to send to the Syslog server * @returns {Promise} - The formatted syslog message sent to the Syslog server * @throws {Error} - Any bubbled-up error */ critical(msg) { return this.send(msg, { severity: 2, colorCode: this.criticalColor, }); } /** * Send a syslog message with a severity level of 2 (Critical) * @public * @param {string} msg - The critical message to send to the Syslog server * @returns {Promise} - The formatted syslog message sent to the Syslog server * @throws {Error} - Any bubbled-up error */ crit(msg) { return this.critical(msg); } /** * Send a syslog message with a severity level of 3 (Error) * @public * @param {string} msg - The error message to send to the Syslog server * @returns {Promise} - The formatted syslog message sent to the Syslog server * @throws {Error} - Any bubbled-up error */ error(msg) { return this.send(msg, { severity: 3, colorCode: this.errorColor, }); } /** * Send a syslog message with a severity level of 3 (Error) * @public * @param {string} msg - The error message to send to the Syslog server * @returns {Promise} - The formatted syslog message sent to the Syslog server * @throws {Error} - Any bubbled-up error */ err(msg) { return this.error(msg); } /** * Send a syslog message with a severity level of 4 (Warning) * @public * @param {string} msg - The warning message to send to the Syslog server * @returns {Promise} - The formatted syslog message sent to the Syslog server * @throws {Error} - Any bubbled-up error */ warning(msg) { return this.send(msg, { severity: 4, colorCode: this.warningColor, }); } /** * Send a syslog message with a severity level of 4 (Warning) * @public * @param {string} msg - The warning message to send to the Syslog server * @returns {Promise} - The formatted syslog message sent to the Syslog server * @throws {Error} - Any bubbled-up error */ warn(msg) { return this.warning(msg); } /** * Send a syslog message with a severity level of 5 (Notice) * @public * @param {string} msg - The notice message to send to the Syslog server * @returns {Promise} - The formatted syslog message sent to the Syslog server * @throws {Error} - Any bubbled-up error */ notice(msg) { return this.send(msg, { severity: 5, colorCode: this.noticeColor, }); } /** * Send a syslog message with a severity level of 5 (Notice) * @public * @param {string} msg - The notice message to send to the Syslog server * @returns {Promise} - The formatted syslog message sent to the Syslog server * @throws {Error} - Any bubbled-up error */ note(msg) { return this.notice(msg); } /** * Send a syslog message with a severity level of 6 (Informational) * @public * @param {string} msg - The informational message to send to the Syslog * server * @returns {Promise} - The formatted syslog message sent to the Syslog server * @throws {Error} - Any bubbled-up error */ informational(msg) { return this.send(msg, { severity: 6, colorCode: this.informationalColor, }); } /** * Send a syslog message with a severity level of 6 (Informational) * @public * @param {string} msg - The informational message to send to the Syslog * server * @returns {Promise} - The formatted syslog message sent to the Syslog server * @throws {Error} - Any bubbled-up error */ info(msg) { return this.informational(msg); } /** * Send a syslog message with a severity level of 6 (Informational) * @public * @param {string} msg - The informational message to send to the Syslog * server * @returns {Promise} - The formatted syslog message sent to the Syslog server * @throws {Error} - Any bubbled-up error */ log(msg) { return this.informational(msg); } /** * Send a syslog message with a severity level of 7 (Debug) * @public * @param {string} msg - The debug message to send to the Syslog server * @returns {Promise} - The formatted syslog message sent to the Syslog server * @throws {Error} - Any bubbled-up error */ debug(msg) { return this.send(msg, { severity: 7, colorCode: this.debugColor, }); } } /** * A class to work with IBM LEEF (Log Event Extended Format) messages this form * of system messages are designed to work with security systems. Messages can * be saved to file (Saving to file if not part of this module but a LEEF * formatted message produced by this module can be saved externally to it) or * sent via Syslog. * Most APIs will return a promise. These APIs can be used using * `then(...)/catch(...)` * * A Syslog class with a configured Syslog server target can also be used as * the input into the formatting classes so that it may run independently. The * LEEF format is designed to send event data to a SIEM system and should not * be as a logging stream. This class is meant to be used once per message. * @requires moment * @version 0.0.0 * @since 0.0.0 */ class LEEF { /** * Construct a new LEEF formatting object with user options * @public * @param {object} [options] - Options object * @param {string} [options.vendor='unknown'] - The vendor of the system that * generated the event being reported * @param {string} [options.product='unknown'] - The product name of the * system that genrated the event being reported * @param {string} [options.version='unknown'] - The version name of the * system that genrated the event being reported * @param {string} [options.eventId='unknown'] - The eventId of the * system that genrated the event being reported * @param {object} [options.attributes] - LEEF message attributes which * defaults to all base attributes with null values, new attributes should * be added as new elements to this object * @param {boolean} [options.syslogHeader='true'] - Should the LEEF message * include a Syslog header with Timestamp and source * @param {Syslog} [options.server=false] - A {@link module:SyslogPro~Syslog| * Syslog server connection} that should be used to send messages directly * from this class. @see SyslogPro~Syslog */ constructor(options) { /** @private @type {boolean} */ this.constructor__ = true; options = options || {}; /** @type {string} */ this.vendor = options.vendor || 'unknown'; /** @type {string} */ this.product = options.product || 'unknown'; /** @type {string} */ this.version = options.version || 'unknown'; /** @type {string} */ this.eventId = options.eventId || 'unknown'; /** @type {boolean} */ this.syslogHeader = typeof options.syslogHeader === 'boolean' ? options.syslogHeader : true; /** @type {object} */ this.attributes = options.attributes || { cat: null, devTime: null, devTimeFormat: null, proto: null, sev: null, src: null, dst: null, srcPort: null, dstPort: null, srcPreNAT: null, dstPreNAT: null, srcPostNAT: null, dstPostNAT: null, usrName: null, srcMAC: null, dstMAC: null, srcPreNATPort: null, dstPreNATPort: null, srcPostNATPort: null, dstPostNATPort: null, identSrc: null, identHostName: null, identNetBios: null, identGrpName: null, identMAC: null, vSrc: null, vSrcName: null, accountName: null, srcBytes: null, dstBytes: null, srcPackets: null, dstPackets: null, totalPackets: null, role: null, realm: null, policy: null, resource: null, url: null, groupID: null, domain: null, isLoginEvent: null, isLogoutEvent: null, identSecondlp: null, calLanguage: null, AttributeLimits: null, calCountryOrRegion: null, }; if (options.server) { if (options.server.constructor__) { /** @private @type {Syslog} */ this.server = options.server; } else { this.server = new Syslog(options.server); } } } /** *Build a formatted message * @public * @return {Promise} - string with formatted message */ buildMessage() { return new Promise((resolve, reject) => { let fmtMsg = 'LEEF:2.0'; fmtMsg += '|' + this.vendor; fmtMsg += '|' + this.product; fmtMsg += '|' + this.version; fmtMsg += '|' + this.eventId; fmtMsg += '|'; ``` Build LEEF Attributes ``` const Tab = '\x09'; const leefAttribs = Object.entries(this.attributes); const leefAttribsLen = leefAttribs.length; for (let attrib = 0; attrib < leefAttribsLen; attrib++) { if (leefAttribs[attrib][1] !== null) { fmtMsg += leefAttribs[attrib][0] + '=' + leefAttribs[attrib][1] + Tab; } } resolve(fmtMsg); }); } /** * @public * @param {Syslog} [options=false] - A {@link module:SyslogPro~Syslog| * Syslog server connection} that should be used to send messages directly * from this class. @see SyslogPro~Syslog */ send(options) { return new Promise((resolve, reject) => { this.buildMessage() .then((result) => { if (!this.server) { this.server = new Syslog(options); } this.server.send(result) .then((sendResult) => { resolve(sendResult); }) .catch((reson) => { reject(reson); }); }); }); } } /** * A class to work with HP CEF (Common Event Format) messages. This form * of system messages are designed to work with security systems. Messages can * be saved to file (Saving to file if not part of this module but a CEF * formatted message produced by this module can be saved externally to it) or * sent via Syslog. * Most APIs will return a promise. These APIs can be used using * `then(...)/catch(...)` * * A Syslog class with a configured Syslog server target can also be used as * the input into the formatting classes so that it may run independently. The * CEF format is designed to send event data to a SIEM system and should not be * as a logging stream. This class is meant to be used once per message. * @requires moment * @version 0.0.0 * @since 0.0.0 */ class CEF { /** * Construct a new CEF formatting object with user options * @public * @param {object} [options] - Options object * @param {string} [options.deviceVendor='unknown'] - The vendor of the system * that generated the event being reported * @param {string} [options.deviceProduct='unknown'] - The product name of the * system that genrated the event being reported * @param {string} [options.deviceVersion='unknown'] - The version name of the * system that genrated the event being reported * @param {string} [options.deviceEventClassId='unknown'] - The eventId of the * system that genrated the event being reported * @param {string} [options.name='unknown'] - Name of the service generating * the notice * @param {string} [options.severity='unknown'] - Severity of the notification * @param {string} [options.extensions={}] - Any CEF Key=Value extensions * @param {Syslog} [options.server=false] - A {@link module:SyslogPro~Syslog| * Syslog server connection} that should be used to send messages directly * from this class. @see SyslogPro~Syslog */ constructor(options) { /** @private @type {boolean} */ this.constructor__ = true; options = options || {}; /** @type {string} */ this.deviceVendor = options.deviceVendor || 'Unknown'; /** @type {string} */ this.deviceProduct = options.deviceProduct || 'Unknown'; /** @type {string} */ this.deviceVersion = options.deviceVersion || 'Unknown'; /** @type {string} */ this.deviceEventClassId = options.deviceEventClassId || 'Unknown'; /** @type {string} */ this.name = options.name || 'Unknown'; /** @type {string} */ this.severity = options.severity || 'Unknown'; /** @type {object} */ this.extensions = options.extensions || { deviceAction: null, applicationProtocol: null, deviceCustomIPv6Address1: null, 'deviceCustomIPv6 Address1Label': null, deviceCustomIPv6Address3: null, 'deviceCustomIPv6Address3 Label': null, 'deviceCustomIPv6 Address4': null, 'deviceCustomIPv6 Address4Label': null, deviceEventCategory: null, deviceCustomFloatingPoint1: null, 'deviceCustom FloatingPoint1Label': null, deviceCustomFloatingPoint2: null, 'deviceCustomFloatingPoint2 Label': null, deviceCustomFloatingPoint3: null, 'deviceCustom FloatingPoint3Label': null, deviceCustomFloatingPoint4: null, 'deviceCustom FloatingPoint4Label': null, deviceCustomNumber1: null, deviceCustomNumber1Label: null, DeviceCustomNumber2: null, deviceCustomNumber2Label: null, deviceCustomNumber3: null, deviceCustomNumber3Label: null, baseEventCount: null, deviceCustomString1: null, deviceCustomString1Label: null, deviceCustomString2: null, deviceCustomString2Label: null, deviceCustomString3: null, deviceCustomString3Label: null, deviceCustomString4: null, deviceCustomString4Label: null, deviceCustomString5: null, deviceCustomString5Label: null, deviceCustomString6: null, deviceCustomString6Label: null, destinationDnsDomain: null, destinationServiceName: null, 'destinationTranslated Address': null, destinationTranslatedPort: null, deviceCustomDate1: null, deviceCustomDate1Label: null, deviceCustomDate2: null, deviceCustomDate2Label: null, deviceDirection: null, deviceDnsDomain: null, deviceExternalId: null, deviceFacility: null, deviceInboundInterface: null, deviceNtDomain: null, deviceOutboundInterface: null, devicePayloadId: null, deviceProcessName: null, deviceTranslatedAddress: null, destinationHostName: null, destinationMacAddress: null, destinationNtDomain: null, destinationProcessId: null, destinationUserPrivileges: null, destinationProcessName: null, destinationPort: null, destinationAddress: null, deviceTimeZone: null, destinationUserId: null, destinationUserName: null, deviceAddress: null, deviceHostName: null, deviceMacAddress: null, deviceProcessId: null, endTime: null, externalId: null, fileCreateTime: null, fileHash: null, fileId: null, fileModificationTime: null, filePath: null, filePermission: null, fileType: null, flexDate1: null, flexDate1Label: null, flexString1: null, flexString1Label: null, flexString2: null, flexString2Label: null, filename: null, fileSize: null, bytesIn: null, message: null, oldFileCreateTime: null, oldFileHash: null, oldFileId: null, oldFileModificationTime: null, oldFileName: null, oldFilePath: null, oldFileSize: null, oldFileType: null, bytesOut: null, eventOutcome: null, transportProtocol: null, Reason: null, requestUrl: null, requestClientApplication: null, requestContext: null, requestCookies: null, requestMethod: null, deviceReceiptTime: null, sourceHostName: null, sourceMacAddress: null, sourceNtDomain: null, sourceDnsDomain: null, sourceServiceName: null, sourceTranslatedAddress: null, sourceTranslatedPort: null, sourceProcessId: null, sourceUserPrivileges: null, sourceProcessName: null, sourcePort: null, sourceAddress: null, startTime: null, sourceUserId: null, sourceUserName: null, type: null, agentDnsDomain: null, agentNtDomain: null, agentTranslatedAddress: null, 'agentTranslatedZone ExternalID': null, agentTranslatedZoneURI: null, agentZoneExternalID: null, agentZoneURI: null, agentAddress: null, agentHostName: null, agentId: null, agentMacAddress: null, agentReceiptTime: null, agentType: null, agentTimeZone: null, agentVersion: null, customerExternalID: null, customerURI: null, 'destinationTranslated ZoneExternalID': null, 'destinationTranslated ZoneURI': null, destinationZoneExternalID: null, destinationZoneURI: null, 'deviceTranslatedZone ExternalID': null, deviceTranslatedZoneURI: null, deviceZoneExternalID: null, deviceZoneURI: null, destinationGeoLatitude: null, destinationGeoLongitude: null, eventId: null, rawEvent: null, sourceGeoLatitude: null, sourceGeoLongitude: null, 'sourceTranslatedZone ExternalID': null, sourceTranslatedZoneURI: null, sourceZoneExternalID: null, sourceZoneURI: null, }; if (options.server) { if (options.server.constructor__) { /** @private @type {Syslog} */ this.server = options.server; } else { this.server = new Syslog(options.server); } } } /** * Validate this CEF object * @public * @return {Promise} - True if validated * @throws {Error} - First element to fail validation */ validate() { return new Promise((resolve, reject) => { const Extensions = { deviceAction: { key: 'act', type: 'String', len: 63, discription: 'Action taken by the device.', }, applicationProtocol: { key: 'app', type: 'String', len: 31, discription: 'Application level protocol, example values are HTTP, ' + 'HTTPS, SSHv2, Telnet, POP, IMPA, IMAPS, and so on.', }, deviceCustomIPv6Address1: { key: 'c6a1', type: 'String', len: null, discription: 'One of four IPv6 address fields available to map ' + 'fields that do not apply to any other in this dictionary. ' + 'TIP: See the guidelines under “User-Defined Extensions” for ' + 'tips on using these fields.', }, 'deviceCustomIPv6 Address1Label': { key: 'c6a1Label', type: 'String', len: 1023, discription: 'All custom fields have a corresponding label field. ' + 'Each of these fields is a string and describes the purpose of ' + 'the custom field.', }, deviceCustomIPv6Address3: { key: 'c6a3', type: 'String', len: null, discription: 'One of four IPv6 address fields available to map ' + 'fields that do not apply to any other in this dictionary. ' + 'TIP: See the guidelines under “User-Defined Extensions” for ' + 'tips on using these fields.', }, 'deviceCustomIPv6Address3 Label': { key: 'c6a3Label', type: 'String', len: 1023, discription: 'All custom fields have a corresponding label field. ' + 'Each of these fields is a string and describes the purpose of ' + 'the custom field.', }, 'deviceCustomIPv6 Address4': { key: 'c6a4', type: 'String', len: null, discription: 'One of four IPv6 address fields available to map ' + 'fields that do not apply to any other in this dictionary. ' + 'TIP: See the guidelines under “User-Defined Extensions” for ' + 'tips on using these fields.', }, 'deviceCustomIPv6 Address4Label': { key: 'C6a4Label', type: 'String', len: 1023, discription: 'All custom fields have a corresponding label field. ' + 'Each of these fields is a string and describes the purpose of ' + 'the custom field.', }, deviceEventCategory: { key: 'cat', type: 'String', len: 1023, discription: 'Represents the category assigned by the originating ' + 'device. Devices often use their own categorization schema to ' + 'classify event. Example: “/Monitor/Disk/Read”', }, deviceCustomFloatingPoint1: { key: 'cfp1', type: 'Number', len: null, discription: 'One of four floating point fields available to map ' + 'fields that do not apply to any other in this dictionary.', }, 'deviceCustom FloatingPoint1Label': { key: 'cfp1Label', type: 'String', len: 1023, discription: 'All custom fields have a corresponding label field. ' + 'Each of these fields is a string and describes the purpose of ' + 'the custom field.', }, deviceCustomFloatingPoint2: { key: 'cfp2', type: 'Number', len: null, discription: 'One of four floating point fields available to map ' + 'fields that do not apply to any other in this dictionary.', }, 'deviceCustomFloatingPoint2 Label': { key: 'cfp2Label', type: 'String', len: 1023, discription: 'All custom fields have a corresponding label field. ' + 'Each of these fields is a string and describes the purpose of ' + 'the custom field.', }, deviceCustomFloatingPoint3: { key: 'cfp3', type: 'Number', len: null, discription: 'One of four floating point fields available to map ' + 'fields that do not apply to any other in this dictionary.', }, 'deviceCustom FloatingPoint3Label': { key: 'cfp3Label', type: 'String', len: 1023, discription: 'All custom fields have a corresponding label field. ' + 'Each of these fields is a string and describes the purpose of ' + 'the custom field.', }, deviceCustomFloatingPoint4: { key: 'cfp4', type: 'Number', len: null, discription: 'One of four floating point fields available to map ' + 'fields that do not apply to any other in this dictionary.', }, 'deviceCustom FloatingPoint4Label': { key: 'cfp4Label', type: 'String', len: 1023, discription: 'All custom fields have a corresponding label field. ' + 'Each of these fields is a string and describes the purpose of ' + 'the custom field.', }, deviceCustomNumber1: { key: 'cn1', type: 'Number', len: null, discription: 'One of three number fields available to map fields ' + 'that do not apply to any other in this dictionary. Use ' + 'sparingly and seek a more specific dictionary supplied field ' + 'when possible.', }, deviceCustomNumber1Label: { key: 'cn1Label', type: 'String', len: 1023, discription: 'All custom fields have a corresponding label field. ' + 'Each of these fields is a string and describes the purpose of ' + 'the custom field.', }, DeviceCustomNumber2: { key: 'cn2', type: 'Number', len: null, discription: 'One of three number fields available to map fields ' + 'that do not apply to any other in this dictionary. Use ' + 'sparingly and seek a more specific, dictionary supplied field ' + 'when possible.', }, deviceCustomNumber2Label: { key: 'cn2Label', type: 'String', len: 1023, discription: 'All custom fields have a corresponding label field. ' + 'Each of these fields is a string and describes the purpose of ' + 'the custom field.', }, deviceCustomNumber3: { key: 'cn3', type: 'Number', len: null, discription: 'One of three number fields available to map fields ' + 'that do not apply to any other in this dictionary. Use ' + 'sparingly and seek a more specific, dictionary supplied field ' + 'when possible.', }, deviceCustomNumber3Label: { key: 'cn3Label', type: 'String', len: 1023, discription: 'All custom fields have a corresponding label field. ' + 'Each of these fields is a string and describes the purpose of ' + 'the custom field.', }, baseEventCount: { key: 'cnt', type: 'Number', len: null, discription: 'A count associated with this event. How many times ' + 'was this same event observed? Count can be omitted if it is 1.', }, deviceCustomString1: { key: 'cs1', type: 'String', len: 4000, discription: 'One of six strings available to map fields that do ' + 'not apply to any other in this dictionary. Use sparingly and ' + 'seek a more specific, dictionary supplied field when ' + 'possible. TIP: See the guidelines under “User-Defined ' + 'Extensions” for tips on using these fields.', }, deviceCustomString1Label: { key: 'cs1Label', type: 'String', len: 1023, discription: 'All custom fields have a corresponding label field. ' + 'Each of these fields is a string and describes the purpose of ' + 'the custom field.', }, deviceCustomString2: { key: 'cs2', type: 'String', len: 4000, discription: 'One of six strings available to map fields that do ' + 'not apply to any other in this dictionary. Use sparingly and ' + 'seek a more specific, dictionary supplied field when ' + 'possible. TIP: See the guidelines under “User-Defined ' + 'Extensions” for tips on using these fields.', }, deviceCustomString2Label: { key: 'cs2Label', type: 'String', len: 1023, discription: 'All custom fields have a corresponding label field. ' + 'Each of these fields is a string and describes the purpose of ' + 'the custom field.', }, deviceCustomString3: { key: 'cs3', type: 'String', len: 4000, discription: 'One of six strings available to map fields that do ' + 'not apply to any other in this dictionary. Use sparingly and ' + 'seek a more specific, dictionary supplied field when ' + 'possible. TIP: See the guidelines under “User-Defined ' + 'Extensions” for tips on using these fields.', }, deviceCustomString3Label: { key: 'cs3Label', type: 'String', len: 1023, discription: 'All custom fields have a corresponding label field. ' + 'Each of these fields is a string and describes the purpose of ' + 'the custom field.', }, deviceCustomString4: { key: 'cs4', type: 'String', len: 4000, discription: 'One of six strings available to map fields that do ' + 'not apply to any other in this dictionary. Use sparingly and ' + 'seek a more specific, dictionary supplied field when ' + 'possible. TIP: See the guidelines under “User-Defined ' + 'Extensions” for tips on using these fields.', }, deviceCustomString4Label: { key: 'cs4Label', type: 'String', len: 1023, discription: 'All custom fields have a corresponding label field. ' + 'Each of these fields is a string and describes the purpose of ' + 'the custom field.', }, deviceCustomString5: { key: 'cs5', type: 'String', len: 4000, discription: 'One of six strings available to map fields that do ' + 'not apply to any other in this dictionary. Use sparingly and ' + 'seek a more specific, dictionary supplied field when ' + 'possible. TIP: See the guidelines under “User-Defined ' + 'Extensions” for tips on using these fields.', }, deviceCustomString5Label: { key: 'cs5Label', type: 'String', len: 1023, discription: 'All custom fields have a corresponding label field. ' + 'Each of these fields is a string and describes the purpose of ' + 'the custom field.', }, deviceCustomString6: { key: 'cs6', type: 'String', len: 4000, discription: 'One of six strings available to map fields that do ' + 'not apply to any other in this dictionary. Use sparingly and ' + 'seek a more specific, dictionary supplied field when ' + 'possible. TIP: See the guidelines under “User-Defined ' + 'Extensions” for tips on using these fields.', }, deviceCustomString6Label: { key: 'cs6Label', type: 'String', len: 1023, discription: 'All custom fields have a corresponding label field. ' + 'Each of these fields is a string and describes the purpose of ' + 'the custom field.', }, destinationDnsDomain: { key: 'destination DnsDomain', type: 'String', len: 255, discription: 'The DNS domain part of the complete fully qualified ' + 'domain name (FQDN).', }, destinationServiceName: { key: 'destination ServiceName', type: 'String', len: 1023, discription: 'The service targeted by this event. Example: “sshd”', }, 'destinationTranslated Address': { key: 'Destination Translated Address', type: 'String', len: null, discription: 'Identifies the translated destination that the event ' + 'refers to in an IP network. The format is an IPv4 address. ' + 'Example: “192.168.10.1”', }, destinationTranslatedPort: { key: 'Destination TranslatedPort', type: 'Number', len: null, discription: 'Port after it was translated; for example, a ' + 'firewall. Valid port numbers are 0 to 65535.', }, deviceCustomDate1: { key: 'deviceCustom Date1', type: 'String', len: null, discription: 'One of two timestamp fields available to map fields ' + 'that do not apply to any other in this dictionary. Use ' + 'sparingly and seek a more specific, dictionary supplied field ' + 'when possible. TIP: See the guidelines under “User-Defined ' + 'Extensions” for tips on using these fields.', }, deviceCustomDate1Label: { key: 'deviceCustom Date1Label', type: 'String', len: 1023, discription: 'All custom fields have a corresponding label field. ' + 'Each of these fields is a string and describes the purpose of ' + 'the custom field.', }, deviceCustomDate2: { key: 'deviceCustom Date2', type: 'String', len: null, discription: 'One of two timestamp fields available to map fields ' + 'that do not apply to any other in this dictionary. Use ' + 'sparingly and seek a more specific, dictionary supplied field ' + 'when possible. TIP: See the guidelines under “User-Defined ' + 'Extensions” for tips on using these fields.', }, deviceCustomDate2Label: { key: 'deviceCustom Date2Label', type: 'String', len: 1023, discription: 'All custom fields have a corresponding label field. ' + 'Each of these fields is a string and describes the purpose of ' + 'the custom field.', }, deviceDirection: { key: 'deviceDirection', type: 'Number', len: null, discription: 'Any information about what direction the observed ' + 'communication has taken. The following values are supported: ' + '“0” for inbound or “1” for outbound', }, deviceDnsDomain: { key: 'deviceDns Domain', type: 'String', len: 255, discription: 'The DNS domain part of the complete fully qualified ' + 'domain name (FQDN).', }, deviceExternalId: { key: 'device ExternalId', type: 'String', len: 255, discription: 'A name that uniquely identifies the device ' + 'generating this event.', }, deviceFacility: { key: 'deviceFacility', type: 'String', len: 1023, discription: 'The facility generating this event. For example, ' + 'Syslog has an explicit facility associated with every event.', }, deviceInboundInterface: { key: 'deviceInbound Interface', type: 'String', len: 128, discription: 'Interface on which the packet or data entered the ' + 'device.', }, deviceNtDomain: { key: 'deviceNt Domain', type: 'String', len: 255, discription: 'The Windows domain name of the device address.', }, deviceOutboundInterface: { key: 'Device Outbound Interface', type: 'String', len: 128, discription: 'Interface on which the packet or data left the ' + 'device.', }, devicePayloadId: { key: 'Device PayloadId', type: 'String', len: 128, discription: 'Unique identifier for the payload associated with ' + 'the event.', }, deviceProcessName: { key: 'deviceProcess Name', type: 'String', len: 1023, discription: 'Process name associated with the event. An example ' + 'might be the process generating the syslog entry in UNIX.', }, deviceTranslatedAddress: { key: 'device Translated Address', type: 'String', len: null, discription: 'Identifies the translated device address that the ' + 'event refers to in an IP network. The format is an IPv4 ' + 'address. Example: “192.168.10.1”', }, destinationHostName: { key: 'dhost', type: 'String', len: 1023, discription: 'Identifies the destination that an event refers to ' + 'in an IP network. The format should be a fully qualified ' + 'domain name (FQDN) associated with the destination node, when ' + 'a node is available. Examples: “host.domain.com” or “host”.', }, destinationMacAddress: { key: 'dmac', type: 'String', len: null, discription: 'Six colon-seperated hexadecimal numbers. Example: ' + '“00:0D:60:AF:1B:61”', }, destinationNtDomain: { key: 'dntdom', type: 'String', len: 255, discription: 'The Windows domain name of the destination address.', }, destinationProcessId: { key: 'dpid', type: 'Number', len: null, discription: 'Provides the ID of the destination process ' + 'associated with the event. For example, if an event contains ' + 'process ID 105, 105” is the process ID.', }, destinationUserPrivileges: { key: 'dpriv', type: 'String', len: 1023, discription: 'The typical values are “Administrator”, “User”, and ' + '“Guest”. This identifies the destination user’s privileges. ' + 'In UNIX, for example, activity executed on the root user ' + 'would be identified with destinationUser Privileges of ' + '“Administrator”.', }, destinationProcessName: { key: 'dproc', type: 'String', len: 1023, discription: 'The name of the event’s destination process. ' + 'Example: “telnetd” or “sshd”.', }, destinationPort: { key: 'dpt', type: 'Number', len: null, discription: 'The valid port numbers are between 0 and 65535.', }, destinationAddress: { key: 'dst', type: 'String', len: null, discription: 'Identifies the destination address that the event ' + 'refers to in an IP network. The format is an IPv4 address. ' + 'Example: “192.168.10.1”', }, deviceTimeZone: { key: 'dtz', type: 'String', len: 255, discription: 'The timezone for the device generating the event.', }, destinationUserId: { key: 'duid', type: 'String', len: 1023, discription: 'Identifies the destination user by ID. For example, ' + 'in UNIX, the root user is generally associated with user ' + 'ID 0.', }, destinationUserName: { key: 'duser', type: 'String', len: 1023, discription: 'Identifies the destination user by name. This is the ' + 'user associated with the event’s destination. Email addresses ' + 'are often mapped into the UserName fields. The recipient is a ' + 'candidate to put into this field.', }, deviceAddress: { key: 'dvc', type: 'String', len: null, discription: 'Identifies the device address that an event refers ' + 'to in an IP network. The format is an IPv4 address. Example: ' + '“192.168.10.1”.', }, deviceHostName: { key: 'dvchost', type: 'String', len: 100, discription: 'The format should be a fully qualified domain name ' + '(FQDN) associated with the device node, when a node is ' + 'available. Example: “host.domain.com” or “host”.', }, deviceMacAddress: { key: 'dvcmac', type: 'String', len: null, discription: 'Six colon-separated hexadecimal numbers. Example: ' + '“00:0D:60:AF:1B:61”', }, deviceProcessId: { key: 'dvcpid', type: 'Number', len: null, discription: 'Provides the ID of the process on the device ' + 'generating the event.', }, endTime: { key: 'end', type: 'String', len: null, discription: 'The time at which the activity related to the event ' + 'ended. The format is MMM dd yyyy HH:mm:ss or milliseconds ' + 'since epoch (Jan 1st1970). An example would be reporting the ' + 'end of a session.', }, externalId: { key: 'externalId', type: 'String', len: 40, discription: 'The ID used by an originating device. They are ' + 'usually increasing numbers, associated with events.', }, fileCreateTime: { key: 'fileCreateTime', type: 'String', len: null, discription: 'Time when the file was created.', }, fileHash: { key: 'fileHash', type: 'String', len: 255, discription: 'Hash of a file.', }, fileId: { key: 'fileId', type: 'String', len: 1023, discription: 'An ID associated with a file could be the inode.', }, fileModificationTime: { key: 'fileModification Time', type: 'String', len: null, discription: 'Time when the file was last modified.', }, filePath: { key: 'filePath', type: 'String', len: 1023, discription: 'Full path to the file, including file name itself. ' + 'Example: C:\Program Files \WindowsNT\Accessories\ wordpad.exe ' + 'or /usr/bin/zip', }, filePermission: { key: 'filePermission', type: 'String', len: 1023, discription: 'Permissions of the file.', }, fileType: { key: 'fileType', type: 'String', len: 1023, discription: 'Type of file (pipe, socket, etc.)', }, flexDate1: { key: 'flexDate1', type: 'String', len: null, discription: 'A timestamp field available to map a timestamp that ' + 'does not apply to any other defined timestamp field in this ' + 'dictionary. Use all flex fields sparingly and seek a more ' + 'specific, dictionary supplied field when possible. These ' + 'fields are typically reserved for customer use and should not ' + 'be set by vendors unless necessary.', }, flexDate1Label: { key: 'flexDate1Label', type: 'String', len: 128, discription: 'The label field is a string and describes the ' + 'purpose of the flex field.', }, flexString1: { key: 'flexString1', type: 'String', len: 1023, discription: 'One of four floating point fields available to map ' + 'fields that do not apply to any other in this dictionary. Use ' + 'sparingly and seek a more specific, dictionary supplied field ' + 'when possible. These fields are typically reserved for ' + 'customer use and should not be set by vendors unless ' + 'necessary.', }, flexString1Label: { key: 'flexString1 Label', type: 'String', len: 128, discription: 'The label field is a string and describes the ' + 'purpose of the flex field.', }, flexString2: { key: 'flexString2', type: 'String', len: 1023, discription: 'One of four floating point fields available to map ' + 'fields that do not apply to any other in this dictionary. Use ' + 'sparingly and seek a more specific, dictionary supplied field ' + 'when possible. These fields are typically reserved for ' + 'customer use and should not be set by vendors unless ' + 'necessary.', }, flexString2Label: { key: 'flex String2Label', type: 'String', len: 128, discription: 'The label field is a string and describes the ' + 'purpose of the flex field.', }, filename: { key: 'fname', type: 'String', len: 1023, discription: 'Name of the file only (without its path).', }, fileSize: { key: 'fsize', type: 'Number', len: null, discription: 'Size of the file.', }, bytesIn: { key: 'in', type: 'Number', len: null, discription: 'Number of bytes transferred inbound, relative to the ' + 'source to destination relationship, meaning that data was ' + 'flowing from source to destination.', }, message: { key: 'msg', type: 'String', len: 1023, discription: 'An arbitrary message giving more details about the ' + 'event. Multi-line entries can be produced by using \n as the ' + 'new line separator.', }, oldFileCreateTime: { key: 'oldFileCreate Time', type: 'String', len: null, discription: 'Time when old file was created.', }, oldFileHash: { key: 'oldFileHash', type: 'String', len: 255, discription: 'Hash of the old file.', }, oldFileId: { key: 'oldFileId', type: 'String', len: 1023, discription: 'An ID associated with the old file could be the ' + 'inode.', }, oldFileModificationTime: { key: 'oldFile Modification Time', type: 'String', len: null, discription: 'Time when old file was last modified.', }, oldFileName: { key: 'oldFileName', type: 'String', len: 1023, discription: 'Name of the old file.', }, oldFilePath: { key: 'oldFilePath', type: 'String', len: 1023, discription: 'Full path to the old fiWindowsNT\\Accessories le, ' + 'including the file name itself. Examples: c:\\Program ' + 'Files\\wordpad.exe or /usr/bin/zip', }, oldFileSize: { key: 'oldFileSize', type: 'Number', len: null, discription: 'Size of the old file.', }, oldFileType: { key: 'oldFileType', type: 'String', len: 1023, discription: 'Type of the old file (pipe, socket, etc.)', }, bytesOut: { key: 'out', type: 'Number', len: null, discription: 'Number of bytes transferred outbound relative to the ' + 'source to destination relationship. For example, the byte ' + 'number of data flowing from the destination to the source.', }, eventOutcome: { key: 'outcome', type: 'String', len: 63, discription: 'Displays the outcome, usually as ‘success’ or ' + '‘failure’.', }, transportProtocol: { key: 'proto', type: 'String', len: 31, discription: 'Identifies the Layer-4 protocol used. The possible ' + 'values are protocols such as TCP or UDP.', }, Reason: { key: 'reason', type: 'String', len: 1023, discription: 'The reason an audit event was generated. For ' + 'example “badd password” or “unknown user”. This could also be ' + 'an error or return code. Example: “0x1234”', }, requestUrl: { key: 'request', type: 'String', len: 1023, discription: 'In the case of an HTTP request, this field contains ' + 'the URL accessed. The URL should contain the protocol as ' + 'well. Example: “http://www/secure.com”', }, requestClientApplication: { key: 'requestClient Application', type: 'String', len: 1023, discription: 'The User-Agent associated with the request.', }, requestContext: { key: 'requestContext', type: 'String', len: 2048, discription: 'Description of the content from which the request ' + 'originated (for example, HTTP Referrer)', }, requestCookies: { key: 'requestCookies', type: 'String', len: 1023, discription: 'Cookies associated with the request.', }, requestMethod: { key: 'requestMethod', type: 'String', len: 1023, discription: 'The method used to access a URL. Possible values: ' + '“POST”, “GET”, etc.', }, deviceReceiptTime: { key: 'rt', type: 'String', len: null, discription: 'The time at which the event related to the activity ' + 'was received. The format is MMM dd yyyy HH:mm:ss or ' + 'milliseconds since epoch (Jan 1st 1970)', }, sourceHostName: { key: 'shost', type: 'String', len: 1023, discription: 'Identifies the source that an event refers to in an ' + 'IP network. The format should be a fully qualified domain ' + 'name (DQDN) associated with the source node, when a mode is ' + 'available. Examples: “host” or “host.domain.com”.', }, sourceMacAddress: { key: 'smac', type: 'String', len: null, discription: 'Six colon-separated hexadecimal numbers. Example: ' + '“00:0D:60:AF:1B:61”', }, sourceNtDomain: { key: 'sntdom', type: 'String', len: 255, discription: 'The Windows domain name for the source address.', }, sourceDnsDomain: { key: 'sourceDns Domain', type: 'String', len: 255, discription: 'The DNS domain part of the complete fully qualified ' + 'domain name (FQDN).', }, sourceServiceName: { key: 'source ServiceName', type: 'String', len: 1023, discription: 'The service that is responsible for generating this ' + 'event.', }, sourceTranslatedAddress: { key: 'source Translated Address', type: 'String', len: null, discription: 'Identifies the translated source that the event ' + 'refers to in an IP network. The format is an IPv4 address. ' + 'Example: “192.168.10.1”.', }, sourceTranslatedPort: { key: 'source TranslatedPort', type: 'Number', len: null, discription: 'A port number after being translated by, for ' + 'example, a firewall. Valid port numbers are 0 to 65535.', }, sourceProcessId: { key: 'spid', type: 'Number', len: null, discription: 'The ID of the source process associated with the ' + 'event.', }, sourceUserPrivileges: { key: 'spriv', type: 'String', len: 1023, discription: 'The typical values are “Administrator”, “User”, and ' + '“Guest”. It identifies the source user’s privileges. In UNIX, ' + 'for example, activity executed by the root user would be ' + 'identified with “Administrator”.', }, sourceProcessName: { key: 'sproc', type: 'String', len: 1023, discription: 'The name of the event’s source process.', }, sourcePort: { key: 'spt', type: 'Number', len: null, discription: 'The valid port numbers are 0 to 65535.', }, sourceAddress: { key: 'src', type: 'String', len: null, discription: 'Identifies the source that an event refers to in an ' + 'IP network. The format is an IPv4 address. Example: ' + '“192.168.10.1”.', }, startTime: { key: 'start', type: 'String', len: null, discription: 'The time when the activity the event referred to ' + 'started. The format is MMM dd yyyy HH:mm:ss or milliseconds ' + 'since epoch (Jan 1st 1970)', }, sourceUserId: { key: 'suid', type: 'String', len: 1023, discription: 'Identifies the source user by ID. This is the user ' + 'associated with the source of the event. For example, in ' + 'UNIX, the root user is generally associated with user ID 0.', }, sourceUserName: { key: 'suser', type: 'String', len: 1023, discription: 'Identifies the source user by name. Email addresses ' + 'are also mapped into the UserName fields. The sender is a ' + 'candidate to put into this field.', }, type: { key: 'type', type: 'Number', len: null, discription: '0 means base event, 1 means aggregated, 2 means ' + 'correlation, and 3 means action. This field can be omitted ' + 'for base events (type 0).', }, agentDnsDomain: { key: 'agentDns Domain', type: 'String', len: 255, discription: 'The DNS domain name of the ArcSight connector that ' + 'processed the event.', }, agentNtDomain: { key: 'agentNtDomain', type: 'String', len: 255, discription: '', }, agentTranslatedAddress: { key: 'agentTranslated Address', type: 'String', len: null, discription: '', }, 'agentTranslatedZone ExternalID': { key: 'agentTranslated ZoneExternalID', type: 'String', len: 200, discription: '', }, agentTranslatedZoneURI: { key: 'agentTranslated Zone URI', type: 'String', len: 2048, discription: '', }, agentZoneExternalID: { key: 'agentZone ExternalID', type: 'String', len: 200, discription: '', }, agentZoneURI: { key: 'agentZoneURI', type: 'String', len: 2048, discription: '', }, agentAddress: { key: 'agt', type: 'String', len: null, discription: 'The IP address of the ArcSight connector that ' + 'processed the event.', }, agentHostName: { key: 'ahost', type: 'String', len: 1023, discription: 'The hostname of the ArcSight connector that ' + 'processed the event.', }, agentId: { key: 'aid', type: 'String', len: 40, discription: 'The agent ID of the ArcSight connector that ' + 'processed the event.', }, agentMacAddress: { key: 'amac', type: 'String', len: null, discription: 'The MAC address of the ArcSight connector that ' + 'processed the event.', }, agentReceiptTime: { key: 'art', type: 'String', len: null, discription: 'The time at which information about the event was ' + 'received by the ArcSight connector.', }, agentType: { key: 'at', type: 'String', len: 63, discription: 'The agent type of the ArcSight connector that ' + 'processed the event', }, agentTimeZone: { key: 'atz', type: 'String', len: 255, discription: 'The agent time zone of the ArcSight connector that ' + 'processed the event.', }, agentVersion: { key: 'av', type: 'String', len: 31, discription: 'The version of the ArcSight connector that processed ' + 'the event.', }, customerExternalID: { key: 'customer ExternalID', type: 'String', len: 200, discription: '', }, customerURI: { key: 'customerURI', type: 'String', len: 2048, discription: '', }, 'destinationTranslated ZoneExternalID': { key: 'destination TranslatedZone ExternalID', type: 'String', len: 200, discription: '', }, 'destinationTranslated ZoneURI': { key: 'destination Translated ZoneURI', type: 'String', len: 2048, discription: 'The URI for the Translated Zone that the destination ' + 'asset has been assigned to in ArcSight.', }, destinationZoneExternalID: { key: 'destinationZone ExternalID', type: 'String', len: 200, discription: '', }, destinationZoneURI: { key: 'destinationZone URI', type: 'String', len: 2048, discription: 'The URI for the Zone that the destination asset has ' + 'been assigned to in ArcSight.', }, 'deviceTranslatedZone ExternalID': { key: 'device TranslatedZone ExternalID', type: 'String', len: 200, discription: '', }, deviceTranslatedZoneURI: { key: 'device TranslatedZone URI', type: 'String', len: 2048, discription: 'The URI for the Translated Zone that the device ' + 'asset has been assigned to in ArcSight.', }, deviceZoneExternalID: { key: 'deviceZone ExternalID', type: 'String', len: 200, discription: '', }, deviceZoneURI: { key: 'deviceZoneURI', type: 'String', len: 2048, discription: 'Thee URI for the Zone that the device asset has been ' + 'assigned to in ArcSight.', }, destinationGeoLatitude: { key: 'dlat', type: 'Number', len: null, discription: 'The latitudinal value from which the ' + 'destination’s IP address belongs.', }, destinationGeoLongitude: { key: 'dlong', type: 'Number', len: null, discription: 'The longitudinal value from which the destination’s ' + 'IP address belongs.', }, eventId: { key: 'eventId', type: 'Number', len: null, discription: 'This is a unique ID that ArcSight assigns to each ' + 'event.', }, rawEvent: { key: 'rawEvent', type: 'String', len: 4000, discription: '', }, sourceGeoLatitude: { key: 'slat', type: 'Number', len: null, discription: '', }, sourceGeoLongitude: { key: 'slong', type: 'Number', len: null, discription: '', }, 'sourceTranslatedZone ExternalID': { key: 'source TranslatedZone ExternalID', type: 'String', len: 200, discription: '', }, sourceTranslatedZoneURI: { key: 'source TranslatedZone URI', type: 'String', len: 2048, discription: 'The URI for the Translated Zone that the destination ' + 'asset has been assigned to in ArcSight.', }, sourceZoneExternalID: { key: 'sourceZone ExternalID', type: 'String', len: 200, discription: '', }, sourceZoneURI: { key: 'sourceZoneURI', type: 'String', len: 2048, discription: 'The URI for the Zone that the source asset has been ' + 'assigned to in ArcSight.' }, }; if (typeof this.deviceVendor !== 'string' || typeof this.deviceProduct !== 'string' || typeof this.deviceVersion !== 'string' ) { reject(new Error('TYPE ERROR: CEF Device Info must be a string')); } if (this.severity && ( ( typeof this.severity === 'string' && ( this.severity !== 'Unknown' && this.severity !== 'Low' && this.severity !== 'Medium' && this.severity !== 'High' && this.severity !== 'Very-High' ) ) || ( typeof this.severity === 'number' && ( this.severity < 0 || this.severity > 10 ) ) ) ) { reject(new Error('TYPE ERROR: CEF Severity not set correctly')); } const cefExts = Object.entries(this.extensions); const cefExtsLen = cefExts.length; for (let ext = 0; ext < cefExtsLen; ext++) { if (cefExts[ext][1] !== null) { if (Extensions[cefExts[ext][0]]) { if (typeof cefExts[ext][1] === Extensions[cefExts[ext][0]] .type .toLowerCase()) { if (Extensions[cefExts[ext][0]].len > 0 && typeof cefExts[ext][1] === 'string' && cefExts[ext][1].length > Extensions[cefExts[ext][0]].len){ let errMsg = 'FORMAT ERROR:'; errMsg += ' CEF Extention Key'; errMsg += ' ' + cefExts[ext][0]; errMsg += ' value length is to long;'; errMsg += ' max length is'; errMsg += ' ' + Extensions[cefExts[ext][0]].len; reject(new Error(errMsg)); } } else { let errMsg = 'TYPE ERROR:'; errMsg += ' CEF Key'; errMsg += ' ' + cefExts[ext][0]; errMsg += ' value type was expected to be'; errMsg += ' ' + Extensions[cefExts[ext][0]].type.toLowerCase(); reject(new Error(errMsg)); } } } } resolve(true); }); } /** * Build a CEF formated string * @public * @return {Promise} - String with formated message */ buildMessage() { return new Promise((resolve, reject) => { let fmtMsg = 'CEF:0'; fmtMsg += '|' + this.deviceVendor; fmtMsg += '|' + this.deviceProduct; fmtMsg += '|' + this.deviceVersion; fmtMsg += '|' + this.deviceEventClassId; fmtMsg += '|' + this.name; fmtMsg += '|' + this.severity; fmtMsg += '|'; const cefExts = Object.entries(this.extensions); const cefExtsLen = cefExts.length; for (let ext = 0; ext < cefExtsLen; ext++) { if (cefExts[ext][1] !== null) { fmtMsg += cefExts[ext][0] + '=' + cefExts[ext][1] + ' '; } } resolve(fmtMsg); }); } /** * @public * @param {Syslog} [options=false] - A {@link module:SyslogPro~Syslog| * Syslog server connection} that should be used to send messages directly * from this class. @see SyslogPro~Syslog */ send(options) { return new Promise((resolve, reject) => { this.buildMessage() .then((result) => { if (!this.server) { this.server = new Syslog(options); } this.server.send(result) .then((sendResult) => { resolve(sendResult); }) .catch((reson) => { reject(reson); }); }); }); } } module.exports = { RgbToAnsi: rgbToAnsi, RFC3164: RFC3164, RFC5424: RFC5424, LEEF: LEEF, CEF: CEF, Syslog: Syslog, }; ```