Add DNS aggregator via PowerDNS and sqlite3
This commit is contained in:
parent
a2a7a1d790
commit
c13a4e84af
|
@ -0,0 +1,183 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
# DNSAggregatorInstance.py - Class implementing a DNS aggregator and run by pvcd
|
||||
# Part of the Parallel Virtual Cluster (PVC) system
|
||||
#
|
||||
# Copyright (C) 2018 Joshua M. Boniface <joshua@boniface.me>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import sqlite3
|
||||
|
||||
import pvcd.log as log
|
||||
import pvcd.zkhandler as zkhandler
|
||||
import pvcd.common as common
|
||||
|
||||
# A barebones PowerDNS sqlite schema (relative to the active dir)
|
||||
sql_schema_file = 'pvcd/powerdns-aggregator-schema.sql'
|
||||
|
||||
class DNSAggregatorInstance(object):
|
||||
# Initialization function
|
||||
def __init__(self, zk_conn, config, logger, d_network):
|
||||
self.zk_conn = zk_conn
|
||||
self.config = config
|
||||
self.logger = logger
|
||||
self.d_network = d_network
|
||||
|
||||
self.active = False
|
||||
self.database_file = self.config['pdns_dynamic_directory'] + '/pdns-aggregator.sqlite3'
|
||||
|
||||
self.dns_server_daemon = None
|
||||
|
||||
self.prepare_db()
|
||||
|
||||
# Preparation function
|
||||
def prepare_db(self):
|
||||
# Connect to the database
|
||||
sql_conn = sqlite3.connect(self.database_file)
|
||||
sql_curs = sql_conn.cursor()
|
||||
|
||||
# Try to access the domains table
|
||||
try:
|
||||
sql_curs.execute(
|
||||
'select * from domains'
|
||||
)
|
||||
write_schema = False
|
||||
# The table doesn't exist, so we should create it
|
||||
except sqlite3.OperationalError:
|
||||
write_schema = True
|
||||
|
||||
if write_schema:
|
||||
with open('{}/{}'.format(os.getcwd(), sql_schema_file), 'r') as schema_file:
|
||||
schema = ''.join(schema_file.readlines())
|
||||
sql_curs.executescript(schema)
|
||||
|
||||
sql_conn.close()
|
||||
|
||||
# Add a new network to the aggregator database
|
||||
def add_client_network(self, network):
|
||||
network_domain = self.d_network[network].domain
|
||||
network_gateway = self.d_network[network].ip_gateway
|
||||
|
||||
self.logger.out(
|
||||
'Adding entry for client domain {}'.format(
|
||||
network_domain
|
||||
),
|
||||
prefix='DNS aggregator',
|
||||
state='o'
|
||||
)
|
||||
# Connect to the database
|
||||
sql_conn = sqlite3.connect(self.database_file)
|
||||
sql_curs = sql_conn.cursor()
|
||||
# Try to access the domains entry
|
||||
sql_curs.execute(
|
||||
'select * from domains where name=?',
|
||||
(network_domain,)
|
||||
)
|
||||
results = sql_curs.fetchall()
|
||||
if results:
|
||||
write_domain = False
|
||||
else:
|
||||
write_domain = True
|
||||
|
||||
if write_domain:
|
||||
sql_curs.execute(
|
||||
'insert into domains (name, master, type, account) values (?, ?, "SLAVE", "internal")',
|
||||
(network_domain, network_gateway)
|
||||
)
|
||||
sql_conn.commit()
|
||||
|
||||
sql_conn.close()
|
||||
|
||||
# Remove a deleted network from the aggregator database
|
||||
def remove_client_network(self, network):
|
||||
network_domain = self.d_network[network].domain
|
||||
|
||||
self.logger.out(
|
||||
'Removing entry for client domain {}'.format(
|
||||
network_domain
|
||||
),
|
||||
prefix='DNS aggregator',
|
||||
state='o'
|
||||
)
|
||||
# Connect to the database
|
||||
sql_conn = sqlite3.connect(self.database_file)
|
||||
sql_curs = sql_conn.cursor()
|
||||
print(network_domain)
|
||||
sql_curs.execute(
|
||||
'delete from domains where name=?',
|
||||
(network_domain,)
|
||||
)
|
||||
sql_conn.commit()
|
||||
sql_conn.close()
|
||||
|
||||
# Force AXFR
|
||||
def get_axfr(self, network):
|
||||
self.logger.out(
|
||||
'Perform AXFR for {}'.format(
|
||||
self.d_network[network].domain
|
||||
),
|
||||
prefix='DNS aggregator',
|
||||
state='o'
|
||||
)
|
||||
common.run_os_command('/usr/bin/pdns_control --socket-dir={} retrieve {}'.format(self.config['pdns_dynamic_directory'], self.d_network[network].domain))
|
||||
|
||||
# Start up the PowerDNS instance
|
||||
def start_aggregator(self):
|
||||
self.logger.out(
|
||||
'Starting PowerDNS zone aggregator',
|
||||
state='o'
|
||||
)
|
||||
# Define the PowerDNS config
|
||||
dns_configuration = [
|
||||
'--no-config',
|
||||
'--daemon=no',
|
||||
'--disable-syslog=yes',
|
||||
'--disable-axfr=no',
|
||||
'--guardian=yes',
|
||||
'--local-address=0.0.0.0',
|
||||
'--local-port=10053',
|
||||
'--log-dns-details=on',
|
||||
'--loglevel=3',
|
||||
'--master=no',
|
||||
'--slave=yes',
|
||||
'--slave-cycle-interval=5',
|
||||
'--version-string=powerdns',
|
||||
'--socket-dir={}'.format(self.config['pdns_dynamic_directory']),
|
||||
'--launch=gsqlite3',
|
||||
'--gsqlite3-database={}'.format(self.database_file),
|
||||
'--gsqlite3-dnssec=no'
|
||||
]
|
||||
# Start the pdns process in a thread
|
||||
self.dns_server_daemon = common.run_os_daemon(
|
||||
'/usr/sbin/pdns_server {}'.format(
|
||||
' '.join(dns_configuration)
|
||||
),
|
||||
environment=None,
|
||||
logfile='{}/pdns-aggregator.log'.format(self.config['pdns_log_directory'])
|
||||
)
|
||||
|
||||
# Stop the PowerDNS instance
|
||||
def stop_aggregator(self):
|
||||
if self.dns_server_daemon:
|
||||
self.logger.out(
|
||||
'Stopping PowerDNS zone aggregator',
|
||||
state='o'
|
||||
)
|
||||
self.dns_server_daemon.signal('term')
|
|
@ -44,6 +44,7 @@ import pvcd.common as common
|
|||
import pvcd.DomainInstance as DomainInstance
|
||||
import pvcd.NodeInstance as NodeInstance
|
||||
import pvcd.VXNetworkInstance as VXNetworkInstance
|
||||
import pvcd.DNSAggregatorInstance as DNSAggregatorInstance
|
||||
|
||||
###############################################################################
|
||||
# PVCD - node daemon startup program
|
||||
|
@ -506,6 +507,12 @@ node_list = []
|
|||
network_list = []
|
||||
domain_list = []
|
||||
|
||||
# Create an instance of the DNS Aggregator if we're a coordinator
|
||||
if config['daemon_mode'] == 'coordinator':
|
||||
dns_aggregator = DNSAggregatorInstance.DNSAggregatorInstance(zk_conn, config, logger, d_network)
|
||||
else:
|
||||
dns_aggregator = None
|
||||
|
||||
# Node objects
|
||||
@zk_conn.ChildrenWatch('/nodes')
|
||||
def update_nodes(new_node_list):
|
||||
|
@ -514,7 +521,7 @@ def update_nodes(new_node_list):
|
|||
# Add any missing nodes to the list
|
||||
for node in new_node_list:
|
||||
if not node in node_list:
|
||||
d_node[node] = NodeInstance.NodeInstance(node, myhostname, zk_conn, config, logger, d_node, d_network, d_domain)
|
||||
d_node[node] = NodeInstance.NodeInstance(node, myhostname, zk_conn, config, logger, d_node, d_network, d_domain, dns_aggregator)
|
||||
|
||||
# Remove any deleted nodes from the list
|
||||
for node in node_list:
|
||||
|
@ -542,6 +549,7 @@ def update_networks(new_network_list):
|
|||
for network in new_network_list:
|
||||
if not network in network_list:
|
||||
d_network[network] = VXNetworkInstance.VXNetworkInstance(network, zk_conn, config, logger, this_node)
|
||||
dns_aggregator.add_client_network(network)
|
||||
# Start primary functionality
|
||||
if this_node.router_state == 'primary':
|
||||
d_network[network].createGatewayAddress()
|
||||
|
@ -557,9 +565,14 @@ def update_networks(new_network_list):
|
|||
# Stop general functionality
|
||||
d_network[network].removeFirewall()
|
||||
d_network[network].removeNetwork()
|
||||
dns_aggregator.remove_client_network(network)
|
||||
# Delete the object
|
||||
del(d_network[network])
|
||||
|
||||
# if config['daemon_mode'] == 'coordinator':
|
||||
# # Update the DNS aggregator
|
||||
# dns_aggregator.update_network_list(d_network)
|
||||
|
||||
# Update and print new list
|
||||
network_list = new_network_list
|
||||
logger.out('{}Network list:{} {}'.format(logger.fmt_blue, logger.fmt_end, ' '.join(network_list)), state='i')
|
||||
|
|
|
@ -35,7 +35,7 @@ import pvcd.common as common
|
|||
|
||||
class NodeInstance(object):
|
||||
# Initialization function
|
||||
def __init__(self, name, this_node, zk_conn, config, logger, d_node, d_network, d_domain):
|
||||
def __init__(self, name, this_node, zk_conn, config, logger, d_node, d_network, d_domain, dns_aggregator):
|
||||
# Passed-in variables on creation
|
||||
self.name = name
|
||||
self.this_node = this_node
|
||||
|
@ -55,6 +55,7 @@ class NodeInstance(object):
|
|||
self.d_node = d_node
|
||||
self.d_network = d_network
|
||||
self.d_domain = d_domain
|
||||
self.dns_aggregator = dns_aggregator
|
||||
# Printable lists
|
||||
self.active_node_list = []
|
||||
self.flushed_node_list = []
|
||||
|
@ -289,17 +290,25 @@ class NodeInstance(object):
|
|||
def become_secondary(self):
|
||||
self.logger.out('Setting router {} to secondary state'.format(self.name), state='i')
|
||||
self.logger.out('Network list: {}'.format(', '.join(self.network_list)))
|
||||
time.sleep(0.5)
|
||||
time.sleep(1)
|
||||
for network in self.d_network:
|
||||
self.d_network[network].stopDHCPServer()
|
||||
self.d_network[network].removeGatewayAddress()
|
||||
self.dns_aggregator.stop_aggregator()
|
||||
|
||||
def become_primary(self):
|
||||
self.logger.out('Setting router {} to primary state.'.format(self.name), state='i')
|
||||
self.logger.out('Network list: {}'.format(', '.join(self.network_list)))
|
||||
self.dns_aggregator.start_aggregator()
|
||||
time.sleep(0.5)
|
||||
# Start up the gateways and DHCP servers
|
||||
for network in self.d_network:
|
||||
self.d_network[network].createGatewayAddress()
|
||||
self.d_network[network].startDHCPServer()
|
||||
time.sleep(0.5)
|
||||
# Handle AXFRs after to avoid slowdowns
|
||||
for network in self.d_network:
|
||||
self.dns_aggregator.get_axfr(network)
|
||||
|
||||
# Flush all VMs on the host
|
||||
def flush(self):
|
||||
|
|
|
@ -324,7 +324,6 @@ add rule inet filter input meta iifname {bridgenic} counter drop
|
|||
dhcp_configuration = [
|
||||
'--domain-needed',
|
||||
'--bogus-priv',
|
||||
# '--no-resolv',
|
||||
'--no-hosts',
|
||||
'--filterwin2k',
|
||||
'--expand-hosts',
|
||||
|
@ -338,10 +337,10 @@ add rule inet filter input meta iifname {bridgenic} counter drop
|
|||
'--bind-interfaces',
|
||||
'--leasefile-ro',
|
||||
'--dhcp-script=/usr/share/pvc/pvcd/dnsmasq-zookeeper-leases.py',
|
||||
'--dhcp-range={},{},4h'.format(self.dhcp_start, self.dhcp_end),
|
||||
'--dhcp-range={},{},48h'.format(self.dhcp_start, self.dhcp_end),
|
||||
'--dhcp-hostsdir={}'.format(self.dnsmasq_hostsdir),
|
||||
'--log-facility=-',
|
||||
'--log-queries=extra',
|
||||
'--log-facility={}/dnsmasq.log'.format(self.config['dnsmasq_log_directory']),
|
||||
'--keep-in-foreground'
|
||||
]
|
||||
# Start the dnsmasq process in a thread
|
||||
|
@ -349,7 +348,8 @@ add rule inet filter input meta iifname {bridgenic} counter drop
|
|||
'/usr/sbin/dnsmasq {}'.format(
|
||||
' '.join(dhcp_configuration)
|
||||
),
|
||||
environment=dhcp_environment
|
||||
environment=dhcp_environment,
|
||||
logfile='{}/dnsmasq-{}.log'.format(self.config['dnsmasq_log_directory'], self.vni)
|
||||
)
|
||||
|
||||
def removeNetwork(self):
|
||||
|
|
|
@ -0,0 +1,92 @@
|
|||
PRAGMA foreign_keys = 1;
|
||||
|
||||
CREATE TABLE domains (
|
||||
id INTEGER PRIMARY KEY,
|
||||
name VARCHAR(255) NOT NULL COLLATE NOCASE,
|
||||
master VARCHAR(128) DEFAULT NULL,
|
||||
last_check INTEGER DEFAULT NULL,
|
||||
type VARCHAR(6) NOT NULL,
|
||||
notified_serial INTEGER DEFAULT NULL,
|
||||
account VARCHAR(40) DEFAULT NULL
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX name_index ON domains(name);
|
||||
|
||||
|
||||
CREATE TABLE records (
|
||||
id INTEGER PRIMARY KEY,
|
||||
domain_id INTEGER DEFAULT NULL,
|
||||
name VARCHAR(255) DEFAULT NULL,
|
||||
type VARCHAR(10) DEFAULT NULL,
|
||||
content VARCHAR(65535) DEFAULT NULL,
|
||||
ttl INTEGER DEFAULT NULL,
|
||||
prio INTEGER DEFAULT NULL,
|
||||
change_date INTEGER DEFAULT NULL,
|
||||
disabled BOOLEAN DEFAULT 0,
|
||||
ordername VARCHAR(255),
|
||||
auth BOOL DEFAULT 1,
|
||||
FOREIGN KEY(domain_id) REFERENCES domains(id) ON DELETE CASCADE ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
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 orderindex ON records(ordername);
|
||||
|
||||
|
||||
CREATE TABLE supermasters (
|
||||
ip VARCHAR(64) NOT NULL,
|
||||
nameserver VARCHAR(255) NOT NULL COLLATE NOCASE,
|
||||
account VARCHAR(40) NOT NULL
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX ip_nameserver_pk ON supermasters(ip, nameserver);
|
||||
|
||||
|
||||
CREATE TABLE comments (
|
||||
id INTEGER PRIMARY KEY,
|
||||
domain_id INTEGER 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,
|
||||
FOREIGN KEY(domain_id) REFERENCES domains(id) ON DELETE CASCADE ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
CREATE INDEX comments_domain_id_index ON comments (domain_id);
|
||||
CREATE INDEX comments_nametype_index ON comments (name, type);
|
||||
CREATE INDEX comments_order_idx ON comments (domain_id, modified_at);
|
||||
|
||||
|
||||
CREATE TABLE domainmetadata (
|
||||
id INTEGER PRIMARY KEY,
|
||||
domain_id INT NOT NULL,
|
||||
kind VARCHAR(32) COLLATE NOCASE,
|
||||
content TEXT,
|
||||
FOREIGN KEY(domain_id) REFERENCES domains(id) ON DELETE CASCADE ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
CREATE INDEX domainmetaidindex ON domainmetadata(domain_id);
|
||||
|
||||
|
||||
CREATE TABLE cryptokeys (
|
||||
id INTEGER PRIMARY KEY,
|
||||
domain_id INT NOT NULL,
|
||||
flags INT NOT NULL,
|
||||
active BOOL,
|
||||
content TEXT,
|
||||
FOREIGN KEY(domain_id) REFERENCES domains(id) ON DELETE CASCADE ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
CREATE INDEX domainidindex ON cryptokeys(domain_id);
|
||||
|
||||
|
||||
CREATE TABLE tsigkeys (
|
||||
id INTEGER PRIMARY KEY,
|
||||
name VARCHAR(255) COLLATE NOCASE,
|
||||
algorithm VARCHAR(50) COLLATE NOCASE,
|
||||
secret VARCHAR(255)
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX namealgoindex ON tsigkeys(name, algorithm);
|
Loading…
Reference in New Issue