-
Notifications
You must be signed in to change notification settings - Fork 2
/
guided_docker_compose.py
executable file
·393 lines (328 loc) · 13.8 KB
/
guided_docker_compose.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
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
#!/usr/bin/env python3
import sys
import socket
import os
import argparse
import validators
import secrets
import string
from urllib.parse import urlparse
import re
def make_color(color, msg):
bcolors = {
'HEADER': '\033[95m',
'OKBLUE': '\033[94m',
'OKGREEN': '\033[92m',
'WARNING': '\033[93m',
'FAIL': '\033[91m',
'ENDC': '\033[0m',
'BOLD': '\033[1m',
'UNDERLINE': '\033[4m',
}
return bcolors[color] + "%s" % msg + bcolors['ENDC']
def touch(fname, times=None):
with open(fname, 'a'):
os.utime(fname, times)
def generate_password(length=32):
alphabet = string.ascii_letters + string.digits
password = ''.join(secrets.choice(alphabet) for i in range(length))
return password
def check_url(url):
"""
Make sure this is a real URL
"""
if validators.url(url):
return url
else:
raise argparse.ArgumentTypeError("%s is an invalid url" % url)
def check_cert_strategy(certificate_strategy, domain):
"""
If it's an IP addr or localhost, certbot won't issue a cert, so set SELFSIGNED if it matches one of those
"""
# we assume they know what they're doing if they've elected to bring their own cert
if certificate_strategy == 'BYO' or certificate_strategy == 'SELFSIGNED':
return certificate_strategy
if (validators.ip_address.ipv4(domain) or
validators.ip_address.ipv6(domain) or
domain.startswith('localhost')):
# "Selected cert strategy was CERTBOT but detected IP address or localhost for Base URL\n"
print(
make_color(
"BOLD",
"Overriding cert strategy to SELFSIGNED since certbot won't issue for IP addresses or localhost")
)
print()
return 'SELFSIGNED'
else:
return certificate_strategy
def generate_sysconfig(output_file, template_file, force_overwrite=True, **kwargs):
with open(template_file) as sysconfig_template_file:
template = sysconfig_template_file.read().format(**kwargs)
if not os.path.exists(output_file) or force_overwrite:
f = open(output_file, 'w')
f.write(template)
f.close()
print("Wrote file to %s" % output_file)
else:
sys.stderr.write("Not writing file, add -f to override\n")
def write_docker_compose(template_file, output_file, mode='w'):
f = open(template_file, 'r')
template = f.read()
f.close()
compose = open(output_file, mode)
compose.write(template)
compose.write("\n")
compose.close()
def configure_chn():
print(
make_color(
"BOLD",
("Please enter the URL where you'd like your CHN web console available. Note that the "
"domain must be resolvable. E.g.: sub.domain.tld or localhost/chn.")))
domain = None
cert_strategy = None
touch('config/sysconfig/chnserver.env')
while not domain:
domain = input('Domain: ')
# if it's a bare fqdn, prepend the proto scheme https so we can use urlparse
# without a scheme, urlparse puts the full url + path all in netloc attribute of its return object
# that makes it difficult later to determine if there's a custom path in the url
if not domain.startswith('http'):
domain = 'https://' + domain
url_parsed = urlparse(domain)
try:
socket.getaddrinfo(url_parsed.netloc, 443)
except Exception as e:
sys.stderr.write(
make_color("FAIL",
"%s is not an active domain name\n" % url_parsed.netloc))
domain = None
while not cert_strategy:
certificate_strategies = {
'CERTBOT':
('Signed certificate by an ACME provider such as LetsEncrypt. '
'Most folks will want to use this. You must ensure your URL is '
'accessible from the ACME hosts for verification here'),
'BYO':
("Bring Your Own. Use this if you already have a signed cert"
", or if you want a real certificate without CertBot"),
'SELFSIGNED':
"Generate a simple self-signed certificate"
}
print(
make_color(
"BOLD",
"Please enter a Certificate Strategy. This should be one of:")
)
print()
for strat, strat_help in certificate_strategies.items():
print("%s: %s" % (strat, strat_help))
cert_strategy = input('Certificate Strategy: ')
if cert_strategy not in certificate_strategies.keys():
print()
sys.stderr.write(
make_color(
"FAIL", "You must use one of %s\n" %
certificate_strategies.keys()))
cert_strategy = None
generate_sysconfig(output_file="config/sysconfig/chnserver.env",
template_file="templates/chn_server.env.template",
server_base_url="https://%s%s" % (
url_parsed.netloc, url_parsed.path),
password=generate_password(),
certificate_strategy=check_cert_strategy(
cert_strategy, url_parsed.netloc)
)
def configure_mnemosyne():
retention = None
while not retention:
print()
print(
make_color(
"BOLD",
"How many days of honeypot data should be maintained in the database (default 30 days)?"
)
)
days_str = input("Number of Days: ")
try:
days = int(days_str)
if days < 1:
print(
make_color("FAIL",
"%s is not a valid number of days. Please choose a number greater than zero." % days_str)
)
continue
retention = days * 60 * 60 * 24
except ValueError:
print(
make_color("FAIL",
"%s is not a valid number." % days_str)
)
retention = None
generate_sysconfig(output_file="config/sysconfig/mnemosyne.env",
template_file="templates/mnemosyne.env.template",
retention=retention
)
def configure_hpfeeds_cif():
valid_url = None
valid_token = None
valid_provider = None
while not valid_url:
print()
cif_server_url = input('Please enter the URL for the remote CIFv3 server: ')
valid_url = validators.url(cif_server_url)
if not valid_url:
print('Invalid URL, please ensure the URL includes the scheme (https://)!')
while not valid_token:
print()
cif_write_token = input('Please enter the *write* API token for the remote CIFv3 server: ')
if re.match('[0-9a-z]{80}', cif_write_token.strip('\n')):
valid_token = True
else:
print('Input provided did not match expected pattern for a CIF API token!')
while not valid_provider:
cif_org = input('Please enter a name you wish to be associated with your organization (partnerX): ')
if re.match('[a-zA-Z0-9_-]+', cif_org):
valid_provider = True
else:
print('Input provided is not a valid provider ID; valid character set is [a-zA-Z0-9_-]')
generate_sysconfig(output_file="config/sysconfig/hpfeeds-cif.env",
template_file="templates/hpfeeds-cif.env.template",
cif_server_url=cif_server_url,
cif_token=cif_write_token,
cif_org=cif_org,
ident=generate_password(8))
def configure_chn_intel_feeds():
valid_url = None
valid_read_token = None
valid_write_token = None
valid_provider = None
while not valid_url:
print()
cif_server_url = input('Please enter the URL for the remote CIFv3 server: ')
valid_url = validators.url(cif_server_url)
if not valid_url:
print('Invalid URL, please ensure the URL includes the scheme (https://)!')
while not valid_read_token:
print()
cif_read_token = input('Please enter the *read* API token for the remote CIFv3 server: ')
if re.match('[0-9a-z]{80}', cif_read_token.strip('\n')):
valid_read_token = True
else:
print('Input provided did not match expected pattern for a CIF API token!')
while not valid_write_token:
print()
cif_write_token = input('Please enter the *write* API token for the remote CIFv3 server: ')
if re.match('[0-9a-z]{80}', cif_write_token.strip('\n')):
valid_write_token = True
else:
print('Input provided did not match expected pattern for a CIF API token!')
while not valid_provider:
cif_org = input('Please enter the name associated with your organization safelist (partnerX): ')
if re.match('[a-zA-Z0-9_-]+', cif_org):
valid_provider = True
else:
print('Input provided is not a valid provider ID; valid character set is [a-zA-Z0-9_-]')
generate_sysconfig(output_file="config/sysconfig/chn-intel-feeds.env",
template_file="templates/chn-intel-feeds.env.template",
cif_server_url=cif_server_url,
cif_write_token=cif_write_token,
cif_read_token=cif_read_token,
cif_org=cif_org)
def configure_hpfeeds_logger():
log_format = None
while not log_format:
logging_formats = {
'splunk':
'Comma delimited key/value logging format for use with Splunk',
'json':
"JSON formatted log format",
'arcsight':
"Log format for use with ArcSight SIEM appliances",
'json_raw':
"Raw JSON output from hpfeeds. More verbose that other formats, but also not normalized. Can generate a large amount of data."
}
print()
for fmt, fmt_help in logging_formats.items():
print("%s: %s" % (fmt, fmt_help))
log_format = input('Logging Format: ')
if log_format not in logging_formats.keys():
print()
sys.stderr.write(
make_color(
"FAIL", "You must use one of %s\n" %
logging_formats.keys()))
log_format = None
generate_sysconfig(output_file="config/sysconfig/hpfeeds-logger.env",
template_file="templates/hpfeeds-logger.env.template",
log_format=log_format,
ident=generate_password(8))
def main():
chn_sysconfig_exists = os.path.exists(
"config/sysconfig/chnserver.env")
reconfig = False
if chn_sysconfig_exists:
answer = input(make_color("BOLD",
"Previous chn-server.env file detected. Do you wish to reconfigure? [y/N]: "))
reconfig = answer.lower() == ("y" or "yes")
if reconfig or not chn_sysconfig_exists:
configure_chn()
configure_mnemosyne()
write_docker_compose(
"templates/docker-compose.yml.template", "docker-compose.yml", 'w')
# Check if user wants to enable hpfeeds-cif
cif_sysconfig_exists = os.path.exists(
"config/sysconfig/hpfeeds-cif.env")
reconfig = False
enable_cif = False
if cif_sysconfig_exists:
answer = input(make_color("BOLD",
"Previous hpfeeds-cif.env file detected. Do you wish to reconfigure? [y/N]: "))
reconfig = answer.lower() == ("y" or "yes")
else:
answer = input(make_color("BOLD",
"Do you wish to enable logging to a remote CIFv3 server? [y/N]: "))
enable_cif = answer.lower() == ("y" or "yes")
if enable_cif or reconfig:
configure_hpfeeds_cif()
if enable_cif or reconfig or cif_sysconfig_exists:
write_docker_compose(
"templates/docker-compose-cif.yml.template", "docker-compose.yml", 'a')
# Check if user wants to enable hpfeeds-logger
logger_sysconfig_exists = os.path.exists(
"config/sysconfig/hpfeeds-logger.env")
reconfig = False
enable_logger = False
if logger_sysconfig_exists:
answer = input(make_color("BOLD",
"Previous hpfeeds-logger.env file detected. Do you wish to reconfigure? [y/N]: "))
reconfig = answer.lower() == ("y" or "yes")
else:
answer = input(make_color("BOLD",
"Do you wish to enable logging to a local file? [y/N]: "))
enable_logger = answer.lower() == ("y" or "yes")
if enable_logger or reconfig:
configure_hpfeeds_logger()
if enable_logger or reconfig or logger_sysconfig_exists:
write_docker_compose(
"templates/docker-compose-log.yml.template", "docker-compose.yml", 'a')
# Check if user wants to enable hpfeeds-logger
feeds_exists = os.path.exists(
"config/sysconfig/chn-intel-feeds.env")
reconfig = False
enable_feeds = False
if feeds_exists:
answer = input(make_color("BOLD",
"Previous chn-intel-feeds.env file detected. Do you wish to reconfigure? [y/N]: "))
reconfig = answer.lower() == ("y" or "yes")
else:
answer = input(make_color("BOLD",
"Do you wish to enable intelligence feeds from a remote CIF instance? [y/N]: "))
enable_feeds = answer.lower() == ("y" or "yes")
if enable_feeds or reconfig:
configure_chn_intel_feeds()
if enable_feeds or reconfig or feeds_exists:
write_docker_compose(
"templates/docker-compose-chn-intel-feeds.yml.template", "docker-compose.yml", 'a')
if __name__ == "__main__":
main()