From: Peter van Dijk Date: Mon, 4 May 2020 15:37:52 +0000 (+0200) Subject: auth/rec/dnsdist: dockerise X-Git-Tag: auth-4.4.0-alpha2~34^2 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=refs%2Fpull%2F9093%2Fhead;p=thirdparty%2Fpdns.git auth/rec/dnsdist: dockerise --- diff --git a/.dockerignore b/.dockerignore index 7a1bfd3327..c194a8a6f0 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,3 +1,5 @@ builder/tmp built_pkgs .git +Dockerfile-* +.dockerignore diff --git a/.gitignore b/.gitignore index 175e7745dc..7407909d45 100644 --- a/.gitignore +++ b/.gitignore @@ -54,3 +54,4 @@ built_pkgs *-shm __pycache__ .circleci/config.yml-local +.env diff --git a/Dockerfile-auth b/Dockerfile-auth new file mode 100644 index 0000000000..ee95298852 --- /dev/null +++ b/Dockerfile-auth @@ -0,0 +1,91 @@ +# our chosen base image +FROM debian:10 AS builder + +# TODO: make sure /source looks roughly the same from git or tar + +# Reusable layer for base update +RUN apt-get update && apt-get -y dist-upgrade && apt-get clean + +# devscripts gives us mk-build-deps (and a lot of other stuff) +RUN apt-get update && apt-get -y dist-upgrade && apt-get install -y --no-install-recommends devscripts equivs && apt-get clean + +# import everything - this could be pdns.git OR an auth tarball! +COPY . /source + +# TODO: control file is not in tarballs at all right now +RUN mk-build-deps -i -t 'apt-get -y -o Debug::pkgProblemResolver=yes --no-install-recommends' /source/builder-support/debian/authoritative/debian-buster/control && \ + apt-get clean + +# build and install (TODO: before we hit this line, rearrange /source structure if we are coming from a tarball) +WORKDIR /source/ + +ARG MAKEFLAGS= +ENV MAKEFLAGS ${MAKEFLAGS:--j2} + +RUN autoreconf -vfi + +# simplify repeated -C calls with SUBDIRS? +RUN mkdir /build && \ + ./configure \ + --with-lua=luajit \ + --sysconfdir=/etc/powerdns \ + --enable-option-checking=fatal \ + --with-dynmodules='bind geoip gmysql godbc gpgsql gsqlite3 ldap lmdb lua2 pipe random remote tinydns' \ + --enable-tools \ + --enable-ixfrdist && \ + make clean && \ + make $MAKEFLAGS -C ext && make $MAKEFLAGS -C modules && make $MAKEFLAGS -C pdns && \ + make -C pdns install DESTDIR=/build && make -C modules install DESTDIR=/build && make clean && \ + strip /build/usr/local/bin/* /build/usr/local/sbin/* +RUN cd /tmp && mkdir /build/tmp/ && mkdir debian && \ + echo 'Source: pdns' > debian/control && \ + dpkg-shlibdeps /build/usr/local/bin/* /build/usr/local/sbin/* /build/usr/local/lib/pdns/*.so && \ + sed 's/^shlibs:Depends=/Depends: /' debian/substvars >> debian/control && \ + equivs-build debian/control && \ + dpkg-deb -I equivs-dummy_1.0_all.deb && cp equivs-dummy_1.0_all.deb /build/tmp/ + +# Runtime +FROM debian:10 + +# Reusable layer for base update - Should be cached from builder +RUN apt-get update && apt-get -y dist-upgrade && apt-get clean + +# Ensure python3 is present (for startup script), and sqlite3 (for db schema), and tini (for signal management) +RUN apt-get install -y python3 sqlite3 tini && apt-get clean + +# Output from builder +COPY --from=builder /build / +RUN chmod 1777 /tmp # FIXME: better not use /build/tmp for equivs at all +RUN setcap 'cap_net_bind_service=+eip' /usr/local/sbin/pdns_server + +# Ensure dependencies are present +RUN apt install -y /tmp/equivs-dummy_1.0_all.deb && apt clean + +# Start script +COPY dockerdata/startup.py /usr/local/sbin/pdns_server-startup + +# Config file(s) from builder +# Should not grab this from builder - since it isn't being built +COPY --from=builder /source/dockerdata/pdns.conf /etc/powerdns/ +RUN mkdir -p /etc/powerdns/pdns.d /var/run/pdns +RUN touch /etc/powerdns-api.conf && chown 953 /etc/powerdns-api.conf +RUN ln -s /etc/powerdns-api.conf /etc/powerdns/pdns.d/api.conf + +# Make database dir before we drop root +RUN mkdir -p /var/lib/powerdns && chown 953 /var/lib/powerdns + +# Work with pdns user - not root +RUN adduser --system --disabled-password --disabled-login --no-create-home --group pdns --uid 953 +RUN chown pdns:pdns /var/run/pdns +USER pdns + +# Set up database - this needs to be smarter +RUN sqlite3 /var/lib/powerdns/pdns.sqlite3 < /usr/local/share/doc/pdns/schema.sqlite3.sql + +# DNS ports +EXPOSE 53/udp +EXPOSE 53/tcp +# webserver port +EXPOSE 8081/tcp + +ENTRYPOINT ["/usr/bin/tini", "--", "/usr/local/sbin/pdns_server-startup"] diff --git a/Dockerfile-dnsdist b/Dockerfile-dnsdist new file mode 100644 index 0000000000..761bc7eab3 --- /dev/null +++ b/Dockerfile-dnsdist @@ -0,0 +1,93 @@ +# our chosen base image +FROM debian:10 AS builder + +# TODO: make sure /source looks roughly the same from git or tar + +# Reusable layer for base update +RUN apt-get update && apt-get -y dist-upgrade && apt-get clean + +# devscripts gives us mk-build-deps (and a lot of other stuff) +RUN apt-get update && apt-get -y dist-upgrade && apt-get install -y --no-install-recommends devscripts equivs && apt-get clean + +# import everything - this could be pdns.git OR a dnsdist tarball! +COPY . /source + +# TODO: control file is not in tarballs at all right now +RUN mk-build-deps -i -t 'apt-get -y -o Debug::pkgProblemResolver=yes --no-install-recommends' /source/builder-support/debian/dnsdist/debian-buster/control && \ + apt-get clean + +# build and install (TODO: before we hit this line, rearrange /source structure if we are coming from a tarball) +WORKDIR /source/pdns/dnsdistdist + +ARG MAKEFLAGS= +ENV MAKEFLAGS ${MAKEFLAGS:--j2} + +RUN touch dnsdist.1 # avoid having to install pandoc and venv + +RUN autoreconf -vfi + +RUN mkdir /build && \ + ./configure \ + --with-lua=luajit \ + LDFLAGS=-rdynamic \ + --sysconfdir=/etc/dnsdist \ + --enable-option-checking=fatal \ + --enable-dnscrypt \ + --enable-dns-over-tls \ + --enable-dns-over-https \ + --with-re2 && \ + make clean && \ + make $MAKEFLAGS install DESTDIR=/build && make clean && \ + strip /build/usr/local/bin/* +RUN cd /tmp && mkdir /build/tmp/ && mkdir debian && \ + echo 'Source: pdns' > debian/control && \ + dpkg-shlibdeps /build/usr/local/bin/dnsdist && \ + sed 's/^shlibs:Depends=/Depends: /' debian/substvars >> debian/control && \ + equivs-build debian/control && \ + dpkg-deb -I equivs-dummy_1.0_all.deb && cp equivs-dummy_1.0_all.deb /build/tmp/ + +# Runtime + +FROM debian:10 + +# Reusable layer for base update - Should be cached from builder +RUN apt-get update && apt-get -y dist-upgrade && apt-get clean + +# Ensure python3 is present (for startup script), and python3-atomicwrites (for backend management), and tini (for signal management) +RUN apt-get install -y python3 python3-atomicwrites tini && apt-get clean + +# Output from builder +COPY --from=builder /build / +RUN chmod 1777 /tmp # FIXME: better not use /build/tmp for equivs at all +RUN setcap 'cap_net_bind_service=+eip' /usr/local/bin/dnsdist + +# Ensure dependencies are present +RUN apt install -y /tmp/equivs-dummy_1.0_all.deb && apt clean + +# Config +RUN mkdir -p /etc/dnsdist/conf.d +RUN touch /etc/dnsdist-api.conf && chown 953 /etc/dnsdist-api.conf +RUN ln -s /etc/dnsdist-api.conf /etc/dnsdist/conf.d/api.conf +COPY --from=builder /source/dockerdata/dnsdist.conf /etc/dnsdist/ + +# Start script +COPY dockerdata/startup.py /usr/local/bin/dnsdist-startup + +# Work with pdns user - not root +RUN adduser --system --disabled-password --disabled-login --no-create-home --group pdns --uid 953 +USER pdns + +# DNS ports +EXPOSE 53/udp +EXPOSE 53/tcp +# console port +EXPOSE 5199/tcp +# webserver port +EXPOSE 8083/tcp + +WORKDIR /etc/dnsdist + +COPY --from=builder /source/dockerdata/dnsdist-resolver.lua /etc/dnsdist/ +COPY --from=builder /source/dockerdata/dnsdist-resolver.py /usr/local/bin/dnsdist-resolver + +ENTRYPOINT ["/usr/bin/tini", "--", "/usr/local/bin/dnsdist-startup"] diff --git a/Dockerfile-recursor b/Dockerfile-recursor new file mode 100644 index 0000000000..cf92515642 --- /dev/null +++ b/Dockerfile-recursor @@ -0,0 +1,97 @@ +# USAGE + +# docker build --build-arg MAKEFLAGS=-j8 -t recursor -f docker/Dockerfile-recursor . +# docker run -p 1053:53 -p 1053:53/udp -ti --rm recursor +# dig a www.example.com @0 -p 1053 + +# Builder +FROM debian:10 AS builder + +# Reusable layer for base update +RUN apt-get update && apt-get -y dist-upgrade && apt-get clean + +# devscripts gives us mk-build-deps (and a lot of other stuff) +RUN apt-get install -y --no-install-recommends devscripts equivs git curl && apt-get clean + +# import everything - this could be pdns.git OR a recursor tarball! +COPY . /source + +# TODO: make sure /source looks roughly the same from git or tar + +# TODO: control file is not in tarballs at all right now +RUN mk-build-deps -i -t 'apt-get -y -o Debug::pkgProblemResolver=yes --no-install-recommends' /source/builder-support/debian/recursor/debian-buster/control && \ + apt-get clean +# RUN apt-get -y install protobuf-compiler && apt-get clean + +# build and install (TODO: before we hit this line, rearrange /source structure if we are coming from a tarball) +WORKDIR /source/pdns/recursordist + +ARG MAKEFLAGS= +ENV MAKEFLAGS ${MAKEFLAGS:--j2} + +# Manpage deps +# RUN apt-get install -y virtualenv && apt-get clean + +# Manpage prevent +RUN touch pdns_recursor.1 rec_control.1 # avoid installing pandoc + +RUN autoreconf -vfi + +RUN mkdir /build && \ + ./configure \ + --with-lua=luajit \ + LDFLAGS=-rdynamic \ + --sysconfdir=/etc/powerdns \ + --enable-option-checking=fatal && \ + make clean && \ + make $MAKEFLAGS install DESTDIR=/build && make clean && \ + strip /build/usr/local/bin/* /build/usr/local/sbin/* +RUN cd /tmp && mkdir /build/tmp/ && mkdir debian && \ + echo 'Source: pdns' > debian/control && \ + dpkg-shlibdeps /build/usr/local/bin/rec_control /build/usr/local/sbin/pdns_recursor && \ + sed 's/^shlibs:Depends=/Depends: /' debian/substvars >> debian/control && \ + equivs-build debian/control && \ + dpkg-deb -I equivs-dummy_1.0_all.deb && cp equivs-dummy_1.0_all.deb /build/tmp/ + +# Runtime +FROM debian:10 + +# Reusable layer for base update - Should be cached from builder +RUN apt-get update && apt-get -y dist-upgrade && apt-get clean + +# Ensure python3 is present (for startup script), and tini for signal management +RUN apt-get install -y python3 tini && apt-get clean + +# Executables from builder +COPY --from=builder /build / +RUN chmod 1777 /tmp # FIXME: better not use /build/tmp for equivs at all +RUN setcap 'cap_net_bind_service=+eip' /usr/local/sbin/pdns_recursor + +# Ensure dependencies are present +RUN apt install -y /tmp/equivs-dummy_1.0_all.deb && apt clean + +# Start script +COPY dockerdata/startup.py /usr/local/sbin/pdns_recursor-startup + +# Config file(s) from builder +# Should not grab this from builder - since it isn't being built +COPY --from=builder /source/dockerdata/recursor.conf /etc/powerdns/ + +# Is recursor.d necessary if we copy the config into recursor.conf? (see above) +RUN mkdir -p /etc/powerdns/recursor.d /var/run/pdns-recursor +RUN touch /etc/powerdns-api.conf && chown 953 /etc/powerdns-api.conf +RUN ln -s /etc/powerdns-api.conf /etc/powerdns/recursor.d/api.conf + +# Work with pdns user - not root +RUN adduser --system --disabled-password --disabled-login --no-create-home --group pdns --uid 953 +RUN chown pdns:pdns /var/run/pdns-recursor +USER pdns + +# DNS ports +EXPOSE 53/udp +EXPOSE 53/tcp + +# webserver port +EXPOSE 8082/tcp + +ENTRYPOINT ["/usr/bin/tini", "--", "/usr/local/sbin/pdns_recursor-startup"] diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000000..d2a4571757 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,39 @@ +--- +version: '2.0' +services: + recursor: + build: + context: . + dockerfile: Dockerfile-recursor + environment: + - PDNS_RECURSOR_API_KEY + ports: + - "2053:53" + - "2053:53/udp" + - "8082:8082" + + dnsdist: + build: + context: . + dockerfile: Dockerfile-dnsdist + environment: + - DNSDIST_API_KEY + links: + - recursor + - auth + ports: + - "3053:53" + - "3053:53/udp" + - "5199:5199" + - "8083:8083" + + auth: + build: + context: . + dockerfile: Dockerfile-auth + environment: + - PDNS_AUTH_API_KEY + ports: + - "1053:53" + - "1053:53/udp" + - "8081:8081" diff --git a/dockerdata/dnsdist-resolver.lua b/dockerdata/dnsdist-resolver.lua new file mode 100644 index 0000000000..d0797d4fe2 --- /dev/null +++ b/dockerdata/dnsdist-resolver.lua @@ -0,0 +1,113 @@ +-- testing oneliner: +-- resolver = require 'dnsdist-resolver' resolver.maintenance() resolver.servers['www.7bits.nl']={pool='blabla'} resolver.maintenance() os.execute('sleep 3') resolver.maintenance() showServers() resolver.servers['www.7bits.nl']=nil resolver.maintenance() os.execute('sleep 3') resolver.maintenance() showServers() + +local _M = {} + +-- these are the servers we want - somebody should populate it +-- example: +-- resolver.servers['ns.example.com'] = { pool='auths', order=3 } +-- do not set name, address, id +_M.servers = {} + +-- these are the servers we have +-- key = name +-- value = {address, serverObject} (should make these named members) +local ourservers = {} + +local resolverpipe = io.popen('/usr/local/bin/dnsdist-resolver', 'w') + +local function tablecopy(t) + local t2 = {} + for k, v in pairs(t) + do + t2[k] = v + end + return t2 +end + +local function removeServer(name) + rmServer(ourservers[name][2]) + ourservers[name] = nil +end + +local function setServer(name, ip) + -- adds a server or changes its IP + local existing = ourservers[name] + if existing ~= nil + then + -- it exists, check IP + infolog(string.format("existing[1] [%s] == ip [%s] ??", existing[1], ip)) + if existing[1] == ip + then + -- IP is correct, done! + return + else + -- IP is wrong, drop and re-add it + removeServer(name) + end + end + + -- it does not exist, let's add it + local settings = tablecopy(_M.servers[name]) + settings.name = name + -- TODO: we only take the first IP + settings.address = ip + ourservers[name] = {ip, newServer(settings)} +end + +function _M.maintenance() + -- TODO: only do this if the list has changed + -- TODO: check return values + for k, v in pairs(_M.servers) + do + resolverpipe:write(k..' ') + end + resolverpipe:write('\n') + resolverpipe:flush() + + -- TODO: maybe this failure should be quiet for the first X seconds? + local ret, resout = pcall(loadfile, '/tmp/dnsdist-resolver.out') + if not ret + then + error(resout) + end + + -- on purpose no pcall, an error here is a bug + resout = resout() + + -- check for servers removed by controller + for name, v in pairs(ourservers) + do + if _M.servers[name] == nil + then + removeServer(name) + end + end + + for name, ips in pairs(resout) + do + infolog("name="..name) + for _, ip in ipairs(ips) + do + infolog(" ip="..ip) + end + + if #ips == 0 + then + -- server has left the building + if ourservers[name] ~= nil + then + removeServer(name) + end + else + -- it has IPs + if _M.servers[name] ~= nil + then + -- we want this server + setServer(name, ips[1]) + end + end + end +end + +return _M diff --git a/dockerdata/dnsdist-resolver.py b/dockerdata/dnsdist-resolver.py new file mode 100755 index 0000000000..7344f5019b --- /dev/null +++ b/dockerdata/dnsdist-resolver.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python3 +import socket +import sys +import threading +import time + +from atomicwrites import atomic_write + +class LookupThread(threading.Thread): + def run(self): + while True: + ips = dict() + for target in self.targets: + addrs = ips.get(target, []) + + try: + res = socket.getaddrinfo(target, 0, proto=socket.IPPROTO_UDP) + addrs = [item[4][0] for item in res] + except socket.gaierror as e: + if e.errno in (socket.EAI_NODATA, socket.EAI_NONAME): + addrs = [] + + ips[target] = addrs + + with atomic_write(self.fname, overwrite=True) as out: + out.write('return {\n') + for name,addrs in ips.items(): + out.write(' ["{}"]='.format(name) + '{\n') + for addr in addrs: + out.write(' "{}",\n'.format(addr)) + out.write(' },\n') + out.write('}\n') + + time.sleep(1) + +if __name__ == '__main__': + lt = LookupThread() + lt.setDaemon(True) + lt.targets = [] + lt.fname = '/tmp/dnsdist-resolver.out' + lt.start() + for line in sys.stdin: + print(line.split()) + lt.targets=line.split() diff --git a/dockerdata/dnsdist.conf b/dockerdata/dnsdist.conf new file mode 100644 index 0000000000..15c85d37eb --- /dev/null +++ b/dockerdata/dnsdist.conf @@ -0,0 +1,15 @@ +setLocal('0.0.0.0') + +-- this example code goes well with the docker-compose.yml file in pdns.git +-- it assumes you create example.com in the auth + +-- resolver = require 'dnsdist-resolver' +-- resolver.servers.auth = {pool='auths'} +-- resolver.servers.recursor = {pool='recursors'} + +-- maintenance = resolver.maintenance + +-- addAction('example.com', PoolAction('auths')) +-- addAction(AllRule(), PoolAction('recursors')) + +includeDirectory('/etc/dnsdist/conf.d') diff --git a/dockerdata/pdns.conf b/dockerdata/pdns.conf new file mode 100644 index 0000000000..ec3dd2bef0 --- /dev/null +++ b/dockerdata/pdns.conf @@ -0,0 +1,5 @@ +local-address=0.0.0.0,:: +launch=gsqlite3 +gsqlite3-dnssec +gsqlite3-database=/var/lib/powerdns/pdns.sqlite3 +include-dir=/etc/powerdns/pdns.d diff --git a/dockerdata/recursor.conf b/dockerdata/recursor.conf new file mode 100644 index 0000000000..efc61ad181 --- /dev/null +++ b/dockerdata/recursor.conf @@ -0,0 +1,2 @@ +local-address=0.0.0.0,:: +include-dir=/etc/powerdns/recursor.d diff --git a/dockerdata/startup.py b/dockerdata/startup.py new file mode 100755 index 0000000000..8f21433aac --- /dev/null +++ b/dockerdata/startup.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python3 +import os +import sys + +program = sys.argv[0].split('-')[0] +product = os.path.basename(program) + +apiconffile = None +apienvvar = None +apiconftemplate = None +args = [] + +if product == 'pdns_recursor': + args = ['--disable-syslog'] + apiconffile = '/etc/powerdns-api.conf' + apienvvar = 'PDNS_RECURSOR_API_KEY' + apiconftemplate = """webserver +api-key={apikey} +webserver-address=0.0.0.0 +webserver-allow-from=0.0.0.0/0 +webserver-password={apikey} +""" +elif product == 'pdns_server': + args = ['--disable-syslog'] + apiconffile = '/etc/powerdns-api.conf' + apienvvar = 'PDNS_AUTH_API_KEY' + apiconftemplate = """webserver +api +api-key={apikey} +webserver-address=0.0.0.0 +webserver-allow-from=0.0.0.0/0 +webserver-password={apikey} +""" +elif product == 'dnsdist': + args = ['--supervised', '--disable-syslog'] + apiconffile = '/etc/dnsdist-api.conf' + apienvvar = 'DNSDIST_API_KEY' + apiconftemplate = """webserver("0.0.0.0:8083", '{apikey}', '{apikey}', {{}}, '0.0.0.0/0') +controlSocket('0.0.0.0:5199') +setKey('{apikey}') +setConsoleACL('0.0.0.0/0') +""" + +apikey = os.getenv(apienvvar) +print("apikey=", apikey) +if apikey is not None: + with open(apiconffile, 'w') as conf: + conf.write(apiconftemplate.format(apikey=apikey)) + +os.execv(program, [program]+args+sys.argv[1:])