Skip to content

Commit

Permalink
Removing BIND9.
Browse files Browse the repository at this point in the history
  • Loading branch information
felixfontein authored and mattclay committed Jul 23, 2018
1 parent e3ccfd7 commit b2c0f1a
Show file tree
Hide file tree
Showing 9 changed files with 93 additions and 102 deletions.
1 change: 1 addition & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
!requirements.txt
!run.sh
!controller.py
!dns_server.py
!acme_tlsalpn.py
!README.md
!LICENSE
Expand Down
11 changes: 2 additions & 9 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,16 @@ RUN go get -u github.com/letsencrypt/pebble/... && \
git checkout ${PEBBLE_CHECKOUT} && \
go install ./...

FROM debian:stretch-slim
FROM python:3.6-slim-stretch
# Install software
RUN apt-get update \
&& apt-get install -y bind9 python3 python3-pip \
&& apt-get clean all \
&& rm -rf /var/lib/apt/lists/*;
ADD requirements.txt /root/
RUN pip3 install -r /root/requirements.txt
# Setup bind9
ADD bind.conf /etc/bind/named.conf
RUN mkdir /etc/bind/zones
# Install pebble
COPY --from=builder /go/bin /go/bin
COPY --from=builder /go/pkg /go/pkg
COPY --from=builder /go/src/github.com/letsencrypt/pebble/test /go/src/github.com/letsencrypt/pebble/test
ADD pebble-config.json /go/src/github.com/letsencrypt/pebble/test/config/pebble-config.json
# Setup controller.py and run.sh
ADD run.sh controller.py acme_tlsalpn.py LICENSE LICENSE-acme README.md /root/
ADD run.sh controller.py dns_server.py acme_tlsalpn.py LICENSE LICENSE-acme README.md /root/
EXPOSE 5000 14000
CMD [ "/bin/sh", "-c", "/root/run.sh" ]
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

A container for integration testing ACME protocol modules.

Uses [Pebble](https://github.com/letsencrypt/Pebble) and [BIND 9](https://www.isc.org/downloads/bind/).
Uses [Pebble](https://github.com/letsencrypt/Pebble).

## Usage

Expand Down
1 change: 1 addition & 0 deletions acme_tlsalpn.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@ def update(self):
self.log_callback('Launching TLS ALPN challenge server...')
self.server = TLSALPN01Server(("", self.port), certs=self.certs, challenge_certs=self.challenge_certs, log_callback=self.log_callback)
self.thread = threading.Thread(target=self.server.serve_forever)
self.thread.daemon = True
self.thread.start()


Expand Down
18 changes: 0 additions & 18 deletions bind.conf

This file was deleted.

83 changes: 11 additions & 72 deletions controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,27 +22,26 @@
import codecs
import logging
import os
import subprocess
import sys

from functools import partial

from flask import Flask
from flask import request

from acme_tlsalpn import ALPNChallengeServer, gen_ss_cert
from OpenSSL import crypto

from dns_server import DNSServer


app = Flask(__name__)
app.config['LOGGER_HANDLER_POLICY'] = 'always'

ZONES_PATH = os.path.abspath(os.environ.get('ZONES_PATH', '.'))

PEBBLE_PATH = os.path.join(os.path.abspath(os.environ.get('GOPATH', '.')), 'src', 'github.com', 'letsencrypt', 'pebble')


zones = set(['example.com', 'example.org'])
challenges = {}
txt_records = {}


def log(message, data=None, program='Controller'):
Expand Down Expand Up @@ -83,45 +82,6 @@ def emit(self, record):
setup_loggers()


def execute(what, command):
try:
p = subprocess.Popen(
command,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
(so, se) = p.communicate()
data = []
if so:
data.append('*** STDOUT:')
data.extend(so.decode('utf8').split('\n'))
if se:
data.append('*** STDERR:')
data.extend(se.decode('utf8').split('\n'))
log(what, data)
except Exception as e:
log('FAILED: {0}'.format(what), e)


def update_zone(zone, restart=True):
result = R"""$TTL 1
@ IN SOA {0}. localhost. (1 1 1 1 1)
@ IN NS localhost.
@ IN A 127.0.0.1
@ IN AAAA ::1
* IN A 127.0.0.1
* IN AAAA ::1
""".format(zone)
for record, values in txt_records.get(zone, {}).items():
for value in values:
result += '{0} IN TXT {1}\n'.format(record if record else '@', value)
log('Updating zone {0}'.format(zone), result.split('\n'))
with open(os.path.join(ZONES_PATH, zone), "wb") as f:
f.write(result.encode('utf-8'))
if restart:
execute('Restarting BIND', ['service', 'bind9', 'restart'])


@app.route('/')
def m_index():
return 'ACME test environment controller'
Expand All @@ -144,32 +104,18 @@ def http_challenge(host, filename):
return 'ok'


dns_server = DNSServer(port=53, log_callback=partial(log, program='DNS Server'))


@app.route('/dns/<string:record>', methods=['PUT', 'DELETE'])
def dns_challenge(record):
i = record.rfind('.')
j = record.rfind('.', 0, i - 1)
if i >= 0 and j >= 0:
zone = record[j + 1:]
record = record[:j]
elif i >= 0:
zone = record
record = ''
else:
return 'cannot parse record', 400
if zone not in zones:
return 'unknown zone "{0}"; must be one of {1}'.format(zone, ', '.join(zones)), 404
if request.method == 'PUT':
if zone not in txt_records:
txt_records[zone] = {}
values = request.get_json(force=True)
log('Adding TXT records for zone {0}, record {1}'.format(zone, record), values)
txt_records[zone][record] = values
log('Adding TXT records for {0}'.format(record), values)
dns_server.set_txt_records(record, values)
else:
if zone not in txt_records or record not in txt_records[zone]:
return 'not found', 404
log('Removing TXT records for zone {0}, record {1}'.format(zone, record))
del txt_records[zone][record]
update_zone(zone)
log('Removing TXT records for {0}'.format(record))
dns_server.clear_txt_records(record)
return 'ok'


Expand Down Expand Up @@ -219,12 +165,5 @@ def get_root_certificate():
return f.read()


def setup_zones():
for zone in zones:
update_zone(zone, restart=False)
execute('Starting BIND', ['service', 'bind9', 'start'])


if __name__ == "__main__":
setup_zones()
app.run(debug=False, host='0.0.0.0', port=int(os.environ.get('CONTROLLER_PORT', 5000)))
75 changes: 75 additions & 0 deletions dns_server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# -*- coding: utf-8 -*-

from dnslib import A, TXT, QTYPE, RCODE, server


class DNSLogger(object):
def __init__(self, log_callback):
self.log_callback = log_callback

def log_pass(self, *args):
pass

def log_recv(self, handler, data):
pass

def log_send(self, handler, data):
pass

def log_request(self, handler, request):
self.log_callback("DNS Request: [{0}:{1}] ({2}) <{3}> : {4}".format(handler.client_address[0], handler.client_address[1], handler.protocol, request.q.qname, QTYPE[request.q.qtype]), data=str(request.toZone("")).split('\n'))

def log_reply(self, handler, reply):
if reply.header.rcode == RCODE.NOERROR:
self.log_callback("DNS Reply: [{0}:{1}] ({2}) / '{3}' ({4}) / RRs: {5}".format(handler.client_address[0], handler.client_address[1], handler.protocol, reply.q.qname, QTYPE[reply.q.qtype], ",".join([QTYPE[a.rtype] for a in reply.rr])), data=str(reply.toZone("")).split('\n'))
else:
self.log_callback("DNS Reply: [{0}:{1}] ({2}) / '{3}' ({4}) / {5}".format(handler.client_address[0], handler.client_address[1], handler.protocol, reply.q.qname, QTYPE[reply.q.qtype], RCODE[reply.header.rcode]), data=str(reply.toZone("")).split('\n'))

def log_truncated(self, handler, reply):
self.log_callback("DNS Truncated Reply: [{0}:{1}] ({2}) / '{3}' ({4}) / RRs: {5}".format(handler.client_address[0], handler.client_address[1], handler.protocol, reply.q.qname, QTYPE[reply.q.qtype], ",".join([QTYPE[a.rtype] for a in reply.rr])), data=str(reply.toZone("")).split('\n'))

def log_error(self, handler, e):
self.log_callback("DNS Invalid Request: [{0}:{1}] ({2}) :: {3}".format(handler.client_address[0], handler.client_address[1], handler.protocol, e))


class DNSServer(object):
def resolve(self, request, handler):
reply = request.reply()
if request.q.qtype == QTYPE.ANY or request.q.qtype == QTYPE.A:
reply.add_answer(server.RR(rname=request.q.qname, rtype=QTYPE.A, rdata=A("127.0.0.1"), ttl=10))
if request.q.qtype == QTYPE.ANY or request.q.qtype == QTYPE.TXT:
records = self.txt_records.get(str(request.q.qname), [])
for record in records:
reply.add_answer(server.RR(rname=request.q.qname, rtype=QTYPE.TXT, rdata=TXT(record), ttl=10))
return reply

def __init__(self, port, log_callback=None):
if log_callback is None:
def f(msg, data=None):
print(msg)
if data is not None:
print(data)

log_callback = f

self.txt_records = {}
self.log_callback = log_callback
self.port = port
self.logger = DNSLogger(self.log_callback)
self.servers = [
server.DNSServer(self, address="localhost", port=self.port, tcp=False, logger=self.logger),
server.DNSServer(self, address="localhost", port=self.port, tcp=True, logger=self.logger),
]
for ds in self.servers:
ds.start_thread()

def _cleanup(self, zone):
if not zone.endswith('.'):
zone = zone + '.'
return zone

def set_txt_records(self, zone, values):
self.txt_records[self._cleanup(zone)] = values

def clear_txt_records(self, zone):
self.txt_records[self._cleanup(zone)] = []
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
Flask==1.0.2
Werkzeug==0.14.1
pyOpenSSL==18.0.0
dnslib==0.9.7
3 changes: 1 addition & 2 deletions run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ set -e
echo nameserver 127.0.0.1 > /etc/resolv.conf
# Start controller in background
export CONTROLLER_PORT=5000
export ZONES_PATH=/etc/bind/zones
/usr/bin/python3 /root/controller.py &
/usr/local/bin/python /root/controller.py &
# Make Pebble sleep at most 5 seconds between auth checks (default is 15 seconds)
export PEBBLE_VA_SLEEPTIME=5
# Start Pebble
Expand Down

0 comments on commit b2c0f1a

Please sign in to comment.