diff --git a/freebsd/__init__.py b/freebsd/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/freebsd/__init__.py diff --git a/freebsd/_linuxcontext.py b/freebsd/_linuxcontext.py new file mode 100644 index 0000000..bbf3ae0 --- /dev/null +++ b/freebsd/_linuxcontext.py @@ -0,0 +1,276 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from os import mkdir, environ, chdir +import platform +from shutil import copy, rmtree, move +import subprocess +import sys + +system = platform.system() +working_directory = sys.path[0] + +try: + chdir(working_directory) + subprocess.call(('pip', 'install', '-r', 'requirements.txt')) + if system == 'Linux': + copy("/root/agent/misc/vm_renewal", "/usr/local/bin/") +except: + pass # hope it works + + +import logging +import fileinput +import tarfile +from os.path import expanduser, join, exists +from glob import glob +from StringIO import StringIO +from base64 import decodestring +from hashlib import md5 + + +from ssh import PubKey +from .network import change_ip_ubuntu, change_ip_rhel +from context import BaseContext + +from twisted.internet import reactor + +logging.basicConfig() +logger = logging.getLogger() +level = environ.get('LOGLEVEL', 'INFO') +logger.setLevel(level) + +SSH_DIR = expanduser('~cloud/.ssh') +AUTHORIZED_KEYS = join(SSH_DIR, 'authorized_keys') + +STORE_DIR = '/store' + +mount_template_linux = ( + '//%(host)s/%(username)s %(dir)s cifs username=%(username)s' + ',password=%(password)s,iocharset=utf8,uid=cloud 0 0\n') + + +distros = {'Scientific Linux': 'rhel', + 'CentOS': 'rhel', + 'CentOS Linux': 'rhel', + 'Debian': 'debian', + 'Ubuntu': 'debian'} +if system == 'Linux': + distro = distros[platform.linux_distribution()[0]] + + +class Context(BaseContext): + + # http://stackoverflow.com/questions/12081310/ + # python-module-to-change-system-date-and-time + def _linux_set_time(time): + import ctypes + import ctypes.util + + CLOCK_REALTIME = 0 + + class timespec(ctypes.Structure): + _fields_ = [("tv_sec", ctypes.c_long), + ("tv_nsec", ctypes.c_long)] + + librt = ctypes.CDLL(ctypes.util.find_library("rt")) + + ts = timespec() + ts.tv_sec = int(time) + ts.tv_nsec = 0 + + librt.clock_settime(CLOCK_REALTIME, ctypes.byref(ts)) + + @staticmethod + def change_password(password): + proc = subprocess.Popen(['/usr/sbin/chpasswd'], + stdin=subprocess.PIPE) + proc.communicate('cloud:%s\n' % password) + + @staticmethod + def restart_networking(): + if distro == 'debian': + subprocess.call(['/etc/init.d/networking', 'restart']) + elif distro == 'rhel': + subprocess.call(['/bin/systemctl', 'restart', 'network']) + pass + + @staticmethod + def change_ip(interfaces, dns): + if distro == 'debian': + change_ip_ubuntu(interfaces, dns) + elif distro == 'rhel': + change_ip_rhel(interfaces, dns) + + @staticmethod + def set_time(time): + Context._linux_set_time(float(time)) + try: + subprocess.call(['/etc/init.d/ntp', 'restart']) + except: + pass + + @staticmethod + def set_hostname(hostname): + if distro == 'debian': + with open('/etc/hostname', 'w') as f: + f.write(hostname) + elif distro == 'rhel': + for line in fileinput.input('/etc/sysconfig/network', + inplace=1): + if line.startswith('HOSTNAME='): + print 'HOSTNAME=%s' % hostname + else: + print line.rstrip() + + with open('/etc/hosts', 'w') as f: + f.write("127.0.0.1 localhost\n" + "127.0.1.1 %s\n" % hostname) + + subprocess.call(['/bin/hostname', hostname]) + + @staticmethod + def mount_store(host, username, password): + data = {'host': host, 'username': username, 'password': password} + data['dir'] = STORE_DIR + if not exists(STORE_DIR): + mkdir(STORE_DIR) + # TODO + for line in fileinput.input('/etc/fstab', inplace=True): + if not (line.startswith('//') and ' cifs ' in line): + print line.rstrip() + + with open('/etc/fstab', 'a') as f: + f.write(mount_template_linux % data) + + subprocess.call('mount -a', shell=True) + + @staticmethod + def get_keys(): + retval = [] + try: + with open(AUTHORIZED_KEYS, 'r') as f: + for line in f.readlines(): + try: + retval.append(PubKey.from_str(line)) + except: + logger.exception(u'Invalid ssh key: ') + except IOError: + pass + return retval + + @staticmethod + def _save_keys(keys): + print keys + try: + mkdir(SSH_DIR) + except OSError: + pass + with open(AUTHORIZED_KEYS, 'w') as f: + for key in keys: + f.write(unicode(key) + '\n') + + @staticmethod + def add_keys(keys): + new_keys = Context.get_keys() + for key in keys: + try: + p = PubKey.from_str(key) + if p not in new_keys: + new_keys.append(p) + except: + logger.exception(u'Invalid ssh key: ') + Context._save_keys(new_keys) + + @staticmethod + def del_keys(keys): + new_keys = Context.get_keys() + for key in keys: + try: + p = PubKey.from_str(key) + try: + new_keys.remove(p) + except ValueError: + pass + except: + logger.exception(u'Invalid ssh key: ') + Context._save_keys(new_keys) + + @staticmethod + def cleanup(): + filelist = ([ + '/root/.bash_history' + '/home/cloud/.bash_history' + '/root/.ssh' + '/home/cloud/.ssh'] + + glob('/etc/ssh/ssh_host_*')) + for f in filelist: + rmtree(f, ignore_errors=True) + + subprocess.call(('/usr/bin/ssh-keygen', '-A')) + + @staticmethod + def start_access_server(): + try: + subprocess.call(('/sbin/start', 'ssh')) + except OSError: + subprocess.call(('/bin/systemctl', 'start', 'sshd.service')) + + @staticmethod + def append(data, filename, chunk_number, uuid): + if chunk_number == 0: + flag = "w" + else: + flag = "a" + with open(filename, flag) as myfile: + myfile.write(data) + + @staticmethod + def update(filename, executable, checksum, uuid): + new_dir = working_directory + '.new' + old_dir = working_directory + '.old.%s' % uuid + with open(filename, "r") as f: + data = f.read() + local_checksum = md5(data).hexdigest() + if local_checksum != checksum: + raise Exception("Checksum missmatch the file is damaged.") + decoded = StringIO(decodestring(data)) + try: + tar = tarfile.TarFile.open("dummy", fileobj=decoded, mode='r|gz') + tar.extractall(new_dir) + except tarfile.ReadError as e: + logger.error(e) + move(working_directory, old_dir) + move(new_dir, working_directory) + logger.info("Transfer completed!") + reactor.stop() + + @staticmethod + def ipaddresses(): + import netifaces + args = {} + interfaces = netifaces.interfaces() + for i in interfaces: + if i == 'lo': + continue + args[i] = [] + addresses = netifaces.ifaddresses(i) + args[i] = ([x['addr'] + for x in addresses.get(netifaces.AF_INET, [])] + + [x['addr'] + for x in addresses.get(netifaces.AF_INET6, []) + if '%' not in x['addr']]) + return args + + @staticmethod + def get_agent_version(): + try: + with open('version.txt') as f: + return f.readline() + except IOError: + return None + + @staticmethod + def send_expiration(url): + import notify + notify.notify(url) diff --git a/freebsd/network.py b/freebsd/network.py new file mode 100644 index 0000000..b6cb525 --- /dev/null +++ b/freebsd/network.py @@ -0,0 +1,139 @@ +import netifaces +from netaddr import IPNetwork +import fileinput +import logging +import subprocess + +logger = logging.getLogger() + +interfaces_file = '/etc/network/interfaces' +ifcfg_template = '/etc/sysconfig/network-scripts/ifcfg-%s' + + +def get_interfaces_linux(interfaces): + for ifname in netifaces.interfaces(): + mac = netifaces.ifaddresses(ifname)[17][0]['addr'] + conf = interfaces.get(mac.upper()) + if conf: + yield ifname, conf + + +def remove_interfaces_ubuntu(devices): + delete_device = False + + for line in fileinput.input(interfaces_file, inplace=True): + line = line.rstrip() + words = line.split() + + if line.startswith('#') or line == '' or line.isspace() or not words: + # keep line + print line + continue + + if (words[0] in ('auto', 'allow-hotplug') and + words[1].split(':')[0] in devices): + # remove line + continue + + if words[0] == 'iface': + ifname = words[1].split(':')[0] + if ifname in devices: + # remove line + delete_device = True + continue + else: + delete_device = False + + if line[0] in (' ', '\t') and delete_device: + # remove line + continue + + # keep line + print line + + +def change_ip_ubuntu(interfaces, dns): + data = list(get_interfaces_linux(interfaces)) + + for ifname, conf in data: + subprocess.call(('/sbin/ifdown', ifname)) + subprocess.call(('/sbin/ip', 'addr', 'flush', 'dev', ifname)) + subprocess.call(('/sbin/ip', 'link', 'set', 'dev', ifname, + 'down')) + remove_interfaces_ubuntu(dict(data).keys()) + + with open(interfaces_file, 'a') as f: + for ifname, conf in data: + ipv4_alias_counter = ipv6_alias_counter = 0 + f.write('auto %s\n' % ifname) + for i in conf['addresses']: + ip_with_prefix = IPNetwork(i) + prefixlen = ip_with_prefix.prefixlen + ip = ip_with_prefix.ip + alias = ifname + if ip.version == 6: + if ipv6_alias_counter > 0: + alias = '%s:%d' % (ifname, ipv6_alias_counter) + ipv6_alias_counter += 1 + else: + if ipv4_alias_counter > 0: + alias = '%s:%d' % (ifname, ipv4_alias_counter) + ipv4_alias_counter += 1 + + f.write( + 'iface %(ifname)s %(proto)s static\n' + ' address %(ip)s\n' + ' netmask %(prefixlen)d\n' + ' gateway %(gw)s\n' + ' dns-nameservers %(dns)s\n' % { + 'ifname': alias, + 'proto': 'inet6' if ip.version == 6 else 'inet', + 'ip': ip, + 'prefixlen': prefixlen, + 'gw': conf['gw6' if ip.version == 6 else 'gw4'], + 'dns': dns}) + for ifname, conf in data: + subprocess.call(('/sbin/ifup', ifname)) + + +# example: +# change_ip_ubuntu({ +# u'02:00:00:02:A3:E8': { +# u'gw4': u'10.1.0.254', 'gw6': '2001::ffff', +# u'addresses': [u'10.1.0.84/24', '10.1.0.1/24', '2001::1/48']}, +# u'02:00:00:02:A3:E9': { +# u'gw4': u'10.255.255.1', u'addresses': [u'10.255.255.9']}}, +# '8.8.8.8') + + +def change_ip_rhel(interfaces, dns): + for ifname, conf in get_interfaces_linux(interfaces): + subprocess.call(('/sbin/ifdown', ifname)) + subprocess.call(('/sbin/ip', 'addr', 'flush', 'dev', ifname)) + subprocess.call(('/sbin/ip', 'link', 'set', 'dev', ifname, 'down')) + with open(ifcfg_template % ifname, + 'w') as f: + f.write('DEVICE=%s\n' + 'DNS1=%s\n' + 'BOOTPROTO=none\n' + 'NM_CONTROLLED=no\n' + 'USERCTL=no\n' + 'ONBOOT=yes\n' % (ifname, dns)) + for i in conf['addresses']: + ip_with_prefix = IPNetwork(i) + ip = ip_with_prefix.ip + if ip.version == 6: + f.write('IPV6INIT=yes\n' + 'IPV6ADDR=%(ip)s/%(prefixlen)d\n' + 'IPV6_DEFAULTGW=%(gw)s\n' % { + 'ip': ip, + 'prefixlen': ip_with_prefix.prefixlen, + 'gw': conf['gw6']}) + else: + f.write('NETMASK=%(netmask)s\n' + 'IPADDR=%(ip)s\n' + 'GATEWAY=%(gw)s\n' % { + 'ip': ip, + 'netmask': str(ip_with_prefix.netmask), + 'gw': conf['gw4']}) + subprocess.call(('/sbin/ifup', ifname)) diff --git a/freebsd/posixvirtio.py b/freebsd/posixvirtio.py new file mode 100644 index 0000000..1a5534a --- /dev/null +++ b/freebsd/posixvirtio.py @@ -0,0 +1,60 @@ +# Copyright (c) Twisted Matrix Laboratories. +# See LICENSE for details. + + +""" +Virtio-Serial Port Protocol +""" + +# system imports +import os + +# dependent on pyserial ( http://pyserial.sf.net/ ) +# only tested w/ 1.18 (5 Dec 2002) + +# twisted imports +from twisted.internet import abstract, fdesc + + +class SerialPort(abstract.FileDescriptor): + """ + A select()able serial device, acting as a transport. + """ + + connected = 1 + + def __init__(self, protocol, deviceNameOrPortNumber, reactor): + abstract.FileDescriptor.__init__(self, reactor) + self.port = deviceNameOrPortNumber + self._serial = os.open( + self.port, os.O_RDWR | os.O_NOCTTY | os.O_NONBLOCK) + self.reactor = reactor + self.protocol = protocol + self.protocol.makeConnection(self) + self.startReading() + + def fileno(self): + return self._serial + + def writeSomeData(self, data): + """ + Write some data to the serial device. + """ + return fdesc.writeToFD(self.fileno(), data) + + def doRead(self): + """ + Some data's readable from serial device. + """ + return fdesc.readFromFD(self.fileno(), self.protocol.dataReceived) + + def connectionLost(self, reason): + """ + Called when the serial port disconnects. + + Will call C{connectionLost} on the protocol that is handling the + serial data. + """ + abstract.FileDescriptor.connectionLost(self, reason) + os.close(self._serial) + self.protocol.connectionLost(reason) diff --git a/freebsd/ssh.py b/freebsd/ssh.py new file mode 100644 index 0000000..b3c91ce --- /dev/null +++ b/freebsd/ssh.py @@ -0,0 +1,106 @@ +from base64 import decodestring +from struct import unpack +import binascii + + +class InvalidKeyType(Exception): + pass + + +class InvalidKey(Exception): + pass + + +class PubKey(object): + + key_types = ('ssh-rsa', 'ssh-dsa', 'ssh-ecdsa') + + # http://stackoverflow.com/questions/2494450/ssh-rsa-public-key- + # validation-using-a-regular-expression + @classmethod + def validate_key(cls, key_type, key): + try: + data = decodestring(key) + except binascii.Error: + raise InvalidKey() + int_len = 4 + str_len = unpack('>I', data[:int_len])[0] + if data[int_len:int_len + str_len] != key_type: + raise InvalidKey() + + def __init__(self, key_type, key, comment): + if key_type not in self.key_types: + raise InvalidKeyType() + self.key_type = key_type + + PubKey.validate_key(key_type, key) + self.key = key + + self.comment = unicode(comment) + + def __hash__(self): + return hash(frozenset(self.__dict__.items())) + + def __eq__(self, other): + return self.__dict__ == other.__dict__ + + @classmethod + def from_str(cls, line): + key_type, key, comment = line.split() + return PubKey(key_type, key, comment) + + def __unicode__(self): + return u' '.join((self.key_type, self.key, self.comment)) + + def __repr__(self): + return u'<PubKey: %s>' % unicode(self) + + +import unittest + + +class SshTestCase(unittest.TestCase): + def setUp(self): + self.p1 = PubKey.from_str('ssh-rsa AAAAB3NzaC1yc2EA comment') + self.p2 = PubKey.from_str('ssh-rsa AAAAB3NzaC1yc2EA comment') + self.p3 = PubKey.from_str('ssh-rsa AAAAB3NzaC1yc2EC comment') + + def test_invalid_key_type(self): + self.assertRaises(InvalidKeyType, PubKey, 'ssh-inv', 'x', 'comment') + + def test_valid_key(self): + PubKey('ssh-rsa', 'AAAAB3NzaC1yc2EA', 'comment') + + def test_invalid_key(self): + self.assertRaises(InvalidKey, PubKey, 'ssh-rsa', 'x', 'comment') + + def test_invalid_key2(self): + self.assertRaises(InvalidKey, PubKey, 'ssh-rsa', + 'AAAAB3MzaC1yc2EA', 'comment') + + def test_repr(self): + p = PubKey('ssh-rsa', 'AAAAB3NzaC1yc2EA', 'comment') + self.assertEqual( + repr(p), '<PubKey: ssh-rsa AAAAB3NzaC1yc2EA comment>') + + def test_unicode(self): + p = PubKey('ssh-rsa', 'AAAAB3NzaC1yc2EA', 'comment') + self.assertEqual(unicode(p), 'ssh-rsa AAAAB3NzaC1yc2EA comment') + + def test_from_str(self): + p = PubKey.from_str('ssh-rsa AAAAB3NzaC1yc2EA comment') + self.assertEqual(unicode(p), 'ssh-rsa AAAAB3NzaC1yc2EA comment') + + def test_eq(self): + self.assertEqual(self.p1, self.p2) + self.assertNotEqual(self.p1, self.p3) + + def test_hash(self): + s = set() + s.add(self.p1) + s.add(self.p2) + s.add(self.p3) + self.assertEqual(len(s), 2) + +if __name__ == '__main__': + unittest.main()