]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
auth/rec/dnsdist: dockerise 9093/head
authorPeter van Dijk <peter.van.dijk@powerdns.com>
Mon, 4 May 2020 15:37:52 +0000 (17:37 +0200)
committerPeter van Dijk <peter.van.dijk@powerdns.com>
Fri, 16 Oct 2020 14:37:55 +0000 (16:37 +0200)
12 files changed:
.dockerignore
.gitignore
Dockerfile-auth [new file with mode: 0644]
Dockerfile-dnsdist [new file with mode: 0644]
Dockerfile-recursor [new file with mode: 0644]
docker-compose.yml [new file with mode: 0644]
dockerdata/dnsdist-resolver.lua [new file with mode: 0644]
dockerdata/dnsdist-resolver.py [new file with mode: 0755]
dockerdata/dnsdist.conf [new file with mode: 0644]
dockerdata/pdns.conf [new file with mode: 0644]
dockerdata/recursor.conf [new file with mode: 0644]
dockerdata/startup.py [new file with mode: 0755]

index 7a1bfd33270e43e2eff2bfac26b8d1c292752193..c194a8a6f0c53441e0b565ae45ff03edfd70009a 100644 (file)
@@ -1,3 +1,5 @@
 builder/tmp
 built_pkgs
 .git
+Dockerfile-*
+.dockerignore
index 175e7745dc47b8d540e799110c9a5357ba7ca445..7407909d458b4f49dbf0b26f16038af7c39c7659 100644 (file)
@@ -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 (file)
index 0000000..ee95298
--- /dev/null
@@ -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 (file)
index 0000000..761bc7e
--- /dev/null
@@ -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 (file)
index 0000000..cf92515
--- /dev/null
@@ -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 (file)
index 0000000..d2a4571
--- /dev/null
@@ -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 (file)
index 0000000..d0797d4
--- /dev/null
@@ -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 (executable)
index 0000000..7344f50
--- /dev/null
@@ -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 (file)
index 0000000..15c85d3
--- /dev/null
@@ -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 (file)
index 0000000..ec3dd2b
--- /dev/null
@@ -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 (file)
index 0000000..efc61ad
--- /dev/null
@@ -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 (executable)
index 0000000..8f21433
--- /dev/null
@@ -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:])