Skip to content

Commit e7928dc

Browse files
author
Sehgal, Rohit
committed
First commit; Basic working setup
0 parents  commit e7928dc

File tree

8 files changed

+351
-0
lines changed

8 files changed

+351
-0
lines changed

.vscode/settings.json

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"python.pythonPath": "/Users/rsehgal/Personal/DNSoverTLS/venv/bin/python3.7",
3+
"python.formatting.provider": "autopep8"
4+
}

DNSoverTLS/__init__.py

Whitespace-only changes.

DNSoverTLS/__main__.py

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import logging.config
2+
3+
from DNSoverTLS.dns_handler import DNSUDPServer, DNSTCPServer
4+
5+
config = {
6+
"logger": {
7+
"version": 1,
8+
"disable_existing_loggers": False,
9+
"formatters": {
10+
"simple": {
11+
"format": "%(asctime)s [%(process)s] [%(processName)s] [%(threadName)s] %(levelname)s %(name)s:%(lineno)d - %(message)s",
12+
"datefmt": "%Y-%m-%d %H:%M:%S"
13+
}
14+
},
15+
"handlers": {
16+
"console": {
17+
"class": "logging.StreamHandler",
18+
"formatter": "simple",
19+
"stream": "ext://sys.stdout"
20+
}
21+
},
22+
"loggers": {
23+
"adal-python": {
24+
"level": "DEBUG"
25+
}
26+
},
27+
"root": {
28+
"level": "INFO",
29+
"handlers": [
30+
"console"
31+
]
32+
}
33+
}
34+
}
35+
36+
logging.config.dictConfig(config['logger'])
37+
38+
39+
_log = logging.getLogger(__name__)
40+
41+
42+
def start():
43+
"""
44+
Start the proxy server.
45+
"""
46+
_log.info('Starting proxy server ...')
47+
_log.info('Starting DNS Server ...')
48+
49+
DNSUDPServer().start_thread()
50+
DNSTCPServer().start_thread()
51+
52+
_log.info('Started DNS Server in threaded mode.')
53+
54+
55+
start()

DNSoverTLS/dns_handler.py

+108
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import socketserver
2+
import threading
3+
import logging
4+
import ssl
5+
import socket
6+
import binascii
7+
8+
_log = logging.getLogger(__name__)
9+
10+
11+
class DNSHandler(socketserver.BaseRequestHandler):
12+
13+
def handle(self):
14+
data, connection = self.request
15+
response = self.resolve(data)
16+
17+
_log.info('Received UDP request.')
18+
connection.sendto(bytes(response), self.client_address)
19+
_log.info('Response UDP sent.')
20+
21+
def resolve(self, query, dns_server='1.1.1.1'):
22+
server = (dns_server, 853)
23+
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
24+
sock.settimeout(100)
25+
26+
ctx = ssl.create_default_context()
27+
ctx = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
28+
ctx.verify_mode = ssl.CERT_REQUIRED
29+
ctx.check_hostname = ctx.load_verify_locations(
30+
'/etc/ssl/certs/ca-certificates.crt')
31+
32+
wrapped_socket = ctx.wrap_socket(sock, server_hostname=dns_server)
33+
wrapped_socket.connect(server)
34+
35+
tcp_msg = "\x00".encode() + chr(len(bytes(query))).encode() + query
36+
37+
wrapped_socket.send(tcp_msg)
38+
data = wrapped_socket.recv(1024)
39+
40+
if data:
41+
udp_result = data[2:]
42+
return udp_result
43+
44+
_log.error('Query not supported.')
45+
46+
47+
class DNSTCPHandler(socketserver.BaseRequestHandler):
48+
49+
def handle(self):
50+
data = self.request.recv(1024).strip()
51+
response = self.resolve(data)
52+
53+
_log.info('Received TCP request.')
54+
self.request.sendall(response)
55+
_log.info('Response TCP sent.')
56+
57+
def resolve(self, query, dns_server='1.1.1.1'):
58+
server = (dns_server, 853)
59+
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
60+
sock.settimeout(100)
61+
62+
ctx = ssl.create_default_context()
63+
ctx = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
64+
ctx.verify_mode = ssl.CERT_REQUIRED
65+
ctx.check_hostname = ctx.load_verify_locations(
66+
'/etc/ssl/certs/ca-certificates.crt')
67+
68+
wrapped_socket = ctx.wrap_socket(sock, server_hostname=dns_server)
69+
wrapped_socket.connect(server)
70+
71+
tcp_msg = query
72+
73+
wrapped_socket.send(tcp_msg)
74+
data = wrapped_socket.recv(1024)
75+
76+
if data:
77+
return data
78+
79+
_log.error('Query not supported.')
80+
81+
82+
class DNSUDPServer():
83+
"""A threaded DNS proxy server."""
84+
85+
def __init__(self, host="0.0.0.0", udp_port=8053, tcp=False):
86+
self.host = host
87+
self.port = udp_port
88+
89+
_log.info('Listening UDP request on {}:{}'.format(host, udp_port))
90+
self.server = socketserver.UDPServer((host, udp_port), DNSHandler)
91+
92+
def start_thread(self):
93+
self.thread = threading.Thread(
94+
target=self.server.serve_forever).start()
95+
96+
class DNSTCPServer():
97+
"""A threaded DNS proxy server."""
98+
99+
def __init__(self, host="0.0.0.0", tcp_port=8853, tcp=False):
100+
self.host = host
101+
self.port = tcp_port
102+
103+
_log.info('Listening TCP request on {}:{}'.format(host, tcp_port))
104+
self.server = socketserver.TCPServer((host, tcp_port), DNSTCPHandler)
105+
106+
def start_thread(self):
107+
self.thread = threading.Thread(
108+
target=self.server.serve_forever).start()

