Commit d2fa1c61 by Czémán Arnold

Merge branch 'freebsd' into 'master'

Freebsd support

See merge request !5
parents 16b8fc3a dda1b05b
NOTES - aka, mivel lehet szivni...
__INSTALL__
* adott egy bootstrap script, ami minden szukseges elokeszuletet megcsinal, ami az agens mukodesehez kell:
+ alap csomagokat telepit fel
+ konyvtarakat hoz letre
+ usereket hoz letre es allit be
+ letolti az agent forrasat
+ felpatcheli az rc-t
+ telepiti az agenst
__RC.D__
* minden egyes circle_agent altal eszkozolt beallitas a /etc/rc.conf.d/-ben talalhato egy kulon file alatt
* a FreeBSD-s rc.subr az agent telepitesekor modositasra kerul, mivel alapbol a partial config file-ok kezelese agyhalal es nem logikus
* az interface beallitasok a /etc/rc.conf.d/ifconfig_IFNAME alatt talalhatok
* az agent daemon-kent fut
__SMBFS__
* mindenhol case insensitive, kiveve a /etc/nsmb.conf-ban es a ~/.nsmbrc-ben, itt a userneveket szigoruan upper-case kell irni
* az /etc/nsmb.conf-ban vagy a ~/.nsmbrc-ben meg kell ismetelni a [SERVER] es a [SERVER:USER] reszeknel az add-t es a password-ot
__IPV6__
* semmi extra, prefixlen kell prefix helyett
__ROUTING__
* {ipv6_,}defaultrouter-eket kell beallitani
__USER__
* a cloud es a root felhasznalo ugyan azt a jelszot birtokolja minden idoben
* a passwd update mindket felhasznalora hatassal van
__SERIAL__
* virtios soros port elsodlegesen, utana hw soros
* \r sorveg eseten hibas csomagok, FreeBSD-n \n az elvart
...@@ -8,7 +8,7 @@ import sys ...@@ -8,7 +8,7 @@ import sys
system = platform.system() system = platform.system()
if system == "Linux": if system == "Linux" or system == "FreeBSD":
try: try:
chdir(sys.path[0]) chdir(sys.path[0])
subprocess.call(('pip', 'install', '-r', 'requirements.txt')) subprocess.call(('pip', 'install', '-r', 'requirements.txt'))
...@@ -84,8 +84,8 @@ class SerialLineReceiver(SerialLineReceiverBase): ...@@ -84,8 +84,8 @@ class SerialLineReceiver(SerialLineReceiverBase):
"uptime": {"seconds": uptime.uptime()}, "uptime": {"seconds": uptime.uptime()},
"disk": disk_usage, "disk": disk_usage,
"user": {"count": len(psutil.get_users())}} "user": {"count": len(psutil.get_users())}}
self.send_response(response='status', self.send_response(response='status', args=args)
args=args) logger.debug("send_status finished")
def _check_args(self, func, args): def _check_args(self, func, args):
if not isinstance(args, dict): if not isinstance(args, dict):
...@@ -123,6 +123,7 @@ class SerialLineReceiver(SerialLineReceiverBase): ...@@ -123,6 +123,7 @@ class SerialLineReceiver(SerialLineReceiverBase):
self._pretty_fun(func)) self._pretty_fun(func))
self._check_args(func, args) self._check_args(func, args)
logger.debug("_get_command finished")
return func return func
@staticmethod @staticmethod
......
#!/bin/sh
#export LOGLEVEL=DEBUG
pkg install -y `cat bootstrap/freebsd/pkg-requirements.txt`
grep "^cloud:" /etc/passwd > /dev/null
ret=$?
if [ $ret -ne 0 ]
then
# create the required backdoor user
pw user add cloud -m
pw group mod wheel -m cloud
fi
sed -i '.orig' -e 's/# \(%wheel ALL=(ALL) ALL\)/\1/g' /usr/local/etc/sudoers
if [ ! -d /usr/ports ]
then
git clone https://github.com/HardenedBSD/freebsd-ports.git /usr/ports
fi
if [ ! -d /store ]
then
mkdir -p /store
fi
if [ ! -d /root/agent ]
then
cd /root
git clone https://github.com/opntr/bme-cloud-circle-agent.git agent
fi
cp -r bootstrap/freebsd/rc.conf.d /etc
cd /root/agent
if [ -d /usr/local/etc/rc.d ]
then
cp bootstrap/freebsd/rc.d/agent /usr/local/etc/rc.d
fi
if [ ! -f /etc/rc.conf.d/agent ]
then
echo 'agent_enable="YES"' > /etc/rc.conf.d/agent
fi
service agent start
for netconf in /etc/rc.conf.d/ifconfig_*
do
load_rc_config $(basename "$netconf")
done
#!/bin/sh
#
# $FreeBSD$
#
# PROVIDE: agent
# REQUIRE: DAEMON NETWORKING
# KEYWORD: shutdown
. /etc/rc.subr
name="agent"
rcvar="agent_enable"
pidfile="/var/run/${name}.pid"
start_cmd="do_start"
stop_cmd="do_stop"
do_start()
{
PATH=${PATH}:/usr/local/bin/
printf "start ${name}: "
/usr/sbin/daemon -P ${pidfile} -r /usr/local/bin/python /root/agent/agent.py
ret=$?
if [ "${ret}" = "0" ]
then
echo "done."
else
echo "failed..."
fi
}
do_stop()
{
printf "stop ${name}: "
if [ -f ${pidfile} ]
then
kill `cat ${pidfile}`
echo "done."
else
echo "not running..."
return 1
fi
}
load_rc_config $name
run_rc_command "$1"
...@@ -27,6 +27,8 @@ def get_context(): ...@@ -27,6 +27,8 @@ def get_context():
from windows._win32context import Context from windows._win32context import Context
elif system == "Linux": elif system == "Linux":
from linux._linuxcontext import Context from linux._linuxcontext import Context
elif system == "FreeBSD":
from freebsd._freebsdcontext import Context
else: else:
raise NotImplementedError("Platform %s is not supported.", system) raise NotImplementedError("Platform %s is not supported.", system)
return Context return Context
...@@ -53,6 +55,15 @@ def get_serial(): ...@@ -53,6 +55,15 @@ def get_serial():
port = '/dev/ttyS0' port = '/dev/ttyS0'
else: else:
from linux.posixvirtio import SerialPort from linux.posixvirtio import SerialPort
elif system == "FreeBSD":
port = "/dev/ttyV0.1"
try:
open(port, 'rw').close()
except (OSError, IOError):
from twisted.internet.serialport import SerialPort
port = '/dev/ttyu0'
else:
from freebsd.posixvirtio import SerialPort
else: else:
raise NotImplementedError("Platform %s is not supported.", system) raise NotImplementedError("Platform %s is not supported.", system)
return (SerialPort, port) return (SerialPort, port)
......
serial: ok
ipv4: ok
ipv6: ok
hostname: ok
time: ?
smb: ok
passwd update: ok
get-ssh-keys: ?
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from os import mkdir, environ, chdir, chmod
import platform
from shutil import copy, rmtree, move
import subprocess
import sys
system = platform.system() # noqa
working_directory = sys.path[0] # noqa
try: # noqa
# load virtio console driver, the device is /dev/ttyV0.1 # noqa
subprocess.call(('/sbin/kldload', '-n', 'virtio_console')) # noqa
subprocess.call(('/sbin/kldload', '-n', 'smbfs')) # noqa
chdir(working_directory) # noqa
subprocess.call( # noqa
('/usr/local/bin/pip', 'install', '-r', 'requirements.txt')) # noqa
copy("/root/agent/misc/vm_renewal", "/usr/local/bin/") # noqa
except Exception: # noqa
pass # hope it works # noqa
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_freebsd
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_freebsd = (
'//CLOUD/%(username)s %(dir)s smbfs '
'rw,-N,-ucloud,-U%(username)s 0 0\n')
NSMBRC = '/etc/nsmb.conf'
nsmbrc_template_freebsd = (
'[CLOUD:%(USERNAME)s]\n'
'addr=%(host)s\n'
'password=%(password)s\n'
'\n'
'[CLOUD]\n'
'addr=%(host)s\n'
'password=%(password)s\n')
class Context(BaseContext):
# http://stackoverflow.com/questions/12081310/
# python-module-to-change-system-date-and-time
def _freebsd_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):
proc0 = subprocess.Popen(
['/usr/sbin/pw', 'user', 'mod', 'cloud', '-h', '0'],
stdin=subprocess.PIPE)
proc0.communicate('%s\n' % password)
proc1 = subprocess.Popen(
['/usr/sbin/pw', 'user', 'mod', 'root', '-h', '0'],
stdin=subprocess.PIPE)
proc1.communicate('%s\n' % password)
@staticmethod
def restart_networking():
logger.debug("restart_networking")
@staticmethod
def change_ip(interfaces, dns):
change_ip_freebsd(interfaces, dns)
@staticmethod
def set_time(time):
Context._freebsd_set_time(float(time))
try:
subprocess.call(['/usr/sbin/service' 'ntpd', 'onerestart'])
except Exception:
pass
@staticmethod
def set_hostname(hostname):
with open('/etc/rc.conf.d/hostname', 'w') as f:
f.write("hostname=\""+hostname+"\"")
with open('/etc/hosts', 'w') as f:
f.write("127.0.0.1 localhost\n"
"127.0.1.1 %s\n" % hostname)
subprocess.call(['/usr/sbin/service', 'hostname', 'restart'])
@staticmethod
def mount_store(host, username, password):
data = {'host': host, 'username': username, 'password': password,
'USERNAME': username.upper()}
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 ' smbfs ' in line):
print line.rstrip()
with open(NSMBRC, 'w') as f:
chmod(NSMBRC, 0600)
f.write(nsmbrc_template_freebsd % data)
with open('/etc/fstab', 'a') as f:
f.write(mount_template_freebsd % data)
subprocess.call('/sbin/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 Exception:
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 Exception:
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 Exception:
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'
'/root/.lesshst'
'/home/cloud/.lesshst'
'/root/.history'
'/home/cloud/.history'
'/root/.viminfo'
'/home/cloud/.viminfo'
'/etc/nsmb.conf'
'/root/.nsmbrc.conf'
'/home/cloud/.nsmbrc.conf'
]
+ 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():
subprocess.call(('/usr/sbin/service', 'ssh', 'start'))
@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 == 'lo0':
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)
import netifaces
from netaddr import IPNetwork
import logging
import subprocess
import os
import os.path
logger = logging.getLogger()
rcconf_dir = '/etc/rc.conf.d/'
def get_interfaces_freebsd(interfaces):
for ifname in netifaces.interfaces():
if ifname == 'lo0':
continue # XXXOP: ?
logger.debug("get_interfaces: " + ifname)
mac = netifaces.ifaddresses(ifname)[18][0]['addr']
logger.debug("get_interfaces: " + mac)
conf = interfaces.get(mac.upper())
if conf:
yield ifname, conf
def remove_interfaces_freebsd(devices):
for device in devices:
if_file = "%s/ifconfig_%s" % (rcconf_dir, device)
if os.path.isfile(if_file):
logger.debug("remove interface configuration: " + if_file)
os.unlink(if_file)
else:
logger.debug(
"unable to remove interface configuration: " + if_file)
def change_ip_freebsd(interfaces, dns):
data = list(get_interfaces_freebsd(interfaces))
for ifname, conf in data:
subprocess.call(('/usr/sbin/service', 'netif', 'stop', ifname))
remove_interfaces_freebsd(dict(data).keys())
for device, conf in data:
if_file = rcconf_dir + "ifconfig_" + device
with open(if_file, 'w') as f:
ipv4_alias_counter = ipv6_alias_counter = 0
for i in conf['addresses']:
alias = ""
ip_with_prefix = IPNetwork(i)
ip = ip_with_prefix.ip
prefixlen = ip_with_prefix.prefixlen
if ip.version == 6:
alias = "_ipv6"
if ipv6_alias_counter > 0:
alias = '_alias%d' % (ipv6_alias_counter-1)
ipv6_alias_counter += 1
f.write("ifconfig_" + device + alias + "=" +
"\"inet6 %(ip)s prefixlen %(pref)s\"\n"
% {'ip': ip, 'pref': prefixlen})
f.write("ipv6_defaultrouter=\""+str(conf['gw6'])+"\"\n")
else:
if ipv4_alias_counter > 0:
# az aliasok szamanak folytonosnak kell lennie
ipv4_alias_counter = ipv6_alias_counter + 1
alias = '_alias%d' % (ipv4_alias_counter)
ipv4_alias_counter += 1
f.write("ifconfig_" + device + alias + "=" +
"\"inet %(ip)s/%(pref)s\"\n"
% {'ip': ip, 'pref': prefixlen})
f.write("defaultrouter=\""+str(conf['gw4'])+"\"\n")
with open("/etc/resolv.conf", "w") as f:
f.write("nameserver "+dns)
for ifname, conf in data:
subprocess.call(('/usr/sbin/service', 'netif', 'start', ifname))
subprocess.call(('/usr/sbin/service', 'routing', 'start'))
# 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')
# 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)
from base64 import decodestring
from struct import unpack
import binascii
import unittest
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)
# Unit tests
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()
from twisted.protocols.basic import LineReceiver from twisted.protocols.basic import LineReceiver
import json import json
import logging import logging
import platform
logger = logging.getLogger() logger = logging.getLogger()
system = platform.system()
class SerialLineReceiverBase(LineReceiver, object): class SerialLineReceiverBase(LineReceiver, object):