Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make SELinux relabelling work on non-transactional systems as well #33

Merged
merged 5 commits into from
Oct 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 15 additions & 3 deletions microos-tools.spec
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,22 @@ BuildRequires: pkgconfig(dracut)
BuildRequires: pkgconfig(rpm)
BuildRequires: pkgconfig(systemd)
Requires: read-only-root-fs
Requires: selinux-autorelabel = %{version}
# for man-online
Requires: mandoc-bin

%description
Files, scripts and directories for openSUSE MicroOS.

%package -n selinux-autorelabel
Summary: Automatic SELinux relabelling during early boot
Requires: /usr/bin/findmnt
Requires: policycoreutils

%description -n selinux-autorelabel
This package contains a dracut module and systemd generator for relabelling
the system during early boot.

%package -n microos-devel-tools
Summary: Tools to develop MicroOS

Expand Down Expand Up @@ -86,7 +96,6 @@ This package contains tools to make developing of MicroOS easier.
%service_del_postun microos-ro.service

%files
%license COPYING
%dir %{_sysconfdir}/selinux
%config %{_sysconfdir}/selinux/fixfiles_exclude_dirs
%{_unitdir}/printenv.service
Expand All @@ -98,12 +107,15 @@ This package contains tools to make developing of MicroOS easier.
%dir %{_distconfdir}/tukit.conf.d
%{_distconfdir}/tukit.conf.d/salt-tukit.conf
%{_sbindir}/setup-systemd-proxy-env
%{_bindir}/man-online
%{_distconfdir}/profile.d/man-online.sh

%files -n selinux-autorelabel
%license COPYING
%dir %{_prefix}/lib/dracut
%dir %{_prefix}/lib/dracut/modules.d
%{_prefix}/lib/dracut/modules.d/98selinux-microos
%{_systemdgeneratordir}/selinux-autorelabel-generator
%{_bindir}/man-online
%{_distconfdir}/profile.d/man-online.sh

%files -n microos-devel-tools
%{_unitdir}/microos-ro.service
Expand Down
21 changes: 20 additions & 1 deletion selinux/98selinux-microos/module-setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,25 @@
# called by dracut
check() {
test -f /etc/selinux/config || return 1

# Relabelling /etc and /var from the initrd needs support for mounting,
# "chroot mount /..." still loads modules from the initrd.
# Dracut handles /etc already, but for /var we need to DIY.
if [[ -f $dracutsysrootdir/etc/fstab ]]; then
_dev="$(findmnt --fstab --noheadings --output SOURCE /var --tab-file "$dracutsysrootdir/etc/fstab")"
if [[ -n $_dev ]]; then
_fstype="$(findmnt --fstab --noheadings --output FSTYPE /var --tab-file "$dracutsysrootdir/etc/fstab")"
_dev="$(expand_persistent_dev "$_dev")"
_dev="$(readlink -f "$_dev")"
if [[ -b $_dev ]]; then
push_host_devs "$_dev"
if [[ -z ${host_fs_types["$_dev"]} ]]; then
host_fs_types["$_dev"]="$_fstype"
fi
fi
fi
fi

return 0
}