Dockerfile

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
FROM python:3.8-slim
2+
3+
COPY DNSoverTLS DNSoverTLS
4+
RUN useradd server
5+
6+
USER server
7+
8+
EXPOSE 8053/udp
9+
EXPOSE 8853
10+
11+
ENTRYPOINT ["python", "-m", "DNSoverTLS"]

Readme.md

+130
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
## DNS-over-TLS proxy
2+
3+
This is very simple and mimimilistic, DNS proxy service capable of running in multi-thread mode, handling multilpe connections in parallel and build using python.
4+
5+
This is packaged with Docker and Dcoker-compose. Docker Compose can help deploy this in docker stack infrastructure or kubernetes.
6+
The module Execution starts from `DNSoverTLS.__main__:start()` method.
7+
8+
9+
```bash
10+
+--------------+ U/T +----------------------+ TCP +-----------------------------+
11+
| + --------> + + --------> + |
12+
| DNS Req | | DNS Proxy | | Cloudflare - TLS - DNS |
13+
| + <-------- + + <-------- + |
14+
+--------------+ U/T +----------------------+ TCP +-----------------------------+
15+
8053(UDP) - 8853(TCP)
16+
17+
```
18+
19+
### How to run:
20+
21+
#### Single service
22+
```bash
23+
# Build Docker
24+
docker build -t dns .
25+
26+
# Run the docker Image
27+
docker run -d -p 8053:8053/udp -p8853:8853 -t dns
28+
29+
# Test the proxy
30+
# Handle TCP connections
31+
dig @127.0.0.1 -p8853 rsehgal.in +tcp
32+
33+
# Handle UDP connections.
34+
dig @127.0.0.1 -p8053 rsehgal.in
35+
36+
; <<>> DiG 9.11.3-1ubuntu1.11-Ubuntu <<>> @127.0.0.1 -p9090 rsehgal.in
37+
; (1 server found)
38+
;; global options: +cmd
39+
;; Got answer:
40+
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 32042
41+
;; flags: qr rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1
42+
43+
;; OPT PSEUDOSECTION:
44+
; EDNS: version: 0, flags:; udp: 1232
45+
; PAD: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 (".........................................................................................................................................................................................................................................................................................................................................................................................................")
46+
;; QUESTION SECTION:
47+
;rsehgal.in. IN A
48+
49+
;; ANSWER SECTION:
50+
rsehgal.in. 20 IN A 206.189.89.118
51+
rsehgal.in. 20 IN A 157.230.35.153
52+
53+
;; Query time: 589 msec
54+
;; SERVER: 127.0.0.1#9090(127.0.0.1)
55+
;; WHEN: Sat Sep 12 08:40:31 UTC 2020
56+
;; MSG SIZE rcvd: 468
57+
```
58+
59+
#### Build and Deploy in cluster
60+
```bash
61+
# Build docker compose
62+
docker-compose build .
63+
64+
# Run compose in docker-swarm mode
65+
# Create master node
66+
docker swarm init
67+
68+
# Join the Swarm, create multinode cluster
69+
docker swarm join --token <SWARMTOKEN>
70+
71+
# Deploy proxy in swarn
72+
docker stack deploy --compose-file docker-compose.yml dns-proxy
73+
74+
# Handle TCP connections
75+
dig @127.0.0.1 -p8853 rsehgal.in +tcp
76+
77+
# Handle UDP connections.
78+
dig @127.0.0.1 -p8053 rsehgal.in
79+
80+
; <<>> DiG 9.11.3-1ubuntu1.11-Ubuntu <<>> @127.0.0.1 -p9090 rsehgal.in
81+
; (1 server found)
82+
;; global options: +cmd
83+
;; Got answer:
84+
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 29786
85+
;; flags: qr rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1
86+
87+
;; OPT PSEUDOSECTION:
88+
; EDNS: version: 0, flags:; udp: 1232
89+
; PAD: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 (".........................................................................................................................................................................................................................................................................................................................................................................................................")
90+
;; QUESTION SECTION:
91+
;rsehgal.in. IN A
92+
93+
;; ANSWER SECTION:
94+
rsehgal.in. 20 IN A 157.230.35.153
95+
rsehgal.in. 20 IN A 157.230.37.202
96+
97+
;; Query time: 522 msec
98+
;; SERVER: 127.0.0.1#9090(127.0.0.1)
99+
;; WHEN: Sat Sep 12 08:51:22 UTC 2020
100+
;; MSG SIZE rcvd: 468
101+
```
102+
103+
### Proxy features
104+
1. Supports TCP and UDP connections with Proxy.
105+
1. Ready to use logging module.
106+
2. Threaded server.
107+
3. Packaged as docker.
108+
4. Packaged as docker-compose.
109+
1. Ready to deploy in docker-swarm cluster, multinode deployment.
110+
5. No third party library used.
111+
6. No dependencies.
112+
7. Currently connects with Cloudflare only, but can me modified to work with any DNS-TLS providers.
113+
7. Server certificate verification using SSL.
114+
115+
116+
### Security consideration
117+
1. Imagine this proxy being deployed in an infrastructure. What would be the security concerns you would raise?
118+
- The infrastructure deployment in current case is packaged with docker-swarn. The default docker-swarm deployment takes care of securing node to node communication. For load balancing, request routing etc.
119+
- Loggin module is plugged in, which can help do detailed loggin with help of python's loggin module. This loggin result can be even sent to SIEM solutions like Splunk. This module is not coded yet, but the way the plugin has been written, this feature can be easily extended.
120+
- Server certificate verification using CA certificates in OS.
121+
122+
2. How would you integrate that solution in a distributed, microservices-oriented and containerized architecture?
123+
- For such solutions, I prefer, docker-swarm, since the application is already packaged as container, deployment is swarm through docker-compose file make it really easy, secure and monitoring.
124+
- Microservices connecting to swarm/
125+
126+
3. What other improvements do you think would be interesting to add to the project?
127+
- Logging to SIEM.
128+
- Handle Zone transfer request.
129+
- There is lot of biolerplate code, that can be removed. Hope fully in next REL ;)
130+
- Scaleable multiprocessing instead of multithreaded, as python suffers from world famous [GIL](https://en.wikipedia.org/wiki/Global_interpreter_lock) issue.

docker-compose.yml

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
version: "3"
2+
3+
services:
4+
proxy-service:
5+
image: dns-proxy
6+
build: ./
7+
networks:
8+
- proxy-network
9+
ports:
10+
- "8053:8053"
11+
- "8853:8853"
12+
13+
networks:
14+
proxy-network:

setup.py

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
"""Setup script."""
2+
import DNSoverTLS
3+
import setuptools
4+
5+
with open("README.md", "r") as fh:
6+
long_description = fh.read()
7+
8+
_version = DNSoverTLS.__version__
9+
_requires = open("requirements.txt").read().splitlines()
10+
11+
setuptools.setup(
12+
name="DockerENT",
13+
version=_version,
14+
description="A simple proxy server to convert DNS Requests to DNS-over-TLS using Cloudflare",
15+
long_description=long_description,
16+
long_description_content_type="text/markdown",
17+
url="",
18+
packages=setuptools.find_packages(),
19+
classifiers=[
20+
"Programming Language :: Python :: 3",
21+
"License :: OSI Approved :: MIT License",
22+
"Operating System :: OS Independent",
23+
],
24+
python_requires=">=3.7",
25+
install_requires=_requires,
26+
entry_points={"console_scripts": {
27+
"DNSoverTLS = DNSoverTLS.__main__:start"}},
28+
keywords="DNS DNS-over-TLS TLS cloudflare",
29+
)

0 commit comments

Comments
 (0)