mirror of
https://github.com/Luzifer/dns.git
synced 2024-12-23 03:11:20 +00:00
Add zone file generator
Signed-off-by: Knut Ahlers <knut@ahlers.me>
This commit is contained in:
parent
fb9e2fc379
commit
c53cfe7330
6 changed files with 180 additions and 18 deletions
|
@ -1,6 +1,9 @@
|
||||||
FROM golang:alpine
|
FROM golang:alpine
|
||||||
|
|
||||||
|
ARG COREDNS_VERSION=v1.0.5
|
||||||
|
|
||||||
ADD ./build.sh /usr/local/bin/build.sh
|
ADD ./build.sh /usr/local/bin/build.sh
|
||||||
|
ADD ./cron_generate.go /src/cron_generate.go
|
||||||
RUN set -ex \
|
RUN set -ex \
|
||||||
&& apk --no-cache add git bash \
|
&& apk --no-cache add git bash \
|
||||||
&& bash /usr/local/bin/build.sh
|
&& 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/
|
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
|
ADD . /src
|
||||||
WORKDIR /src
|
WORKDIR /src
|
||||||
|
|
||||||
|
|
28
build.sh
28
build.sh
|
@ -1,25 +1,17 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
set -euxo pipefail
|
set -euxo pipefail
|
||||||
|
|
||||||
IFS=$'\n'
|
# Download sourcecode
|
||||||
|
mkdir -p /go/src/github.com/coredns
|
||||||
GOPKGS=(
|
git clone https://github.com/coredns/coredns.git /go/src/github.com/coredns/coredns
|
||||||
'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'
|
|
||||||
)
|
|
||||||
|
|
||||||
|
# Ensure version pinning
|
||||||
cd /go/src/github.com/coredns/coredns
|
cd /go/src/github.com/coredns/coredns
|
||||||
for insert in ${PLUGINS[@]}; do
|
git reset --hard ${COREDNS_VERSION}
|
||||||
sed -i "${insert}" plugin.cfg
|
|
||||||
done
|
|
||||||
|
|
||||||
go generate
|
# Copy cron drop-in
|
||||||
|
cp /src/cron_generate.go .
|
||||||
|
|
||||||
|
# Get dependencies and build
|
||||||
|
go get -d -v
|
||||||
go install
|
go install
|
||||||
|
|
42
cron_generate.go
Normal file
42
cron_generate.go
Normal 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
99
generateZonefiles.py
Normal 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
3
requirements.txt
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
jinja2
|
||||||
|
PyYAML
|
||||||
|
dnspython
|
18
zone_template.j2
Normal file
18
zone_template.j2
Normal 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 -%}
|
Loading…
Reference in a new issue