🔒 Repository is read-only – file editing is disabled.

PaganLinux/tmp-patch-recipes-hardening.py main

169 linii Raw ← Powrót
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")