This is needed because on a mounted `bootc` container `setfiles` without excluding `/sysroot` will create many warnings like: ``` setfiles: conflicting specifications for /run/osbuild/tree/sysroot/ostree/repo/objects/00/0ef9ada2ee87792e8ba21afd65aa00d79a1253018832652b8694862fb80e84.file and /run/osbuild/tree/usr/lib/firmware/cirrus/cs35l41-dsp1-spk-prot-103c8b8f-r1.bin.xz, using system_u:object_r:lib_t:s0. ``` but simply excluding this dir fixes them.
91 lines
2.8 KiB
Python
91 lines
2.8 KiB
Python
"""SELinux utility functions"""
|
|
|
|
import errno
|
|
import os
|
|
import subprocess
|
|
from typing import Dict, List, Optional, TextIO
|
|
|
|
# Extended attribute name for SELinux labels
|
|
XATTR_NAME_SELINUX = b"security.selinux"
|
|
|
|
|
|
def parse_config(config_file: TextIO):
|
|
"""Parse an SELinux configuration file"""
|
|
config = {}
|
|
for line in config_file:
|
|
line = line.strip()
|
|
if not line:
|
|
continue
|
|
if line.startswith('#'):
|
|
continue
|
|
k, v = line.split('=', 1)
|
|
config[k.strip()] = v.strip()
|
|
return config
|
|
|
|
|
|
def config_get_policy(config: Dict[str, str]):
|
|
"""Return the effective SELinux policy
|
|
|
|
Checks if SELinux is enabled and if so returns the
|
|
policy; otherwise `None` is returned.
|
|
"""
|
|
enabled = config.get('SELINUX', 'disabled')
|
|
if enabled not in ['enforcing', 'permissive']:
|
|
return None
|
|
return config.get('SELINUXTYPE', None)
|
|
|
|
|
|
def setfiles(spec_file: str, root: str, *paths, exclude_paths: Optional[List[str]] = None) -> None:
|
|
"""Initialize the security context fields for `paths`
|
|
|
|
Initialize the security context fields (extended attributes)
|
|
on `paths` using the given specification in `spec_file`. The
|
|
`root` argument determines the root path of the file system
|
|
and the entries in `path` are interpreted as relative to it.
|
|
Uses the setfiles(8) tool to actually set the contexts.
|
|
Paths can be excluded via the exclude_paths argument.
|
|
"""
|
|
if exclude_paths is None:
|
|
exclude_paths = []
|
|
exclude_paths_args = []
|
|
for p in exclude_paths:
|
|
exclude_paths_args.extend(["-e", p])
|
|
|
|
for path in paths:
|
|
subprocess.run(["setfiles", "-F",
|
|
"-r", root,
|
|
*exclude_paths_args,
|
|
spec_file,
|
|
f"{root}{path}"],
|
|
check=True)
|
|
|
|
|
|
def getfilecon(path: str) -> str:
|
|
"""Get the security context associated with `path`"""
|
|
label = os.getxattr(path, XATTR_NAME_SELINUX,
|
|
follow_symlinks=False)
|
|
return label.decode().strip('\n\0')
|
|
|
|
|
|
def setfilecon(path: str, context: str) -> None:
|
|
"""
|
|
Set the security context associated with `path`
|
|
|
|
Like `setfilecon`(3), but does not attempt to translate
|
|
the context via `selinux_trans_to_raw_context`.
|
|
"""
|
|
|
|
try:
|
|
os.setxattr(path, XATTR_NAME_SELINUX,
|
|
context.encode(),
|
|
follow_symlinks=True)
|
|
except OSError as err:
|
|
# in case we get a not-supported error, check if
|
|
# the context we want to set is already set and
|
|
# ignore the error in that case. This follows the
|
|
# behavior of `setfilecon(3)`.
|
|
if err.errno == errno.ENOTSUP:
|
|
have = getfilecon(path)
|
|
if have == context:
|
|
return
|
|
raise
|