# Guy Bruneau, guybruneau@outlook.com
# Date: 5 March 2021
# Version: 0.5
#
# Date: 31 May 2021
# Version: 1.0
# Added related.ip and related.hosts ECS fields
#
# Date: 30 March 2022
# Version: 1.0
#
# Date: 31 March 2023
# Version: 1.1
# Fixed the date from 2023-03-31 to 3/31/2023
#
# This update Geoip for ECS version 8.x
#

# Based on Elastic Common Schema (ECS) format
#
# This is a Windows 2012r2 DNS log parser
# Log setup: https://nxlog.co/documentation/nxlog-user-guide/windows-dns-server.html
# https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2003/cc776361(v=ws.10)?redirectedfrom=MSDN
# https://nxlog.co/news/disappearing-windows-dns-debug-log
# Log location: C:\DNSlogs\dns\windns.log
# Filename: windns.log

# Format:
# https://nxlog.co/documentation/nxlog-user-guide/windows-dns-server.html


input {
  beats {
    port => 5044
    ssl => "false"
  }
}

filter {

# This example uses the dns.log filename to parse the data
  if [log][file][path] =~ "windns.log" {
    grok {
      match => {
        "message" => [
        "^(?<timestamp>(%{YEAR}-%{MONTHNUM}-%{MONTHDAY} %{HOUR}:%{MINUTE}:%{SECOND}\s+%{WORD:t1}))\s+\S+\s+PACKET\s+\S+\s+%{WORD:network.transport}\s+(?<network.direction>Snd|Rcv)\s+%{IP:dns.resolved_ip}\s+%{WORD:dns.id}\s+((?<dns.type>R)\s+)?%{WORD:dns.op_code}\s+\[\d+\s+((?<dns.header_flags>\S.+)\s+)?%{WORD:dns.response_code}\]\s+%{WORD:dns.question.type}\s+\(\d+\)%{GREEDYDATA:dns.question.name}\(0\)",

# 12/1/2023 3:43:17 PM 0DF0 PACKET  00000173028FB4E0 UDP Rcv 192.168.25.175  d406   Q [0001   D   NOERROR] A      (3)aic(7)lgtviot(3)com(0)

        "(?<datetimestamp>(%{MONTHNUM}/%{MONTHDAY}/%{YEAR} %{HOUR}:%{MINUTE}:%{SECOND}\s+%{WORD:t1}))\s+\S+\s+PACKET\s+\S+\s+%{WORD:network.transport}\s+(?<network.direction>Snd|Rcv)\s+%{IP:dns.resolved_ip}\s+%{WORD:dns.id}\s+((?<dns.type>R)\s+)?%{WORD:dns.op_code}\s+\[\d+\s+((?<dns.header_flags>\S.+)\s+)?%{WORD:dns.response_code}\]\s+%{WORD:dns.question.type}\s+\(\d+\)%{GREEDYDATA:dns.question.name}\(0\)"
        ]
      }
    }
  }
}
# https://www.elastic.co/guide/en/logstash/master/plugins-filters-mutate.html
# Adding several default fields to metatada

filter {
  if [log][file][path] =~ "windns.log" {
    mutate {
      add_field => { "event.category" => "network, network_traffic" }
      add_field => { "event.dataset" => "dns" }
      add_field => { "event.kind" => "event" }
      add_field => { "event.type" => "connection, protocol" }
      add_field => { "host.os.family" => "windows" }
      add_field => { "host.os.name" => "windows" }
      add_field => { "host.os.platform" => "windows" }
      add_field => { "process.name" => "microsoft.dns" }
      add_field => { "network.protocol" => "dns" }
      add_field => { "network.type" => "ipv4" }
      copy => { "dns.resolved_ip" => "related.ip" }
    }
  }
}

# Rename the op_code from its single letter to its name
# Q = Standard Query, N = Notify, U = Update, ? = Unknown
# Blank = Query, R = Response

