Monitor Edgerouter DNS With Elastic Stack

I use an Edgerouter 4 at home. In addition to routing, it also provides DNS and DHCP for all of my VLANs. Over the past few days, I’ve investigated different ways to monitor DNS queries on my network without deploying some kind of monitoring agent to each of my systems.

I started by trying to cross-compile packetbeat for the Edgerouter’s CPU architecture, mips64. In the end, I abandoned this idea because of the long compilation times and tedious troubleshooting process.

My Edgerouter forwards its logs to a syslog server on my network. After configuring dnsmasq to log queries, I was pleased to find that the logs for those queries appeared in the forwarded logs on my syslog server. I set out to write some filebeat processors for the forwarded logs. The filebeat configuration I ended up with is below:

# ============================== Filebeat modules ==============================

filebeat.config.modules:
  # Glob pattern for configuration loading
  path: ${path.config}/modules.d/*.yml

  # Set to true to enable config reloading
  reload.enabled: true

  # Period on which files under path should be checked for changes
  reload.period: 10s

# ======================= Elasticsearch template setting =======================

setup.template.name: "dns-filebeat"
setup.template.pattern: "dns-filebeat-*"
setup.template.overwrite: true
setup.template.settings:
  index.number_of_shards: 1
  #index.codec: best_compression
  #_source.enabled: false


# ================================== General ===================================

# The name of the shipper that publishes the network data. It can be used to group
# all the transactions sent by a single shipper in the web interface.
name: filebeat-dns

# The tags of the shipper are included in their own field with each
# transaction published.
#tags: ["service-X", "web-tier"]

# Optional fields that you can specify to add additional information to the
# output.
#fields:
#  env: staging

# ================================== Outputs ===================================

# ---------------------------- Elasticsearch Output ----------------------------
output.elasticsearch:
  # Array of hosts to connect to.
  hosts: ["elasticsearch:443"]

  # Index
  index: "dns-filebeat-%{[agent.version]}-%{+yyyy.MM.dd}" 

  # Protocol - either `http` (default) or `https`.
  protocol: "https"

  # Authentication credentials - either API key or username/password.
  #api_key: "id:api_key"
  username: "beats"
  password: "PasswordGoesHere"
  ssl.verification_mode: none

# ================================= Processors =================================

processors:
  - drop_event:
        when:
          not:
            contains:
              message: " dnsmasq["
  - if:
      contains:
        message: " dnsmasq["
    then: 
      - add_fields:
          target: dns
          fields:
            type: "query"
  - dissect:
      when:
        equals:
          dns.type: "query"
      tokenizer: "%{junk1} dnsmasq[%{process.id}]: %{dns.op_code} %{dns.question.name} %{junk2} %{client.address}"
      field: "message"
      target_prefix: ""
  - if:
      or:
        - equals:
            dns.op_code: "config"
        - equals:
            dns.op_code: "reply"
        - equals:
            dns.op_code: "/etc/hosts"
        - equals:
            dns.op_code: "cached"
    then:
      - add_fields:
          target: dns
          fields: 
            type: "answer"
      - rename:
          fields:
            - from: "client.address"
              to: "dns.answers.data"
          ignore_missing: true
      - copy_fields:
          fields:
            - from: "dns.question.name"
              to: "dns.answers.name"
          ignore_missing: true
  - if:
      not:
        or: 
          - equals:
              dns.answers.data: "<CNAME>"
          - equals:
              dns.answers.data: "NODATA-IPv6"
          - equals:
              dns.answers.data: "NXDOMAIN"
    then:
      - copy_fields:
          fields:
            - from: "dns.answers.data"
              to: "dns.resolved_ip"
          ignore_missing: true 
  - if: 
      equals:
        dns.op_code: "forwarded"
    then: 
      - rename:
          fields:
          - from: "client.address"
            to: "server.address"
          ignore_missing: true
  - drop_fields:
      fields: ["junk1","junk2"]
      ignore_missing: true 
  - registered_domain:
      field: dns.question.name
      target_field: dns.question.registered_domain
      ignore_failure: true
      ignore_missing: true

# ================================= Other Stuff =================================

# for setting up index templates and such on Opendistro
setup.ilm.enabled: false
setup.ilm.check_exists: false

I wrote the processors with the intention of adhering to the Elastic Common Schema as much as possible. Now, I have all my DNS logs parsed and visualized in Kibana:

dns dashboard

I keep a copy of this config in a gitlab snippet. I will keep it up-to-date as I experiment further with it.