The Hub, GDM and sudo now share /etc/shadow as the single source of
truth. /var/lib/secrets/{free,root}-password become root-only display
copies, written only by system_password.py and verified against shadow
before being shown. The GNOME login keyring is decoupled from the login
password (blank master password, pam_gnome_keyring off), so nothing
needs re-keying or wiping when the password changes and the keyring
never pops an unlock prompt.
- add app/sovran_systemsos_web/system_password.py: the single module
that verifies / sets / displays the free + root passwords and drives
the legacy-migration onboarding step (same endpoints and UI)
- add modules/password-model.nix: PAM gnome-keyring off, blank
login.keyring seed with a one-time migration of old encrypted
keyrings, first-boot-only password application (no more boot-revert),
LIBCRYPT for the Hub
- server.py: login verifies against shadow and heals stale display
copies; change-password takes the current password; security reset
sets both passwords through the same writer (root is shown as a
password again, not a hash); all keyring wipes removed
- credentials.nix: drop root-password-setup, free-password-setup and
free-password-migration (replaced by password-model.nix) and the
keyring wipe in change-free-password
- configuration.nix: drop enableGnomeKeyring and the keyring tmpfiles
hack (replaced by password-model.nix)
- service-detail.js: "Current password" field; credentials changed
outside the Hub render with a hint instead of a stale value
Installed machines keep their exact passwords: the first-boot services
no-op once an account has a real hash, and the upgrade never writes
/etc/shadow. The legacy-migration onboarding step and "reset all
passwords" keep working.
77 lines
2.9 KiB
Nix
77 lines
2.9 KiB
Nix
{ config, pkgs, lib, ... }:
|
|
|
|
let
|
|
# ── Helper: change 'free' password and save it ─────────────
|
|
change-free-password = pkgs.writeShellScriptBin "change-free-password" ''
|
|
set -euo pipefail
|
|
SECRET_FILE="/var/lib/secrets/free-password"
|
|
|
|
if [ "$(id -u)" -ne 0 ]; then
|
|
echo "Error: must be run as root (use sudo)." >&2
|
|
exit 1
|
|
fi
|
|
|
|
echo -n "New password for free: "
|
|
read -rs NEW_PASS
|
|
echo
|
|
echo -n "Confirm password: "
|
|
read -rs CONFIRM
|
|
echo
|
|
|
|
if [ "$NEW_PASS" != "$CONFIRM" ]; then
|
|
echo "Passwords do not match." >&2
|
|
exit 1
|
|
fi
|
|
|
|
if [ -z "$NEW_PASS" ]; then
|
|
echo "Password cannot be empty." >&2
|
|
exit 1
|
|
fi
|
|
|
|
echo "free:$NEW_PASS" | ${pkgs.shadow}/bin/chpasswd
|
|
mkdir -p /var/lib/secrets
|
|
echo "$NEW_PASS" > "$SECRET_FILE"
|
|
chmod 600 "$SECRET_FILE"
|
|
echo "Password for 'free' updated and saved."
|
|
'';
|
|
in
|
|
{
|
|
# ── Make helper available system-wide ───────────────────────
|
|
environment.systemPackages = [ change-free-password ];
|
|
|
|
# ── Shell aliases: intercept 'passwd free' ─────────────────
|
|
programs.bash.interactiveShellInit = ''
|
|
passwd() {
|
|
if [ "$1" = "free" ]; then
|
|
echo ""
|
|
echo "╔══════════════════════════════════════════════════════╗"
|
|
echo "║ ⚠ Use 'sudo change-free-password' instead. ║"
|
|
echo "║ ║"
|
|
echo "║ 'passwd free' only updates /etc/shadow. ║"
|
|
echo "║ The Hub credentials view will NOT be updated. ║"
|
|
echo "╚══════════════════════════════════════════════════════╝"
|
|
echo ""
|
|
return 1
|
|
fi
|
|
command passwd "$@"
|
|
}
|
|
'';
|
|
|
|
programs.fish.interactiveShellInit = ''
|
|
function passwd --wraps passwd
|
|
if test "$argv[1]" = "free"
|
|
echo ""
|
|
echo "╔══════════════════════════════════════════════════════╗"
|
|
echo "║ ⚠ Use 'sudo change-free-password' instead. ║"
|
|
echo "║ ║"
|
|
echo "║ 'passwd free' only updates /etc/shadow. ║"
|
|
echo "║ The Hub credentials view will NOT be updated. ║"
|
|
echo "╚════════════════════════════��═════════════════════════╝"
|
|
echo ""
|
|
return 1
|
|
end
|
|
command passwd $argv
|
|
end
|
|
'';
|
|
}
|