# -*- 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()