Commit bcace337 authored by Tomáš Peterka's avatar Tomáš Peterka

Add TUN interface to every partition instead of allowing SUDO command to do that

parent 318c4925
...@@ -34,6 +34,7 @@ import fcntl ...@@ -34,6 +34,7 @@ import fcntl
import grp import grp
import json import json
import logging import logging
import math
import netaddr import netaddr
import netifaces import netifaces
import os import os
...@@ -60,6 +61,8 @@ from slapos.util import mkdir_p ...@@ -60,6 +61,8 @@ from slapos.util import mkdir_p
import slapos.slap as slap import slapos.slap as slap
from slapos import version from slapos import version
logger = logging.getLogger("slapos.format")
def prettify_xml(xml): def prettify_xml(xml):
root = lxml.etree.fromstring(xml) root = lxml.etree.fromstring(xml)
return lxml.etree.tostring(root, pretty_print=True) return lxml.etree.tostring(root, pretty_print=True)
...@@ -408,7 +411,7 @@ class Computer(object): ...@@ -408,7 +411,7 @@ class Computer(object):
tap_gateway_interface=tap_gateway_interface, tap_gateway_interface=tap_gateway_interface,
) )
for partition_dict in dumped_dict['partition_list']: for i, partition_dict in enumerate(dumped_dict['partition_list']):
if partition_dict['user']: if partition_dict['user']:
user = User(partition_dict['user']['name']) user = User(partition_dict['user']['name'])
...@@ -425,6 +428,12 @@ class Computer(object): ...@@ -425,6 +428,12 @@ class Computer(object):
else: else:
tap = Tap(partition_dict['reference']) tap = Tap(partition_dict['reference'])
if partition_dict.get('tun') is not None and partition_dict['tun'].get('ipv4_addr') is not None:
tun = Tun(partition_dict['tun']['name'], i, len(dumped_dict['partition_list']))
tun.ipv4_addr = partition_dict['tun']['ipv4_addr']
else:
tun = Tun("slaptun" + str(i), i, len(dumped_dict['partition_list']))
address_list = partition_dict['address_list'] address_list = partition_dict['address_list']
external_storage_list = partition_dict.get('external_storage_list', []) external_storage_list = partition_dict.get('external_storage_list', [])
...@@ -434,6 +443,7 @@ class Computer(object): ...@@ -434,6 +443,7 @@ class Computer(object):
user=user, user=user,
address_list=address_list, address_list=address_list,
tap=tap, tap=tap,
tun=tun,
external_storage_list=external_storage_list, external_storage_list=external_storage_list,
) )
...@@ -479,7 +489,12 @@ class Computer(object): ...@@ -479,7 +489,12 @@ class Computer(object):
def construct(self, alter_user=True, alter_network=True, create_tap=True, use_unique_local_address_block=False): def construct(self, alter_user=True, alter_network=True, create_tap=True, use_unique_local_address_block=False):
""" """
Construct the computer object as it is. Setup underlaying OS so it reflects this instance (``self``).
- setup interfaces and addresses
- setup TAP and TUN interfaces
- add groups and users
- construct partitions inside slapgrid
""" """
if alter_network and self.address is not None: if alter_network and self.address is not None:
self.interface.addAddr(self.address, self.netmask) self.interface.addAddr(self.address, self.netmask)
...@@ -506,20 +521,6 @@ class Computer(object): ...@@ -506,20 +521,6 @@ class Computer(object):
os.chown(slapsoft.path, slapsoft_pw.pw_uid, slapsoft_pw.pw_gid) os.chown(slapsoft.path, slapsoft_pw.pw_uid, slapsoft_pw.pw_gid)
os.chmod(self.software_root, 0o755) os.chmod(self.software_root, 0o755)
# XXX-katomaso TODO: remove hardcoded hack for amarisoft
# Any slapuser needs to have access to LTE service to start/stop it
# Amarisoft uses (misplaced) systemd unit file /usr/lib/systemd/system/lte.service
# We allow anybody from the group "slapsoft" to operate that service
if os.path.exists("/usr/lib/systemd/system/lte.service"):
with open("/etc/sudoers.d/slapos-amarisoft", "wt") as fo:
fo.writelines((
"%slapsoft ALL=(root) NOPASSWD: /bin/systemctl start lte,\\\n",
" /bin/systemctl stop lte,\\\n",
" /bin/systemctl enable lte,\\\n",
" /bin/systemctl disable lte\n",
""))
os.chmod("/etc/sudoers.d/slapos-amarisoft", 0o440)
# get list of instance external storage if exist # get list of instance external storage if exist
instance_external_list = [] instance_external_list = []
if self.instance_storage_home: if self.instance_storage_home:
...@@ -579,6 +580,9 @@ class Computer(object): ...@@ -579,6 +580,9 @@ class Computer(object):
partition.tap.ipv4_gateway = gateway_addr_dict['addr'] partition.tap.ipv4_gateway = gateway_addr_dict['addr']
partition.tap.ipv4_network = gateway_addr_dict['network'] partition.tap.ipv4_network = gateway_addr_dict['network']
partition.tap.createRoutes() partition.tap.createRoutes()
# create TUN interface per partition as well
partition.tun.createWithOwner(owner)
partition.tun.createRoutes()
# Reconstructing partition's directory # Reconstructing partition's directory
partition.createPath(alter_user) partition.createPath(alter_user)
...@@ -628,14 +632,16 @@ class Computer(object): ...@@ -628,14 +632,16 @@ class Computer(object):
class Partition(object): class Partition(object):
"Represent a computer partition" "Represent a computer partition"
def __init__(self, reference, path, user, address_list, tap, external_storage_list=[]): def __init__(self, reference, path, user, address_list,
tap, external_storage_list=[], tun=None):
""" """
Attributes: Attributes:
reference: String, the name of the partition. reference: String, the name of the partition.
path: String, the path to the partition folder. path: String, the path to the partition folder.
user: User, the user linked to this partition. user: User, the user linked to this partition.
address_list: List of associated IP addresses. address_list: List of associated IP addresses.
tap: Tap, the tap interface linked to this partition. tap: Tap, the tap interface linked to this partition e.g. used as a bridge for kvm
tun: Tun interface used for special apps simulating ethernet connections
external_storage_list: Base path list of folder to format for data storage external_storage_list: Base path list of folder to format for data storage
""" """
...@@ -644,10 +650,11 @@ class Partition(object): ...@@ -644,10 +650,11 @@ class Partition(object):
self.user = user self.user = user
self.address_list = address_list or [] self.address_list = address_list or []
self.tap = tap self.tap = tap
self.tun = tun
self.external_storage_list = [] self.external_storage_list = []
def __getinitargs__(self): def __getinitargs__(self):
return (self.reference, self.path, self.user, self.address_list, self.tap) return (self.reference, self.path, self.user, self.address_list, self.tap, self.tun)
def createPath(self, alter_user=True): def createPath(self, alter_user=True):
""" """
...@@ -754,6 +761,7 @@ class Tap(object): ...@@ -754,6 +761,7 @@ class Tap(object):
IFF_TAP = 0x0002 IFF_TAP = 0x0002
TUNSETIFF = 0x400454ca TUNSETIFF = 0x400454ca
KEEP_TAP_ATTACHED_EVENT = threading.Event() KEEP_TAP_ATTACHED_EVENT = threading.Event()
MODE = "tap"
def __init__(self, tap_name): def __init__(self, tap_name):
""" """
...@@ -807,9 +815,9 @@ class Tap(object): ...@@ -807,9 +815,9 @@ class Tap(object):
except IOError as error: except IOError as error:
# If EBUSY, it means another program is already attached, thus just # If EBUSY, it means another program is already attached, thus just
# ignore it... # ignore it...
logger.warning("Cannot create interface " + self.name + ". Does it exist already?")
if error.errno != errno.EBUSY: if error.errno != errno.EBUSY:
os.close(tap_fd) os.close(tap_fd)
raise
else: else:
# Block until the caller send an event stating that the program can be # Block until the caller send an event stating that the program can be
# now detached safely, thus bringing down the TAP device (from 2.6.36) # now detached safely, thus bringing down the TAP device (from 2.6.36)
...@@ -827,21 +835,27 @@ class Tap(object): ...@@ -827,21 +835,27 @@ class Tap(object):
def createWithOwner(self, owner, attach_to_tap=False): def createWithOwner(self, owner, attach_to_tap=False):
""" """
Create a tap interface on the system. Create a tap interface on the system if it doesn't exist yet.
""" """
# some systems does not have -p switch for tunctl
#callAndRead(['tunctl', '-p', '-t', self.name, '-u', owner.name])
check_file = '/sys/devices/virtual/net/%s/owner' % self.name check_file = '/sys/devices/virtual/net/%s/owner' % self.name
owner_id = None owner_id = None
if os.path.exists(check_file): if os.path.exists(check_file):
owner_id = open(check_file).read().strip() with open(check_file) as fx:
owner_id = fx.read().strip()
try: try:
owner_id = int(owner_id) owner_id = int(owner_id)
except ValueError: except ValueError:
pass owner_id = pwd.getpwnam(owner_id).pw_uid
if owner_id != pwd.getpwnam(owner.name).pw_uid: #
callAndRead(['tunctl', '-t', self.name, '-u', owner.name]) if owner_id != pwd.getpwnam(owner.name).pw_uid:
logger.warning("Wrong owner of TUN/TAP interface {}! Not touching it."
"Expected {:d} got {:d}".format(
self.name, pwd.getpwnam(owner.name).pw_uid, owner_id))
# if the interface already exists - don't do anything
return
callAndRead(['ip', 'tuntap', 'add', 'dev', self.name, 'mode',
self.MODE, 'user', owner.name])
callAndRead(['ip', 'link', 'set', self.name, 'up']) callAndRead(['ip', 'link', 'set', self.name, 'up'])
if attach_to_tap: if attach_to_tap:
...@@ -853,13 +867,61 @@ class Tap(object): ...@@ -853,13 +867,61 @@ class Tap(object):
""" """
if self.ipv4_addr: if self.ipv4_addr:
# Check if this route exits # Check if this route exits
code, result = callAndRead(['ip', 'route', 'show', self.ipv4_addr]) code, result = callAndRead(['ip', 'route', 'show', self.ipv4_addr],
raise_on_error=False)
if code == 0 and self.ipv4_addr in result and self.name in result: if code == 0 and self.ipv4_addr in result and self.name in result:
return return
callAndRead(['route', 'add', '-host', self.ipv4_addr, 'dev', self.name]) callAndRead(['ip', 'route', 'add', self.ipv4_addr, 'dev', self.name])
else: else:
raise ValueError("%s should not be empty. No ipv4 address assigned to %s" % raise ValueError("%s should not be empty. No ipv4 address assigned to %s" %
(self.ipv4_addr, self.name)) (self.ipv4_addr, self.name))
# Add iptables rule to accept connections from this interface
chain_rule = ['INPUT', '-i', self.name, '-j', 'ACCEPT']
code, _ = callAndRead(['iptables', '-C'] + chain_rule, raise_on_error=False)
if code == 0:
# 0 means the rule does not exits so we are free to insert it
callAndRead(['iptables', '-I'] + chain_rule)
class Tun(Tap):
"""Represent TUN interface which might be many per user."""
MODE = "tun"
BASE_MASK = 12
BASE_NETWORK = "172.16.0.0"
def __init__(self, name, sequence=None, partitions=None):
"""Create TUN interface with subnet according to the optional ``sequence`` number.
:param name: name which will appear in ``ip list`` afterwards
:param sequence: {int} position of this TUN among all ``partitions``
"""
super(Tun, self).__init__(name)
if sequence is not None:
assert 0 <= sequence < partitions, "0 <= {} < {}".format(sequence, partitions)
# create base IPNetwork
ip_network = netaddr.IPNetwork(Tun.BASE_NETWORK + "/" + str(Tun.BASE_MASK))
# compute shift in BITS to separate ``partitions`` networks into subset
# example: for 30 partitions we need log2(30) = 8 BITS
mask_shift = int(math.ceil(math.log(int(partitions), 2.0)))
# IPNetwork.subnet returns iterator over all possible subnets of given mask
ip_subnets = list(ip_network.subnet(Tun.BASE_MASK + mask_shift))
self.ipv4_network = self.ipv4_addr = str(ip_subnets[sequence])
def createRoutes(self):
"""Extend for physical addition of network address because TAP let this on external class."""
if self.ipv4_network:
# add an address
code, _ = callAndRead(['ip', 'addr', 'add', self.ipv4_network, 'dev', self.name],
raise_on_error=False)
if code == 0:
# address added to the interface - wait
time.sleep(1)
else:
raise RuntimeError("Cannot setup address on interface {}. "
"Address is missing.".format(self.name))
super(Tun, self).createRoutes()
class Interface(object): class Interface(object):
"""Represent a network interface on the system""" """Represent a network interface on the system"""
...@@ -1162,12 +1224,15 @@ def parse_computer_definition(conf, definition_path): ...@@ -1162,12 +1224,15 @@ def parse_computer_definition(conf, definition_path):
address, netmask = a.split('/') address, netmask = a.split('/')
address_list.append(dict(addr=address, netmask=netmask)) address_list.append(dict(addr=address, netmask=netmask))
tap = Tap(computer_definition.get(section, 'network_interface')) tap = Tap(computer_definition.get(section, 'network_interface'))
tun = Tun("slaptun" + str(partition_number),
partition_number,
int(conf.partition_amount)) if conf.create_tap else None
partition = Partition(reference=computer_definition.get(section, 'pathname'), partition = Partition(reference=computer_definition.get(section, 'pathname'),
path=os.path.join(conf.instance_root, path=os.path.join(conf.instance_root,
computer_definition.get(section, 'pathname')), computer_definition.get(section, 'pathname')),
user=user, user=user,
address_list=address_list, address_list=address_list,
tap=tap) tap=tap, tun=tun)
partition_list.append(partition) partition_list.append(partition)
computer.partition_list = partition_list computer.partition_list = partition_list
return computer return computer
...@@ -1220,7 +1285,8 @@ def parse_computer_xml(conf, xml_path): ...@@ -1220,7 +1285,8 @@ def parse_computer_xml(conf, xml_path):
conf.partition_base_name, i)), conf.partition_base_name, i)),
user=User('%s%s' % (conf.user_base_name, i)), user=User('%s%s' % (conf.user_base_name, i)),
address_list=None, address_list=None,
tap=Tap('%s%s' % (conf.tap_base_name, i)) tap=Tap('%s%s' % (conf.tap_base_name, i)),
tun=Tun('slaptun' + str(i), i, partition_amount)
) )
computer.partition_list.append(partition) computer.partition_list.append(partition)
...@@ -1403,11 +1469,7 @@ class FormatConfig(object): ...@@ -1403,11 +1469,7 @@ class FormatConfig(object):
if not self.dry_run: if not self.dry_run:
if self.alter_user: if self.alter_user:
self.checkRequiredBinary(['groupadd', 'useradd', 'usermod', ['passwd', '-h']]) self.checkRequiredBinary(['groupadd', 'useradd', 'usermod', ['passwd', '-h']])
if self.create_tap: if self.create_tap or self.alter_network:
self.checkRequiredBinary([['tunctl', '-d']])
if self.tap_gateway_interface:
self.checkRequiredBinary(['route'])
if self.alter_network:
self.checkRequiredBinary(['ip']) self.checkRequiredBinary(['ip'])
# Required, even for dry run # Required, even for dry run
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment