#!/usr/bin/python -u # -*- coding: utf-8 -*- import os import re import sys import subprocess import syslog import copy import jinja2 import ipaddr as ipaddress from swsssdk import ConfigDBConnector # FILE PAM_AUTH_CONF = "/etc/pam.d/common-auth-sonic" PAM_AUTH_CONF_TEMPLATE = "/usr/share/sonic/templates/common-auth-sonic.j2" NSS_TACPLUS_CONF = "/etc/tacplus_nss.conf" NSS_TACPLUS_CONF_TEMPLATE = "/usr/share/sonic/templates/tacplus_nss.conf.j2" NSS_CONF = "/etc/nsswitch.conf" # TACACS+ TACPLUS_SERVER_PASSKEY_DEFAULT = "" TACPLUS_SERVER_TIMEOUT_DEFAULT = "5" TACPLUS_SERVER_AUTH_TYPE_DEFAULT = "pap" def is_valid_hostname(hostname): if hostname[-1] == "." or len(hostname) > 253: return False allowed = re.compile("(?!-)[A-Z\d-]{1,63}(? {1}.new; mv -f {1} {1}.old; mv -f {1}.new {1}".format(' -e '.join(operations), filename) os.system(cmd) def modify_conf_file(self): auth = self.auth_default.copy() auth.update(self.auth) tacplus_global = self.tacplus_global_default.copy() tacplus_global.update(self.tacplus_global) servers_conf = [] if self.tacplus_servers: for addr in self.tacplus_servers: server = tacplus_global.copy() server['ip'] = addr server.update(self.tacplus_servers[addr]) servers_conf.append(server) servers_conf = sorted(servers_conf, key=lambda t: int(t['priority']), reverse=True) template_file = os.path.abspath(PAM_AUTH_CONF_TEMPLATE) env = jinja2.Environment(loader=jinja2.FileSystemLoader('/'), trim_blocks=True) env.filters['sub'] = sub template = env.get_template(template_file) pam_conf = template.render(auth=auth, servers=servers_conf) with open(PAM_AUTH_CONF, 'w') as f: f.write(pam_conf) # Modify common-auth include file in /etc/pam.d/login and sshd if os.path.isfile(PAM_AUTH_CONF): self.modify_single_file('/etc/pam.d/sshd', [ "'/^@include/s/common-auth$/common-auth-sonic/'" ]) self.modify_single_file('/etc/pam.d/login', [ "'/^@include/s/common-auth$/common-auth-sonic/'" ]) else: self.modify_single_file('/etc/pam.d/sshd', [ "'/^@include/s/common-auth-sonic$/common-auth/'" ]) self.modify_single_file('/etc/pam.d/login', [ "'/^@include/s/common-auth-sonic$/common-auth/'" ]) # Add tacplus in nsswitch.conf if TACACS+ enable if 'tacacs+' in auth['login']: if os.path.isfile(NSS_CONF): self.modify_single_file(NSS_CONF, [ "'/tacplus/b'", "'/^passwd/s/compat/tacplus &/'"]) else: if os.path.isfile(NSS_CONF): self.modify_single_file(NSS_CONF, [ "'/^passwd/s/tacplus //'" ]) # Set tacacs+ server in nss-tacplus conf template_file = os.path.abspath(NSS_TACPLUS_CONF_TEMPLATE) template = env.get_template(template_file) nss_tacplus_conf = template.render(debug=self.debug, servers=servers_conf) with open(NSS_TACPLUS_CONF, 'w') as f: f.write(nss_tacplus_conf) class HostConfigDaemon: def __init__(self): self.config_db = ConfigDBConnector() self.config_db.connect(wait_for_init=True, retry_on=True) syslog.syslog(syslog.LOG_INFO, 'ConfigDB connect success') aaa = self.config_db.get_table('AAA') tacacs_global = self.config_db.get_table('TACPLUS') tacacs_server = self.config_db.get_table('TACPLUS_SERVER') self.aaacfg = AaaCfg() self.aaacfg.load(aaa, tacacs_global, tacacs_server) self.hostname_cache="" lpbk_table = self.config_db.get_table('LOOPBACK_INTERFACE') self.iptables = Iptables() self.iptables.load(lpbk_table) def aaa_handler(self, key, data): self.aaacfg.aaa_update(key, data) def tacacs_server_handler(self, key, data): self.aaacfg.tacacs_server_update(key, data) log_data = copy.deepcopy(data) if log_data.has_key('passkey'): log_data['passkey'] = obfuscate(log_data['passkey']) syslog.syslog(syslog.LOG_INFO, 'value of {} changed to {}'.format(key, log_data)) def tacacs_global_handler(self, key, data): self.aaacfg.tacacs_global_update(key, data) log_data = copy.deepcopy(data) if log_data.has_key('passkey'): log_data['passkey'] = obfuscate(log_data['passkey']) syslog.syslog(syslog.LOG_INFO, 'value of {} changed to {}'.format(key, log_data)) def hostname_handler(self, key, data): if key != "localhost": return hostname = data.get("hostname") if not hostname: syslog.syslog(syslog.LOG_WARNING, "hostname key is missing") return if not is_valid_hostname(hostname): syslog.syslog(syslog.LOG_WARNING, "hostname {} is invalid".format(hostname)) return if hostname == self.hostname_cache: return syslog.syslog(syslog.LOG_INFO, "Get all running containers") cmd = 'docker ps --format "{{.Names}}"' try: containers = subprocess.check_output(cmd, shell=True).split("\n")[:-1] except subprocess.CalledProcessError as err: syslog.syslog(syslog.LOG_ERR, "{} - failed: return code - {}, output:\n{}" .format(err.cmd, err.returncode, err.output)) for name in containers: script = '/usr/bin/{}.sh'.format(name) exists = os.path.isfile(script) if not exists: syslog.syslog(syslog.LOG_ERR, "Can't find control script for {}".format(name)) continue cmd = "{} updateHostName {}".format(script, hostname) try: subprocess.check_call(cmd, shell=True) except subprocess.CalledProcessError as err: syslog.syslog(syslog.LOG_ERR, "{} - failed: return code - {}, output:\n{}" .format(err.cmd, err.returncode, err.output)) self.hostname_cache = hostname def lpbk_handler(self, key, data): key = ConfigDBConnector.deserialize_key(key) #Check if delete operation by fetch existing keys keys = self.config_db.get_keys('LOOPBACK_INTERFACE') if key in keys: add = True else: add = False self.iptables.iptables_handler(key, data, add) def feature_status_handler(self, key, data): status_data = self.config_db.get_table('FEATURE') for key in status_data.keys(): if not key: syslog.syslog(syslog.LOG_WARNING, "FEATURE key is missing") return status = status_data[key]['status'] if not status: syslog.syslog(syslog.LOG_WARNING, "status is missing for {}".format(key)) return if status == "enabled": start_cmds=[] start_cmds.append("sudo systemctl enable {}".format(key)) start_cmds.append("sudo systemctl start {}".format(key)) for cmd in start_cmds: syslog.syslog(syslog.LOG_INFO, "Running cmd - {}".format(cmd)) try: subprocess.check_call(cmd, shell=True) except subprocess.CalledProcessError as err: syslog.syslog(syslog.LOG_ERR, "{} - failed: return code - {}, output:\n{}" .format(err.cmd, err.returncode, err.output)) return syslog.syslog(syslog.LOG_INFO, "Feature '{}' is enabled and started".format(key)) elif status == "disabled": stop_cmds=[] stop_cmds.append("sudo systemctl stop {}".format(key)) stop_cmds.append("sudo systemctl disable {}".format(key)) for cmd in stop_cmds: syslog.syslog(syslog.LOG_INFO, "Running cmd - {}".format(cmd)) try: subprocess.check_call(cmd, shell=True) except subprocess.CalledProcessError as err: syslog.syslog(syslog.LOG_ERR, "{} - failed: return code - {}, output:\n{}" .format(err.cmd, err.returncode, err.output)) return syslog.syslog(syslog.LOG_INFO, "Feature '{}' is stopped and disabled".format(key)) else: syslog.syslog(syslog.LOG_ERR, "Unexpected status value '{}' for '{}'".format(status, key)) def start(self): self.config_db.subscribe('AAA', lambda table, key, data: self.aaa_handler(key, data)) self.config_db.subscribe('TACPLUS_SERVER', lambda table, key, data: self.tacacs_server_handler(key, data)) self.config_db.subscribe('TACPLUS', lambda table, key, data: self.tacacs_global_handler(key, data)) self.config_db.subscribe('DEVICE_METADATA', lambda table, key, data: self.hostname_handler(key, data)) self.config_db.subscribe('LOOPBACK_INTERFACE', lambda table, key, data: self.lpbk_handler(key, data)) self.config_db.subscribe('FEATURE', lambda table, key, data: self.feature_status_handler(key, data)) self.config_db.listen() def main(): daemon = HostConfigDaemon() daemon.start() if __name__ == "__main__": main()