Expand All @@ -14,5 +33,5 @@ depends() {
# called by dracut
install() {
inst_hook pre-pivot 50 "$moddir/selinux-microos-relabel.sh"
inst_multiple chroot cut grep
inst_multiple chroot cut findmnt grep
}
64 changes: 46 additions & 18 deletions selinux/98selinux-microos/selinux-microos-relabel.sh
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,19 @@ rd_microos_relabel()
{
info "SELinux: relabeling root filesystem"

root_is_btrfs=
if [ "$(findmnt --noheadings --output FSTYPE --target "$NEWROOT")" = "btrfs" ]; then
root_is_btrfs=y
fi
etc_is_overlay=
if [ "$(findmnt --fstab --noheadings --output FSTYPE /etc --tab-file "${NEWROOT}/etc/fstab")" = "overlay" ]; then
etc_is_overlay=y
fi

# If this doesn't exist because e.g. it's not mounted yet due to a bug
# (boo#1197309), the exclusion is ignored. If it gets mounted during
# the relabel, it gets wrong labels assigned.
if ! [ -d "$NEWROOT/var/lib/overlay" ]; then
if [ -n "$etc_is_overlay" ] && ! [ -d "$NEWROOT/var/lib/overlay" ]; then
warn "ERROR: /var/lib/overlay doesn't exist - /var not mounted (yet)?"
return 1
fi
Expand All @@ -48,24 +57,41 @@ rd_microos_relabel()
ret=1
fi
done
if [ $ret -eq 0 ]; then
# Mount /var and /etc, need to be relabelled as well for booting.
for mp in /var /etc; do
if ! findmnt "${ROOT_SELINUX}${mp}" >/dev/null \
&& findmnt --fstab --output TARGET --tab-file "${ROOT_SELINUX}/etc/fstab" "$mp" >/dev/null; then
chroot "$ROOT_SELINUX" mount "$mp" || ret=1
fi
done
fi
if [ $ret -eq 0 ]; then
info "SELinux: mount root read-write and relabel"
mount -o remount,rw "${ROOT_SELINUX}"
oldrovalue="$(btrfs prop get "${ROOT_SELINUX}" ro | cut -d= -f2)"
btrfs prop set "${ROOT_SELINUX}" ro false
if [ -n "$root_is_btrfs" ]; then
oldrovalue="$(btrfs prop get "${ROOT_SELINUX}" ro | cut -d= -f2)"
btrfs prop set "${ROOT_SELINUX}" ro false
fi
FORCE=
[ -e "${ROOT_SELINUX}"/etc/selinux/.autorelabel ] && FORCE="$(cat "${ROOT_SELINUX}"/etc/selinux/.autorelabel)"
. "${ROOT_SELINUX}"/etc/selinux/config
# Marker when we had relabelled the filesystem. This is relabelled as well.
> "${ROOT_SELINUX}"/etc/selinux/.relabelled
LANG=C chroot "$ROOT_SELINUX" /sbin/setfiles $FORCE -T 0 -e /var/lib/overlay -e /proc -e /sys -e /dev -e /etc "/etc/selinux/${SELINUXTYPE}/contexts/files/file_contexts" $(chroot "$ROOT_SELINUX" cut -d" " -f2 /proc/mounts)
# On overlayfs, st_dev isn't consistent so setfiles thinks it's a different mountpoint, ignoring it.
# st_dev changes also on copy-up triggered by setfiles itself, so the only way to relabel properly
# is to list every file explicitly.
# That's not all: There's a kernel bug that security.selinux of parent directories is lost on copy-up (bsc#1210690).
# Work around that by visiting children first and only then the parent directories.
LANG=C chroot "$ROOT_SELINUX" find /etc -depth -exec /sbin/setfiles $FORCE "/etc/selinux/${SELINUXTYPE}/contexts/files/file_contexts" \{\} +
btrfs prop set "${ROOT_SELINUX}" ro "${oldrovalue}"
if [ -n "$etc_is_overlay" ]; then
LANG=C chroot "$ROOT_SELINUX" /sbin/setfiles $FORCE -T 0 -e /var/lib/overlay -e /proc -e /sys -e /dev -e /etc "/etc/selinux/${SELINUXTYPE}/contexts/files/file_contexts" $(chroot "$ROOT_SELINUX" cut -d" " -f2 /proc/mounts)
# On overlayfs, st_dev isn't consistent so setfiles thinks it's a different mountpoint, ignoring it.
# st_dev changes also on copy-up triggered by setfiles itself, so the only way to relabel properly
# is to list every file explicitly.
# That's not all: There's a kernel bug that security.selinux of parent directories is lost on copy-up (bsc#1210690).
# Work around that by visiting children first and only then the parent directories.
LANG=C chroot "$ROOT_SELINUX" find /etc -depth -exec /sbin/setfiles $FORCE "/etc/selinux/${SELINUXTYPE}/contexts/files/file_contexts" \{\} +
else
LANG=C chroot "$ROOT_SELINUX" /sbin/setfiles $FORCE -T 0 -e /proc -e /sys -e /dev "/etc/selinux/${SELINUXTYPE}/contexts/files/file_contexts" $(chroot "$ROOT_SELINUX" cut -d" " -f2 /proc/mounts)
fi
if [ -n "$root_is_btrfs" ]; then
btrfs prop set "${ROOT_SELINUX}" ro "${oldrovalue}"
fi
fi

umount -R "${ROOT_SELINUX}"
Expand All @@ -79,23 +105,25 @@ rd_microos_relabel()
return $ret
}

if test -e "$NEWROOT"/.autorelabel -a "$NEWROOT"/.autorelabel -nt "$NEWROOT"/etc/selinux/.relabelled ; then
cp -a "$NEWROOT"/.autorelabel "$NEWROOT"/etc/selinux/.autorelabel
if [ -e "$NEWROOT"/.autorelabel ] && [ "$NEWROOT"/.autorelabel -nt "$NEWROOT"/etc/selinux/.relabelled ]; then
mount -o remount,rw "$NEWROOT" || return 1
cp -a "$NEWROOT"/.autorelabel "$NEWROOT"/etc/selinux/.autorelabel || return 1
rm -f "$NEWROOT"/.autorelabel 2>/dev/null
fi

if rd_is_selinux_enabled; then
if test -f "$NEWROOT"/etc/selinux/.autorelabel; then
rd_microos_relabel
elif getarg "autorelabel" > /dev/null; then
rd_microos_relabel
if [ -f "$NEWROOT"/etc/selinux/.autorelabel ] || getarg "autorelabel" > /dev/null; then
if ! rd_microos_relabel; then
warn "SELinux autorelabelling failed!"
return 1
fi
fi
elif test -e "$NEWROOT"/etc/selinux/.relabelled; then
# SELinux is off but looks like some labeling took place before.
# So probably a boot with manually disabled SELinux. Make sure
# the system gets relabelled next time SELinux is on.
> "$NEWROOT"/etc/selinux/.autorelabel
warn "SElinux is off in labelled system!"
warn "SELinux is off in labelled system!"
fi

return 0
88 changes: 45 additions & 43 deletions selinux/selinux-autorelabel-generator
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/bin/sh
set -e
#!/bin/bash
set -euo pipefail

# This systemd.generator(7) detects if SELinux is running and if the
# user requested an autorelabel. If so, services will be enabled to
Expand All @@ -8,65 +8,67 @@ set -e

# If invoked with no arguments (for testing) write to /tmp.
generatordir="/tmp"
if [ -n "$1" ]; then
generatordir="$1"
if [ -n "${1-}" ]; then
generatordir="$1"
fi

enable_units() {
mkdir -p "${generatordir}"/local-fs.target.requires
mkdir -p "${generatordir}"/local-fs.target.requires

relabel_unit_list=""
relabel_unit_list=""

for realdir in "/.snapshots" "/home" "/opt" "/root" "/srv" "/usr/local" \
"/boot/grub2/i386-pc" "/boot/grub2/x86_64-efi" \
"/boot/grub2/arm64-efi" "/boot/writable"; do
# Make sure the directory exist, else we create
# services for non existing mount points
test -d "${realdir}" || continue
mountunit=$(systemd-escape --path ${realdir})
unitfile="${mountunit}-relabel.service"
relabel_unit_list="$unitfile $relabel_unit_list"
while read -r realdir; do
# Skip non-fs (swap) mounts, /, /var, /etc (already done in the initrd) and mountpoints with noauto
if [ "${realdir:0:1}" != "/" ] \
|| [ "${realdir}" = "/" ] || [ "${realdir}" = "/var" ] || [ "${realdir}" = "/etc" ] \
|| findmnt --fstab --noheadings --output OPTIONS --target "${realdir}" | grep -qw noauto; then
continue
fi

opts="-T 0"
[ "${realdir}" == "/.snapshots" ] && opts="${opts} -x"
mountunit=$(systemd-escape --path "${realdir}")
unitfile="${mountunit}-relabel.service"
relabel_unit_list="$unitfile $relabel_unit_list"

opts="-T 0"
[ "${realdir}" == "/.snapshots" ] && opts="${opts} -x"

cat >"${generatordir}/${unitfile}" <<-EOF
[Unit]
Description=Relabel ${realdir}
DefaultDependencies=no
RequiresMountsFor=${realdir}
Before=local-fs.target
ConditionSecurity=selinux

[Service]
Type=oneshot
ExecStart=/sbin/restorecon -R ${opts} ${realdir}
EOF

ln -sf ../"${unitfile}" "${generatordir}"/local-fs.target.requires/"${unitfile}"
done < <(findmnt --fstab --noheadings --output TARGET)

unitfile="mark-autorelabel-done.service"
cat >"${generatordir}/${unitfile}" <<-EOF
[Unit]
Description=Relabel ${realdir}
Description=Mark autorelabel as done
DefaultDependencies=no
RequiresMountsFor=${realdir}
Before=local-fs.target
After=${relabel_unit_list}
Requires=${relabel_unit_list}
ConditionSecurity=selinux
ConditionPathExists=/etc/selinux/.autorelabel

[Service]
Type=oneshot
ExecStart=/sbin/restorecon -R ${opts} ${realdir}
EOF

ln -sf ../"${unitfile}" "${generatordir}"/local-fs.target.requires/"${unitfile}"
done

unitfile="mark-autorelabel-done.service"
cat >"${generatordir}/${unitfile}" <<-EOF
[Unit]
Description=Mark autorelabel as done
DefaultDependencies=no
Before=local-fs.target
After=${relabel_unit_list}
Requires=${relabel_unit_list}
ConditionSecurity=selinux
ConditionPathExists=/etc/selinux/.autorelabel

[Service]
Type=oneshot
ExecStart=/usr/bin/rm /etc/selinux/.autorelabel
ExecStart=/usr/bin/rm /etc/selinux/.autorelabel
EOF

ln -sf "../${unitfile}" "${generatordir}/local-fs.target.requires/${unitfile}"
ln -sf "../${unitfile}" "${generatordir}/local-fs.target.requires/${unitfile}"
}

if [ -x /usr/sbin/selinuxenabled ] && /usr/sbin/selinuxenabled; then
if [ -f /etc/selinux/.autorelabel ] || grep -wq autorelabel /proc/cmdline; then
enable_units
fi
if [ -f /etc/selinux/.autorelabel ] || grep -wq autorelabel /proc/cmdline; then
enable_units
fi
fi