feat: add auto secure boot to new kernel update
This commit is contained in:
13
README.md
13
README.md
@ -9,6 +9,7 @@ Daemon de mises à jour automatiques pour CachyOS. Il vérifie les mises à jour
|
|||||||
- *Sûrs* : installés immédiatement en arrière-plan (aucune interruption)
|
- *Sûrs* : installés immédiatement en arrière-plan (aucune interruption)
|
||||||
- *Reportés* : kernel, systemd, pilotes GPU, glibc, etc. — pré-téléchargés pendant la session, puis installés à la prochaine extinction
|
- *Reportés* : kernel, systemd, pilotes GPU, glibc, etc. — pré-téléchargés pendant la session, puis installés à la prochaine extinction
|
||||||
- **Installation à l'extinction** — les paquets reportés sont installés pendant la séquence d'arrêt, sans accès réseau nécessaire (pré-téléchargés)
|
- **Installation à l'extinction** — les paquets reportés sont installés pendant la séquence d'arrêt, sans accès réseau nécessaire (pré-téléchargés)
|
||||||
|
- **Secure Boot conservé** — si une clé MOK existe déjà, l'ESP reste montée et les noyaux/GRUB sont resignés et vérifiés après la transaction
|
||||||
- **Interface graphique** — fenêtre GTK4/Libadwaita affichant les mises à jour en attente et l'historique des installations
|
- **Interface graphique** — fenêtre GTK4/Libadwaita affichant les mises à jour en attente et l'historique des installations
|
||||||
|
|
||||||
## Dépendances
|
## Dépendances
|
||||||
@ -19,6 +20,18 @@ Daemon de mises à jour automatiques pour CachyOS. Il vérifie les mises à jour
|
|||||||
- `libadwaita`
|
- `libadwaita`
|
||||||
- `pacman-contrib` (fournit `checkupdates`)
|
- `pacman-contrib` (fournit `checkupdates`)
|
||||||
|
|
||||||
|
L'intégration Secure Boot est activée automatiquement lorsque
|
||||||
|
`/root/secureboot-mok/MOK.key` et `/root/secureboot-mok/MOK.crt` existent. Le
|
||||||
|
certificat correspondant doit déjà être enrôlé dans le firmware via MOK. Les
|
||||||
|
chemins peuvent être personnalisés dans
|
||||||
|
`/etc/cachyos-updater/secureboot.conf` :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
SECURE_BOOT_KEY=/root/secureboot-mok/MOK.key
|
||||||
|
SECURE_BOOT_CERT=/root/secureboot-mok/MOK.crt
|
||||||
|
SECURE_BOOT_ESP=/boot/efi
|
||||||
|
```
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
26
install.sh
26
install.sh
@ -22,6 +22,13 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|||||||
info "Vérification des dépendances…"
|
info "Vérification des dépendances…"
|
||||||
|
|
||||||
deps=(python python-gobject gtk4 libadwaita pacman-contrib)
|
deps=(python python-gobject gtk4 libadwaita pacman-contrib)
|
||||||
|
|
||||||
|
# Active l'intégration Secure Boot si une paire de clés MOK existe déjà.
|
||||||
|
secure_boot_configured=false
|
||||||
|
if [[ -r /root/secureboot-mok/MOK.key && -r /root/secureboot-mok/MOK.crt ]]; then
|
||||||
|
secure_boot_configured=true
|
||||||
|
deps+=(grub shim-signed sbsigntools)
|
||||||
|
fi
|
||||||
missing=()
|
missing=()
|
||||||
for dep in "${deps[@]}"; do
|
for dep in "${deps[@]}"; do
|
||||||
if ! pacman -Qi "$dep" &>/dev/null; then
|
if ! pacman -Qi "$dep" &>/dev/null; then
|
||||||
@ -47,6 +54,19 @@ install -m 755 "$SCRIPT_DIR"/bin/cachyos-updater /usr/bin/cachyos-updater
|
|||||||
install -m 755 "$SCRIPT_DIR"/bin/cachyos-updater-ui /usr/bin/cachyos-updater-ui
|
install -m 755 "$SCRIPT_DIR"/bin/cachyos-updater-ui /usr/bin/cachyos-updater-ui
|
||||||
install -m 755 "$SCRIPT_DIR"/bin/cachyos-updater-shutdown /usr/bin/cachyos-updater-shutdown
|
install -m 755 "$SCRIPT_DIR"/bin/cachyos-updater-shutdown /usr/bin/cachyos-updater-shutdown
|
||||||
|
|
||||||
|
if $secure_boot_configured; then
|
||||||
|
install -d /usr/local/sbin /etc/pacman.d/hooks
|
||||||
|
install -m 750 \
|
||||||
|
"$SCRIPT_DIR"/secureboot/secureboot-sign-cachyos \
|
||||||
|
/usr/local/sbin/secureboot-sign-cachyos
|
||||||
|
install -m 644 \
|
||||||
|
"$SCRIPT_DIR"/secureboot/99-secureboot-sign-cachyos.hook \
|
||||||
|
/etc/pacman.d/hooks/99-secureboot-sign-cachyos.hook
|
||||||
|
info "Intégration Secure Boot installée"
|
||||||
|
else
|
||||||
|
warn "Clés MOK absentes : intégration Secure Boot non installée"
|
||||||
|
fi
|
||||||
|
|
||||||
# Unités systemd
|
# Unités systemd
|
||||||
install -m 644 "$SCRIPT_DIR"/systemd/cachyos-updater.service \
|
install -m 644 "$SCRIPT_DIR"/systemd/cachyos-updater.service \
|
||||||
/usr/lib/systemd/system/cachyos-updater.service
|
/usr/lib/systemd/system/cachyos-updater.service
|
||||||
@ -77,8 +97,10 @@ systemctl daemon-reload
|
|||||||
systemctl enable --now cachyos-updater.timer
|
systemctl enable --now cachyos-updater.timer
|
||||||
info "Timer activé : vérification au démarrage puis toutes les heures"
|
info "Timer activé : vérification au démarrage puis toutes les heures"
|
||||||
|
|
||||||
# Activer le service de shutdown (installation avant extinction)
|
# Récrée les liens pour migrer les anciennes installations qui ciblaient
|
||||||
systemctl enable cachyos-updater-shutdown.service
|
# directement poweroff.target/reboot.target, puis arme ExecStop.
|
||||||
|
systemctl reenable --now cachyos-updater-shutdown.service
|
||||||
|
systemctl start cachyos-updater-shutdown.service
|
||||||
info "Service shutdown activé : installation des mises à jour reportées à l'extinction"
|
info "Service shutdown activé : installation des mises à jour reportées à l'extinction"
|
||||||
|
|
||||||
# ── Résumé ─────────────────────────────────────────────────────────────────
|
# ── Résumé ─────────────────────────────────────────────────────────────────
|
||||||
|
|||||||
@ -4,6 +4,8 @@ from dataclasses import dataclass, field
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
|
from secure_boot import prepare_secure_boot, refresh_secure_boot
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
PACMAN_LOCK = Path("/var/lib/pacman/db.lck")
|
PACMAN_LOCK = Path("/var/lib/pacman/db.lck")
|
||||||
@ -75,12 +77,30 @@ def install_packages(names: list[str]) -> InstallResult:
|
|||||||
if not names:
|
if not names:
|
||||||
return InstallResult(success=True)
|
return InstallResult(success=True)
|
||||||
|
|
||||||
|
secure_boot = prepare_secure_boot(names)
|
||||||
|
if not secure_boot.success:
|
||||||
|
return InstallResult(
|
||||||
|
success=False,
|
||||||
|
failed=names,
|
||||||
|
error=f"Préparation Secure Boot impossible : {secure_boot.error}",
|
||||||
|
)
|
||||||
|
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
["pacman", "-S", "--noconfirm", "--needed", "--noprogressbar"] + names,
|
["pacman", "-S", "--noconfirm", "--needed", "--noprogressbar"] + names,
|
||||||
capture_output=True, text=True, timeout=600
|
capture_output=True, text=True, timeout=600
|
||||||
)
|
)
|
||||||
|
|
||||||
if result.returncode == 0:
|
if result.returncode == 0:
|
||||||
|
secure_boot = refresh_secure_boot(names)
|
||||||
|
if not secure_boot.success:
|
||||||
|
return InstallResult(
|
||||||
|
success=False,
|
||||||
|
failed=names,
|
||||||
|
error=(
|
||||||
|
"Les paquets ont été installés, mais la chaîne de démarrage "
|
||||||
|
f"n'a pas pu être sécurisée : {secure_boot.error}"
|
||||||
|
),
|
||||||
|
)
|
||||||
return InstallResult(success=True, installed=names)
|
return InstallResult(success=True, installed=names)
|
||||||
else:
|
else:
|
||||||
return InstallResult(
|
return InstallResult(
|
||||||
@ -100,12 +120,30 @@ def upgrade_cached_packages(expected_names: list[str]) -> InstallResult:
|
|||||||
if not expected_names:
|
if not expected_names:
|
||||||
return InstallResult(success=True)
|
return InstallResult(success=True)
|
||||||
|
|
||||||
|
secure_boot = prepare_secure_boot(expected_names)
|
||||||
|
if not secure_boot.success:
|
||||||
|
return InstallResult(
|
||||||
|
success=False,
|
||||||
|
failed=expected_names,
|
||||||
|
error=f"Préparation Secure Boot impossible : {secure_boot.error}",
|
||||||
|
)
|
||||||
|
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
["pacman", "-Su", "--noconfirm", "--noprogressbar"],
|
["pacman", "-Su", "--noconfirm", "--noprogressbar"],
|
||||||
capture_output=True, text=True, timeout=600
|
capture_output=True, text=True, timeout=600
|
||||||
)
|
)
|
||||||
|
|
||||||
if result.returncode == 0:
|
if result.returncode == 0:
|
||||||
|
secure_boot = refresh_secure_boot(expected_names)
|
||||||
|
if not secure_boot.success:
|
||||||
|
return InstallResult(
|
||||||
|
success=False,
|
||||||
|
failed=expected_names,
|
||||||
|
error=(
|
||||||
|
"Les paquets ont été installés, mais la chaîne de démarrage "
|
||||||
|
f"n'a pas pu être sécurisée : {secure_boot.error}"
|
||||||
|
),
|
||||||
|
)
|
||||||
return InstallResult(success=True, installed=expected_names)
|
return InstallResult(success=True, installed=expected_names)
|
||||||
else:
|
else:
|
||||||
return InstallResult(
|
return InstallResult(
|
||||||
|
|||||||
113
lib/secure_boot.py
Normal file
113
lib/secure_boot.py
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
"""Intégration Secure Boot pour les transactions qui modifient le démarrage."""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Iterable
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
SIGNER_PATH = Path("/usr/local/sbin/secureboot-sign-cachyos")
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class SecureBootResult:
|
||||||
|
success: bool
|
||||||
|
configured: bool
|
||||||
|
error: str | None = None
|
||||||
|
|
||||||
|
|
||||||
|
def boot_artifacts_may_change(package_names: Iterable[str]) -> bool:
|
||||||
|
"""Indique si la transaction peut remplacer un noyau ou un chargeur EFI."""
|
||||||
|
for name in package_names:
|
||||||
|
if name in {"grub", "shim-signed", "systemd"}:
|
||||||
|
return True
|
||||||
|
if name == "linux" or name.startswith("linux-"):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def _run(command: list[str], timeout: int = 120) -> subprocess.CompletedProcess:
|
||||||
|
return subprocess.run(
|
||||||
|
command,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
timeout=timeout,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _error_message(result: subprocess.CompletedProcess) -> str:
|
||||||
|
output = (result.stderr or result.stdout).strip()
|
||||||
|
return output[-800:] if output else f"code de sortie {result.returncode}"
|
||||||
|
|
||||||
|
|
||||||
|
def prepare_secure_boot(package_names: Iterable[str]) -> SecureBootResult:
|
||||||
|
"""Garantit que l'ESP est montée avant une transaction sensible.
|
||||||
|
|
||||||
|
L'absence du signer signifie simplement que Secure Boot n'est pas géré par
|
||||||
|
cette installation. En revanche, une configuration présente mais
|
||||||
|
inutilisable bloque la transaction pour ne pas installer un noyau non signé.
|
||||||
|
"""
|
||||||
|
names = list(package_names)
|
||||||
|
if not boot_artifacts_may_change(names) or not SIGNER_PATH.is_file():
|
||||||
|
return SecureBootResult(success=True, configured=False)
|
||||||
|
|
||||||
|
if os.geteuid() != 0:
|
||||||
|
return SecureBootResult(
|
||||||
|
success=False,
|
||||||
|
configured=True,
|
||||||
|
error="la préparation Secure Boot doit être exécutée en root",
|
||||||
|
)
|
||||||
|
|
||||||
|
if not os.access(SIGNER_PATH, os.X_OK):
|
||||||
|
return SecureBootResult(
|
||||||
|
success=False,
|
||||||
|
configured=True,
|
||||||
|
error=f"le signer Secure Boot n'est pas exécutable : {SIGNER_PATH}",
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = _run([str(SIGNER_PATH), "--prepare"])
|
||||||
|
except (OSError, subprocess.TimeoutExpired) as error:
|
||||||
|
return SecureBootResult(
|
||||||
|
success=False,
|
||||||
|
configured=True,
|
||||||
|
error=f"impossible d'exécuter la pré-vérification : {error}",
|
||||||
|
)
|
||||||
|
|
||||||
|
if result.returncode != 0:
|
||||||
|
return SecureBootResult(
|
||||||
|
success=False,
|
||||||
|
configured=True,
|
||||||
|
error=f"pré-vérification du signer échouée : {_error_message(result)}",
|
||||||
|
)
|
||||||
|
|
||||||
|
return SecureBootResult(success=True, configured=True)
|
||||||
|
|
||||||
|
|
||||||
|
def refresh_secure_boot(package_names: Iterable[str]) -> SecureBootResult:
|
||||||
|
"""Reconstruit et signe la chaîne CachyOS après la transaction pacman."""
|
||||||
|
names = list(package_names)
|
||||||
|
if not boot_artifacts_may_change(names) or not SIGNER_PATH.is_file():
|
||||||
|
return SecureBootResult(success=True, configured=False)
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = _run([str(SIGNER_PATH)], timeout=180)
|
||||||
|
except (OSError, subprocess.TimeoutExpired) as error:
|
||||||
|
return SecureBootResult(
|
||||||
|
success=False,
|
||||||
|
configured=True,
|
||||||
|
error=f"impossible d'exécuter le signer : {error}",
|
||||||
|
)
|
||||||
|
|
||||||
|
if result.returncode != 0:
|
||||||
|
return SecureBootResult(
|
||||||
|
success=False,
|
||||||
|
configured=True,
|
||||||
|
error=f"signature Secure Boot échouée : {_error_message(result)}",
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.info("Chaîne de démarrage Secure Boot reconstruite et vérifiée")
|
||||||
|
return SecureBootResult(success=True, configured=True)
|
||||||
20
secureboot/99-secureboot-sign-cachyos.hook
Normal file
20
secureboot/99-secureboot-sign-cachyos.hook
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
[Trigger]
|
||||||
|
Operation = Install
|
||||||
|
Operation = Upgrade
|
||||||
|
Type = Package
|
||||||
|
Target = grub
|
||||||
|
Target = shim-signed
|
||||||
|
|
||||||
|
[Trigger]
|
||||||
|
Operation = Install
|
||||||
|
Operation = Upgrade
|
||||||
|
Type = Path
|
||||||
|
Target = boot/vmlinuz-*
|
||||||
|
|
||||||
|
[Action]
|
||||||
|
Description = Signing and verifying CachyOS Secure Boot artifacts...
|
||||||
|
When = PostTransaction
|
||||||
|
Depends = grub
|
||||||
|
Depends = shim-signed
|
||||||
|
Depends = sbsigntools
|
||||||
|
Exec = /usr/local/sbin/secureboot-sign-cachyos
|
||||||
112
secureboot/secureboot-sign-cachyos
Executable file
112
secureboot/secureboot-sign-cachyos
Executable file
@ -0,0 +1,112 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
CONFIG=/etc/cachyos-updater/secureboot.conf
|
||||||
|
if [[ -r "$CONFIG" ]]; then
|
||||||
|
# Le fichier est installé et contrôlé par root.
|
||||||
|
# shellcheck source=/dev/null
|
||||||
|
source "$CONFIG"
|
||||||
|
fi
|
||||||
|
|
||||||
|
KEY=${SECURE_BOOT_KEY:-/root/secureboot-mok/MOK.key}
|
||||||
|
CERT=${SECURE_BOOT_CERT:-/root/secureboot-mok/MOK.crt}
|
||||||
|
ESP=${SECURE_BOOT_ESP:-/boot/efi}
|
||||||
|
SBAT=${SECURE_BOOT_SBAT:-/usr/share/grub/sbat.csv}
|
||||||
|
GRUB_MODULES="all_video bli boot chain configfile echo efi_gop efi_uga ext2 fat font gettext gfxmenu gfxterm gzio linux loadenv normal part_gpt part_msdos png search search_fs_uuid search_label terminal video video_bochs video_cirrus"
|
||||||
|
|
||||||
|
die() {
|
||||||
|
echo "Secure Boot : $*" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
[[ $EUID -eq 0 ]] || die "le signer doit être exécuté en root"
|
||||||
|
|
||||||
|
for command in findmnt grub-install install sbverify sbsign; do
|
||||||
|
command -v "$command" >/dev/null 2>&1 \
|
||||||
|
|| die "commande requise introuvable : $command"
|
||||||
|
done
|
||||||
|
|
||||||
|
[[ -r "$KEY" ]] || die "clé privée introuvable : $KEY"
|
||||||
|
[[ -r "$CERT" ]] || die "certificat introuvable : $CERT"
|
||||||
|
[[ -r "$SBAT" ]] || die "fichier SBAT introuvable : $SBAT"
|
||||||
|
|
||||||
|
if ! findmnt --mountpoint "$ESP" >/dev/null 2>&1; then
|
||||||
|
echo "Secure Boot : montage de $ESP"
|
||||||
|
mount "$ESP"
|
||||||
|
fi
|
||||||
|
|
||||||
|
ESP_FS=$(findmnt --noheadings --output FSTYPE --mountpoint "$ESP" | tr -d '[:space:]')
|
||||||
|
[[ "$ESP_FS" == "vfat" ]] \
|
||||||
|
|| die "$ESP n'est pas une partition EFI vfat montée (type : ${ESP_FS:-inconnu})"
|
||||||
|
|
||||||
|
if [[ ${1:-} == "--prepare" ]]; then
|
||||||
|
echo "Secure Boot : pré-vérification réussie sur $ESP"
|
||||||
|
exit 0
|
||||||
|
elif [[ $# -gt 0 ]]; then
|
||||||
|
die "option inconnue : $1"
|
||||||
|
fi
|
||||||
|
|
||||||
|
sign_if_needed() {
|
||||||
|
local file=$1
|
||||||
|
local tmp
|
||||||
|
|
||||||
|
[[ -f "$file" ]] || return 0
|
||||||
|
if sbverify --cert "$CERT" "$file" >/dev/null 2>&1; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
tmp="${file}.signed.$$"
|
||||||
|
trap 'rm -f "$tmp"' RETURN
|
||||||
|
sbsign --key "$KEY" --cert "$CERT" --output "$tmp" "$file" >/dev/null
|
||||||
|
chown --reference="$file" "$tmp"
|
||||||
|
chmod --reference="$file" "$tmp"
|
||||||
|
mv "$tmp" "$file"
|
||||||
|
trap - RETURN
|
||||||
|
}
|
||||||
|
|
||||||
|
verify_signature() {
|
||||||
|
local file=$1
|
||||||
|
sbverify --cert "$CERT" "$file" >/dev/null 2>&1 \
|
||||||
|
|| die "signature MOK invalide : $file"
|
||||||
|
}
|
||||||
|
|
||||||
|
grub-install \
|
||||||
|
--target=x86_64-efi \
|
||||||
|
--efi-directory="$ESP" \
|
||||||
|
--bootloader-id=CACHYOS \
|
||||||
|
--modules="$GRUB_MODULES" \
|
||||||
|
--sbat="$SBAT" \
|
||||||
|
--no-nvram
|
||||||
|
|
||||||
|
install -d "$ESP/EFI/CACHYOS" "$ESP/EFI/BOOT"
|
||||||
|
for component in shimx64.efi mmx64.efi fbx64.efi; do
|
||||||
|
[[ -r "/usr/share/shim-signed/$component" ]] \
|
||||||
|
|| die "composant shim introuvable : $component"
|
||||||
|
install -m 0644 \
|
||||||
|
"/usr/share/shim-signed/$component" \
|
||||||
|
"$ESP/EFI/CACHYOS/${component^^}"
|
||||||
|
install -m 0644 \
|
||||||
|
"/usr/share/shim-signed/$component" \
|
||||||
|
"$ESP/EFI/BOOT/${component^^}"
|
||||||
|
done
|
||||||
|
|
||||||
|
sign_if_needed "$ESP/EFI/CACHYOS/GRUBX64.EFI"
|
||||||
|
install -m 0644 \
|
||||||
|
"$ESP/EFI/CACHYOS/GRUBX64.EFI" \
|
||||||
|
"$ESP/EFI/BOOT/GRUBX64.EFI"
|
||||||
|
|
||||||
|
shopt -s nullglob
|
||||||
|
kernels=(/boot/vmlinuz-*)
|
||||||
|
[[ ${#kernels[@]} -gt 0 ]] || die "aucun noyau trouvé dans /boot"
|
||||||
|
for kernel in "${kernels[@]}"; do
|
||||||
|
sign_if_needed "$kernel"
|
||||||
|
done
|
||||||
|
|
||||||
|
verify_signature "$ESP/EFI/CACHYOS/GRUBX64.EFI"
|
||||||
|
verify_signature "$ESP/EFI/BOOT/GRUBX64.EFI"
|
||||||
|
for kernel in "${kernels[@]}"; do
|
||||||
|
verify_signature "$kernel"
|
||||||
|
done
|
||||||
|
|
||||||
|
sync
|
||||||
|
echo "Secure Boot : GRUB et ${#kernels[@]} noyau(x) signés et vérifiés"
|
||||||
@ -1,19 +1,20 @@
|
|||||||
[Unit]
|
[Unit]
|
||||||
Description=CachyOS Updater - Installation des mises à jour reportées avant extinction
|
Description=CachyOS Updater - Installation des mises à jour reportées avant extinction
|
||||||
DefaultDependencies=no
|
# L'ordre est inversé à l'arrêt : le service s'arrête avant les montages.
|
||||||
Before=poweroff.target halt.target reboot.target shutdown.target
|
|
||||||
# Après Plymouth pour pouvoir lui envoyer des messages
|
|
||||||
After=local-fs.target plymouth-start.service
|
After=local-fs.target plymouth-start.service
|
||||||
|
# Maintient explicitement l'ESP jusqu'à la fin de ExecStop.
|
||||||
|
RequiresMountsFor=/boot/efi
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
Type=oneshot
|
Type=oneshot
|
||||||
ExecStart=/usr/bin/cachyos-updater-shutdown
|
ExecStart=/usr/bin/true
|
||||||
|
ExecStop=/usr/bin/cachyos-updater-shutdown
|
||||||
|
RemainAfterExit=yes
|
||||||
# Jusqu'à 10 minutes pour installer les mises à jour (kernel, systemd...)
|
# Jusqu'à 10 minutes pour installer les mises à jour (kernel, systemd...)
|
||||||
TimeoutStartSec=600
|
TimeoutStopSec=600
|
||||||
RemainAfterExit=no
|
|
||||||
StandardOutput=journal
|
StandardOutput=journal
|
||||||
StandardError=journal
|
StandardError=journal
|
||||||
SyslogIdentifier=cachyos-updater-shutdown
|
SyslogIdentifier=cachyos-updater-shutdown
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=poweroff.target halt.target reboot.target
|
WantedBy=multi-user.target
|
||||||
|
|||||||
55
tests/test_package_manager_secure_boot.py
Normal file
55
tests/test_package_manager_secure_boot.py
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
import sys
|
||||||
|
import unittest
|
||||||
|
from pathlib import Path
|
||||||
|
from unittest.mock import Mock, patch
|
||||||
|
|
||||||
|
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "lib"))
|
||||||
|
|
||||||
|
import package_manager
|
||||||
|
from secure_boot import SecureBootResult
|
||||||
|
|
||||||
|
|
||||||
|
class SecureBootTransactionTests(unittest.TestCase):
|
||||||
|
@patch.object(package_manager.subprocess, "run")
|
||||||
|
@patch.object(package_manager, "prepare_secure_boot")
|
||||||
|
def test_aborts_before_pacman_when_preflight_fails(
|
||||||
|
self, prepare_mock, run_mock
|
||||||
|
):
|
||||||
|
prepare_mock.return_value = SecureBootResult(
|
||||||
|
success=False,
|
||||||
|
configured=True,
|
||||||
|
error="ESP indisponible",
|
||||||
|
)
|
||||||
|
|
||||||
|
result = package_manager.install_packages(["linux-cachyos"])
|
||||||
|
|
||||||
|
self.assertFalse(result.success)
|
||||||
|
self.assertIn("ESP indisponible", result.error)
|
||||||
|
run_mock.assert_not_called()
|
||||||
|
|
||||||
|
@patch.object(package_manager, "refresh_secure_boot")
|
||||||
|
@patch.object(package_manager.subprocess, "run")
|
||||||
|
@patch.object(package_manager, "prepare_secure_boot")
|
||||||
|
def test_reports_post_transaction_signing_failure(
|
||||||
|
self, prepare_mock, run_mock, refresh_mock
|
||||||
|
):
|
||||||
|
prepare_mock.return_value = SecureBootResult(
|
||||||
|
success=True,
|
||||||
|
configured=True,
|
||||||
|
)
|
||||||
|
run_mock.return_value = Mock(returncode=0, stdout="", stderr="")
|
||||||
|
refresh_mock.return_value = SecureBootResult(
|
||||||
|
success=False,
|
||||||
|
configured=True,
|
||||||
|
error="signature invalide",
|
||||||
|
)
|
||||||
|
|
||||||
|
result = package_manager.upgrade_cached_packages(["linux"])
|
||||||
|
|
||||||
|
self.assertFalse(result.success)
|
||||||
|
self.assertIn("paquets ont été installés", result.error)
|
||||||
|
self.assertIn("signature invalide", result.error)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
53
tests/test_secure_boot.py
Normal file
53
tests/test_secure_boot.py
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import unittest
|
||||||
|
from pathlib import Path
|
||||||
|
from unittest.mock import Mock, patch
|
||||||
|
|
||||||
|
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "lib"))
|
||||||
|
|
||||||
|
import secure_boot
|
||||||
|
|
||||||
|
|
||||||
|
class BootArtifactsMayChangeTests(unittest.TestCase):
|
||||||
|
def test_detects_kernel_and_bootloader_packages(self):
|
||||||
|
for name in ("linux", "linux-cachyos", "linux-zen", "grub", "shim-signed"):
|
||||||
|
with self.subTest(name=name):
|
||||||
|
self.assertTrue(secure_boot.boot_artifacts_may_change([name]))
|
||||||
|
|
||||||
|
def test_ignores_unrelated_packages(self):
|
||||||
|
self.assertFalse(
|
||||||
|
secure_boot.boot_artifacts_may_change(["firefox", "mesa", "curl"])
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class PrepareSecureBootTests(unittest.TestCase):
|
||||||
|
@patch.object(secure_boot, "SIGNER_PATH")
|
||||||
|
def test_skips_when_signer_is_not_configured(self, signer_path):
|
||||||
|
signer_path.is_file.return_value = False
|
||||||
|
result = secure_boot.prepare_secure_boot(["linux-cachyos"])
|
||||||
|
self.assertTrue(result.success)
|
||||||
|
self.assertFalse(result.configured)
|
||||||
|
|
||||||
|
@patch.object(secure_boot, "_run")
|
||||||
|
@patch.object(os, "access", return_value=True)
|
||||||
|
@patch.object(os, "geteuid", return_value=0)
|
||||||
|
@patch.object(secure_boot, "SIGNER_PATH")
|
||||||
|
def test_mounts_and_validates_esp(
|
||||||
|
self, signer_path, _geteuid, _access, run_mock
|
||||||
|
):
|
||||||
|
signer_path.is_file.return_value = True
|
||||||
|
signer_path.__str__ = Mock(return_value="/usr/local/sbin/signer")
|
||||||
|
run_mock.return_value = Mock(returncode=0, stdout="", stderr="")
|
||||||
|
|
||||||
|
result = secure_boot.prepare_secure_boot(["linux-cachyos"])
|
||||||
|
|
||||||
|
self.assertTrue(result.success)
|
||||||
|
self.assertTrue(result.configured)
|
||||||
|
run_mock.assert_called_once_with(
|
||||||
|
["/usr/local/sbin/signer", "--prepare"]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
Reference in New Issue
Block a user