PowerDNS instead of bind9 #2

Merged
kunsi merged 23 commits from kunsi-feature-powerdns-instead-of-bind into main 2020-10-17 11:12:35 +00:00
35 changed files with 739 additions and 330 deletions

1
.gitignore vendored
View file

@ -1 +1,2 @@
.secrets.cfg
__pycache__

View file

@ -67,10 +67,12 @@ pkg_apt = {
'arping': {},
'at': {},
'build-essential': {},
'bzip2': {},
'curl': {},
'diffutils': {},
'dnsutils': {},
'git': {},
'grep': {},
'gzip': {},
'htop': {},
@ -88,6 +90,7 @@ pkg_apt = {
'netcat': {},
'nmap': {},
'python3': {},
'python3-dev': {},
'python3-pip': {},
'python3-virtualenv': {},
'tar': {},

View file

@ -1,6 +0,0 @@
% for key in keys:
key ${key['name']} {
algorithm ${key['algorithm']};
secret "${key['secret']}";
};
% endfor

View file

@ -1,30 +0,0 @@
include "/etc/bind/keys.conf";
% for zone in sorted(primary_zones):
zone "${zone}" IN {
type master;
file "/var/lib/bind/primary/${zone}";
};
% endfor
zone "10.in-addr.arpa" { type master; file "/etc/bind/db.empty"; };
zone "16.172.in-addr.arpa" { type master; file "/etc/bind/db.empty"; };
zone "17.172.in-addr.arpa" { type master; file "/etc/bind/db.empty"; };
zone "18.172.in-addr.arpa" { type master; file "/etc/bind/db.empty"; };
zone "19.172.in-addr.arpa" { type master; file "/etc/bind/db.empty"; };
zone "20.172.in-addr.arpa" { type master; file "/etc/bind/db.empty"; };
zone "21.172.in-addr.arpa" { type master; file "/etc/bind/db.empty"; };
zone "22.172.in-addr.arpa" { type master; file "/etc/bind/db.empty"; };
zone "23.172.in-addr.arpa" { type master; file "/etc/bind/db.empty"; };
zone "24.172.in-addr.arpa" { type master; file "/etc/bind/db.empty"; };
zone "25.172.in-addr.arpa" { type master; file "/etc/bind/db.empty"; };
zone "26.172.in-addr.arpa" { type master; file "/etc/bind/db.empty"; };
zone "27.172.in-addr.arpa" { type master; file "/etc/bind/db.empty"; };
zone "28.172.in-addr.arpa" { type master; file "/etc/bind/db.empty"; };
zone "29.172.in-addr.arpa" { type master; file "/etc/bind/db.empty"; };
zone "30.172.in-addr.arpa" { type master; file "/etc/bind/db.empty"; };
zone "31.172.in-addr.arpa" { type master; file "/etc/bind/db.empty"; };
zone "168.192.in-addr.arpa" { type master; file "/etc/bind/db.empty"; };

View file

@ -1,3 +0,0 @@
% for o in node.metadata.get('bind', {}).get('options', []):
<%include file="options/${o}"/>
% endfor

View file

@ -1,146 +0,0 @@
from os import listdir
from os.path import isfile, join
from datetime import datetime
from subprocess import check_output
ZONE_HEADER = """
; _ ____ _ _ _____ _ _ _ _ ____
; / \\ / ___| | | |_ _| | | | \\ | |/ ___|
; / _ \\| | | |_| | | | | | | | \\| | | _
; / ___ \\ |___| _ | | | | |_| | |\\ | |_| |
; /_/ \\_\\____|_| |_| |_| \\___/|_| \\_|\\____|
;
; --> Diese Datei wird von BundleWrap verwaltet! <--
$TTL 60
@ IN SOA ns-1.kunbox.net. hostmaster.kunbox.net. (
{serial}
3600
3600
86400
300
)
@ IN NS bind01.gce.kunbox.net.
IN NS b.ns14.net.
IN NS c.ns14.net.
IN NS d.ns14.net.
"""
svc_systemd = {
'bind9': {
'needs': {
'pkg_apt:bind9',
},
},
}
pkg_apt = {
'bind9': {},
}
directories = {
"/var/lib/bind/primary": {
'group': 'bind',
'needs': {
'pkg_apt:bind9',
},
'owner': 'bind',
'purge': True,
},
"/var/log/named": {
'group': 'bind',
'needs': {
'pkg_apt:bind9',
},
'owner': 'bind',
},
}
files = {
"/etc/bind/keys.conf": {
'content_type': 'mako',
'group': 'bind',
'mode': '0440',
'context': {
'keys': node.metadata.get('bind', {}).get('keys', []),
},
'triggers': {
'svc_systemd:bind9:reload',
},
'needs': {
'pkg_apt:bind9',
},
},
"/etc/bind/named.conf.options": {
'content_type': 'mako',
'group': 'bind',
'mode': '0440',
'triggers': {
'svc_systemd:bind9:reload',
},
'needs': {
'pkg_apt:bind9',
},
},
}
if node.metadata.get('bind', {}).get('rndc', ''):
files['/etc/bind/rndc.conf'] = {
'mode': '0440',
'source': 'rndc/{}'.format(node.metadata['bind']['rndc']),
'content_type': 'mako',
'triggers': {
'svc_systemd:bind9:reload',
},
}
# this looks for zones either directly at data/bind/zones/ or in a subdirectory if so configured
zone_path = join(
repo.path,
'data', 'bind', 'files', 'zones',
node.metadata.get('bind', {}).get('zone_path', ""),
)
primary_zones = set()
for zone in listdir(zone_path):
if not isfile(join(zone_path, zone)) or zone.startswith(".") or zone.startswith("_"):
continue
output = check_output(['git', 'log', '-1', '--pretty=%ci', join(zone_path, zone)]).decode('utf-8').strip()
serial = datetime.strptime(output, '%Y-%m-%d %H:%M:%S %z').strftime('%y%m%d%H%M')
primary_zones.add(zone)
files["/var/lib/bind/primary/{}".format(zone)] = {
'content_type': 'mako',
'context': {
'header': ZONE_HEADER.format(serial=serial),
'metadata_records': node.metadata.get('bind', {}).get('zones_primary', {}).get(zone, {}).get('records', []),
},
'mode': '0444',
'owner': 'bind',
'source': 'zones/{}'.format(join(node.metadata.get('bind', {}).get('zone_path', ""), zone)),
'triggers': {
'svc_systemd:bind9:reload',
},
'needs': {
'pkg_apt:bind9'
},
}
primary_zones.union(set(node.metadata.get('bind', {}).get('zones_primary', {}).keys()))
files['/etc/bind/named.conf.local'] = {
'content_type': 'mako',
'context': {
'primary_zones': list(primary_zones),
},
'group': 'bind',
'triggers': {
'svc_systemd:bind9:reload',
},
'needs': {
'pkg_apt:bind9',
},
}

View file

@ -1,72 +0,0 @@
from bundlewrap.metadata import atomic
defaults = {
'icinga2_api': {
'bind': {
'services': {
'BIND PROCESS': {
'command_on_monitored_host': '/usr/lib/nagios/plugins/check_procs -C named -c 1:1',
},
},
},
},
}
@metadata_reactor
def port_checks(metadata):
services = {}
for interface in metadata.get('bind/listen', set()):
services[f'BIND PORT {interface}'] = {
'check_command': 'tcp',
'vars.tcp_address': metadata.get(f'interfaces/{interface}/ip_addresses')[0],
'vars.tcp_port': 53,
}
return {
'icinga2_api': {
'bind': {
'services': services,
},
},
}
@metadata_reactor
def generate_dns_entries_for_nodes(metadata):
results = set()
for rnode in repo.nodes:
node_name_split = rnode.name.split('.')
node_name_split.reverse()
dns_name = '.'.join(node_name_split)
ip4 = None
ip6 = None
# We only need this for GCE, because machines over there don't
# have a public ipv4 address.
if rnode.metadata.get('external_ipv4', None):
ip4 = rnode.metadata.get('external_ipv4')
for iface, config in sorted(rnode.metadata.get('interfaces', {}).items()):
if not ip4 and 'ipv4' in config:
ip4 = sorted(config['ipv4'])[0]
if not ip6 and 'ipv6' in config:
ip6 = sorted(config['ipv6'])[0]
if ip4:
results.add('{} IN A {}'.format(dns_name, ip4))
if ip6:
results.add('{} IN AAAA {}'.format(dns_name, ip6))
return {
'bind': {
'zones_primary': {
'kunbox.net': {
'records': results,
},
},
},
}

View file

@ -1,6 +1,7 @@
pkg_apt = {
'postgresql-11': {},
'postgresql-client-11': {},
'postgresql-server-dev-11': {},
}
if node.has_bundle('zfs'):

View file

@ -0,0 +1,2 @@
launch+=bind
bind-config=/etc/powerdns/named.conf

View file

@ -0,0 +1,6 @@
% for zone in sorted(zones):
zone "${zone}" {
file "/var/lib/powerdns/zones/${zone}";
type native;
};
% endfor

View file

@ -0,0 +1,28 @@
launch=
include-dir=/etc/powerdns/pdns.d
api=yes
api-key=${api_key}
webserver=yes
disable-syslog=yes
log-timestamp=no
max-tcp-connections=500
max-tcp-connections-per-client=10
security-poll-suffix=
server-id=${my_hostname}
default-ttl=60
% if is_secondary:
# Primary servers: ${', '.join(sorted(my_primary_servers['nodes']))}
slave=yes
superslave=yes
allow-notify-from=${','.join(sorted(my_primary_servers['ips']))}
% else:
allow-notify-from=
master=yes
% endif

View file

@ -0,0 +1,6 @@
launch+=gpgsql
gpgsql-host=localhost
gpgsql-port=5432
gpgsql-dbname=powerdns
gpgsql-user=powerdns
gpgsql-password=${password}

View file

@ -0,0 +1,105 @@
-- 4.3 schema, https://doc.powerdns.com/authoritative/backends/generic-postgresql.html
CREATE TABLE domains (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
master VARCHAR(128) DEFAULT NULL,
last_check INT DEFAULT NULL,
type VARCHAR(6) NOT NULL,
notified_serial BIGINT DEFAULT NULL,
account VARCHAR(40) DEFAULT NULL,
CONSTRAINT c_lowercase_name CHECK (((name)::TEXT = LOWER((name)::TEXT)))
);
CREATE UNIQUE INDEX name_index ON domains(name);
ALTER TABLE domains OWNER TO ${user};
CREATE TABLE records (
id BIGSERIAL PRIMARY KEY,
domain_id INT DEFAULT NULL,
name VARCHAR(255) DEFAULT NULL,
type VARCHAR(10) DEFAULT NULL,
content VARCHAR(65535) DEFAULT NULL,
ttl INT DEFAULT NULL,
prio INT DEFAULT NULL,
change_date INT DEFAULT NULL,
disabled BOOL DEFAULT 'f',
ordername VARCHAR(255),
auth BOOL DEFAULT 't',
CONSTRAINT domain_exists
FOREIGN KEY(domain_id) REFERENCES domains(id)
ON DELETE CASCADE,
CONSTRAINT c_lowercase_name CHECK (((name)::TEXT = LOWER((name)::TEXT)))
);
CREATE INDEX rec_name_index ON records(name);
CREATE INDEX nametype_index ON records(name,type);
CREATE INDEX domain_id ON records(domain_id);
CREATE INDEX recordorder ON records (domain_id, ordername text_pattern_ops);
ALTER TABLE records OWNER TO ${user};
CREATE TABLE supermasters (
ip INET NOT NULL,
nameserver VARCHAR(255) NOT NULL,
account VARCHAR(40) NOT NULL,
PRIMARY KEY(ip, nameserver)
);
ALTER TABLE supermasters OWNER TO ${user};
CREATE TABLE comments (
id SERIAL PRIMARY KEY,
domain_id INT NOT NULL,
name VARCHAR(255) NOT NULL,
type VARCHAR(10) NOT NULL,
modified_at INT NOT NULL,
account VARCHAR(40) DEFAULT NULL,
comment VARCHAR(65535) NOT NULL,
CONSTRAINT domain_exists
FOREIGN KEY(domain_id) REFERENCES domains(id)
ON DELETE CASCADE,
CONSTRAINT c_lowercase_name CHECK (((name)::TEXT = LOWER((name)::TEXT)))
);
CREATE INDEX comments_domain_id_idx ON comments (domain_id);
CREATE INDEX comments_name_type_idx ON comments (name, type);
CREATE INDEX comments_order_idx ON comments (domain_id, modified_at);
ALTER TABLE comments OWNER TO ${user};
CREATE TABLE domainmetadata (
id SERIAL PRIMARY KEY,
domain_id INT REFERENCES domains(id) ON DELETE CASCADE,
kind VARCHAR(32),
content TEXT
);
CREATE INDEX domainidmetaindex ON domainmetadata(domain_id);
ALTER TABLE domainmetadata OWNER TO ${user};
CREATE TABLE cryptokeys (
id SERIAL PRIMARY KEY,
domain_id INT REFERENCES domains(id) ON DELETE CASCADE,
flags INT NOT NULL,
active BOOL,
content TEXT
);
CREATE INDEX domainidindex ON cryptokeys(domain_id);
ALTER TABLE cryptokeys OWNER TO ${user};
CREATE TABLE tsigkeys (
id SERIAL PRIMARY KEY,
name VARCHAR(255),
algorithm VARCHAR(50),
secret VARCHAR(255),
CONSTRAINT c_lowercase_name CHECK (((name)::TEXT = LOWER((name)::TEXT)))
);
CREATE UNIQUE INDEX namealgoindex ON tsigkeys(name, algorithm);
ALTER TABLE tsigkeys OWNER TO ${user};

163
bundles/powerdns/items.py Normal file
View file

@ -0,0 +1,163 @@
from datetime import datetime
from os import listdir
from os.path import isfile, join
from subprocess import check_output
zone_path = join(repo.path, 'data', 'powerdns', 'files', 'bind-zones')
ZONE_HEADER = """
; _ ____ _ _ _____ _ _ _ _ ____
; / \\ / ___| | | |_ _| | | | \\ | |/ ___|
; / _ \\| | | |_| | | | | | | | \\| | | _
; / ___ \\ |___| _ | | | | |_| | |\\ | |_| |
; /_/ \\_\\____|_| |_| |_| \\___/|_| \\_|\\____|
;
; --> Diese Datei wird von BundleWrap verwaltet! <--
$TTL 60
@ IN SOA ns-1.kunbox.net. hostmaster.kunbox.net. (
{serial}
3600
3600
86400
300
)
"""
for rnode in sorted(repo.nodes_in_group('dns')):
ZONE_HEADER += '@ IN NS {}.\n'.format(rnode.metadata.get('powerdns', {}).get('my_hostname', rnode.hostname))
directories = {
'/etc/powerdns/pdns.d': {
'purge': True,
'needs': {
'pkg_apt:pdns-server',
'pkg_apt:pdns-backend-bind',
'pkg_apt:pdns-backend-pgsql',
},
'triggers': {
'svc_systemd:pdns:restart',
},
},
'/var/lib/powerdns/zones': {
'purge': True,
'needs': {
'pkg_apt:pdns-backend-bind',
},
}
}
files = {
'/etc/powerdns/pdns.conf': {
'content_type': 'mako',
'context': {
'api_key': node.metadata['powerdns']['api_key'],
'my_hostname': node.metadata['powerdns'].get('my_hostname', node.name),
'is_secondary': node.metadata['powerdns'].get('is_secondary', False),
'my_primary_servers': node.metadata['powerdns'].get('my_primary_servers', {}),
},
'needs': {
'pkg_apt:pdns-server',
},
'triggers': {
'svc_systemd:pdns:restart',
},
},
}
svc_systemd = {
'pdns': {
'needs': {
'directory:',
'file:',
'pkg_apt:pdns-server',
'pkg_apt:pdns-backend-bind',
'pkg_apt:pdns-backend-pgsql',
},
},
}
actions = {
'powerdns_reload_zones': {
'triggered': True,
'command': 'pdns_control rediscover; pdns_control reload',
'needs': {
'svc_systemd:pdns',
},
},
}
if node.metadata['powerdns'].get('features', {}).get('bind', False):
primary_zones = set()
for zone in listdir(zone_path):
if not isfile(join(zone_path, zone)) or zone.startswith(".") or zone.startswith("_"):
continue
try:
output = check_output(['git', 'log', '-1', '--pretty=%ci', join(zone_path, zone)]).decode('utf-8').strip()
serial = datetime.strptime(output, '%Y-%m-%d %H:%M:%S %z').strftime('%y%m%d%H%M')
except:
serial = datetime.now().strftime('%y%m%d0000')
primary_zones.add(zone)
files["/var/lib/powerdns/zones/{}".format(zone)] = {
'content_type': 'mako',
'context': {
'header': ZONE_HEADER.format(serial=serial),
'metadata_records': node.metadata.get('powerdns', {}).get('bind-zones', {}).get(zone, {}).get('records', []),
},
'source': 'bind-zones/{}'.format(zone),
'triggers': {
'action:powerdns_reload_zones',
},
}
files['/etc/powerdns/pdns.d/bind.conf'] = {
'needs': {
'pkg_apt:pdns-backend-bind',
},
'triggers': {
'action:powerdns_reload_zones',
},
}
files['/etc/powerdns/named.conf'] = {
'content_type': 'mako',
'context': {
'zones': primary_zones,
},
'needs': {
'pkg_apt:pdns-backend-bind',
},
'triggers': {
'action:powerdns_reload_zones',
},
}
if node.metadata['powerdns'].get('features', {}).get('pgsql', False):
files['/etc/powerdns/pdns.d/pgsql.conf'] = {
'content_type': 'mako',
'context': {
'password': node.metadata['postgresql']['users']['powerdns']['password'],
},
'needs': {
'pkg_apt:pdns-backend-pgsql',
},
'triggers': {
'action:powerdns_reload_zones',
},
}
files['/etc/powerdns/schema.pgsql.sql'] = {}
actions['powerdns_load_pgsql_schema'] = {
'command': node.metadata['postgresql']['users']['powerdns']['password'].format_into('PGPASSWORD={} psql -h 127.0.0.1 -d powerdns -U powerdns -w < /etc/powerdns/schema.pgsql.sql'),
'unless': 'sudo -u postgres psql -d powerdns -c "\dt" | grep domains 2>&1 >/dev/null',
'needs': {
'bundle:postgresql',
'file:/etc/powerdns/schema.pgsql.sql',
},
'needed_by': {
'svc_systemd:pdns',
},
}

View file

@ -0,0 +1,91 @@
from bundlewrap.exceptions import NoSuchGroup
defaults = {
'apt': {
'packages': {
'pdns-server': {},
'pdns-tools': {},
'pdns-backend-bind': {},
'pdns-backend-pgsql': {},
},
},
'powerdns': {
'api_key': repo.vault.password_for('{} powerdns api'.format(node.name)),
},
'postgresql': {
'users': {
'powerdns': {
'password': repo.vault.password_for('{} postgresql powerdns'.format(node.name)),
},
},
'databases': {
'powerdns': {
'owner': 'powerdns',
},
},
},
}
@metadata_reactor
def get_ips_of_primary_nameservers(metadata):
if not metadata.get('powerdns/is_secondary', False):
return {}
ips = set()
nodes = set()
for rnode in repo.nodes_in_group('dns'):
if not rnode.metadata.get('powerdns/is_secondary', False):
ips.update({
str(ip) for ip in repo.libs.tools.resolve_identifier(repo, rnode.name)
})
nodes.add(rnode.name)
return {
'powerdns': {
'my_primary_servers': {
'ips': ips,
'nodes': nodes,
},
},
}
@metadata_reactor
def generate_dns_entries_for_nodes(metadata):
results = set()
for rnode in repo.nodes:
node_name_split = rnode.name.split('.')
node_name_split.reverse()
dns_name = '.'.join(node_name_split)
ip4 = None
ip6 = None
# We only need this for GCE, because machines over there don't
# have a public ipv4 address.
if rnode.metadata.get('external_ipv4', None):
ip4 = rnode.metadata.get('external_ipv4')
for iface, config in sorted(rnode.metadata.get('interfaces', {}).items()):
if not ip4 and 'ipv4' in config:
ip4 = sorted(config['ipv4'])[0]
if not ip6 and 'ipv6' in config:
ip6 = sorted(config['ipv6'])[0]
if ip4:
results.add('{} IN A {}'.format(dns_name, ip4))
if ip6:
results.add('{} IN AAAA {}'.format(dns_name, ip6))
return {
'powerdns': {
'bind-zones': {
'kunbox.net': {
'records': results,
},
},
},
}

View file

@ -0,0 +1,14 @@
SALT = '${repo.vault.decrypt('encrypt$gAAAAABfidFVqVEgWvlXgP-GSQUgVtcTxzoZx2G8VYWHaGKRpgaLDchlTRcKwqgvfG5orNpXt7aDd5i2aehi6cvIlxYNdL87twfVhDLBDho8j-Uz5Vga8-9cEzEZULl5pFCIcRlYUCKyEIOcdXSaLCM3p8pGjrh-O8_g49rbADKmLFoJx2vVTVs=')}'
SECRET_KEY = '${repo.vault.password_for('{} powerdnsadmin secret_key'.format(node.name))}'
BIND_ADDRESS = '127.0.0.1'
PORT = 9191
OFFLINE_MODE = True
SQLA_DB_USER = 'powerdnsadmin'
SQLA_DB_PASSWORD = '${node.metadata['postgresql']['users']['powerdnsadmin']['password']}'
SQLA_DB_HOST = '127.0.0.1'
SQLA_DB_NAME = 'powerdnsadmin'
SQLALCHEMY_TRACK_MODIFICATIONS = True
SQLALCHEMY_DATABASE_URI = 'postgresql://' + SQLA_DB_USER + ':' + SQLA_DB_PASSWORD + '@' + SQLA_DB_HOST + '/' + SQLA_DB_NAME
SAML_ENABLED = False

View file

@ -0,0 +1,14 @@
[Unit]
Description=PowerDNS-Admin
After=network.target postgresql.service
[Service]
User=powerdnsadmin
Group=powerdnsadmin
Environment=FLASK_CONF=/opt/powerdnsadmin/config.py
WorkingDirectory=/opt/powerdnsadmin/src
ExecStartPre=-/bin/chown powerdnsadmin:powerdnsadmin /opt/powerdnsadmin/src/powerdnsadmin/static
ExecStart=/opt/powerdnsadmin/venv/bin/gunicorn 'powerdnsadmin:create_app()'
[Install]
WantedBy=multi-user.target

View file

@ -0,0 +1,87 @@
assert node.has_bundle('nodejs')
assert node.has_bundle('postgresql')
directories = {
'/opt/powerdnsadmin/src': {},
}
git_deploy = {
'/opt/powerdnsadmin/src': {
'repo': 'https://github.com/ngoduykhanh/PowerDNS-Admin.git',
'rev': 'master',
'triggers': {
'action:powerdnsadmin_install_deps',
'action:powerdnsadmin_upgrade_database',
'action:powerdnsadmin_compile_assets',
'svc_systemd:powerdnsadmin:restart',
},
},
}
files = {
'/opt/powerdnsadmin/config.py': {
'content_type': 'mako',
},
'/etc/systemd/system/powerdnsadmin.service': {
'triggers': {
'action:systemd-reload',
},
},
}
actions = {
'powerdnsadmin_create_virtualenv': {
'command': '/usr/bin/python3 -m virtualenv -p python3 /opt/powerdnsadmin/venv/',
'unless': 'test -d /opt/powerdnsadmin/venv/',
'needs': {
'directory:/opt/powerdnsadmin', # provided by bundle:users
},
},
'powerdnsadmin_install_deps': {
'triggered': True,
'command': '/opt/powerdnsadmin/venv/bin/pip install -r /opt/powerdnsadmin/src/requirements.txt',
'needs': {
'action:powerdnsadmin_create_virtualenv',
'pkg_apt:',
},
},
'powerdnsadmin_install_deps': {
'triggered': True,
'command': '/opt/powerdnsadmin/venv/bin/pip install -r /opt/powerdnsadmin/src/requirements.txt',
'needs': {
'action:powerdnsadmin_create_virtualenv',
'pkg_apt:',
},
},
'powerdnsadmin_upgrade_database': {
'triggered': True,
'command': 'FLASK_CONF=/opt/powerdnsadmin/config.py FLASK_APP=/opt/powerdnsadmin/src/powerdnsadmin/__init__.py /opt/powerdnsadmin/venv/bin/flask db upgrade',
# TODO unless
'needs': {
'action:powerdnsadmin_install_deps',
'bundle:postgresql',
'pkg_apt:',
},
},
'powerdnsadmin_compile_assets': {
'triggered': True,
'command': 'cd /opt/powerdnsadmin/src && yarn install --pure-lockfile && FLASK_APP=/opt/powerdnsadmin/src/powerdnsadmin/__init__.py /opt/powerdnsadmin/venv/bin/flask assets build',
'needs': {
'action:powerdnsadmin_install_deps',
'pkg_apt:',
},
},
}
svc_systemd = {
'powerdnsadmin': {
'needs': {
'file:/opt/powerdnsadmin/config.py',
'file:/etc/systemd/system/powerdnsadmin.service',
'git_deploy:/opt/powerdnsadmin/src',
'action:powerdnsadmin_install_deps',
'action:powerdnsadmin_upgrade_database',
'action:powerdnsadmin_compile_assets',
},
},
}

View file

@ -0,0 +1,35 @@
defaults = {
'apt': {
'packages': {
'default-libmysqlclient-dev': {},
'libffi-dev': {},
'libldap2-dev': {},
'libsasl2-dev': {},
'libssl-dev': {},
'libxml2-dev': {},
'libxmlsec1-dev': {},
'libxslt1-dev': {},
'pkg-config': {},
'python3-psycopg2': {},
'python3-wheel': {},
},
},
'users': {
'powerdnsadmin': {
'home': '/opt/powerdnsadmin',
},
},
'postgresql': {
'users': {
'powerdnsadmin': {
'password': repo.vault.password_for('{} postgresql powerdnsadmin'.format(node.name)),
},
},
'databases': {
'powerdnsadmin': {
'owner': 'powerdnsadmin',
},
},
},
}

View file

@ -1,28 +0,0 @@
${header}
$ORIGIN franzi.business.
@ IN A 94.130.52.224
IN AAAA 2a01:4f8:10b:2a5f::2
IN MX 10 mx0.kunbox.net.
IN TXT v=spf1 mx ~all
* IN A 94.130.52.224
IN AAAA 2a01:4f8:10b:2a5f::2
grafana IN A 165.232.105.69
IN AAAA 2a03:b0c0:1:e0::627:8001
icinga IN A 165.232.42.173
IN AAAA 2a03:b0c0:1:e0::665:8001
sewfile IN A 116.203.205.248
IN AAAA 2a01:4f8:c0c:c71b::1
IN TXT v=spf1 a mx ~all
_matrix._tcp IN SRV 10 10 8448 matrix
2019._domainkey IN TXT v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwkg6UAcu3V98hal1UVf6yB0WT1CKDS0AK83CUlSP8bUwraPxkxK1nkQOUsmjbQs6a3FhdsKprMi32GeUaTVvZg81JIybPk3jNugfNWfSjs2TXPomYu+XD2pmmbR3cZlzC5NGR2nmBFt/P/S2ihPHj35KziiBIwK1TdvOi1M2+upCjK33Icco0ByCm0gJpD2O0cbqcBcUKqd6X440vYhNXH1ygp0e91P0iRnvS9sg6yD0xjD8kD6j/8GfxBY+9bpU3EvDoBgyJSbjw5b6PUVJbKMXzw1NIRNj0SXKs5BakjS8+7u62vR11IPCYRwy+yr0rDT0tNegM7gStIIgoTpOoQIDAQAB
_dmarc IN TXT v=DMARC1; p=none; rua=mailto:postmaster@kunsmann.eu; ruf=mailto:postmaster@kunsmann.eu; fo=0:d:s; adkim=r; aspf=r
_token._dnswl IN TXT gg3mbwjx9bbuo5osvhq7oz6bc881wcmc

View file

@ -1,27 +0,0 @@
${header}
$ORIGIN kunbox.net.
@ IN A 94.130.52.224
IN AAAA 2a01:4f8:10b:2a5f::2
; Needs to have a working Mail address, otherwise Telekom goes mimimi
IN MX 10 mx0
IN TXT v=spf1 mx ~all
; Mail servers
mx0 IN A 94.130.52.224
IN AAAA 2a01:4f8:10b:2a5f::2
*.mx0 IN CNAME mx0
% for record in sorted(metadata_records):
${record}
% endfor
2019._domainkey IN TXT v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwkg6UAcu3V98hal1UVf6yB0WT1CKDS0AK83CUlSP8bUwraPxkxK1nkQOUsmjbQs6a3FhdsKprMi32GeUaTVvZg81JIybPk3jNugfNWfSjs2TXPomYu+XD2pmmbR3cZlzC5NGR2nmBFt/P/S2ihPHj35KziiBIwK1TdvOi1M2+upCjK33Icco0ByCm0gJpD2O0cbqcBcUKqd6X440vYhNXH1ygp0e91P0iRnvS9sg6yD0xjD8kD6j/8GfxBY+9bpU3EvDoBgyJSbjw5b6PUVJbKMXzw1NIRNj0SXKs5BakjS8+7u62vR11IPCYRwy+yr0rDT0tNegM7gStIIgoTpOoQIDAQAB
_dmarc IN TXT v=DMARC1; p=none; rua=mailto:postmaster@kunsmann.eu; ruf=mailto:postmaster@kunsmann.eu; fo=0:d:s; adkim=r; aspf=r
_token._dnswl IN TXT 6akc10htbgmg56e072w0w2n0wql4oezu
f2k1.de._report._dmarc IN TXT v=DMARC1
franzi.business._report._dmarc IN TXT v=DMARC1
kunsmann.eu._report._dmarc IN TXT v=DMARC1

View file

@ -1,15 +0,0 @@
${header}
$ORIGIN kunsmann.eu.
@ IN A 94.130.52.224
IN AAAA 2a01:4f8:10b:2a5f::2
IN MX 10 mx0.kunbox.net.
IN TXT v=spf1 a mx ~all
* IN A 94.130.52.224
IN AAAA 2a01:4f8:10b:2a5f::2
2019._domainkey IN TXT v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwkg6UAcu3V98hal1UVf6yB0WT1CKDS0AK83CUlSP8bUwraPxkxK1nkQOUsmjbQs6a3FhdsKprMi32GeUaTVvZg81JIybPk3jNugfNWfSjs2TXPomYu+XD2pmmbR3cZlzC5NGR2nmBFt/P/S2ihPHj35KziiBIwK1TdvOi1M2+upCjK33Icco0ByCm0gJpD2O0cbqcBcUKqd6X440vYhNXH1ygp0e91P0iRnvS9sg6yD0xjD8kD6j/8GfxBY+9bpU3EvDoBgyJSbjw5b6PUVJbKMXzw1NIRNj0SXKs5BakjS8+7u62vR11IPCYRwy+yr0rDT0tNegM7gStIIgoTpOoQIDAQAB
_dmarc IN TXT v=DMARC1; p=none; rua=mailto:postmaster@kunsmann.eu; ruf=mailto:postmaster@kunsmann.eu; fo=0:d:s; adkim=r; aspf=r
_token._dnswl IN TXT 5mx0rv9ru8s1zz4tf4xlt48osh09czmg

View file

@ -0,0 +1,28 @@
${header}
$ORIGIN franzi.business.
@ IN A 94.130.52.224
IN AAAA 2a01:4f8:10b:2a5f::2
IN MX 10 mx0.kunbox.net.
IN TXT "v=spf1 mx ~all"
* IN A 94.130.52.224
IN AAAA 2a01:4f8:10b:2a5f::2
grafana IN A 165.232.105.69
IN AAAA 2a03:b0c0:1:e0::627:8001
icinga IN A 165.232.42.173
IN AAAA 2a03:b0c0:1:e0::665:8001
sewfile IN A 116.203.205.248
IN AAAA 2a01:4f8:c0c:c71b::1
IN TXT "v=spf1 a mx ~all"
_matrix._tcp IN SRV 10 10 8448 matrix
2019._domainkey IN TXT "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwkg6UAcu3V98hal1UVf6yB0WT1CKDS0AK83CUlSP8bUwraPxkxK1nkQOUsmjbQs6a3FhdsKprMi32GeUaTVvZg81JIybPk3jNugfNWfSjs2TXPomYu+XD2pmmbR3cZlzC5NGR2nmBFt/P/S2ihPHj35KziiBIwK1TdvOi1M2+upCjK33Icco0ByCm0gJpD2O0cbqcBcUKqd6X440vYhNXH1ygp0e91P0iRnvS9sg6yD0xjD8kD6j/8GfxBY+9bpU3EvDoBgyJSbjw5b6PUVJbKMXzw1NIRNj0SXKs5BakjS8+7u62vR11IPCYRwy+yr0rDT0tNegM7gStIIgoTpOoQIDAQAB"
_dmarc IN TXT "v=DMARC1; p=none; rua=mailto:postmaster@kunsmann.eu; ruf=mailto:postmaster@kunsmann.eu; fo=0:d:s; adkim=r; aspf=r"
_token._dnswl IN TXT "gg3mbwjx9bbuo5osvhq7oz6bc881wcmc"

View file

@ -0,0 +1,30 @@
${header}
$ORIGIN kunbox.net.
@ IN A 94.130.52.224
IN AAAA 2a01:4f8:10b:2a5f::2
; Needs to have a working Mail address, otherwise Telekom goes mimimi
IN MX 10 mx0
IN TXT "v=spf1 mx ~all"
; Mail servers
mx0 IN A 94.130.52.224
IN AAAA 2a01:4f8:10b:2a5f::2
*.mx0 IN CNAME mx0
; Nameservers
ns-1 IN A 34.89.208.78
% for record in sorted(metadata_records):
${record}
% endfor
2019._domainkey IN TXT "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwkg6UAcu3V98hal1UVf6yB0WT1CKDS0AK83CUlSP8bUwraPxkxK1nkQOUsmjbQs6a3FhdsKprMi32GeUaTVvZg81JIybPk3jNugfNWfSjs2TXPomYu+XD2pmmbR3cZlzC5NGR2nmBFt/P/S2ihPHj35KziiBIwK1TdvOi1M2+upCjK33Icco0ByCm0gJpD2O0cbqcBcUKqd6X440vYhNXH1ygp0e91P0iRnvS9sg6yD0xjD8kD6j/8GfxBY+9bpU3EvDoBgyJSbjw5b6PUVJbKMXzw1NIRNj0SXKs5BakjS8+7u62vR11IPCYRwy+yr0rDT0tNegM7gStIIgoTpOoQIDAQAB"
_dmarc IN TXT "v=DMARC1; p=none; rua=mailto:postmaster@kunsmann.eu; ruf=mailto:postmaster@kunsmann.eu; fo=0:d:s; adkim=r; aspf=r"
_token._dnswl IN TXT "6akc10htbgmg56e072w0w2n0wql4oezu"
f2k1.de._report._dmarc IN TXT "v=DMARC1"
franzi.business._report._dmarc IN TXT "v=DMARC1"
kunsmann.eu._report._dmarc IN TXT "v=DMARC1"

View file

@ -0,0 +1,15 @@
${header}
$ORIGIN kunsmann.eu.
@ IN A 94.130.52.224
IN AAAA 2a01:4f8:10b:2a5f::2
IN MX 10 mx0.kunbox.net.
IN TXT "v=spf1 a mx ~all"
* IN A 94.130.52.224
IN AAAA 2a01:4f8:10b:2a5f::2
2019._domainkey IN TXT "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwkg6UAcu3V98hal1UVf6yB0WT1CKDS0AK83CUlSP8bUwraPxkxK1nkQOUsmjbQs6a3FhdsKprMi32GeUaTVvZg81JIybPk3jNugfNWfSjs2TXPomYu+XD2pmmbR3cZlzC5NGR2nmBFt/P/S2ihPHj35KziiBIwK1TdvOi1M2+upCjK33Icco0ByCm0gJpD2O0cbqcBcUKqd6X440vYhNXH1ygp0e91P0iRnvS9sg6yD0xjD8kD6j/8GfxBY+9bpU3EvDoBgyJSbjw5b6PUVJbKMXzw1NIRNj0SXKs5BakjS8+7u62vR11IPCYRwy+yr0rDT0tNegM7gStIIgoTpOoQIDAQAB"
_dmarc IN TXT "v=DMARC1; p=none; rua=mailto:postmaster@kunsmann.eu; ruf=mailto:postmaster@kunsmann.eu; fo=0:d:s; adkim=r; aspf=r"
_token._dnswl IN TXT "5mx0rv9ru8s1zz4tf4xlt48osh09czmg"

View file

@ -3,7 +3,7 @@ ${header}
$ORIGIN trans-agenda.eu.
@ IN MX 10 mx0.kunbox.net.
IN TXT v=spf1 a mx ~all
IN TXT "v=spf1 a mx ~all"
part.of.the IN A 94.130.52.224
part.of.the IN AAAA 2a01:4f8:10b:2a5f::2

View file

@ -7,6 +7,17 @@ groups['webserver'] = {
groups['dns'] = {
'bundles': {
'bind',
'postgresql',
'powerdns',
},
'metadata': {
'powerdns': {
'features': {
'bind': True,
'pgsql': True,
},
# Overridden in node metadata for primary server
'is_secondary': True,
},
},
}

31
libs/tools.py Normal file
View file

@ -0,0 +1,31 @@
from bundlewrap.exceptions import NoSuchGroup, NoSuchNode
from ipaddress import ip_address
def resolve_identifier(repo, identifier):
"""
Try to resolve an identifier (group or node). Return a set of ip
addresses valid for this identifier.
"""
try:
nodes = {repo.get_node(identifier)}
except NoSuchNode:
try:
nodes = repo.nodes_in_group(identifier)
except NoSuchGroup:
try:
return {ip_address(identifier)}
except:
return set()
found_ips = set()
for node in nodes:
for interface, config in node.metadata.get('interfaces', {}).items():
for ip in config.get('ipv4', set()):
found_ips.add(ip_address(ip))
for ip in config.get('ipv4', set()):
found_ips.add(ip_address(ip))
if node.metadata.get('external_ipv4'):
found_ips.add(ip_address(node.metadata.get('external_ipv4')))
return found_ips

11
nodes/a.ns14.net.py Normal file
View file

@ -0,0 +1,11 @@
# This node is not actually part of this repository, it's a DNS server
# managed by AutoDNS. It needs a node file, because we're using that to
# auto-generate DNS configs.
nodes['a.ns14.net'] = {
'hostname': 'a.ns14.net',
'dummy': True,
'groups': {
'dns',
},
}

11
nodes/b.ns14.net.py Normal file
View file

@ -0,0 +1,11 @@
# This node is not actually part of this repository, it's a DNS server
# managed by AutoDNS. It needs a node file, because we're using that to
# auto-generate DNS configs.
nodes['b.ns14.net'] = {
'hostname': 'b.ns14.net',
'dummy': True,
'groups': {
'dns',
},
}

11
nodes/c.ns14.net.py Normal file
View file

@ -0,0 +1,11 @@
# This node is not actually part of this repository, it's a DNS server
# managed by AutoDNS. It needs a node file, because we're using that to
# auto-generate DNS configs.
nodes['c.ns14.net'] = {
'hostname': 'c.ns14.net',
'dummy': True,
'groups': {
'dns',
},
}

11
nodes/d.ns14.net.py Normal file
View file

@ -0,0 +1,11 @@
# This node is not actually part of this repository, it's a DNS server
# managed by AutoDNS. It needs a node file, because we're using that to
# auto-generate DNS configs.
nodes['d.ns14.net'] = {
'hostname': 'd.ns14.net',
'dummy': True,
'groups': {
'dns',
},
}

View file

@ -1,9 +1,14 @@
# ns-3.kunbox.net
# ns-1.kunbox.net
# Frankfurt, Germany
nodes['gce.bind01'] = {
'bundles': {
'nodejs',
'powerdnsadmin',
},
'groups': {
'dns',
'webserver',
},
'metadata': {
'interfaces': {
@ -15,6 +20,22 @@ nodes['gce.bind01'] = {
},
},
'external_ipv4': '34.89.208.78',
'nginx': {
'vhosts': {
'ns-1.kunbox.net': {
'proxy': {
'/': {
'target': 'http://127.0.0.1:8000/',
},
},
},
},
},
'powerdns': {
'is_secondary': False,
'secondary_nameservers': 'dns',
'my_hostname': 'ns-1.kunbox.net',
},
'vm': {
'cpu': 1,
'ram': 1,