From 1c967a0431b3f49474191d29cb29740306171853 Mon Sep 17 00:00:00 2001 From: Disassembler Date: Tue, 2 Oct 2018 22:13:39 +0200 Subject: [PATCH] Add package manager prototype --- basic/srv/vm/config.json | 131 +------------------------------------ basic/srv/vm/mgr/pkgmgr.py | 98 +++++++++++++++++++++++++++ basic/srv/vm/packages.pub | 5 ++ build-all.sh | 1 + 4 files changed, 107 insertions(+), 128 deletions(-) create mode 100644 basic/srv/vm/mgr/pkgmgr.py create mode 100644 basic/srv/vm/packages.pub diff --git a/basic/srv/vm/config.json b/basic/srv/vm/config.json index 0de0323..5f83b0d 100644 --- a/basic/srv/vm/config.json +++ b/basic/srv/vm/config.json @@ -1,139 +1,14 @@ { - "apps": { - "ckan": { - "host": "ckan", - "login": "N/A", - "password": "N/A", - "title": "CKAN", - "visible": false - }, - "crisiscleanup": { - "host": "cc", - "login": "N/A", - "password": "N/A", - "title": "Crisis Cleanup", - "visible": false - }, - "cts": { - "host": "cts", - "login": "N/A", - "password": "N/A", - "title": "CTS", - "visible": false - }, - "frontlinesms": { - "host": "sms", - "login": "N/A", - "password": "N/A", - "title": "Frontline SMS", - "visible": false - }, - "gnuhealth": { - "host": "gh", - "login": "N/A", - "password": "N/A", - "title": "GNU Health", - "visible": false - }, - "kanboard": { - "host": "kb", - "login": "N/A", - "password": "N/A", - "title": "KanBoard", - "visible": false - }, - "mifosx": { - "host": "mifosx", - "login": "N/A", - "password": "N/A", - "title": "Mifos X", - "visible": false - }, - "motech": { - "host": "motech", - "login": "N/A", - "password": "N/A", - "title": "Motech", - "visible": false - }, - "opendatakit": { - "host": "odk", - "login": "N/A", - "password": "N/A", - "title": "OpenDataKit Aggregate", - "visible": false - }, - "opendatakit-build": { - "host": "odkbuild", - "login": "N/A", - "password": "N/A", - "title": "OpenDataKit Build", - "visible": false - }, - "openmapkit": { - "host": "omk", - "login": "N/A", - "password": "N/A", - "title": "OpenMapKit", - "visible": false - }, - "pandora": { - "host": "pandora", - "login": "N/A", - "password": "N/A", - "title": "Pan.do/ra", - "visible": false - }, - "sahana": { - "host": "sahana", - "login": "N/A", - "password": "N/A", - "title": "Sahana EDEN", - "visible": false - }, - "sahana-demo": { - "host": "sahana-demo", - "login": "N/A", - "password": "N/A", - "title": "Sahana EDEN Demo", - "visible": false - }, - "sambro": { - "host": "sambro", - "login": "N/A", - "password": "N/A", - "title": "Sahana EDEN SAMBRO", - "visible": false - }, - "seeddms": { - "host": "dms", - "login": "N/A", - "password": "N/A", - "title": "SeedDMS", - "visible": false - }, - "sigmah": { - "host": "sigmah", - "login": "N/A", - "password": "N/A", - "title": "Sigmah", - "visible": false - }, - "ushahidi": { - "host": "ush", - "login": "N/A", - "password": "N/A", - "title": "Ushahidi", - "visible": false - } - }, + "apps": {}, "common": { "email": "admin@example.com", "gmaps-api-key": "" }, "host": { + "repo": "https://dl.dasm.cz/spotter-repo", "adminpwd": "$2b$12$nLrIefUoWN.pK6j90gsfkO0/tg4EGXDmdjN8HOGB0U.9BcHTFxzWS", "domain": "spotter.vm", + "firstrun": true, "port": "443" } } diff --git a/basic/srv/vm/mgr/pkgmgr.py b/basic/srv/vm/mgr/pkgmgr.py new file mode 100644 index 0000000..cea4f32 --- /dev/null +++ b/basic/srv/vm/mgr/pkgmgr.py @@ -0,0 +1,98 @@ +# -*- coding: utf-8 -*- + +import json +import requests +import shutil +import tempfile + +from cryptography.exceptions import InvalidSignature +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.asymmetric import ec +from cryptography.hazmat.primitives.serialization import load_pem_public_key + +CONF_FILE = '/srv/vm/config.json' +CERT_FILE = '/srv/vm/packages.pub' +LXC_ROOT = '/var/lib/lxc' + +class PackageManager: + def __init__(self): + # Load JSON configuration + with open(CONF_FILE, 'r') as f: + self.conf = json.load(f) + self.online_packages = {} + + def save_conf(self): + # Save a sorted JSON configuration object with indentation + with open(CONF_FILE, 'w') as f: + json.dump(self.conf, f, sort_keys=True, indent=4) + + def get_online_packages(self): + # Fetches and verifies online packages. Can raise InvalidSignature + repo_url = self.conf['host']['repo'] + packages = requests.get('{}/packages'.format(repo_url)).content + packages_sig = requests.get('{}/packages.sig'.format(repo_url)).content + with open(CERT_FILE, 'rb') as f: + pub_key = load_pem_public_key(f.read(), default_backend()) + pub_key.verify(packages_sig, packages, ec.ECDSA(hashes.SHA512())) + return json.loads(packages) + + def install_package(self, name): + self.online_packages = get_online_packages() + for dep in self.get_deps(name): + if dep not in self.conf['apps']: + self.download_package(name) + if 'host' in self.online_packages[name]: + self.register_app(name, self.online_packages[name]) + self.setup_package() + + def download_package(self, name): + # Downloads, verifies, unpacks and sets up a package + local_archive = tempfile.mkstemp('.tar.xz') + r = requests.get('{}/{}.tar.xz'.format(self.repo_url, name), stream=True) + with open(local_archive, 'wb') as f: + for chunk in r.iter_content(chunk_size=65536): + if chunk: + f.write(chunk) + # Verify hash + if self.online_packages[name]['sha512'] != hash_file(local_archive): + raise InvalidSignature(name) + # Unpack + subprocess.run(['tar', 'xJf', local_archive], cwd=LXC_ROOT) + os.unlink(local_archive) + + def register_app(self, name, metadata): + self.conf['apps'][name] = { + 'title': metadata['title'], + 'version': metadata['version'], + 'host': metadata['host'], + 'login': 'N/A', + 'password': 'N/A', + 'visible': False + } + self.save_conf() + + def setup_package(self): + setup_dir = os.path.join(LXC_ROOT, 'setup') + setup_script = os.path.join(LXC_ROOT, 'setup.sh') + if os.path.exists(setup_script): + subprocess.run(setup_script) + os.unlink(setup_script) + if os.path.exists(setup_dir): + shutil.rmtree(setup_dir) + + def get_deps(self, name): + deps = self.online_packages[name]['deps'] + [name] + for dep in deps: + deps[:0] = [d for d in self.get_deps(dep) if d not in deps] + return deps + +def hash_file(file_path): + sha512 = hashlib.sha512() + with open(file_path, 'rb') as f: + while True: + data = f.read(65536) + if not data: + break + sha512.update(data) + return sha512.hexdigest() diff --git a/basic/srv/vm/packages.pub b/basic/srv/vm/packages.pub new file mode 100644 index 0000000..60532d9 --- /dev/null +++ b/basic/srv/vm/packages.pub @@ -0,0 +1,5 @@ +-----BEGIN PUBLIC KEY----- +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEWJXH4Qm0kt2L86sntQH+C1zOJNQ0qMRt +0vx4krTxRs9HQTQYAy//JC92ea2aKleA8OL0JF90b1NYXcQCWdAS+vE/ng9IEAii +8C2+5nfuFeZ5YUjbQhfFblwHSM0c7hEG +-----END PUBLIC KEY----- diff --git a/build-all.sh b/build-all.sh index e021ebb..0783daf 100755 --- a/build-all.sh +++ b/build-all.sh @@ -52,6 +52,7 @@ lxc-build ${SOURCE_DIR}/solr lxc-build ${SOURCE_DIR}/ushahidi # Create packages +lxc-pack ${SOURCE_DIR}/basic-runtimes/alpine.pkg lxc-pack ${SOURCE_DIR}/basic-runtimes/java.pkg lxc-pack ${SOURCE_DIR}/basic-runtimes/libxml.pkg lxc-pack ${SOURCE_DIR}/basic-runtimes/php.pkg