filter {
  if "Q" in [dns.op_code] {
    mutate {
      gsub => [
        "dns.op_code", "Q", "Standard Query"
      ]
    }
  }
  if "N" in [dns.op_code] {
    mutate {
      gsub => [
        "dns.op_code", "N", "Notify"
      ]
    }
  }
  if "U" in [dns.op_code] {
    mutate {
      gsub => [
        "dns.op_code", "U", "Update"
      ]
    }
  }
  if "\?" in [dns.op_code] {
    mutate {
      gsub => [
        "dns.op_code", "\?", "Unknown"
      ]
    }
  }
  if "R" in [dns.type] {
    mutate {
      gsub => [
        "dns.type", "R", "Response"
      ]
    }
  }
  if [dns.type] != "Response" {
    mutate {
      add_field => { "dns.type" => "Query" }
    }
  }
  mutate {
    gsub => [
      # Remove (.) from the hostname
      "dns.question.name", "\(\d+\)", ".",
      # Remove the spaces from the header flags
      "dns.header_flags", "\s+", ""
    ]
  }
}

# This filter drop internal domain and internal IP range with in-addr.arpa
# This reduce the amount of noise
filter {
  if [log][file][path] =~ "windns.log" {
    if [dns.question.name] =~ /168\.192\.in-addr\.arpa/ {
      drop {}
    }
    if [dns.question.name] =~ /erin\.ca/ {
      drop {}
    }
  }
}

# http://joda-time.sourceforge.net/apidocs/org/joda/time/format/DateTimeFormat.html
# Converting timestamp to accepted format
# "timestamp": "2021-05-31 6:47:07 PM" 
# "datetimestamp": "12/1/2023 3:43:17 PM"

filter {
  date {
    match => ["timestamp", "YYYY-MM-dd KK:mm:ss aa"]
#    timezone => "UTC"
    target => "@timestamp"
  }
}

filter {
  date {
    match => ["timestamp", "MM/dd/YYYY KK:mm:ss aa"]
#    timezone => "UTC"
    target => "@timestamp"
  }
}

# DNS lookup for dns.resolved_ip that start with 192.168.25

filter {
  if [log][file][path] =~ "windns.log" and [dns.resolved_ip] =~ /192\.168\.25/ {
    mutate {
      copy => { "dns.resolved_ip" => "source.registered_domain" }
#      convert   => { "geoip.postal_code" => "string" }
      # copy dns.question.name to related.host
      copy => { "dns.question.name" => "related.hosts" }
    }
    dns {
      reverse => [ "source.registered_domain" ]
      action => "replace"
    }
  }
}


# Cleanup uneeded logs

filter {
  if [log][file][path] =~ "windns.log" {
    mutate {
      # Remove original message
      remove_field => [ "message"]
      remove_field => [ "t1" ]
      remove_tag => [ "beats_input_codec_plain_applied", "_geoip_lookup_failure" ]
      remove_tag => [ "_dateparsefailure", "_grokparsefailure" ]
    }
  }
}

# Add GeoIP information to destination hostname

filter {
  if [log][file][path] =~ "windns.log" {
    geoip {
      source => "source.ip"
      target => "source.geo"
      database => "/etc/logstash/ingest-geoip/GeoLite2-City.mmdb"
    }
  }
}

output {
  if [log][file][path] =~ "windns.log" {
    elasticsearch {
      hosts => [ "https://elastic.domain.ca:9200" ]
      cacert => '/opt/certs/ca/ca.crt'
      user => "elastic"
      password => "elastic"
      index => "microsoft.dns-%{+yyyy.MM.dd}-%{index_num}"
      ilm_rollover_alias => "microsoft.dns"
      ilm_pattern => "{now/d}-000001"
      ilm_policy => "microsoft.dns"
    }
  }
  stdout { codec => rubydebug { metadata => true } }
}

