🔒 Repository is read-only – file editing is disabled.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
#!/usr/bin/env python3
"""Reszta hardeningu: faillock.conf (shadow), secure_path (sudo), hooki (8)."""
import re, sys, yaml
R = "/var/lib/pagan-sync/recipes"
# ── helper 1: podmiana wartosci klucza-scalara (shadow.build) ──
def scalar_range(text, key):
m = re.search(r"(?m)^" + re.escape(key) + r":", text)
if not m:
sys.exit(f"brak klucza {key}")
i = m.end()
while i < len(text) and text[i] in " \t":
i += 1
if i < len(text) and text[i] == '"':
j = i + 1
while j < len(text):
if text[j] == "\\":
j += 2
continue
if text[j] == '"':
return i, j + 1
j += 1
if i < len(text) and text[i] in "|>":
line_start = text.rfind("\n", 0, m.start()) + 1
ind = m.start() - line_start
j = end = text.find("\n", i) + 1
while j < len(text):
nl = text.find("\n", j)
if nl == -1:
nl = len(text)
line = text[j:nl]
if line.strip() == "":
j = end = nl + 1
continue
if (len(line) - len(line.lstrip(" "))) > ind:
j = end = nl + 1
else:
break
return text.find("\n", i) + 1, end
sys.exit("nieobslugiwany styl")
def esc(v):
return '"' + v.replace("\\", "\\\\").replace('"', '\\"').replace("\n", "\\n") + '"'
def edit_scalar(path, key, old, new, label):
text = open(path, encoding="utf-8").read()
cur = (yaml.safe_load(text) or {}).get(key) or ""
if new in cur:
print(f"-- {label} (juz)")
return
if cur.count(old) != 1:
sys.exit(f"[{label}] {cur.count(old)} wystapien w {key}")
nv = cur.replace(old, new, 1)
s, e = scalar_range(text, key)
open(path, "w", encoding="utf-8").write(text[:s] + esc(nv) + text[e:])
print(f"OK {label}")
# ── helper 2: edycja tekstowa tolerujaca styl (literal / escaped) ──
def edit_text(path, old, new, label):
text = open(path, encoding="utf-8").read()
for v in (old, old.replace("\\", "\\\\").replace("\n", "\\n").replace('"', '\\"')):
if v in text:
if text.count(v) != 1:
sys.exit(f"[{label}] {text.count(v)} wystapien")
repl = new if v == old else new.replace("\\", "\\\\").replace("\n", "\\n").replace('"', '\\"')
open(path, "w", encoding="utf-8").write(text.replace(v, repl, 1))
print(f"OK {label}")
return
sys.exit(f"[{label}] nie znaleziono fragmentu")
def bump(path, label, expect=None):
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")
cur = int(m.group(1))
if expect is not None and cur != expect:
print(f"-- {label}: pkgrel juz {cur}")
return
open(path, "w", encoding="utf-8").write(text[:m.start()] + f"pkgrel: '{cur + 1}'" + text[m.end():])
print(f"OK {label}: pkgrel -> {cur + 1}")
# ── 1. shadow: /etc/security/faillock.conf ──
SH = f"{R}/core/shadow/PAGBUILD.yaml"
edit_scalar(SH, "build",
"# Pliki system-* ZOSTAJA w pakiecie",
"install -v -m755 -d ${PKGDIR}/etc/security\n"
"cat > ${PKGDIR}/etc/security/faillock.conf << \"EOF\"\n"
"# Blokada konta po nieudanych probach logowania (pam_faillock,\n"
"# uzywany przez /etc/pam.d/system-auth i system-account).\n"
"deny = 5\n"
"fail_interval = 900\n"
"unlock_time = 900\n"
"# even_deny_root nie jest ustawione - root nie zostaje zablokowany.\n"
"EOF\n"
"\n"
"# Pliki system-* ZOSTAJA w pakiecie",
"shadow: /etc/security/faillock.conf")
# ── 2. sudo: secure_path ──
SU = f"{R}/core/sudo/PAGBUILD.yaml"
edit_text(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(SU, "sudo", expect=2)
# ── 3. hooki (punkt 8) ──
BA = f"{R}/core/bash/PAGBUILD.yaml"
edit_text(BA, "rm -f bin/sh\nrm -f bin/bash\n",
"# Usuniete kasowanie /bin/sh i /bin/bash: pakiet dostarcza oba pliki,\n"
"# a gdy transakcja padala po rm, system zostawal bez powloki.\n",
"bash: bez rm /bin/sh")
bump(BA, "bash")
GL = f"{R}/core/glibc/PAGBUILD.yaml"
edit_text(GL, "rm -f /etc/nsswitch.conf\n",
"# Usuniete kasowanie /etc/nsswitch.conf: plik jest w pakiecie, a pag\n"
"# chroni zmiany uzytkownika (zapisuje .pacnew) - rm byl zbedny.\n",
"glibc: bez rm nsswitch.conf")
bump(GL, "glibc")
NG = f"{R}/utils/nginx/PAGBUILD.yaml"
edit_text(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(NG, "nginx")
PA = f"{R}/gui/pulseaudio/PAGBUILD.yaml"
edit_text(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(PA, "pulseaudio")
TI = f"{R}/gui/telepathy-idle/PAGBUILD.yaml"
edit_text(TI, "killall -HUP dbus-daemon 2>&1",
"systemctl reload dbus 2>/dev/null || true",
"telepathy-idle: reload dbus")
bump(TI, "telepathy-idle")
print("gotowe")