From 24915485f21d419f6d69a94f1d50c43b56a04688 Mon Sep 17 00:00:00 2001 From: Jochen Schalanda Date: Thu, 9 Mar 2017 00:12:40 +0100 Subject: [PATCH] Add support for FortiGate syslog events --- .../impl/event/FortiGateSyslogEvent.java | 176 ++++++++++++++++++ .../impl/event/FortiGateSyslogEventTest.java | 34 ++++ 2 files changed, 210 insertions(+) create mode 100644 src/main/java/org/graylog2/syslog4j/server/impl/event/FortiGateSyslogEvent.java create mode 100644 src/test/java/org/graylog2/syslog4j/server/impl/event/FortiGateSyslogEventTest.java diff --git a/src/main/java/org/graylog2/syslog4j/server/impl/event/FortiGateSyslogEvent.java b/src/main/java/org/graylog2/syslog4j/server/impl/event/FortiGateSyslogEvent.java new file mode 100644 index 0000000..c65ab18 --- /dev/null +++ b/src/main/java/org/graylog2/syslog4j/server/impl/event/FortiGateSyslogEvent.java @@ -0,0 +1,176 @@ +package org.graylog2.syslog4j.server.impl.event; + +import org.graylog2.syslog4j.server.SyslogServerEventIF; + +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static java.util.Objects.requireNonNull; + +/** + * FortiGateSyslogEvent provides an implementation of the + * SyslogServerEventIF interface that supports receiving of + * FortiGate/FortiOS syslog messages. + * + * @see FortiGate logging and reporting overview + */ +public class FortiGateSyslogEvent implements SyslogServerEventIF { + private static final Pattern PRI_PATTERN = Pattern.compile("^<(\\d{1,3})>(.*)$"); + private static final Pattern KV_PATTERN = Pattern.compile("(\\w+)=([^\\s\"]*)"); + private static final Pattern QUOTED_KV_PATTERN = Pattern.compile("(\\w+)=\"([^\"]*)\""); + + private final String rawEvent; + private Date date; + private int facility; + private int level; + private String host; + private String message; + private Charset charSet = StandardCharsets.UTF_8; + private Map fields = Collections.emptyMap(); + + public FortiGateSyslogEvent(final String rawEvent) { + this.rawEvent = requireNonNull(rawEvent, "rawEvent"); + parse(rawEvent); + } + + private void parse(String event) { + final Matcher matcher = PRI_PATTERN.matcher(event); + if (!matcher.find()) { + throw new IllegalArgumentException("Invalid Fortigate syslog message"); + } else { + final String priority = matcher.group(1); + final String message = matcher.group(2); + + parsePriority(priority); + setMessage(message); + parseFields(message); + parseDate(fields.get("date"), fields.get("time")); + setHost(fields.get("devname")); + } + } + + private void parsePriority(String priorityString) { + try { + final int priority = Integer.parseInt(priorityString); + setFacility(priority / 8); + setLevel(priority % 8); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Couldn't parse message priority", e); + } + } + + private void parseFields(String event) { + final Map fields = new HashMap<>(); + final Matcher matcher = KV_PATTERN.matcher(event); + while (matcher.find()) { + fields.put(matcher.group(1), matcher.group(2)); + } + final Matcher quotedMatcher = QUOTED_KV_PATTERN.matcher(event); + while (quotedMatcher.find()) { + fields.put(quotedMatcher.group(1), quotedMatcher.group(2)); + } + setFields(fields); + } + + private void parseDate(String date, String time) { + if (date != null && time != null) { + final ZonedDateTime dateTime = ZonedDateTime.of( + LocalDate.parse(date, DateTimeFormatter.ISO_LOCAL_DATE.withZone(ZoneOffset.UTC)), + LocalTime.parse(time, DateTimeFormatter.ISO_LOCAL_TIME.withZone(ZoneOffset.UTC)), + ZoneOffset.UTC); + setDate(Date.from(dateTime.toInstant())); + + } else { + setDate(new Date()); + } + } + + @Override + public byte[] getRaw() { + return rawEvent.getBytes(charSet); + } + + @Override + public int getFacility() { + return facility; + } + + @Override + public void setFacility(int facility) { + this.facility = requireNonNull(facility, "facility"); + } + + @Override + public Date getDate() { + return date; + } + + @Override + public void setDate(Date date) { + this.date = requireNonNull(date, "date"); + } + + @Override + public int getLevel() { + return level; + } + + @Override + public void setLevel(int level) { + this.level = requireNonNull(level, "level"); + } + + @Override + public String getHost() { + return host; + } + + @Override + public void setHost(String host) { + this.host = host; + } + + @Override + public boolean isHostStrippedFromMessage() { + return false; + } + + @Override + public String getMessage() { + return message; + } + + @Override + public void setMessage(String message) { + this.message = requireNonNull(message, "message"); + } + + @Override + public String getCharSet() { + return charSet.name(); + } + + @Override + public void setCharSet(String charSet) { + this.charSet = Charset.forName(charSet); + } + + public Map getFields() { + return Collections.unmodifiableMap(fields); + } + + public void setFields(Map fields) { + this.fields = requireNonNull(fields, "fields"); + } +} diff --git a/src/test/java/org/graylog2/syslog4j/server/impl/event/FortiGateSyslogEventTest.java b/src/test/java/org/graylog2/syslog4j/server/impl/event/FortiGateSyslogEventTest.java new file mode 100644 index 0000000..ff430f6 --- /dev/null +++ b/src/test/java/org/graylog2/syslog4j/server/impl/event/FortiGateSyslogEventTest.java @@ -0,0 +1,34 @@ +package org.graylog2.syslog4j.server.impl.event; + +import org.junit.Test; + +import java.nio.charset.StandardCharsets; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; + +import static org.assertj.core.api.Assertions.assertThat; + +public class FortiGateSyslogEventTest { + @Test + public void testFortiGateMessage() { + final String rawMessage = "<45>date=2017-03-06 time=12:53:10 devname=DEVICENAME devid=DEVICEID logid=0000000013 type=traffic subtype=forward level=notice vd=ALIAS srcip=IP srcport=45748 srcintf=\"IF\" dstip=IP dstport=443 dstintf=\"IF\" sessionid=1122686199 status=close policyid=77 dstcountry=\"COUNTRY\" srccountry=\"COUNTRY\" trandisp=dnat tranip=IP tranport=443 service=HTTPS proto=6 appid=41540 app=\"SSL_TLSv1.2\" appcat=\"Network.Service\" applist=\"ACLNAME\" appact=detected duration=1 sentbyte=2313 rcvdbyte=14883 sentpkt=19 rcvdpkt=19 utmaction=passthrough utmevent=app-ctrl attack=\"SSL\" hostname=\"HOSTNAME\" custom=\"white space\""; + final FortiGateSyslogEvent event = new FortiGateSyslogEvent(rawMessage); + + assertThat(event).isNotNull(); + assertThat(event.getFacility()).isEqualTo(5); + assertThat(event.getLevel()).isEqualTo(5); + assertThat(event.getHost()).isEqualTo("DEVICENAME"); + assertThat(ZonedDateTime.ofInstant(event.getDate().toInstant(), ZoneOffset.UTC)) + .isEqualTo(ZonedDateTime.of(2017, 3, 6, 12, 53, 10, 0, ZoneOffset.UTC)); + assertThat(event.getRaw()).isEqualTo(rawMessage.getBytes(StandardCharsets.UTF_8)); + assertThat(event.getMessage()).isEqualTo("date=2017-03-06 time=12:53:10 devname=DEVICENAME devid=DEVICEID logid=0000000013 type=traffic subtype=forward level=notice vd=ALIAS srcip=IP srcport=45748 srcintf=\"IF\" dstip=IP dstport=443 dstintf=\"IF\" sessionid=1122686199 status=close policyid=77 dstcountry=\"COUNTRY\" srccountry=\"COUNTRY\" trandisp=dnat tranip=IP tranport=443 service=HTTPS proto=6 appid=41540 app=\"SSL_TLSv1.2\" appcat=\"Network.Service\" applist=\"ACLNAME\" appact=detected duration=1 sentbyte=2313 rcvdbyte=14883 sentpkt=19 rcvdpkt=19 utmaction=passthrough utmevent=app-ctrl attack=\"SSL\" hostname=\"HOSTNAME\" custom=\"white space\""); + assertThat(event.getFields()) + .containsEntry("date", "2017-03-06") + .containsEntry("time", "12:53:10") + .containsEntry("devname", "DEVICENAME") + .containsEntry("devid", "DEVICEID") + .containsEntry("hostname", "HOSTNAME") + .containsEntry("custom", "white space"); + } + +} \ No newline at end of file