Ship CUPS authorization through settings package

This commit is contained in:
Ryan Hughes
2026-08-29 02:18:44 -04:00
parent b07374f03c
commit 74997fd523
7 changed files with 180 additions and 183 deletions
+90
View File
@@ -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
-1
View File
@@ -7,6 +7,5 @@ run_logged "$OMARCHY_INSTALL/config/ssh-keepalive.sh"
run_logged "$OMARCHY_INSTALL/config/docker.sh"
run_logged "$OMARCHY_INSTALL/config/snapper.sh"
run_logged "$OMARCHY_INSTALL/config/locate.sh"
run_logged "$OMARCHY_INSTALL/config/printing.sh"
run_logged "$OMARCHY_INSTALL/config/enable-services.sh"
run_logged "$OMARCHY_INSTALL/config/firewall.sh"
-83
View File
@@ -1,83 +0,0 @@
# cups-browsed manages queues through CUPS and does not need Unix root. Give
# only its locked service account passwordless CUPS administration; interactive
# users go through cups-pk-helper and Polkit instead.
cups_files_conf="${OMARCHY_CUPS_FILES_CONF:-/etc/cups/cups-files.conf}"
cups_browsed_sysusers_conf="${OMARCHY_CUPS_BROWSED_SYSUSERS_CONF:-/etc/sysusers.d/omarchy-cups-browsed.conf}"
if [[ -f $cups_browsed_sysusers_conf ]]; then
systemd-sysusers "$cups_browsed_sysusers_conf"
fi
if [[ -L $cups_files_conf ]]; then
echo "Refusing to rewrite symlinked CUPS authorization config: $cups_files_conf" >&2
false
elif [[ -f $cups_files_conf ]]; then
staged_conf=$(mktemp --tmpdir="${cups_files_conf%/*}" ".${cups_files_conf##*/}.XXXXXX")
if ! awk '
NR == FNR {
if (tolower($1) == "systemgroup") {
for (i = 2; i <= NF; i++) {
if (substr($i, 1, 1) == "#")
break
if ($i != "wheel" && !seen_group[$i]) {
system_groups[++system_group_count] = $i
seen_group[$i] = 1
}
}
}
next
}
tolower($1) == "systemgroup" {
comment_start = index($0, "#")
if (!wrote_system_group) {
printf "SystemGroup"
for (i = 1; i <= system_group_count; i++)
printf " %s", system_groups[i]
if (!seen_group["cups-browsed"])
printf " cups-browsed"
if (comment_start)
printf " %s", substr($0, comment_start)
print ""
wrote_system_group = 1
} else if (comment_start) {
print substr($0, comment_start)
}
next
}
tolower($1) == "peercred" {
comment_start = index($0, "#")
if (!saw_peer_cred) {
printf "PeerCred on"
if (comment_start)
printf " %s", substr($0, comment_start)
print ""
} else if (comment_start) {
print substr($0, comment_start)
}
saw_peer_cred = 1
next
}
{ print }
END {
if (!wrote_system_group)
print "SystemGroup sys root cups-browsed"
if (!saw_peer_cred)
print "PeerCred on"
}
' "$cups_files_conf" "$cups_files_conf" >"$staged_conf"; then
rm -f "$staged_conf"
false
fi
if ! chmod --reference="$cups_files_conf" "$staged_conf" ||
! chown --reference="$cups_files_conf" "$staged_conf" ||
! mv -f "$staged_conf" "$cups_files_conf"; then
rm -f "$staged_conf"
false
fi
fi
+6 -4
View File
@@ -3,11 +3,13 @@
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
# omarchy-settings skips this override until cups-browsed is actually present
# to avoid pacman creating cups-browsed.conf.pacnew during ISO package install.
if [[ -f $OMARCHY_PATH/etc-overrides/cups-cups-browsed.conf && -d /etc/cups ]]; then
# omarchy-settings skips these overrides until CUPS is actually present to
# avoid pacman creating .pacnew files during ISO package installation.
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
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
source "$OMARCHY_INSTALL/hardware/pacman.sh"
+22 -2
View File
@@ -4,6 +4,28 @@ machine_marker="${OMARCHY_CUPS_MIGRATION_MARKER:-/var/lib/omarchy/migrations/178
[[ ! -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
@@ -20,8 +42,6 @@ if systemctl is-active --quiet cups-browsed.service 2>/dev/null; then
fi
if omarchy-pkg-present cups; then
sudo env OMARCHY_PATH="$OMARCHY_PATH" \
bash -euo pipefail "$OMARCHY_PATH/install/config/printing.sh"
sudo systemctl daemon-reload
sudo systemctl try-reload-or-restart cups.service
fi
+1 -1
View File
@@ -86,7 +86,7 @@ verify_printing_security() {
[[ $(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 | grep -qw cups ||
[[ " $(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
+61 -92
View File
@@ -6,6 +6,7 @@ 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"
@@ -26,6 +27,21 @@ grep -qxF 'CreateRemoteCUPSPrinterQueues No' "$cups_browsed_conf" ||
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"
@@ -53,15 +69,25 @@ test_tmp=$(mktemp -d)
trap 'rm -rf "$test_tmp"' EXIT
mock_bin="$test_tmp/bin"
mkdir -p "$mock_bin" "$test_tmp/etc/cups" "$test_tmp/var/lib/omarchy/migrations"
mkdir -p "$mock_bin" "$test_tmp/var/lib/omarchy/migrations"
cat >"$mock_bin/systemd-sysusers" <<'SH'
passwd_db="$test_tmp/passwd"
group_db="$test_tmp/group"
touch "$passwd_db" "$group_db"
cat >"$mock_bin/getent" <<'SH'
#!/bin/bash
printf 'sysusers\t%s\n' "$*" >>"$OMARCHY_CUPS_TEST_LOG"
SH
cat >"$mock_bin/chown" <<'SH'
#!/bin/bash
printf 'chown\t%s\n' "$*" >>"$OMARCHY_CUPS_TEST_LOG"
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
@@ -85,62 +111,40 @@ exec "$@"
SH
chmod +x "$mock_bin"/*
authorization_conf="$test_tmp/etc/cups/cups-files.conf"
cat >"$authorization_conf" <<'CONF'
# Keep this custom preamble.
SystemGroup sys root wheel custom-admin wheel # Keep this inline comment.
SystemGroup wheel print-operators # Keep this second inline comment.
PeerCred off # Keep this PeerCred comment.
PeerCred off # Keep this second PeerCred comment.
CONF
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"
run_printing_setup() {
PATH="$mock_bin:$PATH" \
OMARCHY_CUPS_FILES_CONF="$authorization_conf" \
OMARCHY_CUPS_BROWSED_SYSUSERS_CONF="$sysusers_conf" \
bash -euo pipefail "$ROOT/install/config/printing.sh"
}
run_printing_setup
grep -qxF 'SystemGroup sys root custom-admin print-operators cups-browsed # Keep this inline comment.' "$authorization_conf" ||
fail "printing setup reserves CUPS administration for the service account"
grep -qxF '# Keep this second inline comment.' "$authorization_conf" ||
fail "printing setup preserves comments from consolidated SystemGroup directives"
grep -qxF 'PeerCred on # Keep this PeerCred comment.' "$authorization_conf" ||
fail "printing setup enables peer credentials for the service account"
grep -qxF '# Keep this second PeerCred comment.' "$authorization_conf" ||
fail "printing setup preserves comments from duplicate PeerCred directives"
grep -qxF '# Keep this custom preamble.' "$authorization_conf" ||
fail "printing setup preserves unrelated CUPS configuration"
[[ $(grep -c '^SystemGroup ' "$authorization_conf") == 1 ]] ||
fail "printing setup emits one SystemGroup directive"
cp "$authorization_conf" "$test_tmp/first-run.conf"
run_printing_setup
cmp -s "$authorization_conf" "$test_tmp/first-run.conf" ||
fail "printing setup is idempotent"
pass "printing setup narrows CUPS authorization without clobbering other configuration"
ln -s "$authorization_conf" "$test_tmp/etc/cups/symlinked.conf"
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_CUPS_FILES_CONF="$test_tmp/etc/cups/symlinked.conf" \
OMARCHY_CUPS_BROWSED_SYSUSERS_CONF="$sysusers_conf" \
bash -euo pipefail "$ROOT/install/config/printing.sh" 2>/dev/null; then
fail "printing setup refuses a symlinked authorization file"
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"
pass "printing setup refuses to rewrite a symlinked privileged configuration"
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_FILES_CONF="$authorization_conf" \
OMARCHY_CUPS_BROWSED_SYSUSERS_CONF="$sysusers_conf" \
OMARCHY_CUPS_MIGRATION_MARKER="$marker" \
bash -euo pipefail "$ROOT/migrations/1787815267.sh"
@@ -153,7 +157,7 @@ grep -qxF $'systemctl\tstop cups-browsed.service' "$log" ||
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 applies narrowed CUPS authorization"
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"
@@ -168,10 +172,8 @@ PATH="$mock_bin:$PATH" \
pass "the migration safely converts an active existing installation once"
# An interrupted earlier run leaves cups-browsed stopped, so the retry that
# follows finds it inactive. It must still be restarted: the retry records the
# machine-wide marker either way, so a restart skipped here would leave printer
# discovery off until the next reboot with nothing left to run.
# 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"
@@ -186,8 +188,6 @@ 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_FILES_CONF="$authorization_conf" \
OMARCHY_CUPS_BROWSED_SYSUSERS_CONF="$sysusers_conf" \
OMARCHY_CUPS_MIGRATION_MARKER="$retry_marker" \
bash -euo pipefail "$ROOT/migrations/1787815267.sh"
@@ -196,8 +196,7 @@ grep -qxF $'systemctl\trestart cups-browsed.service' "$retry_log" ||
pass "a run following an interrupted one still resumes printer discovery"
# A unit the user masked or disabled reports not-enabled, and restarting it
# would fail and abort the migration before it records completion.
# 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"
@@ -212,8 +211,6 @@ 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_FILES_CONF="$authorization_conf" \
OMARCHY_CUPS_BROWSED_SYSUSERS_CONF="$sysusers_conf" \
OMARCHY_CUPS_MIGRATION_MARKER="$masked_marker" \
bash -euo pipefail "$ROOT/migrations/1787815267.sh"
@@ -222,31 +219,3 @@ OMARCHY_CUPS_TEST_LOG="$masked_log" \
[[ -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"
# cupsd compares directive names case-insensitively, so a hand-edited lowercase
# directive is live configuration. Matching it exactly would skip the line and
# append a second one, and cupsd accumulates the groups of every SystemGroup
# directive it reads -- leaving wheel with passwordless administration.
lowercase_conf="$test_tmp/etc/cups/lowercase.conf"
cat >"$lowercase_conf" <<'CONF'
systemgroup sys root wheel
peercred off
CONF
PATH="$mock_bin:$PATH" \
OMARCHY_CUPS_FILES_CONF="$lowercase_conf" \
OMARCHY_CUPS_BROWSED_SYSUSERS_CONF="$sysusers_conf" \
bash -euo pipefail "$ROOT/install/config/printing.sh"
! grep -qiE '^[[:space:]]*systemgroup\b.*\bwheel\b' "$lowercase_conf" ||
fail "printing setup removes wheel from a lowercase SystemGroup directive" "$(cat "$lowercase_conf")"
[[ $(grep -ciE '^[[:space:]]*systemgroup\b' "$lowercase_conf") == 1 ]] ||
fail "printing setup leaves one SystemGroup directive whatever case it was written in" "$(cat "$lowercase_conf")"
grep -qxF 'SystemGroup sys root cups-browsed' "$lowercase_conf" ||
fail "printing setup reserves administration for the service account" "$(cat "$lowercase_conf")"
[[ $(grep -ciE '^[[:space:]]*peercred\b' "$lowercase_conf") == 1 ]] ||
fail "printing setup leaves one PeerCred directive" "$(cat "$lowercase_conf")"
grep -qxF 'PeerCred on' "$lowercase_conf" ||
fail "printing setup enables peer credentials whatever case they were written in" "$(cat "$lowercase_conf")"
pass "printing setup rewrites directives cupsd reads case-insensitively"