1
0
Fork 0
mirror of https://github.com/Luzifer/dns.git synced 2024-12-22 19:01:20 +00:00

Add zone file generator

Signed-off-by: Knut Ahlers <knut@ahlers.me>
This commit is contained in:
Knut Ahlers 2018-02-05 07:03:08 +01:00
parent fb9e2fc379
commit c53cfe7330
Signed by: luzifer
GPG key ID: DC2729FDD34BE99E
6 changed files with 180 additions and 18 deletions

View file

@ -1,6 +1,9 @@
FROM golang:alpine
ARG COREDNS_VERSION=v1.0.5
ADD ./build.sh /usr/local/bin/build.sh
ADD ./cron_generate.go /src/cron_generate.go
RUN set -ex \
&& apk --no-cache add git bash \
&& bash /usr/local/bin/build.sh
@ -11,6 +14,11 @@ LABEL maintainer Knut Ahlers <knut@ahlers.me>
COPY --from=0 /go/bin/coredns /usr/local/bin/
ADD ./requirements.txt /src/requirements.txt
RUN set -ex \
&& apk --no-cache add python3 \
&& pip3 install -r /src/requirements.txt
ADD . /src
WORKDIR /src

View file

@ -1,25 +1,17 @@
#!/bin/bash
set -euxo pipefail
IFS=$'\n'
GOPKGS=(
'github.com/coredns/coredns'
'github.com/Luzifer/alias'
)
for pkg in ${GOPKGS[@]}; do
go get -d -v "${pkg}"
done
PLUGINS=(
'/^file:file/ i alias:github.com/Luzifer/alias'
)
# Download sourcecode
mkdir -p /go/src/github.com/coredns
git clone https://github.com/coredns/coredns.git /go/src/github.com/coredns/coredns
# Ensure version pinning
cd /go/src/github.com/coredns/coredns
for insert in ${PLUGINS[@]}; do
sed -i "${insert}" plugin.cfg
done
git reset --hard ${COREDNS_VERSION}
go generate
# Copy cron drop-in
cp /src/cron_generate.go .
# Get dependencies and build
go get -d -v
go install

42
cron_generate.go Normal file
View file

@ -0,0 +1,42 @@
package main
import (
"context"
"os/exec"
"time"
"github.com/Sirupsen/logrus"
"github.com/robfig/cron"
)
func init() {
c := cron.New()
c.AddFunc("0 * * * * *", generateZonefiles)
c.Start()
}
func generateZonefiles() {
logger := logrus.WithFields(logrus.Fields{
"fkt": "generateZonefiles",
})
var (
iw = logger.WriterLevel(logrus.InfoLevel)
ew = logger.WriterLevel(logrus.ErrorLevel)
)
defer iw.Close()
defer ew.Close()
ctx, cancel := context.WithTimeout(context.Background(), 59*time.Second)
defer cancel()
cmd := exec.CommandContext(ctx, "/usr/bin/python", "generateZonefiles.py")
cmd.Stdout = iw
cmd.Stderr = ew
cmd.Dir = "/src"
if err := cmd.Run(); err != nil {
logger.WithError(err).Error("Command execution failed")
}
}

99
generateZonefiles.py Normal file
View file

@ -0,0 +1,99 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os
# Third-party imports
import dns.resolver
import dns.rdatatype
import jinja2
import yaml
DEFAULT_TTL = 3600
def default(d, key, default=None):
if key in d:
return d[key]
return default
def resolve_alias(entry):
result = []
answers = []
answers.extend(dns.resolver.query(
entry['alias'], 'A', raise_on_no_answer=False))
answers.extend(dns.resolver.query(
entry['alias'], 'AAAA', raise_on_no_answer=False))
if len(answers) == 0:
raise Exception(
"Alias {} was not resolvable: No answers!".format(entry['alias']))
for rdata in answers:
new_entry = entry.copy()
del new_entry['alias']
new_entry['type'] = dns.rdatatype.to_text(rdata.rdtype)
new_entry['data'] = rdata.address
result.append(new_entry)
return result
def sanitize(entry):
result = []
if entry['name'] == '':
entry['name'] = '@'
if 'alias' in entry:
return resolve_alias(entry)
for rr in entry['records']:
new_entry = entry.copy()
del new_entry['records']
new_entry['data'] = rr
if new_entry['type'] == 'TXT' and new_entry['data'][0] != '"':
new_entry['data'] = '"{}"'.format(new_entry['data'])
result.append(new_entry)
return result
def write_zone(zone, ttl, soa, nameserver, mailserver, entries):
tpl = jinja2.Template(open("zone_template.j2").read())
zone_content = tpl.render({
"zone": zone,
"ttl": ttl,
"soa": soa,
"nameserver": nameserver,
"mailserver": mailserver,
"entries": entries,
})
with open("zones/tmp.{}".format(zone), 'w') as zf:
zf.write(zone_content)
# FIXME (kahlers): Check if contents changed
os.rename("zones/tmp.{}".format(zone), "zones/db.{}".format(zone))
def main():
zone_data = yaml.load(open("zones.yml"))
for zone, config in zone_data['zones'].items():
ttl = default(config, "default_ttl", DEFAULT_TTL)
entries = []
for entry in config['entries']:
entries.extend(sanitize(entry))
write_zone(zone, ttl, zone_data['soa'],
zone_data['nameserver'], default(config, 'mailserver', {}), entries)
if __name__ == "__main__":
main()

3
requirements.txt Normal file
View file

@ -0,0 +1,3 @@
jinja2
PyYAML
dnspython

18
zone_template.j2 Normal file
View file

@ -0,0 +1,18 @@
; Auto-generated using generateZonefiles.py
$ORIGIN {{ zone }}.
$TTL {{ ttl }}
{{ zone }}. {{ ttl }} IN SOA {{ soa.auth_ns }} {{ soa.contact }} {{ soa.serial }} {{ soa.refresh }} {{ soa.retry }} {{ soa.expire }} {{ soa.ttl }}
{% for ns in nameserver -%}
{{ zone }}. {{ ttl }} IN NS {{ ns }}
{% endfor %}
{%- if mailserver | length > 0 %}
{% for mailserver, weight in mailserver.items() -%}
{{ zone }}. {{ ttl }} IN MX {{ weight }} {{ mailserver }}
{% endfor -%}
{%- endif %}
{% for entry in entries -%}
{{ entry.name }} {{ entry.ttl | default(ttl) }} {{ entry.class | default('IN') }} {{ entry.type }} {% if entry.weight %}{{ entry.weight }} {% endif %}{{ entry.data }}
{% endfor -%}