1 diff --git a/handlers/main.yml b/handlers/main.yml
2 index d18df56..6b58a86 100644
3 --- a/handlers/main.yml
4 +++ b/handlers/main.yml
9 + auth_type: "{{ time.auth_type }}"
10 + ntpservers: "{{ ntp_config_server }}"
11 + hosts: "{{ hosts }}"
12 + filepath: "{{ time.serverkeys_path }}"
14 +- name: create redundant fallback ntp servers
15 + fallback_ntp_servers:
16 + hosts: "{{ hosts }}"
19 service: name={{ ntp_service_name }} state=restarted
20 diff --git a/library/fallback_ntp_servers.py b/library/fallback_ntp_servers.py
22 index 0000000..7d7907e
24 +++ b/library/fallback_ntp_servers.py
27 +# Copyright (C) 2017 Nokia
28 +# All rights reserved
30 +from ansible.module_utils.basic import AnsibleModule
34 +module: fallback_ntp_servers
35 +short_description: adding peers and fallback option
36 +description: Adding peers and an optional fallback option to the controllers.
40 + description: a list of controller hostnames
45 + return socket.gethostname().split('.')[0]
47 +def get_controllers(hosts):
50 + if "controller" in hosts[item]['service_profiles']:
51 + controllers.append(item)
54 +def is_installation_host(hosts):
55 + hostname = get_hostname()
57 + if hosts[hostname]['installation_host']:
63 +def add_peers(hosts):
64 + hosts.remove(get_hostname())
65 + with open("/etc/ntp.conf") as cnf:
66 + content = cnf.readlines()
67 + for index, line in enumerate(content):
68 + if "server" in line:
70 + content.insert(index, "peer %s\n" % peer)
72 + with open("/etc/ntp.conf", "w") as cnf:
73 + for line in content:
76 +def add_fallback(hosts):
77 + if is_installation_host(hosts):
78 + with open("/etc/ntp.conf") as cnf:
79 + content = cnf.readlines()
80 + for index, line in enumerate(content):
82 + content.insert(index, "fudge 127.127.1.0 stratum 10\n")
84 + with open("/etc/ntp.conf", "w") as cnf:
85 + for line in content:
89 + module_args = dict(hosts=dict(type='dict', required=True))
90 + module = AnsibleModule(argument_spec=module_args)
91 + controllers = get_controllers(module.params['hosts'])
92 + if get_hostname() in controllers:
93 + add_peers(controllers)
94 + add_fallback(module.params['hosts'])
95 + module.exit_json(msg="configured")
97 +if __name__ == "__main__":
99 diff --git a/library/ntp.py b/library/ntp.py
101 index 0000000..cef833e
106 +# Copyright (C) 2018 Nokia
107 +# All rights reserved
111 +from subprocess import check_output, CalledProcessError
112 +from ansible.module_utils.basic import AnsibleModule
118 +from urlparse import urlparse
122 +short_description: configuring authentication
123 +description: Configuring the authentication of NTP servers.
126 + option-name: auth_type
127 + description: the authentication type used by the server
129 + choices: crypto, symmetric, none
132 + description: a list of the controllers, in order to decide if the script is executed on controller or compute host
134 + option-name: ntpservers
135 + description: a list the NTP servers
137 + option-name: filepath
138 + description: the url of the required keys
140 + default: empty string
144 + return socket.gethostname().split('.')[0]
146 +def get_controllers(hosts):
149 + if "controller" in hosts[item]['service_profiles']:
150 + controllers.append(item)
153 +crypto_keys_dir = "/etc/ntp/crypto"
154 +crypto_parameter_file = crypto_keys_dir + "/params"
155 +symmetric_key_file = "/etc/ntp/keys"
157 +class KeyAuthDisabled(Exception):
160 +class SymmetricKeyNotFound(Exception):
163 +class NtpCryptoKeyHandler(object):
164 + supported_types = {"iff": "ntpkey_iffkey_", "mv": "ntpkey_mvta_", "mvta": "ntpkey_mvta_", "gq": "ntpkey_gqkey_"}
166 + def del_key(self, server, type):
167 + filename = "%s/%s" % (crypto_keys_dir, self._get_filename(type, server))
168 + os.remove(filename)
169 + with open("/etc/ntp.conf") as cnf:
170 + content = cnf.readlines()
171 + regex = re.compile("\Aserver\s+%s\s+autokey" % server)
172 + for index, line in enumerate(content):
173 + if re.match(regex, line) is not None:
175 + content.insert(index, "server %s\n" % server)
176 + with open("/etc/ntp.conf", "w") as cnf:
177 + for line in content:
180 + def add_key(self, servers):
181 + self._remove_symmetric_keys()
182 + self._enable_crypto_auth()
183 + self._copy_keys(servers)
184 + self._remove_old_client_keys(servers)
185 + self._create_client_key()
186 + self.set_key_permissions()
188 + def update_client_certificate(self):
189 + passwd = self._create_client_password()
190 + os.system("cd %s; ntp-keygen -q %s" % (crypto_keys_dir, passwd))
192 + def _enable_crypto_auth(self):
193 + self._create_client_password()
194 + with open("/etc/ntp.conf") as cnf:
195 + content = cnf.readlines()
196 + includefile_is_correct = False
197 + keysdir_is_correct = False
198 + for index, line in enumerate(content):
199 + if line.startswith("crypto"):
201 + elif line.startswith("includefile"):
202 + self.replace_line_in_ntpconf(line, content, index, "includefile", crypto_parameter_file)
203 + includefile_is_correct = True
204 + elif line.startswith("keysdir"):
205 + self.replace_line_in_ntpconf(line, content, index, "keysdir", crypto_keys_dir)
206 + keysdir_is_correct = True
207 + if not includefile_is_correct:
208 + content.append("includefile %s\n" % crypto_parameter_file)
209 + if not keysdir_is_correct:
210 + content.append("keysdir %s\n" % crypto_keys_dir)
211 + with open("/etc/ntp.conf", "w") as cnf:
212 + for line in content:
215 + def replace_line_in_ntpconf(self, line, filecontent, index, linestarting, lineparameter):
216 + if line.split()[1] != lineparameter:
217 + filecontent.pop(index)
218 + filecontent.insert(index, "%s %s\n" % (linestarting, lineparameter))
220 + def _create_client_password(self):
221 + if not os.path.exists(crypto_parameter_file):
222 + os.mknod(crypto_parameter_file)
223 + with open(crypto_parameter_file) as param:
224 + content = param.readlines()
225 + for line in content:
226 + match = re.match(re.compile("\Acrypto\s+pw\s+"), line)
227 + if match is not None:
228 + return line.split(None, 2)[2]
229 + randstr = ''.join([random.choice(string.ascii_letters + string.digits) for n in xrange(32)])
230 + content.append("crypto pw %s\n" % randstr)
231 + with open(crypto_parameter_file, "w") as param:
232 + for line in content:
236 + def _remove_symmetric_keys(self):
237 + with open(symmetric_key_file, "w") as obj:
240 + def _remove_old_client_keys(self, servers):
241 + present_files = os.listdir(crypto_keys_dir)
242 + possible_needed_files = [os.path.basename(crypto_parameter_file), os.path.basename(symmetric_key_file)]
243 + for srv in servers:
244 + possible_needed_files = possible_needed_files + self._get_all_supported_filenames(srv['server'])
245 + for f in possible_needed_files:
247 + present_files.remove(f)
250 + for f in present_files:
251 + os.remove("%s/%s" % (crypto_keys_dir, f))
253 + def _create_client_key(self):
254 + clientpassword = self._create_client_password()
255 + os.system("cd %s; ntp-keygen -H -c RSA-SHA1 -p %s" % (crypto_keys_dir, clientpassword))
257 + def _copy_keys(self, servers):
258 + print "copying keys"
259 + for srv in servers:
260 + self._remove_old_versions_of_key(str(srv["server"]))
261 + filename = self._get_filename(str(srv["key"]["type"]), str(srv["server"]))
262 + if not os.path.exists(filename):
264 + with open("%s/%s" % (crypto_keys_dir, filename), "w") as keyfile:
265 + for key in srv["key"]["keys"]:
266 + keyfile.write("-----BEGIN ENCRYPTED PRIVATE KEY-----\n")
267 + keyfile.write("%s\n" % key)
268 + keyfile.write("-----END ENCRYPTED PRIVATE KEY-----\n")
270 + def _get_filename(self, type, server):
271 + filename = "%s%s" % (NtpCryptoKeyHandler.supported_types[type], server)
274 + def _get_all_supported_filenames(self, server):
276 + for type in NtpCryptoKeyHandler.supported_types:
277 + filenames.append(self._get_filename(type, server))
280 + def _remove_old_versions_of_key(self, server):
281 + possible_filenames = self._get_all_supported_filenames(server)
282 + for key in possible_filenames:
284 + os.remove("%s/%s" % (crypto_keys_dir, key))
288 + def set_key_permissions(self):
289 + dircontent = os.listdir(crypto_keys_dir)
290 + for f in dircontent:
291 + os.chmod("%s/%s" % (crypto_keys_dir, f), 0600)
294 +class NtpSymmetricKeyHandler(object):
296 + def add_key(self, key, server):
297 + keys_file = symmetric_key_file
298 + if not self._is_symmetric_key_auth_enabled():
299 + self.enable_symmetric_key_auth(keys_file)
301 + keys_file = self._get_symmetric_key_file()
303 + key_id = self._get_symmetric_key_id(key, keys_file)
304 + except SymmetricKeyNotFound:
305 + key_id = self._find_highest_id(keys_file) + 1
306 + with open(keys_file, "a") as keys:
307 + keys.write("# %s\n" % server)
308 + keys.write("%s M %s\n" % (key_id, key))
309 + self._add_trustedkey(key_id)
310 + self._add_controlkey(key_id)
311 + self._add_requestkey(key_id)
313 + def _enable_key_in_ntpconf(self, old, key_id):
314 + is_replaced = False
315 + key_id = str(key_id)
316 + with open("/etc/ntp.conf", "r") as file:
317 + buff = file.readlines()
318 + for index, line in enumerate(buff):
319 + if (line.startswith(old)) and (key_id not in line):
320 + buff[index] = line.rstrip('\n') + " " + str(key_id) + "\n"
323 + elif (line.startswith(old)) and (key_id in line):
327 + with open("/etc/ntp.conf", "w") as file:
331 + with open("/etc/ntp.conf", "a") as file:
332 + file.write("%s %s\n" % (old, str(key_id)))
334 + def _add_trustedkey(self, key_id):
335 + self._enable_key_in_ntpconf("trustedkey", str(key_id))
337 + def _add_controlkey(self, key_id):
338 + self._enable_key_in_ntpconf("controlkey", str(key_id))
340 + def _add_requestkey(self, key_id):
341 + self._enable_key_in_ntpconf("requestkey", str(key_id))
343 + def _find_highest_id(self, keys_file):
345 + with open(keys_file) as keys:
346 + for o in keys.readlines():
347 + id = re.findall("^[0-9]+", o)
349 + ids.append(int(id[0]))
356 + def _is_symmetric_key_auth_enabled(self):
357 + with open("/etc/ntp.conf") as cnf:
358 + for line in cnf.read().split('\n'):
359 + if line.startswith("keys"):
363 + def _get_symmetric_key_file(self):
364 + with open("/etc/ntp.conf") as cnf:
365 + for line in cnf.read().split('\n'):
367 + return line.split()[1]
368 + raise KeyAuthDisabled()
370 + def _get_symmetric_key_id(self, key, keysfile=symmetric_key_file):
371 + with open(keysfile) as keys:
372 + for line in keys.read().split('\n'):
374 + return line.split()[0]
375 + raise SymmetricKeyNotFound()
377 + def enable_symmetric_key_auth(self, keys_loc=symmetric_key_file):
379 + self._get_symmetric_key_file()
380 + except KeyAuthDisabled:
381 + with open("/etc/ntp.conf", "a") as cnf:
382 + cnf.write("keys %s\n" % keys_loc)
385 +class NtpServerHandler(object):
387 + def __init__(self, auth_type):
388 + if auth_type == "symmetric":
389 + self.keyhandler = NtpSymmetricKeyHandler()
391 + self.keyhandler = NtpCryptoKeyHandler()
392 + self.auth_type = auth_type
394 + def delete_other_keys(self):
395 + if os.path.exists(crypto_keys_dir):
396 + dircontent = os.listdir(crypto_keys_dir)
397 + for f in dircontent:
398 + os.remove("%s/%s" % (crypto_keys_dir, f))
400 + def delete_server(self, server):
401 + with open("/etc/ntp.conf") as conf:
402 + contents = conf.readlines()
403 + for index, line in enumerate(contents):
405 + contents.pop(index)
407 + with open("/etc/ntp.conf", "w") as conf:
408 + for line in contents:
410 + self._restart_ntpd()
412 + def add_server(self, servers):
413 + self.delete_other_keys()
414 + for srv in servers:
415 + if self.auth_type != "none":
416 + if self.auth_type == "symmetric":
417 + self.keyhandler.add_key(srv['key'], srv['server'])
419 + self.keyhandler.add_key(servers)
420 + self._insert_server_to_config(srv['server'], self.auth_type, srv['key'])
422 + def _restart_ntpd(self):
423 + os.system("systemctl restart ntpd")
425 + def _insert_server_to_config(self, server, auth_type, key=None):
426 + if auth_type == "symmetric":
427 + keyfile = self.keyhandler._get_symmetric_key_file()
428 + id = self.keyhandler._get_symmetric_key_id(key, keyfile)
429 + serverline = "server " + server + " key " + str(id) + '\n'
430 + elif auth_type == "crypto":
431 + serverline = "server " + server + " autokey\n"
433 + serverline = "server " + server + '\n'
434 + with open("/etc/ntp.conf") as cnf:
435 + contents = cnf.readlines()
436 + server_was_found = False
437 + for index, line in enumerate(contents):
438 + if (server in line) and (line.startswith("server")):
439 + contents[index] = serverline
440 + server_was_found = True
442 + if not server_was_found:
444 + for line in contents:
445 + if line.startswith("server"):
448 + contents.insert(index, serverline)
449 + with open("/etc/ntp.conf", "w") as cnf:
450 + for line in contents:
453 + def get_ntp_status(self):
455 + print check_output(["systemctl", "status", "ntpd"])
456 + except CalledProcessError as e:
459 + print check_output(["ntpstat", "-u"])
460 + except CalledProcessError as e:
463 + print check_output(["ntpq", "-c", "as"])
464 + except CalledProcessError as e:
467 +def find_old_crypto_keys(remaining_servers):
468 + files = os.listdir(crypto_keys_dir)
471 + for s in remaining_servers:
472 + if 'ntpkey_' in f and s in f:
473 + with open("%s/%s" % (crypto_keys_dir, f)) as keyfcnt:
474 + content = keyfcnt.readlines()
475 + regex = re.compile("-----BEGIN ENCRYPTED PRIVATE KEY-----\n|-----END ENCRYPTED PRIVATE KEY-----\n")
476 + keycont = ''.join(content)
477 + raw_keys = re.split(regex, keycont)
478 + raw_keys = filter(None, raw_keys)
479 + if f == 'ntpkey_iffkey_%s' % s:
481 + elif f == 'ntpkey_gqkey_%s' % s:
483 + elif f == 'ntpkey_mvta_%s' % s:
486 + raise Exception("Something is wrong with the filename %s/%s" % (crypto_keys_dir, f))
487 + found_keys.append({'server': s, 'key': {'type': type, 'keys': raw_keys}})
492 +def find_old_symmetric_keys(remaining_servers):
493 + keyfile = NtpSymmetricKeyHandler()._get_symmetric_key_file()
495 + with open(keyfile) as kf:
496 + content = kf.readlines()
497 + for srv in remaining_servers:
498 + if "# %s" % srv in content:
500 + index = content.index("# %s" % srv)
501 + key = content[index + 1].split()[2]
502 + retlist.append({"server": srv, "key": key})
508 +def get_ntp_servers(url, ntpservers, auth_type):
509 + file_reachable = True
511 + if url.startswith("file://"):
512 + path = url.lstrip("file://")
514 + with open(path) as f:
515 + f_content = f.read()
517 + file_reachable = False
520 + r = requests.get(url)
521 + if r.status_code != 200:
522 + raise requests.exceptions.ConnectionError()
523 + f_content = r.content
524 + except requests.exceptions.ConnectionError:
525 + file_reachable = False
527 + yaml_content = yaml.load(f_content)
528 + for item in yaml_content:
529 + srv = item.keys()[0]
530 + if srv in ntpservers:
531 + element = {"server": srv, "key": item[srv]}
532 + servers.append(element)
533 + found_servers = [item['server'] for item in servers]
534 + if len(found_servers) != len(ntpservers):
535 + remaining_servers = [item for item in ntpservers if item not in found_servers]
536 + if auth_type == "crypto":
537 + leftover_servers = find_old_crypto_keys(remaining_servers)
538 + elif auth_type == "symmetric":
539 + leftover_servers = find_old_symmetric_keys(remaining_servers)
541 + raise Exception("Unknown authentication type for NTP!")
542 + servers = servers + leftover_servers
543 + if len(servers) != len(ntpservers):
544 + raise Exception("Something must be messed up in your config. The NTP servers provided by your key file and by your configuration doesn't match!")
550 + if o.scheme == "file":
556 + module_args = dict(auth_type=dict(type='str', required=True),
557 + hosts=dict(type='dict', required=True),
558 + ntpservers=dict(type='list', required=True),
559 + filepath=dict(type='str', required=True))
560 + module = AnsibleModule(argument_spec=module_args)
561 + controllers = get_controllers(module.params['hosts'])
562 + hostname = get_hostname()
563 + if hostname not in controllers:
564 + remove(module.params['filepath'])
565 + module.exit_json(msg="nothing to do; the script is not executed on a controller host")
567 + if module.params['auth_type'] == "symmetric" or module.params['auth_type'] == "crypto":
568 + servers = get_ntp_servers(module.params['filepath'], module.params['ntpservers'], module.params['auth_type'])
569 + handler = NtpServerHandler(module.params['auth_type'])
570 + handler.add_server(servers)
571 + elif module.params['auth_type'] == "none":
574 + raise Exception("Invalid authentication type: %s" % module.params['auth_type'])
575 + remove(module.params['filepath'].rstrip("file://"))
576 + module.exit_json(msg="all done")
578 +if __name__ == "__main__":
581 diff --git a/tasks/main.yml b/tasks/main.yml
582 index 5c80a4a..cc4f4b6 100644
587 +- shell: file=/etc/sysconfig/ntpdate; sed -i 's/SYNC_HWCLOCK=no/SYNC_HWCLOCK=yes/g' ${file};grep "SYNC_HWCLOCK=yes" ${file} || echo "SYNC_HWCLOCK=yes" >> ${file}
589 - name: Add the OS specific variables
590 include_vars: '{{ ansible_os_family }}.yml'
591 tags: [ 'configuration', 'package', 'service', 'ntp' ]
593 - name: Copy the ntp.conf template file
594 template: src=ntp.conf.j2 dest=/etc/ntp.conf
597 + - create redundant fallback ntp servers
599 tags: [ 'configuration', 'package', 'ntp' ]
601 +- name: Clear step-tickers
603 + dest: /etc/ntp/step-tickers
607 +- name: enable ntpdate at boot time
608 + shell: chkconfig ntpdate on
610 - name: Start/stop ntp service
611 service: name={{ ntp_service_name }} state={{ ntp_service_state }} enabled={{ ntp_service_enabled }} pattern='/ntpd'
612 tags: [ 'service', 'ntp' ]