-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathunbound_reload_forwards.py
196 lines (153 loc) · 5.17 KB
/
unbound_reload_forwards.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
#!/usr/bin/python
"""
Synchronize Unbound forward zones without reloading Unbound
This script synchronizes Unbound file and live forward zone config without
reloading the Unbound service. This script assumes that all zone forwards look
like the statement below and only have one IPv4 forward address.
forward-zone:
name: "domain.com"
forward-addr: 128.66.0.1
"""
import logging
import logging.handlers
import os
import re
import subprocess
import sys
__author__ = "Mikal Sande"
__email__ = "[email protected]"
#
# Variables
#
INPUT_FILES = [
'/etc/unbound/conf.d/malwarezones.conf',
]
UNBOUND_CONTROL = '/usr/sbin/unbound-control'
INPUT_SELECTOR = r'(^name:|^forward-addr:)'
INPUT_VALIDATOR = r'.*\..*\. IN forward '
ZONES_ADDED = []
ZONES_REMOVED = []
#
# Functions
#
def loadfileconfig():
"""
Load list of configured forward zones from file into an array.
"""
zones = []
for infile in INPUT_FILES:
# read file into array
rawinput = []
with open(infile) as inputfile:
rawinput = inputfile.readlines()
inputfile.close()
selectedinput = []
for line in rawinput:
# remove leading and trailing whitespace
line = line.strip()
# remove double quotes, "
line = line.replace('"', '')
# select the lines we need and extract
# the second field
if re.search(INPUT_SELECTOR, line):
line = line.split(' ')
line = line[1]
selectedinput.append(line)
# Merge two and two items into a list of tuples
# put into zones
iterator = iter(selectedinput)
zones.extend(zip(iterator, iterator))
return zones
def loadliveconfig():
"""
Load list of configured forward zones from unbound-control into an array.
"""
zones = []
# load live config from unbound-control
(out, err) = subprocess.Popen([UNBOUND_CONTROL, 'list_forwards'],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE).communicate()
# exit if unbound-control returns an error
if len(err) > 0:
for split in err.splitlines():
print split
sys.exit(1)
# do input validation
for line in out.splitlines():
if line is None:
continue
if re.search(INPUT_VALIDATOR, line):
# split line into array
line = line.split()
# extract and clean fields
domain = line[0].strip()
domain = domain.strip('.')
ipaddress = line[3].strip()
# append tuple to array
zones.append((domain, ipaddress))
return zones
def compareconfigs(fileconfig, liveconfig):
"""
Compare two lists of forwarded zones.
"""
# Make sets from the input arrays
file_set = set(fileconfig)
unbound_set = set(liveconfig)
# Set of forward zones to be added to live config
zoneadd = file_set.difference(unbound_set)
# Set of forward zones to be removed from live config
zoneremove = unbound_set.difference(file_set)
return (zoneadd, zoneremove)
def addforwards(zoneadd):
"""
Call unbound-control to add a forward zone to live config
"""
for zone in zoneadd:
os.system('{0} forward_add {1} {2} > /dev/null 2>&1'.format(
UNBOUND_CONTROL, zone[0], zone[1]))
ZONES_ADDED.append(zone)
def removeforwards(zoneremove):
"""
Call unbound-control to remove a forwarded zone from live config
"""
for zone in zoneremove:
os.system('{0} forward_remove {1} > /dev/null 2>&1'.format(
UNBOUND_CONTROL, zone[0]))
ZONES_REMOVED.append(zone)
#
# Main
#
if __name__ == "__main__":
# Load live config and file config
FILE_ZONES = loadfileconfig()
UNBOUND_ZONES = loadliveconfig()
# Compare file config with live config and get two sets of zones back
(ZONE_ADD, ZONE_REMOVE) = compareconfigs(FILE_ZONES, UNBOUND_ZONES)
# Add forward zones to live config
addforwards(ZONE_ADD)
# Remove forward zones from live config
removeforwards(ZONE_REMOVE)
# Reload live config
UNBOUND_ZONES = loadliveconfig()
# Compare file config with live config on more time
(ZONE_ADD, ZONE_REMOVE) = compareconfigs(FILE_ZONES, UNBOUND_ZONES)
# Print to stdout and syslog if there is a difference between them
if len(ZONE_ADD) > 0 or len(ZONE_REMOVE) > 0:
MESSAGE = """unbound_reload_forwards.py: There is difference between file and
live config! This should not happen!"""
print MESSAGE
HANLDER = logging.handlers.SysLogHandler(address='/dev/log')
LOGGER = logging.getLogger('unbound-fix-forwards.py')
LOGGER.addHandler(HANLDER)
LOGGER.critical(MESSAGE)
# Print status if run interactively
if os.isatty(sys.stdout.fileno()):
if len(ZONES_ADDED) > 0:
print "Added:"
for x in ZONES_ADDED:
print " {0}".format(x)
if len(ZONES_REMOVED) > 0:
print
print "Removed:"
for x in ZONES_REMOVED:
print " {0}".format(x)