dns-tool

The Go binary dns-tool is a proxy server that routes un-encrypted DNS quries over TCP to a TLS DNS server. As such, it handles the encryption of a DNS quries for frontend clients and returns them cleartext answers from a trusted DNS resolver.

Code

Usage

Usage of ./dns-tool:
  -address string
    	address the proxy server listens on
  -port string
    	port the proxy server binds to (default "53")
  -resolver-addr string
    	the trusted dns resolver's ip address and port (default "1.1.1.1:853")
  -resolver-fqdn string
    	the trusted dns resolver's common name (default "cloudflare-dns.com")
  -resolver-pin string
    	the base64 encoded sha256 hash of the trusted dns resolver's tls cert (SPKI)
  -timeout int
    	global read/write deadline for proxy server and tls-server client (default 3)

Build and run Docker image and test container

make build && \
  sudo docker run -d --dns 127.0.0.1 --name dns-tool dnck.github.io/dns-tool

Note that running with –dns will overwrite the container’s /etc/resolv.conf file

Test

sudo docker exec -it dns-tool dig +tcp www.danjcook.com && \
  sudo docker logs dns-tool

Build and run Go binary and test

go build . && \
  ./dns-tool --port 5353

Test

dig +tcp -p 5353 @127.0.0.1 www.danjcook.com

Inspect Encryption

To check on encryption, you can use the command line utilities tcpdump and dig.

First, show the encrypted TCP client/tls-server packets:

CLIENT_ADDRESS="dnck.fritz.box"
  TLS_SERVER_ADDRESS="1.1.1.1"
    sudo tcpdump -X host "$HOST_ADDRESS" and "$TLS_SERVER_ADDRESS"

Next, open a new terminal and send a DNS query to the our dns-tool frontend server:

DNS_TOOL_ADDRESS="127.0.0.1"
  DNS_TOOL_PORT="5353"
    dig +timeout=10 +tcp -p "$DNS_TOOL_PORT" @"$DNS_TOOL_ADDRESS" www.danjcook.com

Questions

Imagine this proxy being deployed in an infrastructure. What would be the security concerns you would raise?

Bootstrap Problem The DNS Client in the current design of the TCP Proxy Server can be provided with the sha256 hash of the TLS-server’s SPKI (its “pin”) at program start, along with the TLS-Server’s IP address and its common name on the certificate. These values are used to confirm the TLS-Server’s identity on each connection. As such, they provide an extra layer of security. However, the program itself can be provided with an untrusted pin at start, which would clearly have implications for its security. For this reason, it is best to follow a Principle of Least Privilege approach on the deployment of the proxy.

Denial-of-service Under the assumption that the frontend clients are trusted, the current design dispatches all requests to a remote TLS DNS Server without validating the requests, rate-limiting, or caching.

On the one hand,this is a nice feature since it reduces the latency of the response. However, if the frontend clients are not trusted, or are prone to misbehavior, then this “nice” feature can result in the remote TLS DNS Server limiting the rate or dns queries or entirely banning the dns client address.

Outside of control Clearly, there are things which are not within the control of the proxy, but nonetheless, there are some valid concerns such as DNS Cache poisoning in which an attacker impersonates an authoritative nameserver and taints the cache of the DNS resolver.

How would you integrate that solution in a distributed, microservices-oriented and containerized architecture?

Sidecars & CoreDNS If the infrastructure is already using a service-mesh such as istio, then deploying the current proxy alongside the existing containers could come at minimal cost. The idea would be to use the caching already built into the istio dns proxy.

However, this alone would be insufficient since each of the pod/containers would need to modify their /etc/resolv.conf file to point to the proxy, which would be burdensome for developers. For this reason, we could also modify the Kubernetes CoreDNS configuration to forward all queries for domains outside of the cluster to a predefined nameserver (e.g. the sidecar proxy).

https://kubernetes.io/docs/tasks/administer-cluster/dns-custom-nameservers/

However, before this is done, the current design should add a feature to serve UDP DNS requests to frontend clients, which is a current limitation.

What other improvements do you think would be interesting to add to the project?

Add Unit-tests and an end-to-end test

Pin expiration The current design stores the pin of the TLS Server at start. If the pin is non-maliciously changed (due to certificate rotation), then requests will no longer be served. It would be interesting to think about ways to solve the aforementioned “bootstrap problem”.

https://www.ietf.org/proceedings/82/slides/websec-1.pdf

Caching As already mentioned, the proxy does not interpret the contents of the queries nor store answers from the nameserver. Caching the most queried records could reduce latency for frontend clients.

Monitoring/statistics The current design is devoid of metrics collection without which it is difficult to determine whether it is meeting any of its SLOs.