🔒 Repository is read-only – file editing is disabled.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
#!/usr/bin/env python3
"""Receptury: PAM hardening (5), sudo secure_path (6), bezpieczniejsze hooki (8).
Idempotentny: jesli nowa tresc juz jest, pomija zmiane.
Tolerancyjny na zawijanie dlugich linii YAML (backslash + newline).
"""
import re, sys
R = "/var/lib/pagan-sync/recipes"
def variants(logical):
"""Reprezentacje tekstu w pliku: literalna i escaped (scalar w cudzyslowie)."""
out = [logical]
esc = logical.replace("\\", "\\\\").replace("\n", "\\n").replace('"', '\\"')
if esc != logical:
out.append(esc)
return out
def tolerant_pattern(logical):
"""Regex dopasowujacy tekst takze gdy YAML zawinal linie (backslash+newline)."""
parts = []
for ch in logical:
if ch == " ":
parts.append(r"(?:[ \t]|\\\n)+")
elif ch == "\n":
parts.append(r"\\n")
elif ch in '"\\':
parts.append(r"\\?" + re.escape(ch))
else:
parts.append(re.escape(ch))
return re.compile("".join(parts))
def applied(text, new):
return any(v in text for v in variants(new))
def edit(path, old, new, label):
text = open(path, encoding="utf-8").read()
if applied(text, new):
print(f"-- {label} (juz zastosowane)")
return
done = False
for v in variants(old):
if text.count(v) == 1:
text = text.replace(v, new if v == old else variants(new)[-1], 1)
done = True
break
if not done:
m = tolerant_pattern(old)
hits = list(m.finditer(text))
if len(hits) != 1:
sys.exit(f"[{label}] {path}: dopasowan {len(hits)} (oczekiwano 1)")
h = hits[0]
text = text[:h.start()] + new + text[h.end():]
open(path, "w", encoding="utf-8").write(text)
print(f"OK {label}")
def bump_pkgrel(path, label):
text = open(path, encoding="utf-8").read()
m = re.search(r"(?m)^pkgrel:\s*'?(\d+)'?\s*$", text)
if not m:
sys.exit(f"[{label}] brak pkgrel")
nr = int(m.group(1)) + 1
text = text[:m.start()] + f"pkgrel: '{nr}'" + text[m.end():]
open(path, "w", encoding="utf-8").write(text)
print(f"OK {label}: pkgrel -> {nr}")
# ══════════════ 5. PAM: faillock + pwquality ══════════════
SH = f"{R}/core/shadow/PAGBUILD.yaml"
edit(SH, "depends: []",
"depends:\n- pam\n- libxcrypt\n- libpwquality",
"shadow: depends")
edit(SH, "s@#ENCRYPT_METHOD DES@ENCRYPT_METHOD SHA512@",
"s@^#ENCRYPT_METHOD .*@ENCRYPT_METHOD SHA512@",
"shadow: ENCRYPT_METHOD SHA512")
edit(SH,
"# Begin /etc/pam.d/system-account\n\naccount required pam_unix.so\n",
"# Begin /etc/pam.d/system-account\n\n"
"account required pam_unix.so\n"
"account required pam_faillock.so\n",
"shadow: system-account + faillock")
edit(SH,
"# Begin /etc/pam.d/system-auth\n\nauth required pam_unix.so\n",
"# Begin /etc/pam.d/system-auth\n\n"
"# Blokada konta po nieudanych probach logowania. Parametry (deny,\n"
"# unlock_time) ustawia /etc/security/faillock.conf; root nie jest objety.\n"
"auth required pam_faillock.so preauth\n"
"auth [success=1 default=ignore] pam_unix.so\n"
"auth [default=die] pam_faillock.so authfail\n"
"auth sufficient pam_faillock.so authsucc\n",
"shadow: system-auth + faillock")
edit(SH,
"password required pam_pwhistory.so retry=3\n",
"# Jakosc hasel - parametry w /etc/security/pwquality.conf.\n"
"# Pamiec: ostatnie 24 hasla.\n"
"password required pam_pwquality.so retry=3\n"
"password required pam_pwhistory.so remember=24 retry=3\n",
"shadow: system-password + pwquality")
edit(SH,
"rm -f ${PKGDIR}/etc/pam.d/system-auth ${PKGDIR}/etc/pam.d/system-account "
"${PKGDIR}/etc/pam.d/system-login ${PKGDIR}/etc/pam.d/system-password "
"${PKGDIR}/etc/pam.d/system-session",
"# Pliki system-* ZOSTAJA w pakiecie - wczesniej byly usuwane i pozostawaly\n"
"# niezarzadzane, przez co zmiany w PAM nie docieraly do systemu.",
"shadow: system-* zarzadzane")
bump_pkgrel(SH, "shadow")
# ══════════════ 6. sudo: secure_path ══════════════
SU = f"{R}/core/sudo/PAGBUILD.yaml"
edit(SU,
'echo "%wheel ALL=(ALL) ALL" >> ${PKGDIR}/etc/sudoers',
'echo "Defaults secure_path=\\"/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\\"" >> ${PKGDIR}/etc/sudoers\n'
'echo "%wheel ALL=(ALL) ALL" >> ${PKGDIR}/etc/sudoers',
"sudo: secure_path")
bump_pkgrel(SU, "sudo")
# ══════════════ 8. hooki ══════════════
BA = f"{R}/core/bash/PAGBUILD.yaml"
edit(BA, "rm -f bin/sh\nrm -f bin/bash\n",
"# Usuniete kasowanie /bin/sh i /bin/bash: pakiet dostarcza oba pliki, a gdy\n"
"# transakcja padala po rm, system zostawal bez powloki.\n",
"bash: bez rm /bin/sh")
bump_pkgrel(BA, "bash")
GL = f"{R}/core/glibc/PAGBUILD.yaml"
edit(GL, "rm -f /etc/nsswitch.conf\n",
"# Usuniete kasowanie /etc/nsswitch.conf: plik jest w pakiecie, a pag chroni\n"
"# zmiany uzytkownika (zapisuje .pacnew) - rm byl zbedny i ryzykowny.\n",
"glibc: bez rm nsswitch.conf")
bump_pkgrel(GL, "glibc")
NG = f"{R}/utils/nginx/PAGBUILD.yaml"
edit(NG,
"(getent passwd $UN > /dev/null) && userdel $UN\n"
"(getent group $UN > /dev/null) && groupdel $UN\n\n"
"UN=apache\n\n"
"(getent passwd $UN > /dev/null) && userdel $UN\n"
"(getent group $UN > /dev/null) && groupdel $UN\n",
"# Usuniete kasowanie uzytkownikow www/apache: userdel zostawial pliki z\n"
"# osieroconym uid. Grupa i uzytkownik http sa tworzone ponizej.\n",
"nginx: bez userdel")
bump_pkgrel(NG, "nginx")
PA = f"{R}/gui/pulseaudio/PAGBUILD.yaml"
edit(PA,
"grep -qe 'autospawn = no' etc/pulse/client.conf||sudo sed '/autospawn/iautospawn = no' -i etc/pulse/client.conf",
"grep -qe 'autospawn = no' etc/pulse/client.conf || sed -i '/autospawn/iautospawn = no' etc/pulse/client.conf",
"pulseaudio: bez sudo")
bump_pkgrel(PA, "pulseaudio")
TI = f"{R}/gui/telepathy-idle/PAGBUILD.yaml"
edit(TI, "killall -HUP dbus-daemon 2>&1",
"systemctl reload dbus 2>/dev/null || true",
"telepathy-idle: reload dbus")
bump_pkgrel(TI, "telepathy-idle")
print("gotowe")