def update(self, force=False): subnet = get_walt_subnet() devices = [] for item in \ self.db.execute(QUERY_DEVICES_WITH_IP).fetchall(): device_ip = ip(item.ip) if device_ip not in subnet: continue if item.type != 'server': devices.append(dict( type=item.type, hostname=item.name, ip=device_ip, mac=item.mac, netsetup=item.netsetup)) conf = generate_dhcpd_conf(subnet, devices) with open(DHCPD_CONF_FILE, 'r') as conf_file: old_conf = conf_file.read() if conf != old_conf: with open(DHCPD_CONF_FILE, 'w') as conf_file: conf_file.write(conf) force = True # perform the restart below if force == True: do('service isc-dhcp-server restart') print('dhcpd conf updated.')
def update(self, force=False): subnet = get_walt_subnet() devices = [] for item in \ self.db.execute(QUERY_DEVICES_WITH_IP).fetchall(): device_ip = ip(item.ip) if device_ip not in subnet: continue if item.type != 'server': devices.append(dict( type=item.type, hostname=item.name, ip=device_ip, mac=item.mac, netsetup=item.netsetup)) conf = generate_dhcpd_conf(subnet, devices) with open(DHCPD_CONF_FILE, 'r') as conf_file: old_conf = conf_file.read() if conf != old_conf: with open(DHCPD_CONF_FILE, 'w') as conf_file: conf_file.write(conf) force = True # perform the restart below if force == True: do('service isc-dhcp-server restart') print 'dhcpd conf updated.'
def generate_device_keys(self, device_mac): device_id = 'vpn_' + device_mac.replace(':', '') with tempfile.TemporaryDirectory() as tmpdirname: do("ssh-keygen -C %(comment)s -N '' -t ecdsa -b 384 -f %(tmpdir)s/key" % dict(comment='walt-vpn@' + device_id, tmpdir=tmpdirname)) do("ssh-keygen -s %(vpn_ca_key)s -I '%(device_id)s' -n %(principal)s %(tmpdir)s/key.pub" % dict(vpn_ca_key=str(VPN_CA_KEY), device_id=device_id, principal='walt-vpn', tmpdir=tmpdirname)) tmpdir = Path(tmpdirname) priv_key = (tmpdir / 'key').read_text() pub_cert_key = (tmpdir / 'key-cert.pub').read_text() return (priv_key, pub_cert_key)
def try_kill_vnode(self, node_name): # get screen session # caution: "screen -S walt.node.vnode1 -X kill" may be ambiguous and # kill screen session of vnode10 instead of vnode1. # That's why we identify the full session name with "grep -ow". try: session_name = subprocess.check_output( 'screen -ls | grep -ow "[[:digit:]]*.walt.node.%(name)s"' % \ dict(name = node_name), shell=True).strip() do('screen -S "%(session)s" -X quit' % \ dict(session = session_name)) except subprocess.CalledProcessError: # screen session was probably manually killed return
def try_kill_vnode(self, node_name): # get screen session # caution: "screen -S walt.node.vnode1 -X kill" may be ambiguous and # kill screen session of vnode10 instead of vnode1. # That's why we identify the full session name with "grep -ow". try: session_name = subprocess.check_output( 'screen -ls | grep -ow "[[:digit:]]*.walt.node.%(name)s"' % \ dict(name = node_name), shell=True).strip().decode(sys.stdout.encoding) do('screen -S "%(session)s" -X quit' % \ dict(session = session_name)) except subprocess.CalledProcessError: # screen session was probably manually killed return
def netsetup_handler(self, requester, device_set, netsetup_value): # Interpret the node set, some of them may be strict devices device_infos = self.devices.parse_device_set(requester, device_set) if device_infos is None: yield False # Check the node set not_nodes = [di for di in device_infos if di.type != "node"] if len(not_nodes) > 0: msg = format_sentence( "%s is(are) not a() node(nodes), " "so it(they) does(do) not support the 'netsetup' setting.\n", [d.name for d in not_nodes], None, 'Device', 'Devices') requester.stderr.write(msg) yield False # Interpret the value new_netsetup_state = None try: new_netsetup_state = NetSetup(netsetup_value) except ValueError: requester.stderr.write( "'%s' is not a valid setting value for netsetup." % (netsetup_value)) yield False # Yield information that all things have ran correctly yield True # Effectively configure nodes for node_info in device_infos: if node_info.netsetup == new_netsetup_state: # skip this node: already configured continue # Update the database self.db.update("nodes", "mac", mac=node_info.mac, netsetup=new_netsetup_state) # Update iptables do("iptables %(action)s WALT --source '%(ip)s' --jump ACCEPT" % dict(ip=node_info.ip, action="--insert" if new_netsetup_state == NetSetup.NAT else "--delete")) # Validate the modifications self.db.commit()
def verify_conf(self): home_dir = WALT_VPN_USER['home_dir'] if not home_dir.exists(): # if not configured # create user walt-vpn home_dir = WALT_VPN_USER['home_dir'] do("useradd -U -d %(home_dir)s walt-vpn" % dict(home_dir=str(home_dir))) # generate VPN CA key VPN_CA_KEY.parent.mkdir(parents=True) do("ssh-keygen -N '' -t ecdsa -b 521 -f %s" % str(VPN_CA_KEY)) ca_pub_key = VPN_CA_KEY_PUB.read_text().strip() # create appropriate authorized_keys file authorized_keys_file = home_dir / '.ssh' / 'authorized_keys' authorized_keys_file.write_text( WALT_VPN_USER['authorized_keys_pattern'] % dict(ca_pub_key=ca_pub_key, unsecure_pub_key=UNSECURE_KEY_PUB)) # fix owner to 'walt-vpn' chown_tree(home_dir, 'walt-vpn', 'walt-vpn')
def netsetup_handler(self, requester, device_set, netsetup_value): # Interpret the node set, some of them may be strict devices device_infos = self.devices.parse_device_set(requester, device_set) if device_infos is None: yield False # Check the node set not_nodes = filter(lambda di: di.type != "node", device_infos) if len(not_nodes) > 0: msg = format_sentence("%s is(are) not a() node(nodes), " "so it(they) does(do) not support the 'netsetup' setting.\n", [d.name for d in not_nodes], None, 'Device', 'Devices') requester.stderr.write(msg) yield False # Interpret the value new_netsetup_state = None try: new_netsetup_state = NetSetup(netsetup_value) except ValueError: requester.stderr.write( "'%s' is not a valid setting value for netsetup." % (netsetup_value)) yield False # Yield information that all things have ran correctly yield True # Effectively configure nodes for node_info in device_infos: if node_info.netsetup == new_netsetup_state: # skip this node: already configured continue # Update the database self.db.update("nodes", "mac", mac=node_info.mac, netsetup=new_netsetup_state) # Update iptables do("iptables %(action)s WALT --source '%(ip)s' --jump ACCEPT" % dict(ip=node_info.ip, action="--insert" if new_netsetup_state == NetSetup.NAT else "--delete")) # Validate the modifications self.db.commit()
def create_bridge_iface(br_iface, interfaces, state_file): do('ip link add %s type bridge' % br_iface) do('brctl stp %s on' % br_iface) if os.path.exists(get_mac_file(br_iface)): with open_mac_file(br_iface, 'r') as mac_file: mac = mac_file.readline() else: mac = get_random_mac() with open_mac_file(br_iface, 'w') as mac_file: mac_file.write(mac + '\n') mac_file.flush() do('ip link set dev %s address %s' % (br_iface, mac)) for iface in interfaces: do('ip link set dev %s master %s' % (iface, br_iface)) set_iface_up(br_iface) state_file.write(br_iface + '\n')
def create_bridge_iface(br_iface, interfaces, state_file): do('ip link add %s type bridge' % br_iface) do('brctl stp %s on' % br_iface) if os.path.exists(get_mac_file(br_iface)): with open_mac_file(br_iface, 'r') as mac_file: mac = mac_file.readline() else: mac = get_random_mac() with open_mac_file(br_iface, 'w') as mac_file: mac_file.write(mac + '\n') mac_file.flush() do('ip link set dev %s address %s' % (br_iface, mac)) for iface in interfaces: do('ip link set dev %s master %s' % (iface, br_iface)) set_iface_up(br_iface) state_file.write(br_iface + '\n')
def prepare_ssh_access_for_ip(self, ip): cmd = CMD_ADD_SSH_KNOWN_HOST % dict(ip=ip) do(cmd)
def setup_ip_conf(iface, ip_conf): if ip_conf == 'dhcp': do('dhclient %s' % iface) else: do('ip addr add %s dev %s' % (ip_conf, iface))
def down(iface): with open_state_file(iface, 'r') as state_file: for line in state_file.readlines(): sub_iface = line.strip() do('ip link del dev %s' % sub_iface) remove_state_file(iface)
def cleanup_netsetup(self): # drop rules set by prepare_netsetup do("iptables --table nat --delete POSTROUTING " "! --out-interface walt-net --source %s " "--jump MASQUERADE" % str(get_walt_subnet())) do("iptables --delete FORWARD " "--in-interface walt-net " "--jump WALT") do("iptables --delete FORWARD " "--out-interface walt-net --match state --state RELATED,ESTABLISHED " "--jump ACCEPT") do("iptables --delete FORWARD " "--in-interface walt-net --out-interface walt-net " "--jump ACCEPT") do("iptables --flush WALT") do("iptables --delete-chain WALT")
def prepare_netsetup(self): # force-create the chain WALT and assert it is empty do("iptables --new-chain WALT") do("iptables --flush WALT") do("iptables --append WALT --jump DROP") # allow traffic on the bridge (virtual <-> physical nodes) do("iptables --append FORWARD " "--in-interface walt-net --out-interface walt-net " "--jump ACCEPT") # allow connections back to WalT do("iptables --append FORWARD " "--out-interface walt-net --match state --state RELATED,ESTABLISHED " "--jump ACCEPT") # jump to WALT chain for other traffic do("iptables --append FORWARD " "--in-interface walt-net " "--jump WALT") # NAT nodes traffic that is allowed to go outside do("iptables --table nat --append POSTROUTING " "! --out-interface walt-net --source %s " "--jump MASQUERADE" % str(get_walt_subnet())) # Set the configuration of all NAT-ed nodes for node_ip in self.db.execute("""\ SELECT ip FROM nodes INNER JOIN devices ON devices.mac = nodes.mac WHERE netsetup = %d; """ % NetSetup.NAT): do("iptables --insert WALT --source '%s' --jump ACCEPT" % node_ip)
def setup_ip_conf(iface, ip_conf): if ip_conf == 'dhcp': do('dhclient %s' % iface) else: do('ip addr add %s dev %s' % ( ip_conf, iface))
def create_vlan_iface(raw_iface, vlan, vlan_iface, state_file): do('ip link add link %s name %s type vlan id %d' % \ (raw_iface, vlan_iface, vlan)) set_iface_up(vlan_iface) state_file.write(vlan_iface + '\n')
def run_shell_cmd(cmd): do(cmd)
def lldp_update(): do('lldpcli update')
def del_ip_from_interface(ip, subnet, intf): do('ip addr del %s/%d dev %s' % (ip, subnet.prefixlen, intf))
def add_ip_to_interface(ip, subnet, intf): do('ip addr add %s/%d dev %s' % (ip, subnet.prefixlen, intf))
def ensure_nfsd_is_running(): if not succeeds('pidof nfsd >/dev/null'): do('service nfs-kernel-server restart')
def create_dummy_iface(iface, state_file): do('ip link add %s type dummy' % iface) set_iface_up(iface) state_file.write(iface + '\n')
def create_vlan_iface(raw_iface, vlan, vlan_iface, state_file): do('ip link add link %s name %s type vlan id %d' % \ (raw_iface, vlan_iface, vlan)) set_iface_up(vlan_iface) state_file.write(vlan_iface + '\n')
def try_kill_vnode(self, node_mac): session_name = self.get_vnode_screen_session_name(node_mac) if session_name is not None: do('screen -S "%(session)s" -X quit' % \ dict(session = session_name))
def create_dummy_iface(iface, state_file): do('ip link add %s type dummy' % iface) set_iface_up(iface) state_file.write(iface + '\n')
def set_iface_up(iface): do('ip link set up dev %s' % iface)
def set_device_config(self, requester, device_set, settings_args): # parse settings all_settings = {} for arg in settings_args: parts = arg.split('=') if len(parts) != 2: requester.stderr.write( "Provide settings as `<setting name>=<setting value>` arguments.\n" ) return all_settings[parts[0]] = parts[1] # ensure the device set is correct device_infos = self.server.devices.parse_device_set( requester, device_set) if device_infos is None: return # issue already reported # ensure all settings are known and pass related checks for setting_name, setting_value in all_settings.items(): # check the setting is known setting_info = self.settings_table.get(setting_name) if setting_info is None: requester.stderr.write( "Unknown setting '%s'. See: 'walt help show device-config'\n" % setting_name) return # verify this setting pass all checks category_check = self.category_checks[setting_info['category']] if not category_check(requester, device_infos, setting_name, setting_value, all_settings): return value_check = setting_info['value-check'] if not value_check(requester, device_infos, setting_name, setting_value, all_settings): return # effectively configure the devices should_reboot_nodes = False db_settings = all_settings.copy() for setting_name, setting_value in all_settings.items(): if setting_name == 'netsetup': new_netsetup_state = NetSetup(setting_value) for node_info in device_infos: if node_info.conf.get('netsetup', 0) == new_netsetup_state: # skip this node: already configured continue # update iptables do("iptables %(action)s WALT --source '%(ip)s' --jump ACCEPT" % dict(ip=node_info.ip, action="--insert" if new_netsetup_state == NetSetup.NAT else "--delete")) db_settings['netsetup'] = int(new_netsetup_state) should_reboot_nodes = True elif setting_name == 'cpu.cores': db_settings['cpu.cores'] = int(setting_value) should_reboot_nodes = True # update in DB (below) is enough elif setting_name == 'ram': should_reboot_nodes = True # update in DB (below) is enough elif setting_name in ('lldp.explore', 'poe.reboots'): setting_value = (setting_value.lower() == 'true' ) # convert value to boolean db_settings[setting_name] = setting_value elif setting_name == 'snmp.version': db_settings[setting_name] = int(setting_value) elif setting_name == 'snmp.community': pass # update in DB (below) is enough elif setting_name == 'type': for device_info in device_infos: device_info = device_info._asdict() device_info.update(requester=requester, type=setting_value) self.server.devices.add_or_update(**device_info) # 'type' is a column of table 'devices', so this setting should not # be recorded in 'devices.conf' column del db_settings['type'] # save in db new_vals = json.dumps(db_settings) for di in device_infos: self.server.db.execute( "update devices set conf = conf || %s::jsonb where mac = %s", (new_vals, di.mac)) self.server.db.commit() # notify user if should_reboot_nodes: requester.stdout.write( 'Done. Reboot node(s) to see new settings in effect.\n') else: requester.stdout.write('Done.\n')
def ensure_root_key_exists(): if not os.path.isfile(SERVER_KEY_PATH): do("ssh-keygen -q -t rsa -f %s -N ''" % SERVER_KEY_PATH)
def ensure_nfsd_is_running(): if not succeeds('pidof nfsd >/dev/null'): do('service nfs-kernel-server restart')
def del_ip_from_interface(ip, subnet, intf): do('ip addr del %s/%d dev %s' % (ip, subnet.prefixlen, intf))
def filter_out_8021q(iface_src, iface_dst): do(DOT1Q_FILTER % dict(src=iface_src, dst=iface_dst))
def lldp_update(): do('lldpcli update')
def prepare_ssh_access_for_ip(self, ip): cmd = CMD_ADD_SSH_KNOWN_HOST % dict(ip = ip) do(cmd)
def down(iface): with open_state_file(iface, 'r') as state_file: for line in state_file.readlines(): sub_iface = line.strip() do('ip link del dev %s' % sub_iface) remove_state_file(iface)
def prepare_netsetup(self): # force-create the chain WALT and assert it is empty do("iptables --new-chain WALT") do("iptables --flush WALT") do("iptables --append WALT --jump DROP") # allow traffic on the bridge (virtual <-> physical nodes) do("iptables --append FORWARD " "--in-interface walt-net --out-interface walt-net " "--jump ACCEPT") # allow connections back to WalT do("iptables --append FORWARD " "--out-interface walt-net --match state --state RELATED,ESTABLISHED " "--jump ACCEPT") # jump to WALT chain for other traffic do("iptables --append FORWARD " "--in-interface walt-net " "--jump WALT") # NAT nodes traffic that is allowed to go outside do("iptables --table nat --append POSTROUTING " "! --out-interface walt-net --source %s " "--jump MASQUERADE" % str(get_walt_subnet())) # Set the configuration of all NAT-ed nodes for node_ip in self.db.execute("""\ SELECT ip FROM nodes INNER JOIN devices ON devices.mac = nodes.mac WHERE netsetup = %d; """ % NetSetup.NAT): do("iptables --insert WALT --source '%s' --jump ACCEPT" % node_ip)
def filter_out_8021q(iface_src, iface_dst): do(DOT1Q_FILTER % dict(src=iface_src, dst=iface_dst))
def cleanup_netsetup(self): # drop rules set by prepare_netsetup do("iptables --table nat --delete POSTROUTING " "! --out-interface walt-net --source %s " "--jump MASQUERADE" % str(get_walt_subnet())) do("iptables --delete FORWARD " "--in-interface walt-net " "--jump WALT") do("iptables --delete FORWARD " "--out-interface walt-net --match state --state RELATED,ESTABLISHED " "--jump ACCEPT") do("iptables --delete FORWARD " "--in-interface walt-net --out-interface walt-net " "--jump ACCEPT") do("iptables --flush WALT") do("iptables --delete-chain WALT")
def set_iface_up(iface): do('ip link set up dev %s' % iface)
def add_ip_to_interface(ip, subnet, intf): do('ip addr add %s/%d dev %s' % (ip, subnet.prefixlen, intf))
def ensure_root_key_exists(): if not os.path.isfile(SERVER_KEY_PATH): do("ssh-keygen -q -t rsa -f %s -N ''" % SERVER_KEY_PATH)