Compare commits
3 Commits
bc5a40f143
...
6ffcc056ad
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6ffcc056ad | ||
|
|
742f680d0d | ||
|
|
c872f1c6b0 |
@@ -2970,9 +2970,13 @@ def _is_free_password_default() -> bool:
|
|||||||
password changes made via GNOME, passwd, or any method other than the Hub
|
password changes made via GNOME, passwd, or any method other than the Hub
|
||||||
are detected correctly.
|
are detected correctly.
|
||||||
"""
|
"""
|
||||||
import crypt # available in Python stdlib (deprecated in 3.13 but present on NixOS)
|
import subprocess
|
||||||
|
import re as _re
|
||||||
|
|
||||||
FACTORY_DEFAULTS = ["free", "gosovransystems"]
|
FACTORY_DEFAULTS = ["free", "gosovransystems"]
|
||||||
|
# Map shadow algorithm IDs to openssl passwd flags (SHA-512 and SHA-256 only,
|
||||||
|
# matching the shell-script counterpart in factory-seal.nix)
|
||||||
|
ALGO_FLAGS = {"6": "-6", "5": "-5"}
|
||||||
try:
|
try:
|
||||||
with open("/etc/shadow", "r") as f:
|
with open("/etc/shadow", "r") as f:
|
||||||
for line in f:
|
for line in f:
|
||||||
@@ -2981,9 +2985,34 @@ def _is_free_password_default() -> bool:
|
|||||||
current_hash = parts[1]
|
current_hash = parts[1]
|
||||||
if not current_hash or current_hash in ("!", "*", "!!"):
|
if not current_hash or current_hash in ("!", "*", "!!"):
|
||||||
return True # locked/no password — treat as default
|
return True # locked/no password — treat as default
|
||||||
|
# Parse hash: $id$[rounds=N$]salt$hash
|
||||||
|
hash_fields = current_hash.split("$")
|
||||||
|
# hash_fields: ["", id, salt_or_rounds, ...]
|
||||||
|
if len(hash_fields) < 4:
|
||||||
|
return True # unrecognized format — assume default for safety
|
||||||
|
algo_id = hash_fields[1]
|
||||||
|
salt_field = hash_fields[2]
|
||||||
|
if algo_id not in ALGO_FLAGS:
|
||||||
|
return True # unrecognized algorithm — assume default for safety
|
||||||
|
if salt_field.startswith("rounds="):
|
||||||
|
return True # can't extract real salt simply — assume default for safety
|
||||||
|
# Validate salt contains only safe characters (alphanumeric, '.', '/', '-', '_')
|
||||||
|
# to guard against unexpected shadow file content before passing to subprocess
|
||||||
|
if not _re.fullmatch(r"[A-Za-z0-9./\-_]+", salt_field):
|
||||||
|
return True # unexpected salt format — assume default for safety
|
||||||
|
openssl_flag = ALGO_FLAGS[algo_id]
|
||||||
for default_pw in FACTORY_DEFAULTS:
|
for default_pw in FACTORY_DEFAULTS:
|
||||||
if crypt.crypt(default_pw, current_hash) == current_hash:
|
try:
|
||||||
return True
|
result = subprocess.run(
|
||||||
|
["openssl", "passwd", openssl_flag, "-salt", salt_field, default_pw],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
timeout=5,
|
||||||
|
)
|
||||||
|
if result.returncode == 0 and result.stdout.strip() == current_hash:
|
||||||
|
return True
|
||||||
|
except Exception:
|
||||||
|
return True # if openssl fails, assume default for safety
|
||||||
return False
|
return False
|
||||||
except (FileNotFoundError, PermissionError):
|
except (FileNotFoundError, PermissionError):
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ in
|
|||||||
Type = "oneshot";
|
Type = "oneshot";
|
||||||
RemainAfterExit = true;
|
RemainAfterExit = true;
|
||||||
};
|
};
|
||||||
path = [ pkgs.coreutils pkgs.e2fsprogs pkgs.python3 pkgs.postgresql pkgs.mariadb pkgs.shadow ];
|
path = [ pkgs.coreutils pkgs.e2fsprogs pkgs.openssl pkgs.postgresql pkgs.mariadb pkgs.shadow ];
|
||||||
script = ''
|
script = ''
|
||||||
# ── Idempotency check ─────────────────────────────────────────
|
# ── Idempotency check ─────────────────────────────────────────
|
||||||
if [ -f /var/lib/sovran-factory-sealed ]; then
|
if [ -f /var/lib/sovran-factory-sealed ]; then
|
||||||
@@ -129,15 +129,30 @@ in
|
|||||||
if [ -f /etc/shadow ]; then
|
if [ -f /etc/shadow ]; then
|
||||||
FREE_HASH=$(grep '^free:' /etc/shadow | cut -d: -f2)
|
FREE_HASH=$(grep '^free:' /etc/shadow | cut -d: -f2)
|
||||||
if [ -n "$FREE_HASH" ] && [ "$FREE_HASH" != "!" ] && [ "$FREE_HASH" != "*" ]; then
|
if [ -n "$FREE_HASH" ] && [ "$FREE_HASH" != "!" ] && [ "$FREE_HASH" != "*" ]; then
|
||||||
|
ALGO_ID=$(printf '%s' "$FREE_HASH" | cut -d'$' -f2)
|
||||||
|
SALT=$(printf '%s' "$FREE_HASH" | cut -d'$' -f3)
|
||||||
STILL_DEFAULT=false
|
STILL_DEFAULT=false
|
||||||
for DEFAULT_PW in "free" "gosovransystems"; do
|
# If the salt field starts with "rounds=", we cannot extract the real salt
|
||||||
EXPECTED=$(DEFAULT_PW="$DEFAULT_PW" FREE_HASH="$FREE_HASH" python3 -c \
|
# with a simple cut — treat as still-default for safety
|
||||||
"import crypt, os; print(crypt.crypt(os.environ['DEFAULT_PW'], os.environ['FREE_HASH']))")
|
if printf '%s' "$SALT" | grep -q '^rounds='; then
|
||||||
if [ "$EXPECTED" = "$FREE_HASH" ]; then
|
STILL_DEFAULT=true
|
||||||
STILL_DEFAULT=true
|
else
|
||||||
break
|
for DEFAULT_PW in "free" "gosovransystems"; do
|
||||||
fi
|
case "$ALGO_ID" in
|
||||||
done
|
6) EXPECTED=$(openssl passwd -6 -salt "$SALT" "$DEFAULT_PW" 2>/dev/null) ;;
|
||||||
|
5) EXPECTED=$(openssl passwd -5 -salt "$SALT" "$DEFAULT_PW" 2>/dev/null) ;;
|
||||||
|
*)
|
||||||
|
# Unknown hash algorithm — treat as still-default for safety
|
||||||
|
STILL_DEFAULT=true
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
if [ -n "$EXPECTED" ] && [ "$EXPECTED" = "$FREE_HASH" ]; then
|
||||||
|
STILL_DEFAULT=true
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
if [ "$STILL_DEFAULT" = "false" ]; then
|
if [ "$STILL_DEFAULT" = "false" ]; then
|
||||||
echo "sovran-auto-seal: password has been changed from factory defaults — live system detected. Restoring flag and exiting."
|
echo "sovran-auto-seal: password has been changed from factory defaults — live system detected. Restoring flag and exiting."
|
||||||
touch /var/lib/sovran-factory-sealed
|
touch /var/lib/sovran-factory-sealed
|
||||||
@@ -209,7 +224,7 @@ in
|
|||||||
Type = "oneshot";
|
Type = "oneshot";
|
||||||
RemainAfterExit = true;
|
RemainAfterExit = true;
|
||||||
};
|
};
|
||||||
path = [ pkgs.coreutils pkgs.python3 ];
|
path = [ pkgs.coreutils pkgs.openssl ];
|
||||||
script = ''
|
script = ''
|
||||||
# If sealed AND onboarded — fully clean, nothing to do
|
# If sealed AND onboarded — fully clean, nothing to do
|
||||||
[ -f /var/lib/sovran-factory-sealed ] && [ -f /var/lib/sovran-customer-onboarded ] && exit 0
|
[ -f /var/lib/sovran-factory-sealed ] && [ -f /var/lib/sovran-customer-onboarded ] && exit 0
|
||||||
@@ -234,15 +249,30 @@ EOF
|
|||||||
if [ -f /etc/shadow ]; then
|
if [ -f /etc/shadow ]; then
|
||||||
FREE_HASH=$(grep '^free:' /etc/shadow | cut -d: -f2)
|
FREE_HASH=$(grep '^free:' /etc/shadow | cut -d: -f2)
|
||||||
if [ -n "$FREE_HASH" ] && [ "$FREE_HASH" != "!" ] && [ "$FREE_HASH" != "*" ]; then
|
if [ -n "$FREE_HASH" ] && [ "$FREE_HASH" != "!" ] && [ "$FREE_HASH" != "*" ]; then
|
||||||
|
ALGO_ID=$(printf '%s' "$FREE_HASH" | cut -d'$' -f2)
|
||||||
|
SALT=$(printf '%s' "$FREE_HASH" | cut -d'$' -f3)
|
||||||
STILL_DEFAULT=false
|
STILL_DEFAULT=false
|
||||||
for DEFAULT_PW in "free" "gosovransystems"; do
|
# If the salt field starts with "rounds=", we cannot extract the real salt
|
||||||
EXPECTED=$(DEFAULT_PW="$DEFAULT_PW" FREE_HASH="$FREE_HASH" python3 -c \
|
# with a simple cut — treat as still-default for safety
|
||||||
"import crypt, os; print(crypt.crypt(os.environ['DEFAULT_PW'], os.environ['FREE_HASH']))")
|
if printf '%s' "$SALT" | grep -q '^rounds='; then
|
||||||
if [ "$EXPECTED" = "$FREE_HASH" ]; then
|
STILL_DEFAULT=true
|
||||||
STILL_DEFAULT=true
|
else
|
||||||
break
|
for DEFAULT_PW in "free" "gosovransystems"; do
|
||||||
fi
|
case "$ALGO_ID" in
|
||||||
done
|
6) EXPECTED=$(openssl passwd -6 -salt "$SALT" "$DEFAULT_PW" 2>/dev/null) ;;
|
||||||
|
5) EXPECTED=$(openssl passwd -5 -salt "$SALT" "$DEFAULT_PW" 2>/dev/null) ;;
|
||||||
|
*)
|
||||||
|
# Unknown hash algorithm — treat as still-default for safety
|
||||||
|
STILL_DEFAULT=true
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
if [ -n "$EXPECTED" ] && [ "$EXPECTED" = "$FREE_HASH" ]; then
|
||||||
|
STILL_DEFAULT=true
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
if [ "$STILL_DEFAULT" = "false" ]; then
|
if [ "$STILL_DEFAULT" = "false" ]; then
|
||||||
# Password was changed — clear any legacy warning and exit
|
# Password was changed — clear any legacy warning and exit
|
||||||
rm -f /var/lib/sovran/security-status /var/lib/sovran/security-warning
|
rm -f /var/lib/sovran/security-status /var/lib/sovran/security-warning
|
||||||
|
|||||||
Reference in New Issue
Block a user