Files
knowledge-base/scripts/kb-audit-creds.py
dttb 1f7d265f16 audit: +kb-audit-npm/creds/dns — расширение karpathy-style
- kb-audit-npm.py: NPM API → сверка с npm-proxy-hosts.md
  детектит новые/удалённые hosts + смену backend/SSL
- kb-audit-creds.py: HEAD/GET-ping всех URL из credentials.md
  с fallback на GET при 501/405, skip embedded-creds URLs
- kb-audit-dns.py: dig @8.8.8.8 и @10.0.0.1 для всех доменов NPM
  детектит NXDOMAIN + split-horizon

Первый прогон нашёл:
- NPM: 2 новых host (router/vpn.dttb.ru), 2 изменения (bitrix24 backend, git SSL)
- Creds: все 12 URL reachable ✓
- DNS: itilegent.ru не резолвится (публичные записи протухли)
2026-04-18 12:35:21 +03:00

159 lines
5.7 KiB
Python
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""
kb-audit-creds — берёт все URL из credentials.md, пингует каждый,
фиксирует unreachable или нестандартные коды ответа.
Пишет audit/YYYY-MM-DD-creds-drift.md.
Проверка только reachability (HTTP status). Не тестирует реальный логин.
"""
import re
import ssl
import sys
import urllib.error
import urllib.request
from datetime import date
from pathlib import Path
VAULT = Path(__file__).resolve().parent.parent
CREDS = VAULT / "projects/dttb/credentials.md"
OUT_DIR = VAULT / "audit"
_CTX = ssl.create_default_context()
_CTX.check_hostname = False
_CTX.verify_mode = ssl.CERT_NONE
def extract_urls(text: str):
"""URL в обычной записи + в markdown-таблице.
Пропускаем URL с embedded credentials (http://user:pass@host) — они для git/curl,
не для reachability-check.
"""
pattern = re.compile(r"https?://[a-zA-Z0-9._:@-]+(?:/[^\s`|<>\"')]*)?")
seen = {}
for m in pattern.finditer(text):
url = m.group(0).rstrip("/.,;:)")
# пропускаем URLs с embedded creds
if re.match(r"https?://[^/]*@", url):
continue
if url not in seen:
start = max(0, m.start() - 30)
end = min(len(text), m.end() + 30)
ctx = text[start:end].replace("\n", " ").replace("|", " ").strip()
seen[url] = ctx[:80]
return seen
def _request(url: str, method: str, timeout: int):
req = urllib.request.Request(url, method=method, headers={"User-Agent": "kb-audit/1.0"})
r = urllib.request.urlopen(req, context=_CTX, timeout=timeout)
return str(r.status), r.reason or ""
def ping(url: str, timeout: int = 6) -> tuple[str, str]:
"""Возвращает (status, detail). Пробуем HEAD, при 501/405 fallback на GET."""
try:
return _request(url, "HEAD", timeout)
except urllib.error.HTTPError as e:
if e.code in (501, 405):
try:
return _request(url, "GET", timeout)
except urllib.error.HTTPError as e2:
return str(e2.code), e2.reason or ""
except Exception as e2:
return "ERR", str(e2)[:60]
return str(e.code), e.reason or ""
except urllib.error.URLError as e:
reason = str(e.reason)
if "ssl" in reason.lower() or "certificate" in reason.lower():
return "SSL", reason[:60]
return "FAIL", reason[:60]
except TimeoutError:
return "TIMEOUT", ""
except Exception as e:
return "ERR", str(e)[:60]
def classify(status: str) -> str:
"""Status → категория для отчёта."""
if status in ("200", "301", "302", "303", "307", "308"):
return "✓ reachable"
if status in ("401", "403"):
return "✓ auth-required (сервер жив)"
if status in ("404", "405"): # 405 на HEAD, 404 ок при ping host root
return "⚠ 4xx (сервер жив, но путь/метод)"
if status.startswith("5"):
return "❌ 5xx server error"
if status in ("FAIL", "ERR", "TIMEOUT", "SSL"):
return "❌ недоступен"
return f"? {status}"
def main():
today = date.today().isoformat()
OUT_DIR.mkdir(parents=True, exist_ok=True)
out = OUT_DIR / f"{today}-creds-drift.md"
text = CREDS.read_text()
urls = extract_urls(text)
if not urls:
print("no URLs found in credentials.md", file=sys.stderr)
sys.exit(1)
results = []
for url, ctx in urls.items():
status, detail = ping(url)
category = classify(status)
results.append((url, ctx, status, detail, category))
unreachable = [r for r in results if "" in r[4]]
questionable = [r for r in results if "" in r[4] or "?" in r[4]]
ok = [r for r in results if "" in r[4]]
lines = [
"---",
f"date: {today}",
"type: audit",
"source: kb-audit-creds.py",
"tags: [audit, creds, reachability]",
"---",
"",
f"# Credentials reachability — {today}",
"",
f"Ping-проверка URL из [[../projects/dttb/credentials|credentials.md]].",
f"Проверяется только reachability (HTTP status), не реальный логин.",
"",
f"- Всего URL: **{len(results)}**",
f"- ✓ Reachable: {len(ok)} / ⚠ Questionable: {len(questionable)} / ❌ Unreachable: {len(unreachable)}",
"",
]
if unreachable:
lines += ["## ❌ Недоступные (проверить: сервер упал? URL поменялся?)", "",
"| URL | Status | Detail | Контекст |", "|---|---|---|---|"]
for url, ctx, st, det, _ in unreachable:
lines.append(f"| `{url}` | {st} | {det[:40]} | {ctx[:50]} |")
lines.append("")
if questionable:
lines += ["## ⚠ Нестандартный ответ", "",
"| URL | Status | Detail |", "|---|---|---|"]
for url, ctx, st, det, _ in questionable:
lines.append(f"| `{url}` | {st} | {det[:40]} |")
lines.append("")
lines += ["## ✓ Все ответили нормально", "",
"| URL | Status | Категория |", "|---|---|---|"]
for url, ctx, st, det, cat in ok:
lines.append(f"| `{url}` | {st} | {cat} |")
lines += ["", "---", "*Автоматически через `scripts/kb-audit-creds.py`.*"]
out.write_text("\n".join(lines))
print(f"creds drift: {out}")
print(f" ok: {len(ok)} / questionable: {len(questionable)} / unreachable: {len(unreachable)}")
if __name__ == "__main__":
main()