Denis Machard

My technical gists

Infrastructure background, developer mindset. I build things for pleasure.
    @github @mastodon @rss

    Play with Extra DNStap field

    In this blog post, we will explore how to include additional metadata in your DNStap logs, such as security flags, resolver IP addresses, and more. The DNStap protocol offers an extra field specifically designed for this purpose.

    // Extra data for this payload.
    // This field can be used for adding an arbitrary byte-string annotation to
    // the payload. No encoding or interpretation is applied or enforced.
    optional bytes      extra = 3;
    

    Let’s begin by exploring how to implement this feature in CoreDNS.

    CoreDNS

    Support for the extra field was introduced in CoreDNS starting from v1.11.1. Below is an example of how to add the upstream IP (either 8.8.8.8 or 9.9.9.9) to the extra field.

    First, create the coredns.conf file. This configuration sets up your CoreDNS instance to:

    • Listen on port 53 for DNS queries.
    • Enable metadata collection.
    • Send dnstapto the remote collector 10.0.0.100 on port 6000 with full logging.
    • Specify the identity as “coredns” for dnstap data.
    • Include upstream addresses in extra field
    • Cache DNS responses.
    • Forward DNS queries to both 8.8.8.8 and 9.9.9.9 as upstream resolvers.
    .:53 {
    	metadata
            dnstap tcp://10.0.0.100:6000 full {
    		identity coredns
    		extra {/forward/upstream}
            }
    	cache
            forward . 8.8.8.8 9.9.9.9
    }
    

    Next, start CoreDNS using the Docker image:

    sudo docker run -d -p 8053:53/tcp -p 8053:53/udp --name=coredns -v $PWD/coredns.conf:/config.conf coredns/coredns:1.11.1 -conf /config.conf
    

    To retrieve DNS logs with support for the extra field, you can utilize the DNS-collector

    Create the dnscollector.conf file. This configuration sets up DNS collector (dnscollector) to listen on port tcp 6000 for incoming dnstap data and send it to the console logger in a specified text format that includes the timestamp, identity, operation, query IP, extra field, query name (qname), and response code (rcode).

    multiplexer:
      collectors:
        - name: tap
          dnstap:
            listen-ip: 0.0.0.0
            listen-port: 6000
    
      loggers:
        - name: console
          stdout:
            mode: text
            text-format: "timestamp-rfc3339ns identity operation queryip extra qname rcode"
    
      routes:
        - from: [ tap ]
          to: [ console ]  
    

    Now, start the DNS-collector using the Docker image:

    sudo docker run -d -p 6000:6000/tcp --name=dnscollector -v $PWD/dnscollector.conf:/config.conf dmachard/go-dnscollector:v0.36.0 -config /config.conf
    

    Execute the dig command to perform a DNS resolution using your CoreDNS instance:

    dig @127.0.0.1 -p 8053 www.twitter.com
    

    To view the DNScollector logs and observe the extra field, use the following command:

    sudo docker logs dnscollector
    

    You should see logs similar to the following, with the extra field equal to 8.8.8.8:53 and 9.9.9.9:53:

    023-09-20T18:08:17.912209255Z coredns1 FORWARDER_QUERY 172.17.0.1 8.8.8.8:53 www.google.com NOERROR
    2023-09-20T18:08:17.943256625Z coredns1 FORWARDER_RESPONSE 172.17.0.1 8.8.8.8:53 www.google.com NOERROR
    2023-09-20T18:08:17.943348497Z coredns1 CLIENT_RESPONSE 172.17.0.1 8.8.8.8:53 www.google.com NOERROR
    
    2023-09-20T18:08:51.271234557Z coredns1 CLIENT_QUERY 172.17.0.1 - www.twitter.com NOERROR
    2023-09-20T18:08:51.271290121Z coredns1 FORWARDER_QUERY 172.17.0.1 9.9.9.9:53 www.twitter.com NOERROR
    2023-09-20T18:08:51.294051653Z coredns1 FORWARDER_RESPONSE 172.17.0.1 9.9.9.9:53 www.twitter.com NOERROR
    2023-09-20T18:08:51.294120571Z coredns1 CLIENT_RESPONSE 172.17.0.1 9.9.9.9:53 www.twitter.com NOERROR
    

    DNSdist

    Next, continue into DNSdist, a tool that offers greater flexibility in adding custom information to the extra field in dnstap messages. Before getting started, within your working directory, generate certificates and a private key for use with the DNS over HTTPS (DoH) service:

    openssl rand -base64 48 > passphrase.txt
    openssl genrsa -aes128 -passout file:passphrase.txt -out server.key 2048
    openssl req -new -passin file:passphrase.txt -key server.key -out server.csr -subj "/C=FR/O=krkr/OU=Domain Control Validated/CN=*.test.dev"
    openssl rsa -in server.key -passin file:passphrase.txt -out doh.key
    openssl x509 -req -days 36500 -in server.csr -signkey doh.key -out doh.crt
    

    Now, create the config_dnsdist.conf file with the following Lua configuration. This DNSdist configuration listen on DoH service with backends load balancing (google, cloudflare, quad9) The lua functions (alterDnstapQuery, alterDnstapResponse, alterDnstapCachedResponse) are used to customize the extra field in dnstap messages.

    addDOHLocal("0.0.0.0:443", "/etc/dnsdist/doh.crt", "/etc/dnsdist/doh.key", "/dns-query", {keepIncomingHeaders=true})
    setACL({'0.0.0.0/0'})
    
    newServer({address = "1.1.1.1", pool="poolA"})
    newServer({address = "9.9.9.9", pool="poolB"})
    newServer({address = "8.8.8.8", pool="poolB"})
    
    function alterDnstapQuery(dq, tap)
      local ua = ""
      for key,value in pairs(dq:getHTTPHeaders()) do
        if key == 'user-agent' then
                ua = value
                break
        end
      end
      tap:setExtra(ua)
    end
    
    function alterDnstapResponse(dr, tap)
      tap:setExtra(dr.pool)
    end
    
    function alterDnstapCachedResponse(dr, tap)
      tap:setExtra("cached")
    end
    
    -- init dnstap remote collector
    rl = newFrameStreamTcpLogger("192.168.1.17:6000")
    
    -- rules for queries
    addAction(AllRule(), DnstapLogAction("dnsdist1", rl, alterDnstapQuery))
    
    addAction(ProbaRule(0.5), PoolAction("poolA"))
    addAction(AllRule(), PoolAction("poolB"))
    
    -- rules for replies
    addResponseAction(AllRule(), DnstapLogResponseAction("dnsdist1", rl, alterDnstapResponse))
    

    To start DNSdist using a Docker image, use the following command:

    sudo docker run -d -p 8443:443/tcp -p 8083:8080 --name=dnsdist -v $PWD/:/etc/dnsdist/:ro powerdns/dnsdist-18:1.8.1
    

    To test the DNSdist configuration, you can install and use the q client. Then, perform a DoH resolution using your DNSdist server with the following command:

    ./q www.google.fr A  @https://127.0.0.1:8443 --tls-no-verify
    

    To view the DNScollector logs and observe the extra field, use the following command:

    sudo docker logs dnscollector
    

    You should see logs similar to the following, which include the “User-Agent” of the HTTP client or the “pool” used:

    2023-09-20T20:20:39.423635597Z dnsdist1 CLIENT_RESPONSE 172.17.0.1 poolA www.google.fr NOERROR
    2023-09-20T20:20:42.883887269Z dnsdist1 CLIENT_QUERY 172.17.0.1 Go-http-client/1.1 www.google.fr NOERROR
    2023-09-20T20:20:42.899695506Z dnsdist1 CLIENT_RESPONSE 172.17.0.1 poolA www.google.fr NOERROR
    2023-09-20T20:20:43.241895751Z dnsdist1 CLIENT_QUERY 172.17.0.1 Go-http-client/1.1 www.google.fr NOERROR
    2023-09-20T20:20:43.254621667Z dnsdist1 CLIENT_RESPONSE 172.17.0.1 poolA www.google.fr NOERROR
    2023-09-20T20:20:43.611780251Z dnsdist1 CLIENT_QUERY 172.17.0.1 Go-http-client/1.1 www.google.fr NOERROR
    2023-09-20T20:20:43.628321105Z dnsdist1 CLIENT_RESPONSE 172.17.0.1 poolB www.google.fr NOERROR
    
    propulsed by hugo and hugo-theme-gists