Fortigate Firewall

// Description:
// Syslog (Event/Traffic logs) from FortiGate NG Firewalls

// {
//  "@message": "date=2023-07-17 time=11:13:00 devname=\"ABCD\" devid=\"FGT401234567AB\" dstcountry=\"United States\"",
//  "@facility": "local7"
// }

// Data input format: ({ obj, size, source }) or ( doc )
function main({obj, size, source}) {
    // Event selection criteria
    let msg = obj["@message"]
    if (!msg){
        return {"status":"abort"}
    }

    if (!startsWith(msg, "time=")){
        return {"status":"abort"}
    }
    let tags = obj["@tags"]
    if (!tags) {
        return {"status":"abort"}
    }
    if (!tags.Some( (_, tag) => startsWith(tag, "date=" ))) {
       return {"status":"abort"}
    }

    // Output field settings
    obj["@type"] = "event"

    obj["@parser"] = "fpl-FortigateFWSyslog"
    obj["@parserVersion"] = "20240308-1"
    obj["@event_type"]="fortigate"
    obj["@eventType"]="FortiGateNGFW"

    // Event parsing
    let m = mergeTagWithMessage(obj)
    let f = decoder_MixedKeyValue(m)

    obj["@fortigate"] = f
    tags = ["fortigate"]
    obj["@tags"] = tags

    // Discard original message
    obj["@message"] = ""

    // Collect device metrics
    recordDeviceMetrics(obj, size)

    // Metaflow, data normalization
    generateFusionEventWithCache(obj)

    return {"status":"pass"}
}

function generateFusionEventWithCache(obj) {
    let f = obj["@fortigate"]

    if (!(f.srcip && f.dstip && f.srcport && f.dstport && f.proto)) {
        // printf("invalid event record for flow: %v", f)
        return
    }

    let ts = obj["@timestamp"]

    // if (f.logid == "0000000020"){}
    // https://community.fortinet.com/t5/FortiGate/Technical-Tip-Notes-on-Traffic-log-generation-and-logging/ta-p/189711
    // https://docs.fortinet.com/document/fortianalyzer/7.4.1/administration-guide/750342/long-lived-session-handling

    let envelop = {
        partition: "default",
        dataType: "event",
        time_ms: ts
    }

    let sp = (f.srcport ? parseInt(f.srcport) : 0)
    let dp = (f.dstport ? parseInt(f.dstport) : 0)
    let prot = (f.proto ? parseInt(f.proto) : 0)

    let dur_E = (f.duration ? parseInt(f.duration) : 0)
    let sentP_E = (f.sentpkt ? parseInt(f.sentpkt) : 0)
    let rcvdP_E = (f.rcvdpkt ? parseInt(f.rcvdpkt) : 0)
    let sentB_E = (f.sentbyte ? parseInt(f.sentbyte) : 0)
    let rcvdB_E = (f.rcvdbyte ? parseInt(f.rcvdbyte) : 0)

    let dur = dur_E // default case
    let sentP = sentP_E
    let rcvdP = rcvdP_E
    let sentB = sentB_E
    let rcvdB = rcvdB_E
  
    let source={
        flow: {
            sip: f.srcip,
            dip: f.dstip,
            sp: sp,
            dp: dp,
            prot: prot,

            rxB: rcvdB,
            txB: sentB,
            totalB: sentB + rcvdB,
            rxP: rcvdP,
            txP: sentP,

            dur: dur,
            time_ms: ts
        },
        dtype:"fortigate"
    }
    obj["@metaflow"] = source
    //printf("%v",source)
    Fluency_FusionEvent(envelop, source)
}

function mergeTagWithMessage(obj) {
    let tags = obj["@tags"]
    if(tags){
        return tags[0] + " " + obj["@message"]
    }
    return obj["@message"]
}

function recordDeviceMetrics(obj, size) {
    let sender = obj["@sender"]
    let source = obj["@source"]
    let f = obj["@fortigate"]

    let deviceName = (f.devname ? f.devname : "unknown")

    let deviceEntry = Fluency_Device_LookupName(deviceName)
    if (!deviceEntry) {
        deviceEntry = {
            name:deviceName,
            ips: [sender],
            group:"FPL-detect: FortiGate NGFW",
            device: {
                name:"FortiGate NGFW",
                category:"Firewall"
            }
        }
        Fluency_Device_Add(deviceEntry)
    }
    let dimensions = {
        namespace:"fluency",
        app:"import",
        eventType:"FortiGateNGFW",
        syslogSender:sender,
        customer: "default",
        importSource: deviceEntry.name,
        deviceType: deviceEntry.device.name
    }
    if (deviceEntry.group) {
        dimensions.group = deviceEntry.group
    }
    Platform_Metric_Counter("fluency_import_count", dimensions, 1)
    Platform_Metric_Counter("fluency_import_bytes", dimensions, size)
}