diff --git a/meta-luneos/classes/webos_ls2_api_info.bbclass b/meta-luneos/classes/webos_ls2_api_info.bbclass new file mode 100644 index 0000000000..0e54528ab5 --- /dev/null +++ b/meta-luneos/classes/webos_ls2_api_info.bbclass @@ -0,0 +1,17 @@ +# Copyright (c) 2021-2024 LG Electronics, Inc. + +LS2_DIR = "${IMAGE_ROOTFS}${datadir}/luna-service2" + +webos_ls2_api_info_data () { + if [ -d "${LS2_DIR}" -a -n "${BUILDHISTORY_DIR_IMAGE}" ] ; then + cd ${LS2_DIR} + if [ -d ${BUILDHISTORY_DIR_IMAGE}/ls2_api ] ; then + rm -rf ${BUILDHISTORY_DIR_IMAGE}/ls2_api + fi + mkdir -p ${BUILDHISTORY_DIR_IMAGE}/ls2_api + cp -r ./* ${BUILDHISTORY_DIR_IMAGE}/ls2_api/ + cd - + fi +} + +ROOTFS_POSTPROCESS_COMMAND += "webos_ls2_api_info_data ;" diff --git a/meta-luneos/classes/webos_ls2_api_list.bbclass b/meta-luneos/classes/webos_ls2_api_list.bbclass new file mode 100644 index 0000000000..6e5d37b5a4 --- /dev/null +++ b/meta-luneos/classes/webos_ls2_api_list.bbclass @@ -0,0 +1,112 @@ +# Copyright (c) 2022-2024 LG Electronics, Inc. +# +# write_ls2_api_list +# +# This class adds write_ls2_api_list task. +# write_ls2_api_list can be run by bitbake -c write_ls2_api_list +# ls2_api_list.json files of build + +LS2_API_LIST_FILENAME ?= "ls2_api_list.json" + +# We need ls2_api_list.json image +addtask write_ls2_api_list after do_rootfs before do_image +do_write_ls2_api_list[doc] = "Collects ls2 api information of the image" + +python do_write_ls2_api_list() { + import os,json,glob,re + + def remove_comments_from_json(json_file): + with open(json_file, 'r') as file: + lines = file.readlines() + + clean_lines = [] + for line in lines: + # Remove single-line comments + line = re.sub(r'//.*', '', line) + # Remove multi-line comments + line = re.sub(r'/\*.*?\*/', '', line, flags=re.DOTALL) + clean_lines.append(line) + + # Combine the cleaned lines back into a single string + clean_json = ''.join(clean_lines) + # Parse the cleaned JSON + try: + parsed_json = json.loads(clean_json) + return parsed_json + except json.JSONDecodeError as e: + bb.note('Error parsing JSON: {e}') + return None + + root_image_path =d.getVar('IMAGE_ROOTFS'); + bb.note("perform do_write_ls2_api_list on ROOTFS : " + root_image_path) + def read_ls2_manifests_directories(image_rootfs): + manifests_directories = [] + with open(os.path.join(image_rootfs, 'etc', 'luna-service2', 'ls-hubd.conf'), 'r') as f: + ls_hubd_conf = f.read() + for line in ls_hubd_conf.splitlines(): + line = line.strip() + #ignore empty lines and comments + if not line or line.startswith('#'): + continue + #ignore section likes [Security] + elif line.startswith('[') and line.endswith(']'): + continue + else: + key, value = line.split('=', 1) + key = key.strip() + value = value.strip() + if key == 'ManifestsDirectories': + manifests_directories = value.split(';') + break + return manifests_directories + + def search_for_manifest_files(root_image_path,manifest_dir_path): + manifest_file_path=os.path.join(root_image_path,manifest_dir_path.lstrip('/'),'*.manifest.json') + bb.debug(1,'Gathering info on api permission file from manifest_file_path %s' % manifest_file_path) + return glob.glob(manifest_file_path) + + def get_api_permissions_for_manifest_file(root_image_path,manifest_file): + bb.debug(1,'Gathering info on api permission file from manifest_file %s' % manifest_file) + apis_details=[]; + with open(manifest_file, 'r') as f: + manifest_data = json.load(f) + if 'apiPermissionFiles' in manifest_data: + for api_permission_file in manifest_data['apiPermissionFiles']: + api_permission_file_path = os.path.join(root_image_path, api_permission_file.lstrip('/')) + if not os.path.isfile(api_permission_file_path) or 'compat.' in api_permission_file_path: + bb.note("File not found or Compat files excluded : " + api_permission_file_path) + continue + # Remove comments and parse JSON + api_permission_data = remove_comments_from_json(api_permission_file_path) + if api_permission_data is None: + bb.note('Failed to parse JSON due to comment removal.') + for key, value in api_permission_data.items(): + apis_details.extend(value) + return apis_details + + + manifests_directories = read_ls2_manifests_directories(root_image_path) + bb.note('Manifests directories: %s' % manifests_directories) + ls_api_info_data = [] + # Iterate over the manifests directories and check for manifest files + for manifests_dir in manifests_directories: + # make absolute manifest path to search for files ending with .manifest.json' + manifest_files =search_for_manifest_files (root_image_path,manifests_dir) + + for manifest_file in manifest_files: + bb.note('Gathering info on api permission file from manifest_file %s' % manifest_file) + apis_details= get_api_permissions_for_manifest_file(root_image_path,manifest_file) + if apis_details: + ls_api_info_data.append({os.path.basename(manifest_file).replace(".manifest.json", ""): apis_details}) + bb.note("webOS Release Code Name ::: " + d.getVar('WEBOS_DISTRO_RELEASE_CODENAME')) + bb.note("webOS Distro Name ::: " + d.getVar('DISTRO')) + ls_api_info= {'ls2_api_list': ls_api_info_data, 'release_code_name': d.getVar('WEBOS_DISTRO_RELEASE_CODENAME'), 'distro': d.getVar('DISTRO')} + + #write ls_api_info to file + ls2_output_file = d.getVar("LS2_API_LIST_FILENAME") + output = os.path.join(d.getVar('BUILDHISTORY_DIR_IMAGE'), ls2_output_file) + json_info_as_string = json.dumps(ls_api_info).replace("'", '"') + with open(output, 'w') as f: + f.write(json_info_as_string) + bb.note("BUILD Path of ls2_api_list.json file : " + output) +} diff --git a/meta-luneos/classes/webos_ls2_conf_validate.bbclass b/meta-luneos/classes/webos_ls2_conf_validate.bbclass index 1e5f957c00..b21a79587e 100644 --- a/meta-luneos/classes/webos_ls2_conf_validate.bbclass +++ b/meta-luneos/classes/webos_ls2_conf_validate.bbclass @@ -1,4 +1,4 @@ -# Copyright (c) 2023 LG Electronics, Inc. +# Copyright (c) 2023-2024 LG Electronics, Inc. # # LS2 security configuration validation # @@ -6,9 +6,7 @@ inherit webos_filesystem_paths WEBOS_LS2_CONF_VALIDATE_ERROR_ON_WARNING ?= "0" -WEBOS_LS2_CONF_VALIDATE_SKIP_GROUP ?= " \ - allowedNames \ -" +WEBOS_LS2_CONF_VALIDATE_SKIP_GROUP ?= "" # For some reason, using expr directly doesn't work accumulate() { @@ -134,37 +132,53 @@ fakeroot python do_validate_ls2_acg() { import os import json - # List of group names to skip checking - skip_group = d.getVar("WEBOS_LS2_CONF_VALIDATE_SKIP_GROUP").split() - if len(skip_group) > 0: - bb.debug(1, "WEBOS_LS2_CONF_VALIDATE_SKIP_GROUP:") - for group in skip_group: - bb.debug(1, " %s" % group) - rootfs_groups_d = d.getVar("IMAGE_ROOTFS") + d.getVar("webos_sysbus_groupsdir") rootfs_api_perms_d = d.getVar("IMAGE_ROOTFS") + d.getVar("webos_sysbus_apipermissionsdir") rootfs_clientperms_dir = d.getVar("IMAGE_ROOTFS") + d.getVar("webos_sysbus_permissionsdir") + # There can be no sysbus directories in a tiny image + if not os.path.isdir(rootfs_groups_d): + bb.note("Directory '%s' is missing, skipping validation." % rootfs_groups_d) + return + if not os.path.isdir(rootfs_api_perms_d): + bb.note("Directory '%s' is missing, skipping validation." % rootfs_api_perms_d) + return + if not os.path.isdir(rootfs_clientperms_dir): + bb.note("Directory '%s' is missing, skipping validation." % rootfs_clientperms_dir) + return + + # List of group names to skip checking + skip_group = d.getVar("WEBOS_LS2_CONF_VALIDATE_SKIP_GROUP").split() + if len(skip_group) > 0: + msg = "=== LIST BEGIN: Groups considered as exception ===\n" + for group in sorted(skip_group): + msg += " %s\n" % group + msg += "=== LIST END ===\n" + bb.warn(msg) + # Always skip 'allowedNames' which is being used a key in old-style groups.json + skip_group.append("allowedNames") + # Returns a set of group names defined in 'dir'. # Json files in 'dir' are expected to have groups as keys. def read_groups(dir): - bb.debug(1, "Reading groups from %s" % dir) + msg = "Reading groups from %s\n" % dir groups = set() with os.scandir(dir) as it: for entry in it: if entry.is_file(): - bb.debug(1, " %s" % entry.name) + msg += " %s\n" % entry.name with open(entry.path) as fp: groups_json = json.load(fp) groups.update(filter(lambda x: x not in skip_group, groups_json.keys())) - bb.debug(1, "Done reading groups from %s" % dir) + msg += "Done reading groups from %s\n" % dir + bb.debug(1, msg) return groups # Returns a set of group names used in 'perm_entry' but not in 'groups'. # 'groups' is a set of group names to match and 'perm_entry' is an # iterator entry of a file that refers groups in an array form. def get_missing_groups_in_perm(groups, perm_entry): - bb.debug(1, "Checking groups in %s" % perm_entry.name) + msg = "Checking groups in %s\n" % perm_entry.name missing_groups = set() with open(perm_entry.path) as fp: perm_json = json.load(fp) @@ -173,40 +187,44 @@ fakeroot python do_validate_ls2_acg() { if not group in groups: missing_groups.add(group) for group in sorted(missing_groups): - bb.debug(1, " %s%s" % (group, " => missing" if not group in groups else "")) - bb.debug(1, "Done checking groups in %s" % perm_entry.name) + msg += " %s%s" % (group, " => missing" if not group in groups else "\n") + msg += "Done checking groups in %s\n" % perm_entry.name + bb.debug(1, msg) return missing_groups # First, we build a set of groups defined in "groups.d". groups_defined = read_groups(rootfs_groups_d) - bb.debug(2, "=== LIST BEGIN: Groups defined in groups.d(%s) ===" % rootfs_groups_d) + msg = "=== LIST BEGIN: Groups defined in groups.d(%s) ===\n" % rootfs_groups_d for group in sorted(groups_defined): - bb.debug(2, " %s" % group) - bb.debug(2, "=== LIST END ===") + msg += " %s\n" % group + msg += "=== LIST END ===\n" # Second, get groups from "api-permissions.d". # Those groups are also considered as valid. groups_defined2 = read_groups(rootfs_api_perms_d) - bb.debug(2, "=== LIST BEGIN: Groups used in api-permissions.d(%s) ===" % rootfs_api_perms_d) + msg += "=== LIST BEGIN: Groups used in api-permissions.d(%s) ===\n" % rootfs_api_perms_d for group in sorted(groups_defined2): - bb.debug(2, " %s" % group) - bb.debug(2, "=== LIST END ===") + msg += " %s\n" % group + msg += "=== LIST END ===\n" + bb.debug(2, msg) # Merge groups from "groups.d" and "api-permissions.d" with showing differences. # Those differences are recommended to define in "groups.d". groups_defined2.difference_update(groups_defined) cnt = len(groups_defined2) if cnt > 0: - bb.warn("Found %d group(s) that appear only in api-permissions.d, consider define them in groups.d" % cnt) - bb.warn("=== LIST BEGIN: Groups used in api-permissions.d but not defined in groups.d ===") + msg = "Found %d group(s) that appear only in api-permissions.d, consider define them in groups.d\n" % cnt + msg += "=== LIST BEGIN: Groups used in api-permissions.d but not defined in groups.d ===\n" for group in sorted(groups_defined2): - bb.warn(" %s" % group) - bb.warn("=== LIST END ===") + msg += " %s\n" % group + msg += "=== LIST END ===\n" + bb.warn(msg) groups_valid = groups_defined.union(groups_defined2) - bb.note("=== LIST BEGIN: Groups considered as valid ===") + msg = "=== LIST BEGIN: Groups considered as valid ===\n" for group in sorted(groups_valid): - bb.note(" %s" % group) - bb.note("=== LIST END ===") + msg += " %s\n" % group + msg += "=== LIST END ===\n" + bb.note(msg) # Iterate files in "client-permissions.d" and list up groups # which don't appear in the set built above. @@ -224,17 +242,18 @@ fakeroot python do_validate_ls2_acg() { # Raise a warning or error(if enabled) if any missing group is found. cnt = len(groups_missing) if cnt > 0: - bb.warn("Found %d group(s) used in client-permissions.d but not defined" % cnt) - bb.warn("=== LIST BEGIN ===") + msg = "Found %d group(s) used in client-permissions.d but not defined\n" % cnt + msg += "=== LIST BEGIN ===\n" for group in sorted(groups_missing): - bb.warn("'%s' being used in:" % group) + msg += "'%s' being used in:\n" % group for entry in sorted(groups_missing[group]): - bb.warn(" %s" % entry) - bb.warn("=== LIST END =====") + msg += " %s\n" % entry + msg += "=== LIST END =====\n" + bb.warn(msg) if d.getVar("WEBOS_LS2_CONF_VALIDATE_ERROR_ON_WARNING") != "0": bb.fatal("Fatal error while checking groups, aborting!") } addtask do_validate_ls2_security_conf after do_rootfs before do_image addtask do_validate_ls2_acg after do_validate_ls2_security_conf before do_image -do_validate_ls2_security_conf[depends] += "libpbnjson-native:do_populate_sysroot" \ No newline at end of file +do_validate_ls2_security_conf[depends] += "libpbnjson-native:do_populate_sysroot"