mirror of
https://github.com/SELinuxProject/refpolicy
synced 2025-01-03 05:22:39 +00:00
Add tool for validating contexts in appconfig files.
Initial implementation only covers files with full contexts. Signed-off-by: Chris PeBenito <pebenito@ieee.org>
This commit is contained in:
parent
09bdfcda4f
commit
0fd1e06fc7
11
.github/workflows/build-userspace.yml
vendored
11
.github/workflows/build-userspace.yml
vendored
@ -11,6 +11,10 @@ on:
|
|||||||
description: "Userspace version (a git commit ID, tag, or branch)"
|
description: "Userspace version (a git commit ID, tag, or branch)"
|
||||||
required: false
|
required: false
|
||||||
type: string
|
type: string
|
||||||
|
python-version:
|
||||||
|
description: "Python version to use"
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
outputs:
|
outputs:
|
||||||
source-id:
|
source-id:
|
||||||
description: "Userspace source artifact ID"
|
description: "Userspace source artifact ID"
|
||||||
@ -34,6 +38,11 @@ jobs:
|
|||||||
ref: "${{ inputs.version }}"
|
ref: "${{ inputs.version }}"
|
||||||
path: "${{ env.SELINUX_SRC }}"
|
path: "${{ env.SELINUX_SRC }}"
|
||||||
|
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: "${{ inputs.python-version }}"
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
@ -62,7 +71,7 @@ jobs:
|
|||||||
# Drop sandbox to break libcap-ng dependence
|
# Drop sandbox to break libcap-ng dependence
|
||||||
sed -i -e 's/ sandbox//' policycoreutils/Makefile
|
sed -i -e 's/ sandbox//' policycoreutils/Makefile
|
||||||
# Compile and install SELinux toolchain
|
# Compile and install SELinux toolchain
|
||||||
make OPT_SUBDIRS=semodule-utils install
|
make OPT_SUBDIRS=semodule-utils install install-pywrap
|
||||||
# set output directory on successful/pre-existing compile
|
# set output directory on successful/pre-existing compile
|
||||||
echo "DESTDIR=\"${DESTDIR}\"" >> $GITHUB_OUTPUT
|
echo "DESTDIR=\"${DESTDIR}\"" >> $GITHUB_OUTPUT
|
||||||
env:
|
env:
|
||||||
|
1
.github/workflows/tests.yml
vendored
1
.github/workflows/tests.yml
vendored
@ -24,6 +24,7 @@ jobs:
|
|||||||
needs: lint_branch_policy
|
needs: lint_branch_policy
|
||||||
with:
|
with:
|
||||||
version: "3.2"
|
version: "3.2"
|
||||||
|
python-version: "3.10"
|
||||||
|
|
||||||
build_setools:
|
build_setools:
|
||||||
uses: ./.github/workflows/build-setools.yml
|
uses: ./.github/workflows/build-setools.yml
|
||||||
|
51
Makefile
51
Makefile
@ -54,28 +54,33 @@ python_path := $(TEST_TOOLCHAIN)$(python_path_plat):$(TEST_TOOLCHAIN)$(python_pa
|
|||||||
else
|
else
|
||||||
python_path := $(TEST_TOOLCHAIN)$(python_path_plat):$(TEST_TOOLCHAIN)$(python_path_pure)
|
python_path := $(TEST_TOOLCHAIN)$(python_path_plat):$(TEST_TOOLCHAIN)$(python_path_pure)
|
||||||
endif
|
endif
|
||||||
tc_usrbindir := env LD_LIBRARY_PATH="$(TEST_TOOLCHAIN)/lib:$(TEST_TOOLCHAIN)/usr/lib" PYTHONPATH="$(python_path)" $(TEST_TOOLCHAIN)$(BINDIR)
|
tc_env := env LD_LIBRARY_PATH="$(TEST_TOOLCHAIN)/lib:$(TEST_TOOLCHAIN)/usr/lib" PYTHONPATH="$(python_path)"
|
||||||
tc_usrsbindir := env LD_LIBRARY_PATH="$(TEST_TOOLCHAIN)/lib:$(TEST_TOOLCHAIN)/usr/lib" PYTHONPATH="$(python_path)" $(TEST_TOOLCHAIN)$(SBINDIR)
|
tc_usrbindir := $(TEST_TOOLCHAIN)$(BINDIR)
|
||||||
tc_sbindir := env LD_LIBRARY_PATH="$(TEST_TOOLCHAIN)/lib:$(TEST_TOOLCHAIN)/usr/lib" PYTHONPATH="$(python_path)" $(TEST_TOOLCHAIN)/sbin
|
tc_usrsbindir := $(TEST_TOOLCHAIN)$(SBINDIR)
|
||||||
|
tc_sbindir := $(TEST_TOOLCHAIN)/sbin
|
||||||
else
|
else
|
||||||
|
tc_env :=
|
||||||
tc_usrbindir := $(BINDIR)
|
tc_usrbindir := $(BINDIR)
|
||||||
tc_usrsbindir := $(SBINDIR)
|
tc_usrsbindir := $(SBINDIR)
|
||||||
tc_sbindir := /sbin
|
tc_sbindir := /sbin
|
||||||
endif
|
endif
|
||||||
CHECKPOLICY ?= $(tc_usrbindir)/checkpolicy
|
CHECKPOLICY ?= $(tc_env) $(tc_usrbindir)/checkpolicy
|
||||||
CHECKMODULE ?= $(tc_usrbindir)/checkmodule
|
CHECKMODULE ?= $(tc_env) $(tc_usrbindir)/checkmodule
|
||||||
SEMODULE ?= $(tc_usrsbindir)/semodule
|
SEMODULE ?= $(tc_env) $(tc_usrsbindir)/semodule
|
||||||
SEMOD_PKG ?= $(tc_usrbindir)/semodule_package
|
SEMOD_PKG ?= $(tc_env) $(tc_usrbindir)/semodule_package
|
||||||
SEMOD_LNK ?= $(tc_usrbindir)/semodule_link
|
SEMOD_LNK ?= $(tc_env) $(tc_usrbindir)/semodule_link
|
||||||
SEMOD_EXP ?= $(tc_usrbindir)/semodule_expand
|
SEMOD_EXP ?= $(tc_env) $(tc_usrbindir)/semodule_expand
|
||||||
LOADPOLICY ?= $(tc_usrsbindir)/load_policy
|
LOADPOLICY ?= $(tc_env) $(tc_usrsbindir)/load_policy
|
||||||
|
# chkcon is not directly run by makefiles; the path is used by the validate-appconfig
|
||||||
|
# tool. The tc_env is added below in the validateappconfig var
|
||||||
|
CHKCON ?= $(tc_usrbindir)/chkcon
|
||||||
ifdef TEST_TOOLCHAIN
|
ifdef TEST_TOOLCHAIN
|
||||||
SEPOLGEN_IFGEN ?= $(tc_usrbindir)/sepolgen-ifgen --attr-helper $(TEST_TOOLCHAIN)$(BINDIR)/sepolgen-ifgen-attr-helper
|
SEPOLGEN_IFGEN ?= $(tc_env) $(tc_usrbindir)/sepolgen-ifgen --attr-helper $(TEST_TOOLCHAIN)$(BINDIR)/sepolgen-ifgen-attr-helper
|
||||||
else
|
else
|
||||||
SEPOLGEN_IFGEN ?= $(tc_usrbindir)/sepolgen-ifgen
|
SEPOLGEN_IFGEN ?= $(tc_env) $(tc_usrbindir)/sepolgen-ifgen
|
||||||
endif
|
endif
|
||||||
SETFILES ?= $(tc_sbindir)/setfiles
|
SETFILES ?= $(tc_env) $(tc_sbindir)/setfiles
|
||||||
SEFCONTEXT_COMPILE ?= $(tc_usrsbindir)/sefcontext_compile
|
SEFCONTEXT_COMPILE ?= $(tc_env) $(tc_usrsbindir)/sefcontext_compile
|
||||||
XMLLINT ?= $(BINDIR)/xmllint
|
XMLLINT ?= $(BINDIR)/xmllint
|
||||||
SECHECK ?= $(BINDIR)/sechecker
|
SECHECK ?= $(BINDIR)/sechecker
|
||||||
|
|
||||||
@ -123,6 +128,7 @@ m4terminate := $(support)/fatal_error.m4
|
|||||||
# so policycoreutils updates are not required (RHEL4)
|
# so policycoreutils updates are not required (RHEL4)
|
||||||
genhomedircon := $(PYTHON) $(support)/genhomedircon.py
|
genhomedircon := $(PYTHON) $(support)/genhomedircon.py
|
||||||
gentemplates := $(support)/gentemplates.sh
|
gentemplates := $(support)/gentemplates.sh
|
||||||
|
validateappconfig := $(tc_env) $(PYTHON) $(support)/validate-appconfig.py -c $(CHKCON)
|
||||||
|
|
||||||
# documentation paths
|
# documentation paths
|
||||||
docs := doc
|
docs := doc
|
||||||
@ -338,6 +344,23 @@ off_mods += $(filter-out $(cmdline_off) $(cmdline_base) $(cmdline_mods), $(mod_c
|
|||||||
# add modules not in modules.conf to the off list
|
# add modules not in modules.conf to the off list
|
||||||
off_mods += $(filter-out $(base_mods) $(mod_mods) $(off_mods),$(notdir $(detected_mods)))
|
off_mods += $(filter-out $(base_mods) $(mod_mods) $(off_mods),$(notdir $(detected_mods)))
|
||||||
|
|
||||||
|
# enable appconfig validation based on enabled modules
|
||||||
|
ifneq "$(filter container.te,$(base_mods) $(mod_mods))" ""
|
||||||
|
validateappconfig += -l
|
||||||
|
endif
|
||||||
|
|
||||||
|
ifneq "$(filter postgresql.te,$(base_mods) $(mod_mods))" ""
|
||||||
|
validateappconfig += -s
|
||||||
|
endif
|
||||||
|
|
||||||
|
ifneq "$(filter virt.te,$(base_mods) $(mod_mods))" ""
|
||||||
|
validateappconfig += -v
|
||||||
|
endif
|
||||||
|
|
||||||
|
ifneq "$(filter xserver.te,$(base_mods) $(mod_mods))" ""
|
||||||
|
validateappconfig += -x
|
||||||
|
endif
|
||||||
|
|
||||||
# filesystems to be used in labeling targets
|
# filesystems to be used in labeling targets
|
||||||
filesystems = $(shell mount | grep -v "context=" | $(GREP) -v '\((|.*,)bind(,.*|)\)' | $(AWK) '/(ext[234]|btrfs| xfs| jfs).*rw/{print $$3}';)
|
filesystems = $(shell mount | grep -v "context=" | $(GREP) -v '\((|.*,)bind(,.*|)\)' | $(AWK) '/(ext[234]|btrfs| xfs| jfs).*rw/{print $$3}';)
|
||||||
fs_names := "btrfs ext2 ext3 ext4 xfs jfs"
|
fs_names := "btrfs ext2 ext3 ext4 xfs jfs"
|
||||||
|
@ -216,14 +216,16 @@ $(builtappconf)/customizable_types: $(base_conf)
|
|||||||
|
|
||||||
########################################
|
########################################
|
||||||
#
|
#
|
||||||
# Validate linking and expanding of modules
|
# Validate linking and expanding of modules, file_contexts, and appconfig
|
||||||
#
|
#
|
||||||
validate: $(base_pkg) $(mod_pkgs) $(tmpdir)/all_mods.fc
|
validate: $(base_pkg) $(mod_pkgs) $(tmpdir)/all_mods.fc $(builtappfiles)
|
||||||
@echo "Validating policy linking."
|
@echo "Validating $(NAME) linking."
|
||||||
$(verbose) $(SEMOD_LNK) -o $(tmpdir)/test.lnk $(base_pkg) $(mod_pkgs)
|
$(verbose) $(SEMOD_LNK) -o $(tmpdir)/test.lnk $(base_pkg) $(mod_pkgs)
|
||||||
$(verbose) $(SEMOD_EXP) $(tmpdir)/test.lnk $(tmpdir)/policy.bin
|
$(verbose) $(SEMOD_EXP) $(tmpdir)/test.lnk $(tmpdir)/policy.bin
|
||||||
@echo "Validating policy file contexts."
|
@echo "Validating $(NAME) file contexts."
|
||||||
$(verbose) $(SETFILES) -q -c $(tmpdir)/policy.bin $(tmpdir)/all_mods.fc
|
$(verbose) $(SETFILES) -q -c $(tmpdir)/policy.bin $(tmpdir)/all_mods.fc
|
||||||
|
@echo "Validating $(NAME) appconfig."
|
||||||
|
$(verbose) $(validateappconfig) $(builtappconf) $(tmpdir)/policy.bin
|
||||||
@echo "Success."
|
@echo "Success."
|
||||||
|
|
||||||
########################################
|
########################################
|
||||||
|
@ -241,11 +241,13 @@ $(fcpath): $(fc) $(loadpath) $(userpath)/system.users
|
|||||||
|
|
||||||
########################################
|
########################################
|
||||||
#
|
#
|
||||||
# Validate file contexts
|
# Validate file contexts and appconfig
|
||||||
#
|
#
|
||||||
validate: $(fc) $(polver)
|
validate: $(fc) $(polver) $(builtappfiles)
|
||||||
@echo "Validating $(NAME) file_contexts."
|
@echo "Validating $(NAME) file_contexts."
|
||||||
$(verbose) $(SETFILES) -q -c $(polver) $(fc)
|
$(verbose) $(SETFILES) -q -c $(polver) $(fc)
|
||||||
|
@echo "Validating $(NAME) appconfig."
|
||||||
|
$(verbose) $(validateappconfig) $(builtappconf) $(polver)
|
||||||
@echo "Success."
|
@echo "Success."
|
||||||
|
|
||||||
########################################
|
########################################
|
||||||
|
331
support/validate-appconfig.py
Executable file
331
support/validate-appconfig.py
Executable file
@ -0,0 +1,331 @@
|
|||||||
|
#!/usr/bin/python3
|
||||||
|
# SPDX-License-Identifier: GPL-2.0-only
|
||||||
|
"""Validate refpolicy userpace configuration files (appconfig) have valid contexts."""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
from contextlib import suppress
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import typing
|
||||||
|
import warnings
|
||||||
|
from xml.dom.minidom import Node
|
||||||
|
|
||||||
|
try:
|
||||||
|
from defusedxml import minidom
|
||||||
|
except ImportError:
|
||||||
|
from xml.dom import minidom
|
||||||
|
|
||||||
|
import selinux as libselinux
|
||||||
|
|
||||||
|
DBUS_CONTEXTS: typing.Final[str] = "dbus_contexts"
|
||||||
|
MEDIA_CONTEXTS: typing.Final[str] = "media"
|
||||||
|
SINGLE_LINE_CONTEXTS_FILES: typing.Final[tuple[str, ...]] = ("initrc_context",
|
||||||
|
"removable_context",
|
||||||
|
"userhelper_context")
|
||||||
|
LXC_CONTEXTS: typing.Final[str] = "lxc_contexts"
|
||||||
|
SEPGSQL_CONTEXTS: typing.Final[str] = "sepgsql_contexts"
|
||||||
|
VIRT_CONTEXTS_FILES: typing.Final[tuple[str, ...]] = ("virtual_domain_context",
|
||||||
|
"virtual_image_context")
|
||||||
|
XSERVER_CONTEXTS: typing.Final[str] = "x_contexts"
|
||||||
|
|
||||||
|
CHKCON_PATHS: typing.Final[tuple[Path, ...]] = (Path("/usr/local/bin"),
|
||||||
|
Path("/usr/local/sbin"),
|
||||||
|
Path("/usr/bin"),
|
||||||
|
Path("/bin"),
|
||||||
|
Path("/usr/sbin"),
|
||||||
|
Path("/sbin"))
|
||||||
|
|
||||||
|
|
||||||
|
class ContextValidator:
|
||||||
|
|
||||||
|
"""Validate contexts using security_check_context or chkcon"""
|
||||||
|
|
||||||
|
def __init__(self, /, policy_path: str | None = None, *,
|
||||||
|
chkcon_path: str | None = None) -> None:
|
||||||
|
|
||||||
|
self.log = logging.getLogger(self.__class__.__name__)
|
||||||
|
self.policy_path = policy_path
|
||||||
|
self.selinux_enabled = libselinux.is_selinux_enabled() == 1
|
||||||
|
self.chkcon_path: Path | str | None = self._find_chkcon(chkcon_path)
|
||||||
|
self.log.debug(f"{self.__class__.__name__}: "
|
||||||
|
f"{self.policy_path=}, "
|
||||||
|
f"{self.selinux_enabled=}, "
|
||||||
|
f"{self.chkcon_path=}")
|
||||||
|
|
||||||
|
def _find_chkcon(self, /, path: Path | str | None) -> Path | str | None:
|
||||||
|
if path:
|
||||||
|
self.log.debug(f"Checking access on provided chkcon path {path}")
|
||||||
|
if os.access(path, os.X_OK):
|
||||||
|
return path
|
||||||
|
|
||||||
|
for p in CHKCON_PATHS:
|
||||||
|
path = p / "chkcon"
|
||||||
|
self.log.debug(f"Trying chkcon path {path}")
|
||||||
|
if os.access(path, os.X_OK):
|
||||||
|
return path
|
||||||
|
|
||||||
|
self.log.warning("chkcon not found, trying to find with \"which\"")
|
||||||
|
result = subprocess.run(["which", "chkcon"],
|
||||||
|
check=False,
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE)
|
||||||
|
return result.stdout.decode().strip() if result.returncode == 0 else None
|
||||||
|
|
||||||
|
def _chkcon_check_context(self, context: str, /) -> bool:
|
||||||
|
assert self.chkcon_path
|
||||||
|
assert self.policy_path
|
||||||
|
result = subprocess.run([self.chkcon_path, self.policy_path, context],
|
||||||
|
check=False,
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE)
|
||||||
|
|
||||||
|
return result.returncode == 0
|
||||||
|
|
||||||
|
def validate_context(self, context: str, /) -> bool:
|
||||||
|
"""Verify that the specified context is valid in the policy."""
|
||||||
|
if self.chkcon_path and self.policy_path:
|
||||||
|
self.log.debug(f"Validating context {context} with chkcon")
|
||||||
|
return self._chkcon_check_context(context)
|
||||||
|
|
||||||
|
if self.selinux_enabled:
|
||||||
|
self.log.debug(f"Validating context {context} with security_check_context")
|
||||||
|
return libselinux.security_check_context(context) == 0
|
||||||
|
|
||||||
|
self.log.critical(f"Warning: Context validation not done for {context}")
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def validate_dbus_contexts(validator: ContextValidator, file_path: Path, /) -> bool:
|
||||||
|
"""
|
||||||
|
Validate the contexts in the specified dbus_contexts file.
|
||||||
|
|
||||||
|
A minimum/empty dbus_contexts file is as follows:
|
||||||
|
|
||||||
|
<busconfig>
|
||||||
|
<selinux>
|
||||||
|
</selinux>
|
||||||
|
</busconfig>
|
||||||
|
|
||||||
|
An example dbus_contexts with dbus service labeling:
|
||||||
|
|
||||||
|
<busconfig>
|
||||||
|
<selinux>
|
||||||
|
<associate own="org.selinux.semanage" context="system_u:system_r:selinux_dbus_t:s0" />
|
||||||
|
<associate own="org.selinux.Restorecond" context="system_u:system_r:restorecond_t:s0" />
|
||||||
|
</selinux>
|
||||||
|
</busconfig>
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Parse the XML file
|
||||||
|
logging.info(f"Using {minidom.__name__} for parsing {file_path}.")
|
||||||
|
dom: typing.Final[minidom.Document] = minidom.parse(str(file_path))
|
||||||
|
|
||||||
|
# Ensure <busconfig> is the top-level tag
|
||||||
|
top_level_elements: list[minidom.Element] = [node for node in dom.childNodes
|
||||||
|
if node.nodeType == Node.ELEMENT_NODE]
|
||||||
|
|
||||||
|
if len(top_level_elements) != 1 or top_level_elements[0].tagName != "busconfig":
|
||||||
|
raise ValueError("The top-level tag must be <busconfig>.")
|
||||||
|
|
||||||
|
busconfig = top_level_elements[0]
|
||||||
|
|
||||||
|
# Not validating that <selinux> is the only tag under <busconfig> as other
|
||||||
|
# tags can work, such as <policy>.
|
||||||
|
selinux_elements: list[minidom.Element] = [node for node in busconfig.childNodes
|
||||||
|
if node.nodeType == Node.ELEMENT_NODE
|
||||||
|
and node.tagName == "selinux"]
|
||||||
|
|
||||||
|
# Ensure there is only one <selinux> element
|
||||||
|
if len(selinux_elements) != 1:
|
||||||
|
raise ValueError(
|
||||||
|
f"Invalid number of <selinux> elements found under <busconfig>: {selinux_elements}.")
|
||||||
|
|
||||||
|
# Check if all child nodes are <associate> elements
|
||||||
|
valid: bool = True
|
||||||
|
for child in selinux_elements[0].childNodes:
|
||||||
|
if child.nodeType != Node.ELEMENT_NODE:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if child.tagName != "associate":
|
||||||
|
print(f"Invalid element found under <selinux>: {child.toxml()}")
|
||||||
|
valid = False
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Validate that each <associate> element has only "own" and "context" attributes
|
||||||
|
attributes: minidom.NamedNodeMap = child.attributes
|
||||||
|
if set(attributes.keys()) != {"own", "context"}:
|
||||||
|
print(f"Invalid associate element: {child.toxml()}")
|
||||||
|
valid = False
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Validate the context attribute
|
||||||
|
own: str = attributes["own"].value
|
||||||
|
context: str = attributes["context"].value
|
||||||
|
if not validator.validate_context(context):
|
||||||
|
print(f"Invalid context for service {own}: {context}")
|
||||||
|
valid = False
|
||||||
|
|
||||||
|
return valid
|
||||||
|
|
||||||
|
|
||||||
|
def validate_lxc_contexts(validator: ContextValidator, fullpath: Path, /) -> bool:
|
||||||
|
"""Validate the lxc_contexts file."""
|
||||||
|
valid: bool = True
|
||||||
|
with open(fullpath, "r", encoding="utf-8") as file:
|
||||||
|
logging.info(f"Validating {fullpath}")
|
||||||
|
for line in file:
|
||||||
|
line = line.strip()
|
||||||
|
items = line.split()
|
||||||
|
with suppress(IndexError):
|
||||||
|
if not items:
|
||||||
|
continue
|
||||||
|
|
||||||
|
context = items[2].strip("\"")
|
||||||
|
if not validator.validate_context(context):
|
||||||
|
print(f"Invalid context in {fullpath}: {line}")
|
||||||
|
valid = False
|
||||||
|
|
||||||
|
return valid
|
||||||
|
|
||||||
|
|
||||||
|
def validate_single_line_context_files(validator: ContextValidator,
|
||||||
|
filenames: list[Path], /) -> bool:
|
||||||
|
"""
|
||||||
|
Validate the contexts in the files with single context per line. This
|
||||||
|
is primarily for files tha have a single context, such as initrc_context,
|
||||||
|
but can also be used for virtual_image_context, which can have multiple
|
||||||
|
lines of a single context.
|
||||||
|
"""
|
||||||
|
valid: bool = True
|
||||||
|
for filename in filenames:
|
||||||
|
with open(filename, "r", encoding="utf-8") as file:
|
||||||
|
logging.info(f"Validating {filename}")
|
||||||
|
for line in file:
|
||||||
|
line = line.strip()
|
||||||
|
if not line:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if not validator.validate_context(line):
|
||||||
|
print(f"Invalid context in {filename}: {line}")
|
||||||
|
valid = False
|
||||||
|
|
||||||
|
return valid
|
||||||
|
|
||||||
|
|
||||||
|
def validate_media_contexts(validator: ContextValidator, fullpath: Path, /) -> bool:
|
||||||
|
"""Validate the contexts in the media file."""
|
||||||
|
valid: bool = True
|
||||||
|
with open(fullpath, "r", encoding="utf-8") as file:
|
||||||
|
logging.info(f"Validating {fullpath}")
|
||||||
|
for line in file:
|
||||||
|
line = line.strip()
|
||||||
|
with suppress(IndexError):
|
||||||
|
if not validator.validate_context(line.split()[1]):
|
||||||
|
print(f"Invalid context in {fullpath}: {line}")
|
||||||
|
valid = False
|
||||||
|
|
||||||
|
return valid
|
||||||
|
|
||||||
|
|
||||||
|
def validate_three_field_contexts(validator: ContextValidator, filepaths: list[Path], /,
|
||||||
|
comment_char: str = "#") -> bool:
|
||||||
|
"""
|
||||||
|
Validate the contexts of a file that has three fields per line, with
|
||||||
|
the third field being the context. Examples are sepgsql_contexts and
|
||||||
|
x_contexts.
|
||||||
|
"""
|
||||||
|
valid: bool = True
|
||||||
|
|
||||||
|
for fullpath in filepaths:
|
||||||
|
with open(fullpath, "r", encoding="utf-8") as file:
|
||||||
|
logging.info(f"Validating {fullpath}")
|
||||||
|
for line in file:
|
||||||
|
line = line.strip()
|
||||||
|
items = line.split()
|
||||||
|
with suppress(IndexError):
|
||||||
|
if not items or items[0].startswith(comment_char):
|
||||||
|
continue
|
||||||
|
|
||||||
|
if not validator.validate_context(items[2]):
|
||||||
|
print(f"Invalid context in {fullpath}: {line}")
|
||||||
|
valid = False
|
||||||
|
|
||||||
|
return valid
|
||||||
|
|
||||||
|
|
||||||
|
def validate_appconfig_files(conf_dir: str, /, *,
|
||||||
|
policy_path: str | None = None,
|
||||||
|
chkcon_path: str | None = None,
|
||||||
|
lxc: bool = True,
|
||||||
|
sepgsql: bool = True,
|
||||||
|
virt: bool = True,
|
||||||
|
xserver: bool = True) -> bool:
|
||||||
|
|
||||||
|
"""Validate the various appconfig userspace config files."""
|
||||||
|
validator: typing.Final[ContextValidator] = ContextValidator(policy_path=policy_path,
|
||||||
|
chkcon_path=chkcon_path)
|
||||||
|
base_path: typing.Final[Path] = Path(conf_dir)
|
||||||
|
|
||||||
|
single_line_contexts = [base_path / p for p in SINGLE_LINE_CONTEXTS_FILES]
|
||||||
|
if virt:
|
||||||
|
single_line_contexts.extend(base_path / p for p in VIRT_CONTEXTS_FILES)
|
||||||
|
|
||||||
|
key_value_contexts = list[Path]()
|
||||||
|
if sepgsql:
|
||||||
|
key_value_contexts.append(base_path / SEPGSQL_CONTEXTS)
|
||||||
|
if xserver:
|
||||||
|
key_value_contexts.append(base_path / XSERVER_CONTEXTS)
|
||||||
|
|
||||||
|
return all((validate_dbus_contexts(validator, base_path / DBUS_CONTEXTS),
|
||||||
|
validate_single_line_context_files(validator, single_line_contexts),
|
||||||
|
validate_media_contexts(validator, base_path / MEDIA_CONTEXTS),
|
||||||
|
validate_three_field_contexts(validator, key_value_contexts),
|
||||||
|
validate_lxc_contexts(validator, base_path / LXC_CONTEXTS) if lxc else True))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
parser = argparse.ArgumentParser(description="Validate userspace app config.",
|
||||||
|
epilog="If no policy is specified, the running policy (if any) is used.")
|
||||||
|
parser.add_argument("APPCONFIG_DIR", type=str,
|
||||||
|
help="Path to the appconfig dir to validate")
|
||||||
|
parser.add_argument("POLICY_PATH", nargs="?", type=str,
|
||||||
|
help="Path to binary policy file (optional)")
|
||||||
|
parser.add_argument("-c", "--chkcon", type=str,
|
||||||
|
help="Path to chkcon executable.")
|
||||||
|
parser.add_argument("-l", "--lxc", action="store_true", help="Check lxc_contexts.")
|
||||||
|
parser.add_argument("-s", "--sepgsql", action="store_true", help="Check sepgsql_contexts.")
|
||||||
|
parser.add_argument("-v", "--virt", action="store_true", help="Check virtual_*_context.")
|
||||||
|
parser.add_argument("-x", "--xserver", action="store_true", help="Check x_contexts.")
|
||||||
|
parser.add_argument("--debug", action="store_true", dest="debug",
|
||||||
|
help="Enable debugging.")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
if args.debug:
|
||||||
|
logging.basicConfig(level=logging.DEBUG,
|
||||||
|
format='%(asctime)s|%(levelname)s|%(name)s|%(message)s')
|
||||||
|
if not sys.warnoptions:
|
||||||
|
warnings.simplefilter("default")
|
||||||
|
else:
|
||||||
|
logging.basicConfig(level=logging.WARNING, format='%(message)s')
|
||||||
|
if not sys.warnoptions:
|
||||||
|
warnings.simplefilter("ignore")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Validate the <associate> elements under <selinux>
|
||||||
|
sys.exit(0 if validate_appconfig_files(args.APPCONFIG_DIR,
|
||||||
|
policy_path=args.POLICY_PATH,
|
||||||
|
chkcon_path=args.chkcon,
|
||||||
|
lxc=args.lxc,
|
||||||
|
sepgsql=args.sepgsql,
|
||||||
|
virt=args.virt,
|
||||||
|
xserver=args.xserver) else 1)
|
||||||
|
|
||||||
|
except Exception as err:
|
||||||
|
if args.debug:
|
||||||
|
raise
|
||||||
|
|
||||||
|
print(err)
|
||||||
|
sys.exit(1)
|
Loading…
Reference in New Issue
Block a user