First version
This commit is contained in:
commit
243e92a9dd
84 files changed
+4012
No files matched your search
@@ -0,0 +1,9 @@
|
||||
// ACPC 2026: the teams may not change the network (Wi-Fi, phone hotspot,
|
||||
// modem, VPN, ...). Only the administrators (sudo group) keep these actions.
|
||||
polkit.addRule(function(action, subject) {
|
||||
if ((action.id.indexOf("org.freedesktop.NetworkManager.") == 0 ||
|
||||
action.id.indexOf("org.freedesktop.ModemManager1.") == 0) &&
|
||||
!subject.isInGroup("sudo")) {
|
||||
return polkit.Result.NO;
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,14 @@
|
||||
// ACPC 2026: the teams may not restart, power off, suspend or hibernate the
|
||||
// machine. A restart would let USBGuard accept a device plugged in before.
|
||||
// Only the administrators (sudo group) keep these actions.
|
||||
polkit.addRule(function(action, subject) {
|
||||
if ((action.id.indexOf("org.freedesktop.login1.reboot") == 0 ||
|
||||
action.id.indexOf("org.freedesktop.login1.power-off") == 0 ||
|
||||
action.id.indexOf("org.freedesktop.login1.halt") == 0 ||
|
||||
action.id.indexOf("org.freedesktop.login1.suspend") == 0 ||
|
||||
action.id.indexOf("org.freedesktop.login1.hibernate") == 0 ||
|
||||
action.id.indexOf("org.freedesktop.login1.set-reboot") == 0) &&
|
||||
!subject.isInGroup("sudo")) {
|
||||
return polkit.Result.NO;
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,8 @@
|
||||
// ACPC 2026: the teams may not mount USB sticks, disks or disk images.
|
||||
// Only the administrators (sudo group) keep the udisks actions.
|
||||
polkit.addRule(function(action, subject) {
|
||||
if (action.id.indexOf("org.freedesktop.udisks2.") == 0 &&
|
||||
!subject.isInGroup("sudo")) {
|
||||
return polkit.Result.NO;
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,7 @@
|
||||
# ACPC 2026: the desktop user gets no direct access to phones and cameras
|
||||
# (MTP and PTP, used to copy files), which bypasses the mounting of disks.
|
||||
# The tags are set by 69-libmtp.rules and 60-libgphoto2, and used by
|
||||
# 73-seat-late.rules, so this file sits between them.
|
||||
ENV{ID_MTP_DEVICE}=="1", TAG-="uaccess"
|
||||
ENV{ID_GPHOTO2}=="1", TAG-="uaccess"
|
||||
ENV{ID_MEDIA_PLAYER}=="1", TAG-="uaccess"
|
||||
@@ -0,0 +1,8 @@
|
||||
# ACPC 2026: the power, reboot and suspend keys do nothing. A long press on
|
||||
# the power button still cuts the power (hardware), in view of the proctors.
|
||||
[Login]
|
||||
HandlePowerKey=ignore
|
||||
HandleRebootKey=ignore
|
||||
HandleSuspendKey=ignore
|
||||
HandleHibernateKey=ignore
|
||||
HandleLidSwitch=ignore
|
||||
@@ -0,0 +1,2 @@
|
||||
# ACPC 2026: no SysRq key combinations (they can restart the machine)
|
||||
kernel.sysrq = 0
|
||||
@@ -0,0 +1,133 @@
|
||||
#!/bin/bash
|
||||
# Turns the contest restrictions off and on, for maintenance. Root only, so
|
||||
# it is used through sudo from the administrator account (icpc).
|
||||
#
|
||||
# sudo contest-restrictions status
|
||||
# sudo contest-restrictions report [SINCE] (refused attempts, default: today)
|
||||
# sudo contest-restrictions off [all|firewall|usb|network]
|
||||
# sudo contest-restrictions on [all|firewall|usb|network]
|
||||
#
|
||||
# firewall firewall, web proxy, DNS filter (/disable_firewall.sh, /enable_firewall.sh)
|
||||
# usb USBGuard, USB disks and phones for the team account
|
||||
# network network settings (Wi-Fi, hotspot, ...) for the team account
|
||||
#
|
||||
# "off" stays in effect after a reboot, until "on".
|
||||
set -u
|
||||
DISABLED=/etc/contest-restrictions/disabled
|
||||
USB_FILES="/etc/polkit-1/rules.d/10-acpc-no-removable-media.rules /etc/udev/rules.d/72-acpc-no-phone-access.rules"
|
||||
NETWORK_FILES="/etc/polkit-1/rules.d/10-acpc-no-network-changes.rules"
|
||||
|
||||
usage() {
|
||||
sed -n '2,14p' "$0" | sed 's/^# \{0,1\}//'
|
||||
exit 2
|
||||
}
|
||||
|
||||
[ "$(id -u)" -eq 0 ] || { echo "Run it with sudo, from the administrator account." >&2; exit 1; }
|
||||
mkdir -p "$DISABLED"
|
||||
chmod 700 /etc/contest-restrictions
|
||||
|
||||
# Moves rule files out of the way (off) or back (on)
|
||||
move_rules() {
|
||||
action=$1
|
||||
shift
|
||||
for f in "$@"; do
|
||||
if [ "$action" = off ] && [ -f "$f" ]; then
|
||||
mv "$f" "$DISABLED/"
|
||||
elif [ "$action" = on ] && [ -f "$DISABLED/$(basename "$f")" ]; then
|
||||
mv "$DISABLED/$(basename "$f")" "$f"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
firewall() {
|
||||
if [ "$1" = off ]; then
|
||||
/disable_firewall.sh
|
||||
else
|
||||
/enable_firewall.sh
|
||||
/refresh_rules.sh
|
||||
fi
|
||||
}
|
||||
|
||||
usb() {
|
||||
if [ "$1" = off ]; then
|
||||
systemctl disable --now usbguard.service
|
||||
# USBGuard leaves the controllers refusing new devices when it stops
|
||||
for f in /sys/bus/usb/devices/usb*/authorized_default; do
|
||||
[ -e "$f" ] && echo 1 > "$f"
|
||||
done
|
||||
move_rules off $USB_FILES
|
||||
udevadm control --reload
|
||||
echo "USB devices allowed (unplug and plug again a device refused before)."
|
||||
echo "USB disks and phones can be used from the team account."
|
||||
else
|
||||
# Removable media mounted while the restriction was off
|
||||
findmnt -rno TARGET | grep '^/run/media/' | while read -r m; do
|
||||
umount "$m" && echo "Unmounted $m"
|
||||
done
|
||||
move_rules on $USB_FILES
|
||||
udevadm control --reload
|
||||
systemctl enable --now usbguard.service
|
||||
echo "USB restrictions on. The devices plugged in right now stay allowed until they are unplugged."
|
||||
fi
|
||||
}
|
||||
|
||||
network() {
|
||||
if [ "$1" = off ]; then
|
||||
move_rules off $NETWORK_FILES
|
||||
echo "The team account can change the network settings."
|
||||
else
|
||||
move_rules on $NETWORK_FILES
|
||||
echo "Network settings restricted to the administrators."
|
||||
fi
|
||||
}
|
||||
|
||||
state() {
|
||||
if [ "$1" = yes ]; then echo "on"; else echo "OFF"; fi
|
||||
}
|
||||
|
||||
status() {
|
||||
fw=no; systemctl is-active --quiet firewall.service && fw=yes
|
||||
us=no; systemctl is-active --quiet usbguard.service && [ -f /etc/polkit-1/rules.d/10-acpc-no-removable-media.rules ] && us=yes
|
||||
nw=no; [ -f /etc/polkit-1/rules.d/10-acpc-no-network-changes.rules ] && nw=yes
|
||||
echo "firewall $(state $fw)"
|
||||
echo "usb $(state $us)"
|
||||
echo "network $(state $nw)"
|
||||
}
|
||||
|
||||
# What the machine refused: web sites, DNS names, USB devices
|
||||
report() {
|
||||
since=${1:-today}
|
||||
echo "== Web sites refused by the proxy (since $since)"
|
||||
journalctl -t contest_proxy --since "$since" --no-pager -o cat 2>/dev/null \
|
||||
| awk '$3 != "200" || $4 == "-" {print $2}' | sort | uniq -c | sort -rn | head -40
|
||||
echo
|
||||
echo "== DNS names refused by the filter"
|
||||
# Names that Firefox, VS Code, the IDEs and Kattis pages look up on their
|
||||
# own in the background
|
||||
background='(^|\.)(mozilla\.(com|org|net)|firefox-portal-detection\.com|googleapis\.com|gvt1\.com|microsoft\.com|vscode-cdn\.net|vscode-unpkg\.net|exp-tas\.com|openh264\.org|ipv4only\.arpa|sentry\.io|cloudflare-dns\.com|jetbrains\.com|ubuntu\.com|canonical\.com|snapcraft\.io)$'
|
||||
names=$(journalctl -u contest-dns --since "$since" --no-pager -o cat 2>/dev/null \
|
||||
| sed -n 's/^config \(.*\) is NXDOMAIN$/\1/p')
|
||||
printf '%s\n' "$names" | grep -v -E "$background" | grep . | sort | uniq -c | sort -rn | head -40
|
||||
echo " ($(printf '%s\n' "$names" | grep -c -E "$background") lookups by Firefox, VS Code and the IDEs in the background, not listed)"
|
||||
echo
|
||||
echo "== USB devices rejected"
|
||||
journalctl -u usbguard --since "$since" --no-pager -o short 2>/dev/null \
|
||||
| grep "target.new='reject'" \
|
||||
| sed -E 's/^([A-Za-z]+ +[0-9]+ [0-9:]+).* name "([^"]*)".*/\1 \2/' | tail -20
|
||||
}
|
||||
|
||||
case "${1:-}" in
|
||||
status) status ;;
|
||||
report) report "${2:-today}" ;;
|
||||
on|off)
|
||||
what=${2:-all}
|
||||
case "$what" in
|
||||
all) firewall "$1"; usb "$1"; network "$1" ;;
|
||||
firewall|usb|network) "$what" "$1" ;;
|
||||
*) usage ;;
|
||||
esac
|
||||
echo
|
||||
status
|
||||
;;
|
||||
*) usage ;;
|
||||
esac
|
||||
@@ -0,0 +1,41 @@
|
||||
#!/bin/bash
|
||||
# Runs as root (contest-restrictions-warning.service). While any contest
|
||||
# restriction is off (after maintenance with contest-restrictions off), shows
|
||||
# a red banner across the top of the screen, so that the proctors notice it.
|
||||
# The banner has no title bar or close button, the team account cannot stop
|
||||
# this process, and the banner comes back within seconds if its window is
|
||||
# destroyed. It is redrawn every 15 seconds to stay above the other windows.
|
||||
export DISPLAY=:0 XAUTHORITY=/var/run/lightdm/root/:0
|
||||
off() {
|
||||
! systemctl is-active --quiet firewall.service ||
|
||||
! systemctl is-active --quiet usbguard.service ||
|
||||
[ -n "$(ls -A /etc/contest-restrictions/disabled 2>/dev/null)" ]
|
||||
}
|
||||
show() {
|
||||
width=$(xdpyinfo 2>/dev/null | awk '/dimensions/ {split($2, a, "x"); print a[1]}')
|
||||
[ -n "$width" ] || return
|
||||
xmessage -geometry "${width}x70+0+0" -buttons "" \
|
||||
-xrm "*overrideRedirect: true" \
|
||||
-fg white -bg red3 -fn "-*-helvetica-bold-r-*-*-34-*-*-*-*-*-*-*" \
|
||||
"CONTEST RESTRICTIONS ARE OFF ON THIS MACHINE - CALL THE STAFF" >/dev/null 2>&1 &
|
||||
pid=$!
|
||||
shown=$(date +%s)
|
||||
}
|
||||
pid=""
|
||||
shown=0
|
||||
while true; do
|
||||
if off && [ -e "$XAUTHORITY" ]; then
|
||||
if [ -z "$pid" ] || ! kill -0 "$pid" 2>/dev/null; then
|
||||
show
|
||||
elif [ $(( $(date +%s) - shown )) -ge 15 ]; then
|
||||
old=$pid
|
||||
show
|
||||
sleep 1
|
||||
kill "$old" 2>/dev/null
|
||||
fi
|
||||
elif [ -n "$pid" ]; then
|
||||
kill "$pid" 2>/dev/null
|
||||
pid=""
|
||||
fi
|
||||
sleep 3
|
||||
done
|
||||
@@ -0,0 +1,11 @@
|
||||
[Unit]
|
||||
Description=Red banner on the screen while a contest restriction is off
|
||||
After=display-manager.service
|
||||
|
||||
[Service]
|
||||
ExecStart=/usr/local/sbin/contest-restrictions-warning
|
||||
Restart=always
|
||||
RestartSec=3
|
||||
|
||||
[Install]
|
||||
WantedBy=graphical.target
|
||||
Executable
+38
@@ -0,0 +1,38 @@
|
||||
#!/bin/bash
|
||||
# Manual staff command for permanently erasing the team account's files.
|
||||
# Stops the desktop, deletes the home and team-owned files on the root
|
||||
# filesystem, then recreates the home. No backup or undo.
|
||||
# Never invoke from boot, login, a timer, cron, a playbook or automated tests.
|
||||
# Run only after explicit staff authorization and verified backups.
|
||||
# The --now argument is internal to the transient service after confirmation.
|
||||
set -u
|
||||
[ "$(id -u)" -eq 0 ] || { echo "Run this script as root (sudo)." >&2; exit 1; }
|
||||
if [ "${1:-}" != --now ]; then
|
||||
echo "The files of the team account will be deleted on this machine."
|
||||
read -rp "Type RESET to continue: " answer
|
||||
[ "$answer" = RESET ] || { echo "Cancelled."; exit 1; }
|
||||
# The reset ends the desktop session, and with it this terminal if the
|
||||
# script was started from the desktop. It runs as a separate service.
|
||||
systemd-run --unit=reset-team-home --collect --quiet "$(readlink -f "$0")" --now
|
||||
echo "Reset started. The screen restarts in a few seconds."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
systemctl stop lightdm
|
||||
loginctl terminate-user team 2>/dev/null
|
||||
sleep 2
|
||||
pkill -KILL -u team 2>/dev/null
|
||||
sleep 1
|
||||
for m in /home/team/.vscode/extensions /home/team/.vscode/extensions-root; do
|
||||
umount "$m" 2>/dev/null
|
||||
done
|
||||
rm -rf --one-file-system /home/team
|
||||
find / -xdev \( -path /proc -o -path /sys -o -path /dev \) -prune -o -user team -print0 2>/dev/null \
|
||||
| xargs -0 -r rm -rf --one-file-system
|
||||
mkdir /home/team
|
||||
cp -a /etc/skel/. /home/team/
|
||||
chown -R team:teams /home/team
|
||||
chmod 750 /home/team
|
||||
systemctl restart team-vscode-extensions.service
|
||||
systemctl start lightdm
|
||||
echo "The team account is clean."
|
||||
@@ -0,0 +1,14 @@
|
||||
[Unit]
|
||||
Description=Wait for the clock before the desktop starts (at most 30 seconds)
|
||||
After=chrony.service firewall.service contest-dns.service contest-proxy.service
|
||||
Wants=chrony.service
|
||||
Before=display-manager.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
# Three checks 10 seconds apart, then the desktop starts anyway
|
||||
ExecStart=-/usr/bin/chronyc waitsync 3 1
|
||||
TimeoutStartSec=45
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
Reference in new issue
Block a user