def _extract_bin_path(self, app): '''Get bin-path for app''' rel = self.bin_paths[app] fn = os.path.join(self.unpack_dir, rel) if not os.path.exists(fn): error("Could not find '%s'" % rel) return fn
def __init__(self, fn, overrides=None): peer_hooks = dict() my_hook = 'push-helper' peer_hooks[my_hook] = dict() peer_hooks[my_hook]['allowed'] = ['apparmor'] peer_hooks[my_hook]['required'] = ['apparmor'] ClickReview.__init__(self, fn, "push_helper", peer_hooks=peer_hooks, overrides=overrides) if not self.is_click and not self.is_snap1: return self.required_keys = ['exec'] self.optional_keys = ['app_id'] self.push_helper_files = dict() # click-show-files and tests self.push_helper = dict() if self.manifest is None: return for app in self.manifest['hooks']: if 'push-helper' not in self.manifest['hooks'][app]: # msg("Skipped missing push-helper hook for '%s'" % app) continue if not isinstance(self.manifest['hooks'][app]['push-helper'], str): error("manifest malformed: hooks/%s/push-helper is not str" % app) (full_fn, jd) = self._extract_push_helper(app) self.push_helper_files[app] = full_fn self.push_helper[app] = jd
def _extract_url_dispatcher(self, app): '''Get url dispatcher json''' u = self.manifest['hooks'][app]['urls'] if not u: error("'urls' definition is empty for '%s'" % app) fn = os.path.join(self.unpack_dir, u) if not os.path.isfile(fn): error("'%s' is not a file" % fn) bn = os.path.basename(fn) if not os.path.exists(fn): error("Could not find '%s'" % bn) fh = open_file_read(fn) contents = "" for line in fh.readlines(): contents += line fh.close() try: jd = json.loads(contents) except Exception as e: error("url-dispatcher json unparseable: %s (%s):\n%s" % (bn, str(e), contents)) if not isinstance(jd, list): error("url-dispatcher json is malformed: %s:\n%s" % (bn, contents)) return (fn, jd)
def __init__(self, fn, overrides=None): peer_hooks = dict() my_hook = 'content-hub' peer_hooks[my_hook] = dict() peer_hooks[my_hook]['allowed'] = ClickReview.app_allowed_peer_hooks peer_hooks[my_hook]['required'] = [] ClickReview.__init__(self, fn, "content_hub", peer_hooks=peer_hooks, overrides=overrides) if not self.is_click and not self.is_snap1: return self.valid_keys = ['destination', 'share', 'source'] self.content_hub_files = dict() # click-show-files and tests self.content_hub = dict() if self.manifest is None: return for app in self.manifest['hooks']: if 'content-hub' not in self.manifest['hooks'][app]: # msg("Skipped missing content-hub hook for '%s'" % app) continue if not isinstance(self.manifest['hooks'][app]['content-hub'], str): error("manifest malformed: hooks/%s/urls is not str" % app) (full_fn, jd) = self._extract_content_hub(app) self.content_hub_files[app] = full_fn self.content_hub[app] = jd
def __init__(self, fn, overrides=None): ClickReview.__init__(self, fn, "framework", overrides=overrides) self.frameworks_file = dict() self.frameworks = dict() if not self.is_snap1: return if self.manifest is not None: for app in self.manifest['hooks']: if 'framework' not in self.manifest['hooks'][app]: # msg("Skipped missing framework hook for '%s'" % app) continue if not isinstance(self.manifest['hooks'][app]['framework'], str): error("manifest malformed: hooks/%s/framework is not str" % app) (full_fn, data) = self._extract_framework(app) self.frameworks_file[app] = full_fn self.frameworks[app] = data self.framework_policy_dirs = ['apparmor', 'seccomp'] self.framework_policy_subdirs = ['templates', 'policygroups'] (self.framework_policy, self.framework_policy_unknown) = \ self._extract_framework_policy()
def __init__(self, fn, overrides=None): peer_hooks = dict() my_hook = 'scope' peer_hooks[my_hook] = dict() peer_hooks[my_hook]['allowed'] = ClickReview.scope_allowed_peer_hooks peer_hooks[my_hook]['required'] = ['apparmor'] ClickReview.__init__(self, fn, "scope", peer_hooks=peer_hooks, overrides=overrides) if not self.is_click and not self.is_snap1: return self.scopes = dict() if self.manifest is None: return for app in self.manifest['hooks']: if 'scope' not in self.manifest['hooks'][app]: # msg("Skipped missing scope hook for '%s'" % app) continue if not isinstance(self.manifest['hooks'][app]['scope'], str): error("manifest malformed: hooks/%s/scope is not str" % app) self.scopes[app] = self._extract_scopes(app)
def __init__(self, fn, overrides=None): peer_hooks = dict() my_hook = 'urls' peer_hooks[my_hook] = dict() peer_hooks[my_hook]['allowed'] = ClickReview.app_allowed_peer_hooks peer_hooks[my_hook]['required'] = [] ClickReview.__init__(self, fn, "url_dispatcher", peer_hooks=peer_hooks, overrides=overrides) if not self.is_click and not self.is_snap1: return self.required_keys = ['protocol'] self.optional_keys = ['domain-suffix'] self.url_dispatcher_files = dict() # click-show-files and tests self.url_dispatcher = dict() if self.manifest is None: return for app in self.manifest['hooks']: if 'urls' not in self.manifest['hooks'][app]: # msg("Skipped missing urls hook for '%s'" % app) continue if not isinstance(self.manifest['hooks'][app]['urls'], str): error("manifest malformed: hooks/%s/urls is not str" % app) (full_fn, jd) = self._extract_url_dispatcher(app) self.url_dispatcher_files[app] = full_fn self.url_dispatcher[app] = jd
def __init__(self, fn, overrides=None): peer_hooks = dict() my_hook = 'desktop' peer_hooks[my_hook] = dict() peer_hooks[my_hook]['allowed'] = ClickReview.app_allowed_peer_hooks peer_hooks[my_hook]['required'] = ["apparmor"] ClickReview.__init__(self, fn, "desktop", peer_hooks=peer_hooks, overrides=overrides) if not self.is_click and not self.is_snap1: return self.desktop_files = dict() # click-show-files and a couple tests self.desktop_entries = dict() self.desktop_hook_entries = 0 if self.manifest is None: return for app in self.manifest['hooks']: if 'desktop' not in self.manifest['hooks'][app]: # msg("Skipped missing desktop hook for '%s'" % app) continue if not isinstance(self.manifest['hooks'][app]['desktop'], str): error("manifest malformed: hooks/%s/desktop is not str" % app) self.desktop_hook_entries += 1 (de, full_fn) = self._extract_desktop_entry(app) self.desktop_entries[app] = de self.desktop_files[app] = full_fn self.required_keys = [ 'Name', 'Type', 'Icon', 'Exec', 'X-Ubuntu-Touch', ] self.expected_execs = [ 'qmlscene', 'webbrowser-app', 'webapp-container', 'ubuntu-html5-app-launcher', ] self.deprecated_execs = [ 'cordova-ubuntu-2.8', ] # TODO: the desktop hook will actually handle this correctly self.blacklisted_keys = ['Path']
def _extract_scopes(self, app): '''Get scopes''' d = dict() s = self.manifest['hooks'][app]['scope'] fn = os.path.join(self.unpack_dir, s) bn = os.path.basename(fn) if not os.path.exists(fn): error("Could not find '%s'" % bn) elif not os.path.isdir(fn): error("'%s' is not a directory" % bn) ini_fn = os.path.join(fn, "%s_%s.ini" % (self.manifest['name'], app)) ini_fn_bn = os.path.relpath(ini_fn, self.unpack_dir) if not os.path.exists(ini_fn): error("Could not find scope INI file '%s'" % ini_fn_bn) try: d["scope_config"] = configparser.ConfigParser() d["scope_config"].read_file(codecs.open(ini_fn, "r", "utf8")) except Exception as e: error("scope config unparseable: %s (%s)" % (ini_fn_bn, str(e))) d["dir"] = fn d["dir_rel"] = bn d["ini_file"] = ini_fn d["ini_file_rel"] = ini_fn_bn return d
def _extract_webapp_manifests(self): '''Extract webapp manifest file''' files = sorted( glob.glob("%s/unity-webapps-*/manifest.json" % self.unpack_dir)) manifests = dict() for fn in files: key = os.path.relpath(fn, self.unpack_dir) try: manifests[key] = json.load(open_file_read(fn)) except Exception: manifests[key] = None error("Could not parse '%s'" % fn, do_exit=False) return manifests
def _extract_framework(self, app): '''Get framework for app''' rel = self.manifest['hooks'][app]['framework'] fn = os.path.join(self.unpack_dir, rel) if not os.path.exists(fn): error("Could not find '%s'" % rel) data = dict() fh = open_file_read(fn) for line in fh.readlines(): tmp = line.split(':') if len(tmp) != 2: continue data[tmp[0].strip()] = tmp[1].strip() fh.close() return (fn, data)
def __init__(self, fn, overrides=None): # bin-path is ignored by snappy install so don't bother with peerhooks ClickReview.__init__(self, fn, "bin-path", overrides=overrides) self.bin_paths_files = dict() self.bin_paths = dict() if not self.is_snap1: return # snappy yaml currently only allows specifying: # - exec (optional) # - description (optional) self.required_keys = [] self.optional_keys = ['description', 'exec'] + self.snappy_exe_security if self.is_snap1 and 'binaries' in self.pkg_yaml: if len(self.pkg_yaml['binaries']) == 0: error("package.yaml malformed: 'binaries' is empty") for binary in self.pkg_yaml['binaries']: if 'name' not in binary: error("package.yaml malformed: required 'name' not found " "for entry in %s" % self.pkg_yaml['binaries']) elif not isinstance(binary['name'], str): error("package.yaml malformed: required 'name' is not str" "for entry in %s" % self.pkg_yaml['binaries']) app = os.path.basename(binary['name']) if 'exec' in binary: rel = binary['exec'] else: rel = binary['name'] self.bin_paths[app] = rel self.bin_paths_files[app] = self._extract_bin_path(app)
def _extract_push_helper(self, app): '''Get push-helper hook content''' c = self.manifest['hooks'][app]['push-helper'] fn = os.path.join(self.unpack_dir, c) bn = os.path.basename(fn) if not os.path.exists(fn): error("Could not find '%s'" % bn) fh = open_file_read(fn) contents = "" for line in fh.readlines(): contents += line fh.close() try: jd = json.loads(contents) except Exception as e: error("push-helper json unparseable: %s (%s):\n%s" % (bn, str(e), contents)) if not isinstance(jd, dict): error("push-helper json is malformed: %s:\n%s" % (bn, contents)) return (fn, jd)
def _extract_desktop_entry(self, app): '''Get DesktopEntry for desktop file and verify it''' d = self.manifest['hooks'][app]['desktop'] fn = os.path.join(self.unpack_dir, d) bn = os.path.basename(fn) if not os.path.exists(fn): error("Could not find '%s'" % bn) fh = open_file_read(fn) contents = "" for line in fh.readlines(): contents += line fh.close() try: de = DesktopEntry(fn) except xdgParsingError as e: error("desktop file unparseable: %s (%s):\n%s" % (bn, str(e), contents)) try: de.parse(fn) except Exception as e: error("desktop file unparseable: %s (%s):\n%s" % (bn, str(e), contents)) return de, fn
def _extract_account(self, app, account_type): '''Extract accounts''' if self.manifest is None: return a = self.manifest['hooks'][app][account_type] fn = os.path.join(self.unpack_dir, a) bn = os.path.basename(fn) if not os.path.exists(fn): error("Could not find '%s'" % bn) # qml-plugin points to a QML file, so just set that we have the # the hook present for now if account_type == "account-qml-plugin": return (fn, True) elif account_type == "accounts": fh = open_file_read(fn) contents = "" for line in fh.readlines(): contents += line fh.close() try: jd = json.loads(contents) except Exception as e: error("accounts json unparseable: %s (%s):\n%s" % (bn, str(e), contents)) if not isinstance(jd, dict): error("accounts json is malformed: %s:\n%s" % (bn, contents)) return (fn, jd) else: try: tree = etree.parse(fn) xml = tree.getroot() except Exception as e: error("accounts xml unparseable: %s (%s)" % (bn, str(e))) return (fn, xml)
def __init__(self, fn, overrides=None): # systemd isn't implemented as a hook any more so don't setup peerhooks ClickReview.__init__(self, fn, "snappy-systemd", overrides=overrides) self.systemd_files = dict() # click-show-files and tests self.systemd = dict() if not self.is_snap1: return # snappy-systemd currently only allows specifying: # - start (required) # - description (required) # - stop # - poststop # - stop-timeout # - caps (checked in in cr_security.py) # - security-template (checked in in cr_security.py) # - security-override (checked in in cr_security.py) # - security-policy (checked in in cr_security.py) self.required_keys = ['start', 'description'] self.optional_keys = [ 'stop', 'poststop', 'stop-timeout', 'bus-name', 'listen-stream', 'socket', 'socket-user', 'socket-group', 'ports' ] + self.snappy_exe_security if self.is_snap1 and 'services' in self.pkg_yaml: if len(self.pkg_yaml['services']) == 0: error("package.yaml malformed: 'services' is empty") for service in self.pkg_yaml['services']: if 'name' not in service: error("package.yaml malformed: required 'name' not found " "for entry in %s" % self.pkg_yaml['services']) elif not isinstance(service['name'], str): error("package.yaml malformed: required 'name' is not str" "for entry in %s" % self.pkg_yaml['services']) app = service['name'] self.systemd[app] = copy.deepcopy(service) del self.systemd[app]['name']
def __init__(self, fn, overrides=None): peer_hooks = dict() peer_hooks['account-application'] = dict() peer_hooks['account-application']['allowed'] = \ ClickReview.app_allowed_peer_hooks + \ ClickReview.scope_allowed_peer_hooks peer_hooks['account-application']['required'] = ['apparmor'] peer_hooks['account-service'] = dict() peer_hooks['account-service']['required'] = [ 'account-application', 'apparmor' ] peer_hooks['account-service']['allowed'] = \ ClickReview.app_allowed_peer_hooks + \ ClickReview.scope_allowed_peer_hooks peer_hooks['accounts'] = dict() peer_hooks['accounts']['allowed'] = \ [h for h in (ClickReview.app_allowed_peer_hooks + ClickReview.scope_allowed_peer_hooks) if not h.startswith('account-')] peer_hooks['accounts']['required'] = ['apparmor'] peer_hooks['account-provider'] = dict() peer_hooks['account-provider']['required'] = [ 'account-qml-plugin', 'apparmor' ] peer_hooks['account-provider']['allowed'] = \ peer_hooks['account-provider']['required'] peer_hooks['account-qml-plugin'] = dict() peer_hooks['account-qml-plugin']['required'] = [ 'account-provider', 'apparmor' ] peer_hooks['account-qml-plugin']['allowed'] = \ peer_hooks['account-qml-plugin']['required'] ClickReview.__init__( self, fn, "online_accounts", peer_hooks=peer_hooks, overrides=overrides, peer_hooks_link= "https://wiki.ubuntu.com/SecurityTeam/Specifications/OnlineAccountsConfinement" ) if not self.is_click and not self.is_snap1: return self.accounts_files = dict() self.accounts = dict() self.account_hooks = [ 'accounts', 'account-application', 'account-provider', 'account-qml-plugin', 'account-service' ] if self.manifest is None: return for app in self.manifest['hooks']: for h in self.account_hooks: if h not in self.manifest['hooks'][app]: # msg("Skipped missing %s hook for '%s'" % (h, app)) continue if not isinstance(self.manifest['hooks'][app][h], str): error("manifest malformed: hooks/%s/%s is not a str" % (app, h)) (full_fn, parsed) = self._extract_account(app, h) if app not in self.accounts_files: self.accounts_files[app] = dict() self.accounts_files[app][h] = full_fn if app not in self.accounts: self.accounts[app] = dict() self.accounts[app][h] = parsed self.required_keys = dict() self.allowed_keys = dict() self.required_keys["service"] = [ ('provider', str), ] self.allowed_keys["service"] = [ ('auth', dict), ('name', str), ('description', str), ] self.required_keys["plugin"] = [ ('name', str), ('icon', str), ('qml', str), ] self.allowed_keys["plugin"] = [ ('auth', dict), ] self.provider_re = re.compile('^[a-zA-Z0-9_.-]+$')