Compare commits
3 Commits
b13fa7dc05
...
876f728aa2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
876f728aa2 | ||
|
|
950a6dabd8 | ||
|
|
1d9589a186 |
@@ -2963,15 +2963,42 @@ async def api_security_status():
|
|||||||
return {"status": status, "warning": warning}
|
return {"status": status, "warning": warning}
|
||||||
|
|
||||||
|
|
||||||
|
def _is_free_password_default() -> bool:
|
||||||
|
"""Check /etc/shadow directly to see if 'free' still has a factory default password.
|
||||||
|
|
||||||
|
Hashes each known factory default against the current shadow hash so that
|
||||||
|
password changes made via GNOME, passwd, or any method other than the Hub
|
||||||
|
are detected correctly.
|
||||||
|
"""
|
||||||
|
import crypt # available in Python stdlib (deprecated in 3.13 but present on NixOS)
|
||||||
|
|
||||||
|
FACTORY_DEFAULTS = ["free", "gosovransystems"]
|
||||||
|
try:
|
||||||
|
with open("/etc/shadow", "r") as f:
|
||||||
|
for line in f:
|
||||||
|
parts = line.strip().split(":")
|
||||||
|
if parts[0] == "free" and len(parts) > 1:
|
||||||
|
current_hash = parts[1]
|
||||||
|
if not current_hash or current_hash in ("!", "*", "!!"):
|
||||||
|
return True # locked/no password — treat as default
|
||||||
|
for default_pw in FACTORY_DEFAULTS:
|
||||||
|
if crypt.crypt(default_pw, current_hash) == current_hash:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
except (FileNotFoundError, PermissionError):
|
||||||
|
pass
|
||||||
|
return True # if /etc/shadow is unreadable, assume default for safety
|
||||||
|
|
||||||
|
|
||||||
@app.get("/api/security/password-is-default")
|
@app.get("/api/security/password-is-default")
|
||||||
async def api_password_is_default():
|
async def api_password_is_default():
|
||||||
"""Check if the free account password is still the factory default."""
|
"""Check if the free account password is still the factory default.
|
||||||
try:
|
|
||||||
with open("/var/lib/secrets/free-password", "r") as f:
|
Uses /etc/shadow as the authoritative source so that password changes made
|
||||||
current = f.read().strip()
|
via GNOME Settings, the passwd command, or any other method are detected
|
||||||
return {"is_default": current == "free"}
|
correctly — not just changes made through the Hub or change-free-password.
|
||||||
except FileNotFoundError:
|
"""
|
||||||
return {"is_default": True}
|
return {"is_default": _is_free_password_default()}
|
||||||
|
|
||||||
|
|
||||||
# ── System password change ────────────────────────────────────────
|
# ── System password change ────────────────────────────────────────
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ in
|
|||||||
Type = "oneshot";
|
Type = "oneshot";
|
||||||
RemainAfterExit = true;
|
RemainAfterExit = true;
|
||||||
};
|
};
|
||||||
path = [ pkgs.coreutils ];
|
path = [ pkgs.coreutils pkgs.python3 ];
|
||||||
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
|
||||||
@@ -119,9 +119,25 @@ EOF
|
|||||||
# If the user completed Hub onboarding, they've addressed security
|
# If the user completed Hub onboarding, they've addressed security
|
||||||
[ -f /var/lib/sovran/onboarding-complete ] && exit 0
|
[ -f /var/lib/sovran/onboarding-complete ] && exit 0
|
||||||
|
|
||||||
# If the free password has been changed from the factory default, no warning needed
|
# If the free password has been changed from ALL known factory defaults, no warning needed
|
||||||
if [ -f /var/lib/secrets/free-password ]; then
|
if [ -f /etc/shadow ]; then
|
||||||
[ "$(cat /var/lib/secrets/free-password)" != "free" ] && exit 0
|
FREE_HASH=$(grep '^free:' /etc/shadow | cut -d: -f2)
|
||||||
|
if [ -n "$FREE_HASH" ] && [ "$FREE_HASH" != "!" ] && [ "$FREE_HASH" != "*" ]; then
|
||||||
|
STILL_DEFAULT=false
|
||||||
|
for DEFAULT_PW in "free" "gosovransystems"; do
|
||||||
|
EXPECTED=$(DEFAULT_PW="$DEFAULT_PW" FREE_HASH="$FREE_HASH" python3 -c \
|
||||||
|
"import crypt, os; print(crypt.crypt(os.environ['DEFAULT_PW'], os.environ['FREE_HASH']))")
|
||||||
|
if [ "$EXPECTED" = "$FREE_HASH" ]; then
|
||||||
|
STILL_DEFAULT=true
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
if [ "$STILL_DEFAULT" = "false" ]; then
|
||||||
|
# Password was changed — clear any legacy warning and exit
|
||||||
|
rm -f /var/lib/sovran/security-status /var/lib/sovran/security-warning
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# No flags at all + secrets exist = legacy (pre-seal era) machine
|
# No flags at all + secrets exist = legacy (pre-seal era) machine
|
||||||
|
|||||||
Reference in New Issue
Block a user