Merge pull request #8627 from mdisec/security/harden-cups-browsed
Harden CUPS printer discovery
This commit is contained in:
@@ -1,4 +1,8 @@
|
|||||||
# Omarchy override of cups-browsed's shipped config. The only behavioural
|
# Keep state away from /var/cache/cups, which is writable by the account CUPS
|
||||||
# change vs the upstream default (all-commented) is enabling auto-registration
|
# uses for print filters. cups-browsed is the only writer to this directory.
|
||||||
# of remote IPP printers discovered via Avahi/mDNS.
|
CacheDir /var/cache/cups-browsed
|
||||||
CreateRemotePrinters Yes
|
|
||||||
|
# Auto-create queues only for modern driverless IPP printers. Remote queues
|
||||||
|
# exported by another CUPS server can still be added manually when needed.
|
||||||
|
CreateIPPPrinterQueues Driverless
|
||||||
|
CreateRemoteCUPSPrinterQueues No
|
||||||
|
|||||||
@@ -0,0 +1,90 @@
|
|||||||
|
#
|
||||||
|
# File/directory/user/group configuration file for the CUPS scheduler.
|
||||||
|
# See "man cups-files.conf" for a complete description of this file.
|
||||||
|
#
|
||||||
|
|
||||||
|
# List of events that are considered fatal errors for the scheduler...
|
||||||
|
#FatalErrors config
|
||||||
|
|
||||||
|
# Strip domain in local username?
|
||||||
|
#StripUserDomain No
|
||||||
|
|
||||||
|
# Do we call fsync() after writing configuration or status files?
|
||||||
|
#SyncOnClose No
|
||||||
|
|
||||||
|
# Default user and group for filters/backends/helper programs; this cannot be
|
||||||
|
# any user or group that resolves to ID 0 for security reasons...
|
||||||
|
User 209
|
||||||
|
Group 209
|
||||||
|
|
||||||
|
# Administrator user group, used to match @SYSTEM in cupsd.conf policy rules...
|
||||||
|
# This cannot contain the Group value for security reasons...
|
||||||
|
SystemGroup cups-browsed sys root
|
||||||
|
|
||||||
|
|
||||||
|
# Are Unix domain socket peer credentials used for authorization?
|
||||||
|
PeerCred on
|
||||||
|
|
||||||
|
# User that is substituted for unauthenticated (remote) root accesses...
|
||||||
|
#RemoteRoot remroot
|
||||||
|
|
||||||
|
# Do we allow file: device URIs other than to /dev/null?
|
||||||
|
#FileDevice No
|
||||||
|
|
||||||
|
# Permissions for configuration and log files...
|
||||||
|
#ConfigFilePerm 0640
|
||||||
|
#LogFilePerm 0644
|
||||||
|
|
||||||
|
# Location of the file logging all access to the scheduler; may be the name
|
||||||
|
# "syslog". If not an absolute path, the value of ServerRoot is used as the
|
||||||
|
# root directory. Also see the "AccessLogLevel" directive in cupsd.conf.
|
||||||
|
AccessLog /var/log/cups/access_log
|
||||||
|
|
||||||
|
# Location of cache files used by the scheduler...
|
||||||
|
#CacheDir /var/cache/cups
|
||||||
|
|
||||||
|
# Location of data files used by the scheduler...
|
||||||
|
#DataDir /usr/share/cups
|
||||||
|
|
||||||
|
# Location of the static web content served by the scheduler...
|
||||||
|
#DocumentRoot /usr/share/cups/doc
|
||||||
|
|
||||||
|
# Location of the file logging all messages produced by the scheduler and any
|
||||||
|
# helper programs; may be the name "syslog". If not an absolute path, the value
|
||||||
|
# of ServerRoot is used as the root directory. Also see the "LogLevel"
|
||||||
|
# directive in cupsd.conf.
|
||||||
|
ErrorLog /var/log/cups/error_log
|
||||||
|
|
||||||
|
# Location of the file logging all pages printed by the scheduler and any
|
||||||
|
# helper programs; may be the name "syslog". If not an absolute path, the value
|
||||||
|
# of ServerRoot is used as the root directory. Also see the "PageLogFormat"
|
||||||
|
# directive in cupsd.conf.
|
||||||
|
PageLog /var/log/cups/page_log
|
||||||
|
|
||||||
|
# Location of the file listing all of the local printers...
|
||||||
|
#Printcap /etc/printcap
|
||||||
|
|
||||||
|
# Format of the Printcap file...
|
||||||
|
#PrintcapFormat bsd
|
||||||
|
#PrintcapFormat plist
|
||||||
|
#PrintcapFormat solaris
|
||||||
|
|
||||||
|
# Location of all spool files...
|
||||||
|
#RequestRoot /var/spool/cups
|
||||||
|
|
||||||
|
# Location of helper programs...
|
||||||
|
#ServerBin /usr/lib/cups
|
||||||
|
|
||||||
|
# SSL/TLS keychain for the scheduler...
|
||||||
|
#ServerKeychain ssl
|
||||||
|
|
||||||
|
# Location of other configuration files...
|
||||||
|
#ServerRoot /etc/cups
|
||||||
|
|
||||||
|
# Location of scheduler state files...
|
||||||
|
#StateDir /run/cups
|
||||||
|
|
||||||
|
# Location of scheduler/helper temporary files. This directory is emptied on
|
||||||
|
# scheduler startup and cannot be one of the standard (public) temporary
|
||||||
|
# directory locations for security reasons...
|
||||||
|
#TempDir /var/spool/cups/tmp
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
[Service]
|
||||||
|
User=cups-browsed
|
||||||
|
Group=cups-browsed
|
||||||
|
CacheDirectory=cups-browsed
|
||||||
|
CacheDirectoryMode=0750
|
||||||
|
UMask=0027
|
||||||
|
NoNewPrivileges=yes
|
||||||
|
ProtectSystem=strict
|
||||||
|
ProtectHome=yes
|
||||||
|
PrivateTmp=yes
|
||||||
|
RestrictSUIDSGID=yes
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
u cups-browsed - "CUPS printer discovery" / -
|
||||||
@@ -19,7 +19,7 @@ cliamp
|
|||||||
cups
|
cups
|
||||||
cups-browsed
|
cups-browsed
|
||||||
cups-filters
|
cups-filters
|
||||||
cups-pdf
|
cups-pk-helper
|
||||||
ddcutil
|
ddcutil
|
||||||
docker
|
docker
|
||||||
docker-buildx
|
docker-buildx
|
||||||
|
|||||||
@@ -3,11 +3,13 @@
|
|||||||
cp -f "$OMARCHY_PATH/default/pacman/pacman-${OMARCHY_MIRROR:-stable}.conf" /etc/pacman.conf
|
cp -f "$OMARCHY_PATH/default/pacman/pacman-${OMARCHY_MIRROR:-stable}.conf" /etc/pacman.conf
|
||||||
cp -f "$OMARCHY_PATH/default/pacman/mirrorlist-${OMARCHY_MIRROR:-stable}" /etc/pacman.d/mirrorlist
|
cp -f "$OMARCHY_PATH/default/pacman/mirrorlist-${OMARCHY_MIRROR:-stable}" /etc/pacman.d/mirrorlist
|
||||||
|
|
||||||
# omarchy-settings skips this override until cups-browsed is actually present
|
# omarchy-settings skips these overrides until CUPS is actually present to
|
||||||
# to avoid pacman creating cups-browsed.conf.pacnew during ISO package install.
|
# avoid pacman creating .pacnew files during ISO package installation.
|
||||||
if [[ -f $OMARCHY_PATH/etc-overrides/cups-cups-browsed.conf && -d /etc/cups ]]; then
|
if [[ -f $OMARCHY_PATH/etc-overrides/cups-cups-browsed.conf && -f /etc/cups/cups-files.conf ]]; then
|
||||||
|
systemd-sysusers /etc/sysusers.d/omarchy-cups-browsed.conf
|
||||||
cp -f "$OMARCHY_PATH/etc-overrides/cups-cups-browsed.conf" /etc/cups/cups-browsed.conf
|
cp -f "$OMARCHY_PATH/etc-overrides/cups-cups-browsed.conf" /etc/cups/cups-browsed.conf
|
||||||
rm -f /etc/cups/cups-browsed.conf.pacnew
|
install -m 0640 -o root -g cups "$OMARCHY_PATH/etc-overrides/cups-cups-files.conf" /etc/cups/cups-files.conf
|
||||||
|
rm -f /etc/cups/cups-browsed.conf.pacnew /etc/cups/cups-files.conf.pacnew
|
||||||
fi
|
fi
|
||||||
|
|
||||||
source "$OMARCHY_INSTALL/hardware/pacman.sh"
|
source "$OMARCHY_INSTALL/hardware/pacman.sh"
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ Turkish|trq
|
|||||||
Ukrainian|ua'
|
Ukrainian|ua'
|
||||||
|
|
||||||
OMARCHY_USERNAME_PATTERN='^[a-z_][a-z0-9_-]*[$]?$'
|
OMARCHY_USERNAME_PATTERN='^[a-z_][a-z0-9_-]*[$]?$'
|
||||||
OMARCHY_RESERVED_USERNAMES='^(root|bin|daemon|mail|ftp|http|nobody|dbus|systemd-coredump|systemd-network|systemd-oom|systemd-journal-remote|systemd-resolve|systemd-timesync|tss|uuidd|alpm|git|avahi|cups|lp|_talkd|polkitd|rtkit|qemu|brltty|gluster|rpc|libvirt-qemu|pcscd|nvidia-persistenced|sddm)$'
|
OMARCHY_RESERVED_USERNAMES='^(root|bin|daemon|mail|ftp|http|nobody|dbus|systemd-coredump|systemd-network|systemd-oom|systemd-journal-remote|systemd-resolve|systemd-timesync|tss|uuidd|alpm|git|avahi|cups|cups-browsed|lp|_talkd|polkitd|rtkit|qemu|brltty|gluster|rpc|libvirt-qemu|pcscd|nvidia-persistenced|sddm)$'
|
||||||
OMARCHY_HOSTNAME_PATTERN='^[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?$'
|
OMARCHY_HOSTNAME_PATTERN='^[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?$'
|
||||||
OMARCHY_HOSTNAME_DEFAULT='omarchy'
|
OMARCHY_HOSTNAME_DEFAULT='omarchy'
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
echo "Separate printer discovery from root and print-filter access"
|
||||||
|
|
||||||
|
machine_marker="${OMARCHY_CUPS_MIGRATION_MARKER:-/var/lib/omarchy/migrations/1787815267}"
|
||||||
|
|
||||||
|
[[ ! -e $machine_marker ]] || exit 0
|
||||||
|
|
||||||
|
# Existing releases allowed a desktop user or shared group named cups-browsed,
|
||||||
|
# which systemd-sysusers would silently reuse for passwordless CUPS access.
|
||||||
|
if omarchy-pkg-present cups; then
|
||||||
|
cups_browsed_account=$(getent passwd cups-browsed || true)
|
||||||
|
cups_browsed_group=$(getent group cups-browsed || true)
|
||||||
|
|
||||||
|
if [[ -n $cups_browsed_account || -n $cups_browsed_group ]]; then
|
||||||
|
IFS=: read -r _ _ cups_browsed_uid cups_browsed_gid cups_browsed_description cups_browsed_home cups_browsed_shell <<<"$cups_browsed_account"
|
||||||
|
IFS=: read -r _ _ cups_browsed_group_gid cups_browsed_group_members <<<"$cups_browsed_group"
|
||||||
|
other_primary_user=$(getent passwd | awk -F: -v gid="$cups_browsed_gid" '$1 != "cups-browsed" && $4 == gid { print $1; exit }')
|
||||||
|
|
||||||
|
if [[ ! $cups_browsed_uid =~ ^[0-9]+$ || ! $cups_browsed_group_gid =~ ^[0-9]+$ ]] ||
|
||||||
|
((cups_browsed_uid <= 0 || cups_browsed_uid >= 1000)) ||
|
||||||
|
[[ $cups_browsed_gid != $cups_browsed_group_gid ]] ||
|
||||||
|
[[ $cups_browsed_description != "CUPS printer discovery" || $cups_browsed_home != "/" || $cups_browsed_shell != "/usr/bin/nologin" ]] ||
|
||||||
|
[[ -n $cups_browsed_group_members || -n $other_primary_user ]]; then
|
||||||
|
echo "Cannot harden printer discovery: the existing cups-browsed user or group is not a dedicated system account." >&2
|
||||||
|
false
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# CUPS-PDF accepts a job-controlled post-processing command in a backend that
|
||||||
|
# CUPS launches as root. Native application print-to-file support replaces it.
|
||||||
|
omarchy-pkg-drop cups-pdf
|
||||||
|
|
||||||
|
# system-config-printer uses this helper to request printer administration
|
||||||
|
# through Polkit now that the desktop user's wheel group is no longer @SYSTEM.
|
||||||
|
if omarchy-pkg-present cups; then
|
||||||
|
omarchy-pkg-add cups-pk-helper
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Stop the root-running daemon before changing the authorization it relies on.
|
||||||
|
if systemctl is-active --quiet cups-browsed.service 2>/dev/null; then
|
||||||
|
sudo systemctl stop cups-browsed.service
|
||||||
|
fi
|
||||||
|
|
||||||
|
if omarchy-pkg-present cups; then
|
||||||
|
sudo systemctl daemon-reload
|
||||||
|
sudo systemctl try-reload-or-restart cups.service
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Resume on whether the unit is enabled, not on whether it was running when this
|
||||||
|
# run started: an interrupted earlier run leaves it stopped, and a retry that
|
||||||
|
# recomputed that would skip the restart and still write the marker below. A
|
||||||
|
# masked or disabled unit reports not-enabled and is left alone.
|
||||||
|
if systemctl is-enabled --quiet cups-browsed.service 2>/dev/null; then
|
||||||
|
sudo systemctl restart cups-browsed.service
|
||||||
|
fi
|
||||||
|
|
||||||
|
sudo install -Dm644 /dev/null "$machine_marker"
|
||||||
@@ -64,6 +64,72 @@ verify_services() {
|
|||||||
pass "user audio services are running"
|
pass "user audio services are running"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
verify_printing_security() {
|
||||||
|
local cups_browsed_pid lpinfo_output printer_name printer_process printer_tmp
|
||||||
|
|
||||||
|
! pacman -Q cups-pdf >/dev/null 2>&1 || fail "CUPS-PDF is absent"
|
||||||
|
pass "the root CUPS-PDF backend is not installed"
|
||||||
|
|
||||||
|
getent passwd cups-browsed >/dev/null || fail "the cups-browsed service account exists"
|
||||||
|
[[ $(systemctl show -P User cups-browsed.service) == "cups-browsed" ]] ||
|
||||||
|
fail "cups-browsed runs as its service account"
|
||||||
|
[[ $(systemctl show -P Group cups-browsed.service) == "cups-browsed" ]] ||
|
||||||
|
fail "cups-browsed runs as its service group"
|
||||||
|
systemctl is-active --quiet cups-browsed.service || fail "cups-browsed is running"
|
||||||
|
|
||||||
|
cups_browsed_pid=$(systemctl show -P MainPID cups-browsed.service)
|
||||||
|
[[ -r /proc/$cups_browsed_pid/status ]] || fail "cups-browsed has a readable process status"
|
||||||
|
[[ $(awk '/^Uid:/{print $2}' "/proc/$cups_browsed_pid/status") != 0 ]] ||
|
||||||
|
fail "cups-browsed does not run with root UID"
|
||||||
|
[[ $(awk '/^CapEff:/{print $2}' "/proc/$cups_browsed_pid/status") == "0000000000000000" ]] ||
|
||||||
|
fail "cups-browsed has no effective Linux capabilities"
|
||||||
|
|
||||||
|
[[ $(stat -c '%a %U:%G' /var/cache/cups-browsed) == "750 cups-browsed:cups-browsed" ]] ||
|
||||||
|
fail "cups-browsed has an isolated cache" "$(stat -c '%a %U:%G' /var/cache/cups-browsed)"
|
||||||
|
[[ " $(id -nG cups-browsed) " != *" cups "* ]] ||
|
||||||
|
fail "cups-browsed is separate from the print-filter group"
|
||||||
|
|
||||||
|
if lpinfo_output=$(LC_ALL=C timeout 10 lpinfo -v </dev/null 2>&1); then
|
||||||
|
fail "the desktop user cannot administer CUPS without authentication"
|
||||||
|
elif [[ $lpinfo_output != *"Forbidden"* ]]; then
|
||||||
|
fail "CUPS explicitly denies unauthenticated desktop administration" "$lpinfo_output"
|
||||||
|
fi
|
||||||
|
|
||||||
|
pass "CUPS discovery is isolated from root, filters, and passwordless desktop administration"
|
||||||
|
|
||||||
|
# A live driverless printer proves the non-root daemon can still discover and
|
||||||
|
# create queues without the CAP_NET_BIND_SERVICE Ubuntu carries downstream.
|
||||||
|
printer_name="OmarchyAcceptancePrinter"
|
||||||
|
printer_tmp=$(mktemp -d)
|
||||||
|
printf '#!/bin/bash\nexit 0\n' >"$printer_tmp/command"
|
||||||
|
chmod 0700 "$printer_tmp/command"
|
||||||
|
mkdir -m 0700 "$printer_tmp/spool"
|
||||||
|
|
||||||
|
ippeveprinter -p 18631 -d "$printer_tmp/spool" -c "$printer_tmp/command" "$printer_name" \
|
||||||
|
>"$printer_tmp/ippeveprinter.log" 2>&1 &
|
||||||
|
printer_process=$!
|
||||||
|
|
||||||
|
printing_test_cleanup() {
|
||||||
|
kill "$printer_process" >/dev/null 2>&1 || true
|
||||||
|
wait "$printer_process" >/dev/null 2>&1 || true
|
||||||
|
rm -rf "$printer_tmp"
|
||||||
|
}
|
||||||
|
trap printing_test_cleanup EXIT
|
||||||
|
|
||||||
|
for _ in {1..30}; do
|
||||||
|
lpstat -v "$printer_name" 2>/dev/null | grep -q "implicitclass://$printer_name/" && break
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
|
||||||
|
lpstat -v "$printer_name" 2>/dev/null | grep -q "implicitclass://$printer_name/" ||
|
||||||
|
fail "non-root cups-browsed discovers a driverless IPP printer" "$(<"$printer_tmp/ippeveprinter.log")"
|
||||||
|
|
||||||
|
printing_test_cleanup
|
||||||
|
trap - EXIT
|
||||||
|
|
||||||
|
pass "non-root cups-browsed still creates driverless IPP queues without capabilities"
|
||||||
|
}
|
||||||
|
|
||||||
verify_runtime_tools() {
|
verify_runtime_tools() {
|
||||||
# Docker access is intentionally NOT granted to the desktop user: the docker
|
# Docker access is intentionally NOT granted to the desktop user: the docker
|
||||||
# group is root-equivalent, so a rogue process running as the user could
|
# group is root-equivalent, so a rogue process running as the user could
|
||||||
@@ -107,7 +173,7 @@ verify_user_setup() {
|
|||||||
pass "Omarchy user state and shell configuration exist"
|
pass "Omarchy user state and shell configuration exist"
|
||||||
}
|
}
|
||||||
|
|
||||||
for check in verify_core_packages verify_defaults verify_services verify_runtime_tools verify_user_setup; do
|
for check in verify_core_packages verify_defaults verify_services verify_printing_security verify_runtime_tools verify_user_setup; do
|
||||||
if ! ("$check"); then
|
if ! ("$check"); then
|
||||||
status=1
|
status=1
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -0,0 +1,221 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
|
||||||
|
|
||||||
|
packages="$ROOT/install/omarchy-base.packages"
|
||||||
|
cups_browsed_conf="$ROOT/etc/cups/cups-browsed.conf"
|
||||||
|
cups_files_conf="$ROOT/etc/cups/cups-files.conf"
|
||||||
|
sysusers_conf="$ROOT/etc/sysusers.d/omarchy-cups-browsed.conf"
|
||||||
|
service_dropin="$ROOT/etc/systemd/system/cups-browsed.service.d/10-omarchy.conf"
|
||||||
|
|
||||||
|
grep -qxF cups-browsed "$packages" || fail "cups-browsed remains in the base package set"
|
||||||
|
grep -qxF cups-pk-helper "$packages" || fail "Polkit printer administration is installed"
|
||||||
|
! grep -qxF cups-pdf "$packages" || fail "the root CUPS-PDF backend is removed"
|
||||||
|
|
||||||
|
pass "the base install keeps discovery and replaces CUPS-PDF with Polkit administration"
|
||||||
|
|
||||||
|
grep -qxF 'CacheDir /var/cache/cups-browsed' "$cups_browsed_conf" ||
|
||||||
|
fail "cups-browsed keeps state outside the print-filter cache"
|
||||||
|
grep -qxF 'CreateIPPPrinterQueues Driverless' "$cups_browsed_conf" ||
|
||||||
|
fail "automatic queues are limited to driverless IPP printers"
|
||||||
|
grep -qxF 'CreateRemoteCUPSPrinterQueues No' "$cups_browsed_conf" ||
|
||||||
|
fail "remote CUPS queues are not created automatically"
|
||||||
|
! grep -q 'CreateRemotePrinters' "$cups_browsed_conf" ||
|
||||||
|
fail "the unsupported CreateRemotePrinters directive is gone"
|
||||||
|
|
||||||
|
pass "cups-browsed uses explicit supported discovery policy and an isolated cache"
|
||||||
|
|
||||||
|
grep -qxF 'SystemGroup cups-browsed sys root' "$cups_files_conf" ||
|
||||||
|
fail "only the printer discovery account receives passwordless CUPS administration"
|
||||||
|
grep -qxF 'PeerCred on' "$cups_files_conf" ||
|
||||||
|
fail "the packaged CUPS policy enables peer credentials"
|
||||||
|
[[ $(grep -ciE '^[[:space:]]*SystemGroup[[:space:]]' "$cups_files_conf") == 1 ]] ||
|
||||||
|
fail "the packaged CUPS policy has one SystemGroup directive"
|
||||||
|
[[ $(grep -ciE '^[[:space:]]*PeerCred[[:space:]]' "$cups_files_conf") == 1 ]] ||
|
||||||
|
fail "the packaged CUPS policy has one PeerCred directive"
|
||||||
|
[[ ! -e $ROOT/install/config/printing.sh ]] ||
|
||||||
|
fail "printing policy is not rewritten by an install script"
|
||||||
|
! grep -q 'config/printing.sh' "$ROOT/install/config/all.sh" "$ROOT/migrations/1787815267.sh" ||
|
||||||
|
fail "neither install nor update invokes a printing rewrite script"
|
||||||
|
|
||||||
|
pass "CUPS authorization ships as a canonical package override"
|
||||||
|
|
||||||
|
grep -qxF 'u cups-browsed - "CUPS printer discovery" / -' "$sysusers_conf" ||
|
||||||
|
fail "a locked cups-browsed system account is declared"
|
||||||
|
|
||||||
|
for setting in \
|
||||||
|
'User=cups-browsed' \
|
||||||
|
'Group=cups-browsed' \
|
||||||
|
'CacheDirectory=cups-browsed' \
|
||||||
|
'CacheDirectoryMode=0750' \
|
||||||
|
'UMask=0027' \
|
||||||
|
'NoNewPrivileges=yes' \
|
||||||
|
'ProtectSystem=strict' \
|
||||||
|
'ProtectHome=yes' \
|
||||||
|
'PrivateTmp=yes' \
|
||||||
|
'RestrictSUIDSGID=yes'; do
|
||||||
|
grep -qxF "$setting" "$service_dropin" ||
|
||||||
|
fail "cups-browsed service hardening includes $setting"
|
||||||
|
done
|
||||||
|
|
||||||
|
! grep -q '^\(Ambient\|CapabilityBoundingSet\).*CAP_NET_BIND_SERVICE' "$service_dropin" ||
|
||||||
|
fail "cups-browsed is not granted an unverified network capability"
|
||||||
|
|
||||||
|
pass "cups-browsed runs as its confined service account without added capabilities"
|
||||||
|
|
||||||
|
test_tmp=$(mktemp -d)
|
||||||
|
trap 'rm -rf "$test_tmp"' EXIT
|
||||||
|
|
||||||
|
mock_bin="$test_tmp/bin"
|
||||||
|
mkdir -p "$mock_bin" "$test_tmp/var/lib/omarchy/migrations"
|
||||||
|
|
||||||
|
passwd_db="$test_tmp/passwd"
|
||||||
|
group_db="$test_tmp/group"
|
||||||
|
touch "$passwd_db" "$group_db"
|
||||||
|
|
||||||
|
cat >"$mock_bin/getent" <<'SH'
|
||||||
|
#!/bin/bash
|
||||||
|
case "$1" in
|
||||||
|
passwd) database="$OMARCHY_CUPS_TEST_PASSWD" ;;
|
||||||
|
group) database="$OMARCHY_CUPS_TEST_GROUP" ;;
|
||||||
|
*) exit 2 ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if (($# == 1)); then
|
||||||
|
cat "$database"
|
||||||
|
else
|
||||||
|
awk -F: -v name="$2" '$1 == name { print; found = 1 } END { exit !found }' "$database"
|
||||||
|
fi
|
||||||
|
SH
|
||||||
|
cat >"$mock_bin/omarchy-pkg-present" <<'SH'
|
||||||
|
#!/bin/bash
|
||||||
|
[[ $1 == "cups" || $1 == "cups-browsed" ]]
|
||||||
|
SH
|
||||||
|
for command in omarchy-pkg-add omarchy-pkg-drop; do
|
||||||
|
cat >"$mock_bin/$command" <<'SH'
|
||||||
|
#!/bin/bash
|
||||||
|
printf '%s\t%s\n' "${0##*/}" "$*" >>"$OMARCHY_CUPS_TEST_LOG"
|
||||||
|
SH
|
||||||
|
done
|
||||||
|
cat >"$mock_bin/systemctl" <<'SH'
|
||||||
|
#!/bin/bash
|
||||||
|
printf 'systemctl\t%s\n' "$*" >>"$OMARCHY_CUPS_TEST_LOG"
|
||||||
|
exit 0
|
||||||
|
SH
|
||||||
|
cat >"$mock_bin/sudo" <<'SH'
|
||||||
|
#!/bin/bash
|
||||||
|
printf 'sudo\t%s\n' "$*" >>"$OMARCHY_CUPS_TEST_LOG"
|
||||||
|
exec "$@"
|
||||||
|
SH
|
||||||
|
chmod +x "$mock_bin"/*
|
||||||
|
|
||||||
|
log="$test_tmp/actions.log"
|
||||||
|
touch "$log"
|
||||||
|
export OMARCHY_CUPS_TEST_LOG="$log"
|
||||||
|
export OMARCHY_CUPS_TEST_PASSWD="$passwd_db"
|
||||||
|
export OMARCHY_CUPS_TEST_GROUP="$group_db"
|
||||||
|
|
||||||
|
printf 'cups-browsed:x:1000:1000:Desktop user:/home/cups-browsed:/usr/bin/bash\n' >"$passwd_db"
|
||||||
|
printf 'cups-browsed:x:1000:\n' >"$group_db"
|
||||||
|
if PATH="$mock_bin:$PATH" \
|
||||||
|
OMARCHY_PATH="$ROOT" \
|
||||||
|
OMARCHY_CUPS_MIGRATION_MARKER="$test_tmp/desktop-collision-marker" \
|
||||||
|
bash -euo pipefail "$ROOT/migrations/1787815267.sh" 2>/dev/null; then
|
||||||
|
fail "the migration accepts an existing desktop user named cups-browsed"
|
||||||
|
fi
|
||||||
|
[[ ! -s $log ]] || fail "an account collision stops the migration before changing the system"
|
||||||
|
|
||||||
|
printf 'alice:x:1000:947:Desktop user:/home/alice:/usr/bin/bash\n' >"$passwd_db"
|
||||||
|
printf 'cups-browsed:x:947:alice\n' >"$group_db"
|
||||||
|
if PATH="$mock_bin:$PATH" \
|
||||||
|
OMARCHY_PATH="$ROOT" \
|
||||||
|
OMARCHY_CUPS_MIGRATION_MARKER="$test_tmp/group-collision-marker" \
|
||||||
|
bash -euo pipefail "$ROOT/migrations/1787815267.sh" 2>/dev/null; then
|
||||||
|
fail "the migration accepts an existing cups-browsed group with members"
|
||||||
|
fi
|
||||||
|
[[ ! -s $log ]] || fail "a group collision stops the migration before changing the system"
|
||||||
|
|
||||||
|
printf 'cups-browsed:x:947:947:CUPS printer discovery:/:/usr/bin/nologin\n' >"$passwd_db"
|
||||||
|
printf 'cups-browsed:x:947:\n' >"$group_db"
|
||||||
|
|
||||||
|
pass "the migration rejects account and group collisions before changing printing"
|
||||||
|
|
||||||
|
marker="$test_tmp/var/lib/omarchy/migrations/1787815267"
|
||||||
|
PATH="$mock_bin:$PATH" \
|
||||||
|
OMARCHY_PATH="$ROOT" \
|
||||||
|
OMARCHY_CUPS_MIGRATION_MARKER="$marker" \
|
||||||
|
bash -euo pipefail "$ROOT/migrations/1787815267.sh"
|
||||||
|
|
||||||
|
grep -qxF $'omarchy-pkg-drop\tcups-pdf' "$log" ||
|
||||||
|
fail "the migration removes CUPS-PDF"
|
||||||
|
grep -qxF $'omarchy-pkg-add\tcups-pk-helper' "$log" ||
|
||||||
|
fail "the migration installs authenticated printer administration"
|
||||||
|
grep -qxF $'systemctl\tstop cups-browsed.service' "$log" ||
|
||||||
|
fail "the migration stops the root cups-browsed process before reconfiguration"
|
||||||
|
grep -qxF $'systemctl\tdaemon-reload' "$log" ||
|
||||||
|
fail "the migration reloads the hardened service"
|
||||||
|
grep -qxF $'systemctl\ttry-reload-or-restart cups.service' "$log" ||
|
||||||
|
fail "the migration reloads the packaged CUPS authorization"
|
||||||
|
grep -qxF $'systemctl\trestart cups-browsed.service' "$log" ||
|
||||||
|
fail "the migration resumes an active cups-browsed service"
|
||||||
|
[[ -f $marker ]] || fail "the migration records machine-wide completion"
|
||||||
|
|
||||||
|
actions_after_first_run=$(wc -l <"$log")
|
||||||
|
PATH="$mock_bin:$PATH" \
|
||||||
|
OMARCHY_PATH="$ROOT" \
|
||||||
|
OMARCHY_CUPS_MIGRATION_MARKER="$marker" \
|
||||||
|
bash -euo pipefail "$ROOT/migrations/1787815267.sh"
|
||||||
|
[[ $(wc -l <"$log") == "$actions_after_first_run" ]] ||
|
||||||
|
fail "the machine-wide migration repeats privileged work"
|
||||||
|
|
||||||
|
pass "the migration safely converts an active existing installation once"
|
||||||
|
|
||||||
|
# An interrupted earlier run leaves cups-browsed stopped. A retry still needs
|
||||||
|
# to resume an enabled service before recording completion.
|
||||||
|
cat >"$mock_bin/systemctl" <<'SH'
|
||||||
|
#!/bin/bash
|
||||||
|
printf 'systemctl\t%s\n' "$*" >>"$OMARCHY_CUPS_TEST_LOG"
|
||||||
|
[[ $1 == "is-active" ]] && exit 1
|
||||||
|
exit 0
|
||||||
|
SH
|
||||||
|
chmod +x "$mock_bin/systemctl"
|
||||||
|
|
||||||
|
retry_log="$test_tmp/retry.log"
|
||||||
|
retry_marker="$test_tmp/var/lib/omarchy/migrations/1787815267-retry"
|
||||||
|
|
||||||
|
OMARCHY_CUPS_TEST_LOG="$retry_log" \
|
||||||
|
PATH="$mock_bin:$PATH" \
|
||||||
|
OMARCHY_PATH="$ROOT" \
|
||||||
|
OMARCHY_CUPS_MIGRATION_MARKER="$retry_marker" \
|
||||||
|
bash -euo pipefail "$ROOT/migrations/1787815267.sh"
|
||||||
|
|
||||||
|
grep -qxF $'systemctl\trestart cups-browsed.service' "$retry_log" ||
|
||||||
|
fail "the retry resumes cups-browsed after an interrupted earlier run"
|
||||||
|
|
||||||
|
pass "a run following an interrupted one still resumes printer discovery"
|
||||||
|
|
||||||
|
# A masked or disabled unit is deliberately left alone.
|
||||||
|
cat >"$mock_bin/systemctl" <<'SH'
|
||||||
|
#!/bin/bash
|
||||||
|
printf 'systemctl\t%s\n' "$*" >>"$OMARCHY_CUPS_TEST_LOG"
|
||||||
|
[[ $1 == "is-active" || $1 == "is-enabled" ]] && exit 1
|
||||||
|
exit 0
|
||||||
|
SH
|
||||||
|
chmod +x "$mock_bin/systemctl"
|
||||||
|
|
||||||
|
masked_log="$test_tmp/masked.log"
|
||||||
|
masked_marker="$test_tmp/var/lib/omarchy/migrations/1787815267-masked"
|
||||||
|
|
||||||
|
OMARCHY_CUPS_TEST_LOG="$masked_log" \
|
||||||
|
PATH="$mock_bin:$PATH" \
|
||||||
|
OMARCHY_PATH="$ROOT" \
|
||||||
|
OMARCHY_CUPS_MIGRATION_MARKER="$masked_marker" \
|
||||||
|
bash -euo pipefail "$ROOT/migrations/1787815267.sh"
|
||||||
|
|
||||||
|
! grep -qxF $'systemctl\trestart cups-browsed.service' "$masked_log" ||
|
||||||
|
fail "the migration leaves a masked or disabled cups-browsed alone"
|
||||||
|
[[ -f $masked_marker ]] || fail "the migration completes with cups-browsed masked"
|
||||||
|
|
||||||
|
pass "a masked or disabled cups-browsed is left alone and does not fail the migration"
|
||||||
@@ -144,11 +144,12 @@ pass "keyboard prompt propagates Esc and Ctrl+C without dying under set -e"
|
|||||||
|
|
||||||
# Username
|
# Username
|
||||||
|
|
||||||
TAKEN_USERS=dhh run_prompt omarchy_prompt_username "0:Not A Username" "0:root" "0:dhh" "0:david"
|
TAKEN_USERS=dhh run_prompt omarchy_prompt_username "0:Not A Username" "0:root" "0:cups-browsed" "0:dhh" "0:david"
|
||||||
assert_status 0 "username prompt accepts a valid name"
|
assert_status 0 "username prompt accepts a valid name"
|
||||||
[[ $(field username) == "david" ]] || fail "username prompt keeps re-asking until the name is valid"
|
[[ $(field username) == "david" ]] || fail "username prompt keeps re-asking until the name is valid"
|
||||||
assert_notices "username prompt explains each rejection" "Username must be alphanumeric with no spaces
|
assert_notices "username prompt explains each rejection" "Username must be alphanumeric with no spaces
|
||||||
Username is reserved for system
|
Username is reserved for system
|
||||||
|
Username is reserved for system
|
||||||
That username already exists on this machine"
|
That username already exists on this machine"
|
||||||
pass "username prompt rejects malformed, reserved, and taken names"
|
pass "username prompt rejects malformed, reserved, and taken names"
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user