def _iptables_get_nflog(): # define list with rules rules = [] # prepare regex for parsing rules rule_pattern = "^-A (?P<rule_definition>{0} -i (?P<interface>[\w\.\*\-]+).*--comment FLOW_ACCOUNTING_RULE.* -j NFLOG.*$)".format( iptables_nflog_chain) rule_re = re.compile(rule_pattern) for iptables_variant in ['iptables', 'ip6tables']: # run iptables, save output and split it by lines iptables_command = "sudo {0} -t {1} -S {2}".format( iptables_variant, iptables_nflog_table, iptables_nflog_chain) cmd(iptables_command, message='Failed to get flows list') iptables_out = stdout.splitlines() # parse each line and add information to list for current_rule in iptables_out: current_rule_parsed = rule_re.search(current_rule) if current_rule_parsed: rules.append({ 'interface': current_rule_parsed.groupdict()["interface"], 'iptables_variant': iptables_variant, 'table': iptables_nflog_table, 'rule_definition': current_rule_parsed.groupdict()["rule_definition"] }) # return list with rules return rules
def backup_partitions(disk: str): """Save sfdisk partitions output to a backup file""" device_path = '/dev/' + disk backup_ts = datetime.now().strftime('%Y-%m-%d-%H:%M') backup_file = '/var/tmp/backup_{}.{}'.format(disk, backup_ts) cmd(f'sudo /sbin/sfdisk -d {device_path} > {backup_file}')
def apply(http_api): if http_api is not None: call('sudo systemctl restart vyos-http-api.service') else: call('sudo systemctl stop vyos-http-api.service') for dep in dependencies: cmd(f'{vyos_conf_scripts_dir}/{dep}', raising=ConfigError)
def apply(cert): if cert is not None: call('systemctl restart certbot.timer') else: call('systemctl stop certbot.timer') return None for dep in dependencies: cmd(f'{vyos_conf_scripts_dir}/{dep}', raising=ConfigError)
def check_and_add_host_key(host_name): """ Filter host keys and prompt for adding key to known_hosts file, if needed. """ known_hosts = '{}/.ssh/known_hosts'.format(os.getenv('HOME')) if not os.path.exists(known_hosts): mode = 0o600 os.mknod(known_hosts, 0o600) keyscan_cmd = 'ssh-keyscan -t rsa {}'.format(host_name) try: host_key = cmd(keyscan_cmd, stderr=DEVNULL) except OSError: sys.exit("Can not get RSA host key") # libssh2 (jessie; stretch) does not recognize ec host keys, and curl # will fail with error 51 if present in known_hosts file; limit to rsa. usable_keys = False offending_keys = [] for line in fileinput.input(known_hosts, inplace=True): if host_name in line and 'ssh-rsa' in line: if line.split()[-1] != host_key.split()[-1]: offending_keys.append(line) continue else: usable_keys = True if host_name in line and not 'ssh-rsa' in line: continue sys.stdout.write(line) if usable_keys: return if offending_keys: print("Host key has changed!") print("If you trust the host key fingerprint below, continue.") fingerprint_cmd = 'ssh-keygen -lf /dev/stdin' try: fingerprint = cmd(fingerprint_cmd, stderr=DEVNULL, input=host_key) except OSError: sys.exit("Can not get RSA host key fingerprint.") print("RSA host key fingerprint is {}".format(fingerprint.split()[1])) response = input("Do you trust this host? [y]/n ") if not response or response == 'y': with open(known_hosts, 'a+') as f: print("Adding {} to the list of known" " hosts.".format(host_name)) f.write(host_key) else: sys.exit("Host not trusted")
def cancel_shutdown(): output = get_shutdown_status() if output and 'MODE' in output: timenow = datetime.now().strftime('%Y-%m-%d %H:%M:%S') try: cmd('/sbin/shutdown -c --no-wall') except OSError as e: sys.exit("Could not cancel a reboot or poweroff: %s" % e) message = "Scheduled %s has been cancelled %s" % (output['MODE'], timenow) run(f'wall {message}') else: print("Reboot or poweroff is not scheduled")
def _iptables_config(configured_ifaces): # define list of iptables commands to modify settings iptable_commands = [] # prepare extended list with configured interfaces configured_ifaces_extended = [] for iface in configured_ifaces: configured_ifaces_extended.append({ 'iface': iface, 'iptables_variant': 'iptables' }) configured_ifaces_extended.append({ 'iface': iface, 'iptables_variant': 'ip6tables' }) # get currently configured interfaces with iptables rules active_nflog_rules = _iptables_get_nflog() # compare current active list with configured one and delete excessive interfaces, add missed active_nflog_ifaces = [] for rule in active_nflog_rules: iptables = rule['iptables_variant'] interface = rule['interface'] if interface not in configured_ifaces: table = rule['table'] rule = rule['rule_definition'] iptable_commands.append(f'sudo {iptables} -t {table} -D {rule}') else: active_nflog_ifaces.append({ 'iface': interface, 'iptables_variant': iptables, }) # do not create new rules for already configured interfaces for iface in active_nflog_ifaces: if iface in active_nflog_ifaces: configured_ifaces_extended.remove(iface) # create missed rules for iface_extended in configured_ifaces_extended: iface = iface_extended['iface'] iptables = iface_extended['iptables_variant'] rule_definition = f'{iptables_nflog_chain} -i {iface} -m comment --comment FLOW_ACCOUNTING_RULE -j NFLOG --nflog-group 2 --nflog-size {default_captured_packet_size} --nflog-threshold 100' iptable_commands.append( f'sudo {iptables} -t {iptables_nflog_table} -I {rule_definition}') # change iptables for command in iptable_commands: cmd(command, raising=ConfigError)
def apply(pppoe): if pppoe['deleted']: # bail out early return None if not pppoe['disable']: # "dial" PPPoE connection intf = pppoe['intf'] cmd(f'systemctl start ppp@{intf}.service') # make logfile owned by root / vyattacfg chown(pppoe['logfile'], 'root', 'vyattacfg') return None
def test_source_nat(self): """ Configure and validate source NAT rule(s) """ path = base_path + ['source'] network = '192.168.0.0/16' self.session.set(path + ['rule', '1', 'destination', 'address', network]) self.session.set(path + ['rule', '1', 'exclude']) # check validate() - outbound-interface must be defined with self.assertRaises(ConfigSessionError): self.session.commit() self.session.set(path + ['rule', '1', 'outbound-interface', 'any']) self.session.commit() tmp = cmd('sudo nft -j list table nat') nftable_json = json.loads(tmp) condensed_json = jmespath.search(snat_pattern, nftable_json)[0] self.assertEqual(condensed_json['comment'], 'DST-NAT-1') self.assertEqual(condensed_json['address']['network'], network.split('/')[0]) self.assertEqual(str(condensed_json['address']['prefix']), network.split('/')[1])
def execute_shutdown(time, reboot=True, ask=True): if not ask: action = "reboot" if reboot else "poweroff" if not ask_yes_no("Are you sure you want to %s this system?" % action): sys.exit(0) action = "-r" if reboot else "-P" if len(time) == 0: ### T870 legacy reboot job support chk_vyatta_based_reboots() ### out = cmd(f'/sbin/shutdown {action} now', stderr=STDOUT) print(out.split(",", 1)[0]) return elif len(time) == 1: # Assume the argument is just time ts = parse_time(time[0]) if ts: cmd(f'/sbin/shutdown {action} {time[0]}', stderr=STDOUT) else: sys.exit("Invalid time \"{0}\". The valid format is HH:MM".format( time[0])) elif len(time) == 2: # Assume it's date and time ts = parse_time(time[0]) ds = parse_date(time[1]) if ts and ds: t = datetime.combine(ds, ts) td = t - datetime.now() t2 = 1 + int(td.total_seconds()) // 60 # Get total minutes cmd('/sbin/shutdown {action} {t2}', stderr=STDOUT) else: if not ts: sys.exit( "Invalid time \"{0}\". The valid format is HH:MM".format( time[0])) else: sys.exit( "Invalid time \"{0}\". A valid format is YYYY-MM-DD [HH:MM]" .format(time[1])) else: sys.exit( "Could not decode date and time. Valids formats are HH:MM or YYYY-MM-DD HH:MM" ) check_shutdown()
def genpsk(): """ generates a preshared key and shows it on stdout, it's stored only in the cli config """ psk = cmd('wg genpsk') print(psk)
def apply(config): # define variables command = None # Check if flow-accounting was removed and define command if not config['flow-accounting-configured']: command = 'systemctl stop uacctd.service' else: command = 'systemctl restart uacctd.service' # run command to start or stop flow-accounting cmd(command, raising=ConfigError, message='Failed to start/stop flow-accounting') # configure iptables rules for defined interfaces if config['interfaces']: _iptables_config(config['interfaces']) else: _iptables_config([])
def apply(config): if config is None: return None ## Send the updated data to vyos-hostsd # vyos-hostsd uses "tags" to identify data sources tag = "static" try: client = vyos.hostsd_client.Client() # Check if disable-dhcp-nameservers is configured, and if yes - delete DNS servers added by DHCP if config['no_dhcp_ns']: client.delete_name_servers('dhcp-.+') client.set_host_name(config['hostname'], config['domain_name'], config['domain_search']) client.delete_name_servers(tag) client.add_name_servers(tag, config['nameserver']) client.delete_hosts(tag) client.add_hosts(tag, config['static_host_mapping']) except vyos.hostsd_client.VyOSHostsdError as e: raise ConfigError(str(e)) ## Actually update the hostname -- vyos-hostsd doesn't do that # No domain name -- the Debian way. hostname_new = config['hostname'] # rsyslog runs into a race condition at boot time with systemd # restart rsyslog only if the hostname changed. hostname_old = cmd('hostnamectl --static') call(f'hostnamectl set-hostname --static {hostname_new}') # Restart services that use the hostname if hostname_new != hostname_old: call("systemctl restart rsyslog.service") # If SNMP is running, restart it too if process_named_running('snmpd'): call('systemctl restart snmpd.service') # restart pdns if it is used - we check for the control dir to not raise # an exception on system startup # # File "/usr/lib/python3/dist-packages/vyos/configsession.py", line 128, in __run_command # raise ConfigSessionError(output) # vyos.configsession.ConfigSessionError: [ system domain-name vyos.io ] # Fatal: Unable to generate local temporary file in directory '/run/powerdns': No such file or directory if os.path.isdir('/run/powerdns'): ret = run('/usr/bin/rec_control --socket-dir=/run/powerdns ping') if ret == 0: call('systemctl restart pdns-recursor.service') return None
def request_certbot(cert): email = cert.get('email') if email is not None: email_flag = '-m {0}'.format(email) else: email_flag = '' domains = cert.get('domains') if domains is not None: domain_flag = '-d ' + ' -d '.join(domains) else: domain_flag = '' certbot_cmd = 'certbot certonly -n --nginx --agree-tos --no-eff-email --expand {0} {1}'.format(email_flag, domain_flag) cmd(certbot_cmd, raising=ConfigError, message="The certbot request failed for the specified domains.")
def get_config(): nat = deepcopy(default_config_data) conf = Config() # read in current nftable (once) for further processing tmp = cmd('nft -j list table raw') nftable_json = json.loads(tmp) # condense the full JSON table into a list with only relevand informations pattern = 'nftables[?rule].rule[?expr[].jump].{chain: chain, handle: handle, target: expr[].jump.target | [0]}' condensed_json = jmespath.search(pattern, nftable_json) if not conf.exists(['nat']): nat['helper_functions'] = 'remove' # Retrieve current table handler positions nat['pre_ct_ignore'] = get_handler(condensed_json, 'PREROUTING', 'VYATTA_CT_HELPER') nat['pre_ct_conntrack'] = get_handler(condensed_json, 'PREROUTING', 'NAT_CONNTRACK') nat['out_ct_ignore'] = get_handler(condensed_json, 'OUTPUT', 'VYATTA_CT_HELPER') nat['out_ct_conntrack'] = get_handler(condensed_json, 'OUTPUT', 'NAT_CONNTRACK') nat['deleted'] = True return nat # check if NAT connection tracking helpers need to be set up - this has to # be done only once if not get_handler(condensed_json, 'PREROUTING', 'NAT_CONNTRACK'): nat['helper_functions'] = 'add' # Retrieve current table handler positions nat['pre_ct_ignore'] = get_handler(condensed_json, 'PREROUTING', 'VYATTA_CT_IGNORE') nat['pre_ct_conntrack'] = get_handler(condensed_json, 'PREROUTING', 'VYATTA_CT_PREROUTING_HOOK') nat['out_ct_ignore'] = get_handler(condensed_json, 'OUTPUT', 'VYATTA_CT_IGNORE') nat['out_ct_conntrack'] = get_handler(condensed_json, 'OUTPUT', 'VYATTA_CT_OUTPUT_HOOK') # set config level for parsing in NAT configuration conf.set_level(['nat']) # use a common wrapper function to read in the source / destination # tree from the config - thus we do not need to replicate almost the # same code :-) for tgt in ['source', 'destination', 'nptv6']: nat[tgt] = parse_source_destination(conf, tgt) return nat
def get_sys_build_version(): if not os.path.exists(verf): return None a = cmd('/usr/libexec/vyos/op_mode/version.py') if re.search('^Built on:.+', a, re.M) == None: return None dt = (re.sub('Built on: +', '', re.search('^Built on:.+', a, re.M).group(0))) return datetime.strptime(dt, '%a %d %b %Y %H:%M %Z')
def apply(config): if config is None: return None ## Send the updated data to vyos-hostsd # vyos-hostsd uses "tags" to identify data sources tag = "static" try: client = vyos.hostsd_client.Client() # Check if disable-dhcp-nameservers is configured, and if yes - delete DNS servers added by DHCP if config['no_dhcp_ns']: client.delete_name_servers('dhcp-.+') client.set_host_name(config['hostname'], config['domain_name'], config['domain_search']) client.delete_name_servers(tag) client.add_name_servers(tag, config['nameserver']) client.delete_hosts(tag) client.add_hosts(tag, config['static_host_mapping']) except vyos.hostsd_client.VyOSHostsdError as e: raise ConfigError(str(e)) ## Actually update the hostname -- vyos-hostsd doesn't do that # No domain name -- the Debian way. hostname_new = config['hostname'] # rsyslog runs into a race condition at boot time with systemd # restart rsyslog only if the hostname changed. hostname_old = cmd('hostnamectl --static') call(f'hostnamectl set-hostname --static {hostname_new}') # Restart services that use the hostname if hostname_new != hostname_old: call("systemctl restart rsyslog.service") # If SNMP is running, restart it too ret = run("pgrep snmpd") if ret == 0: call("systemctl restart snmpd.service") # restart pdns if it is used ret = run('/usr/bin/rec_control ping') if ret == 0: call('/etc/init.d/pdns-recursor restart >/dev/null') return None
def failsafe(config_file_name): fail_msg = """ !!!!! There were errors loading the configuration Please examine the errors in {0} and correct !!!!! """.format(TRACE_FILE) print(fail_msg, file=sys.stderr) users = [x[0] for x in pwd.getpwall()] if 'vyos' in users: return try: with open(config_file_name, 'r') as f: config_file = f.read() except Exception as e: print("Catastrophic: no default config file " "'{0}'".format(config_file_name)) sys.exit(1) config = ConfigTree(config_file) if not config.exists([ 'system', 'login', 'user', 'vyos', 'authentication', 'encrypted-password' ]): print("No password entry in default config file;") print("unable to recover password for user 'vyos'.") sys.exit(1) else: passwd = config.return_value([ 'system', 'login', 'user', 'vyos', 'authentication', 'encrypted-password' ]) cmd(f"useradd -s /bin/bash -G 'users,sudo' -m -N -p '{passwd}' vyos")
def generate_self_signed(cert_data): san_config = None if ssl.OPENSSL_VERSION_INFO < (1, 1, 1, 0, 0): san_config = tempfile.NamedTemporaryFile() with open(san_config.name, 'w') as fd: fd.write('[req]\n') fd.write('distinguished_name=req\n') fd.write('[san]\n') fd.write('subjectAltName=DNS:vyos\n') openssl_req_cmd = ('openssl req -x509 -nodes -days {lifetime} ' '-newkey rsa:4096 -keyout {key} -out {crt} ' '-subj "/O=Sentrium/OU=VyOS/CN=vyos" ' '-extensions san -config {san_conf}' ''.format(san_conf=san_config.name, **cert_data)) else: openssl_req_cmd = ('openssl req -x509 -nodes -days {lifetime} ' '-newkey rsa:4096 -keyout {key} -out {crt} ' '-subj "/O=Sentrium/OU=VyOS/CN=vyos" ' '-addext "subjectAltName=DNS:vyos"' ''.format(**cert_data)) try: cmd(openssl_req_cmd, message='Called process error') except OSError as err: print(err) # XXX: seems wrong to ignore the failure os.chmod('{key}'.format(**cert_data), 0o400) with open('{conf}'.format(**cert_data), 'w') as f: f.write('ssl_certificate {crt};\n'.format(**cert_data)) f.write('ssl_certificate_key {key};\n'.format(**cert_data)) if san_config: san_config.close()
def status_self_signed(cert_data): # check existence and expiration date path = pathlib.Path(cert_data['conf']) if not path.is_file(): return False path = pathlib.Path(cert_data['crt']) if not path.is_file(): return False path = pathlib.Path(cert_data['key']) if not path.is_file(): return False # check if certificate is 1/2 past lifetime, with openssl -checkend end_days = int(cert_data['lifetime']) end_seconds = int(0.5 * 60 * 60 * 24 * end_days) checkend_cmd = 'openssl x509 -checkend {end} -noout -in {crt}'.format( end=end_seconds, **cert_data) try: cmd(checkend_cmd, message='Called process error') return True except OSError as err: if err.errno == 1: return False print(err)
def _get_flows_list(): # run command to get flows list out = cmd(f'/usr/bin/pmacct -s -O json -T flows -p {uacctd_pipefile}', message='Failed to get flows list') # read output flows_out = out.splitlines() # make a list with flows flows_list = [] for flow_line in flows_out: flows_list.append(eval(flow_line)) # return list of flows return flows_list
def test_add_address_single(self): super().test_add_address_single() command = 'sudo ip -j l2tp show session' json_out = json.loads(cmd(command)) for interface in self._options: for config in json_out: if config['interface'] == interface: # convert list with configuration items into a dict dict = {} for opt in self._options[interface]: dict.update( {opt.split()[0].replace('-', '_'): opt.split()[1]}) for key in [ 'peer_session_id', 'peer_tunnel_id', 'session_id', 'tunnel_id' ]: self.assertEqual(str(config[key]), dict[key])
def _get_ifaces_dict(): # run command to get ifaces list out = cmd('/bin/ip link show') # read output ifaces_out = out.splitlines() # make a dictionary with interfaces and indexes ifaces_dict = {} regex_filter = re.compile( '^(?P<iface_index>\d+):\ (?P<iface_name>[\w\d\.]+)[:@].*$') for iface_line in ifaces_out: if regex_filter.search(iface_line): ifaces_dict[int( regex_filter.search(iface_line).group('iface_index') )] = regex_filter.search(iface_line).group('iface_name') # return dictioanry return ifaces_dict
#!/usr/bin/env python3 # # Copyright (C) 2019 VyOS maintainers and contributors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as # published by the Free Software Foundation. # # 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 <http://www.gnu.org/licenses/>. import sys from vyos.util import ask_yes_no from vyos.util import cmd if not ask_yes_no('Do you really want to remove the existing SSH host keys?'): sys.exit(0) cmd('sudo rm -v /etc/ssh/ssh_host_*') cmd('sudo dpkg-reconfigure openssh-server') cmd('sudo systemctl restart ssh')
if __name__ == '__main__': if (len(sys.argv) < 1): print("Must specify OpenVPN interface name!") sys.exit(1) interface = sys.argv[1] if os.path.isfile(get_config_name(interface)): pidfile = '/var/run/openvpn/{}.pid'.format(interface) if process_running(pidfile): command = 'start-stop-daemon' command += ' --stop' command += ' --oknodo' command += ' --quiet' command += ' --pidfile ' + pidfile cmd(command) # When stopping OpenVPN we need to wait for the 'old' interface to # vanish from the Kernel, if it is not gone, OpenVPN will report: # ERROR: Cannot ioctl TUNSETIFF vtun10: Device or resource busy (errno=16) while interface in interfaces(): sleep(0.250) # 250ms # re-start OpenVPN process command = 'start-stop-daemon' command += ' --start' command += ' --oknodo' command += ' --quiet' command += ' --pidfile ' + get_pid_file(interface) command += ' --exec /usr/sbin/openvpn' # now pass arguments to openvpn binary
type=int, required=False, help='top records for output filtration') # parse arguments cmd_args = cmd_args_parser.parse_args() # main logic # do nothing if uacctd daemon is not running if not _uacctd_running(): print("flow-accounting is not active") sys.exit(1) # restart pmacct daemon if cmd_args.action == 'restart': # run command to restart flow-accounting cmd('/usr/bin/sudo /bin/systemctl restart uacctd', message='Failed to restart flow-accounting') # clear in-memory collected flows if cmd_args.action == 'clear': _check_imt() # run command to clear flows cmd(f'/usr/bin/pmacct -e -p {uacctd_pipefile}', message='Failed to clear flows') # show table with flows if cmd_args.action == 'show': _check_imt() # get interfaces index and names ifaces_dict = _get_ifaces_dict() # get flows flows_list = _get_flows_list()
def make_password_hash(password): """ Makes a password hash for /etc/shadow using mkpasswd """ mkpassword = '******' return cmd(mkpassword, input=password, timeout=5)
def list_rules(): command = 'ip -j -4 rule show' answer = loads(cmd(command)) return [_ for _ in answer if _]
def _cmd(command): cmd(command, raising=ConfigError, message='Error changing VRF')
Hardware vendor: {{hardware_vendor}} Hardware model: {{hardware_model}} Hardware S/N: {{hardware_serial}} Hardware UUID: {{hardware_uuid}} Copyright: VyOS maintainers and contributors """ if __name__ == '__main__': args = parser.parse_args() version_data = vyos.version.get_version_data() # Get system architecture (well, kernel architecture rather) version_data['system_arch'] = cmd('uname -m') # Get hypervisor name, if any system_type = "bare metal" try: hypervisor = cmd('hvinfo', stderr=DEVNULL) system_type = "{0} guest".format(hypervisor) except OSError: # hvinfo returns 1 if it cannot detect any hypervisor pass version_data['system_type'] = system_type # Get boot type, it can be livecd, installed image, or, possible, a system installed # via legacy "install system" mechanism # In installed images, the squashfs image file is named after its image version, # while on livecd it's just "filesystem.squashfs", that's how we tell a livecd boot