# -*- coding: utf-8 -*- import hashlib import json import os import requests import shutil import subprocess 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' PUB_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.repo_url = self.conf['host']['repo'] 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 fetch_online_packages(self): # Fetches and verifies online packages. Can raise InvalidSignature packages = requests.get('{}/packages'.format(self.repo_url)).content packages_sig = requests.get('{}/packages.sig'.format(self.repo_url)).content with open(PUB_FILE, 'rb') as f: pub_key = load_pem_public_key(f.read(), default_backend()) pub_key.verify(packages_sig, packages, ec.ECDSA(hashes.SHA512())) self.online_packages = json.loads(packages) def install_package(self, name): self.fetch_online_packages() for dep in self.get_deps(name): if dep not in self.conf['packages']: self.download_package(dep) self.register_package(dep) self.setup_package() def download_package(self, name): # Downloads, verifies, unpacks and sets up a package tmp_archive = tempfile.mkstemp('.tar.xz')[1] r = requests.get('{}/{}.tar.xz'.format(self.repo_url, name), auth=('test', 'txUqqZLaM.Z;3E2E'), stream=True) # TODO: Remove the testing password with open(tmp_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(tmp_archive): raise InvalidSignature(name) # Unpack subprocess.run(['tar', 'xJf', tmp_archive], cwd=LXC_ROOT) os.unlink(tmp_archive) def register_package(self, name): metadata = self.online_packages[name] self.conf['packages'][name] = { 'version': metadata['version'], } if 'host' in metadata: self.conf['apps'][name] = { 'title': metadata['title'], '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'].copy() for dep in deps: deps[:0] = [d for d in self.get_deps(dep) if d not in deps] deps.append(name) 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()