_lsy-repo-keyscan.py 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. # -*- coding: utf-8 -*-
  2. """全库体检:机构密钥有没有出现在**任何 git 跟踪文件**里(不限 logs/)。
  3. 为什么要有它:_lsy-key-scan.py 只扫 logs/ 下的 _lsy* 产物 —— 那是「我准备提交什么」的量具。
  4. 但「仓库里有没有这个值」是另一个问题,射程不同:本探针扫 git ls-files 的**全部 1233 个**跟踪
  5. 文件的工作区版本,外加 HEAD:application.yml(索引/提交里那一份)。
  6. 两个量具都留着:前者窄而针对提交动作,后者宽而针对仓库状态。
  7. 只打:密钥长度、密钥 sha256 前 16 位(不可逆指纹)、文件名、命中次数、**命中行的字段名**
  8. (该行里的密钥整段替换成 <KEY>)。绝不打印密钥本身或其任何子串。
  9. 正对照:工作区的 application.yml 必须命中 ≥1 次,否则自判「探针失效」并以非零退出。
  10. 用法:
  11. python logs/_lsy-repo-keyscan.py # 计数版(默认,打在 stdout)
  12. python logs/_lsy-repo-keyscan.py --ctx # 定位版:每个命中处的整行(密钥已替换)
  13. """
  14. import io
  15. import os
  16. import re
  17. import subprocess
  18. import sys
  19. from hashlib import sha256
  20. YML = "java/src/main/resources/application.yml"
  21. VAR = re.compile(r"^\$\{([A-Za-z0-9_]+):(.*)\}$")
  22. def read_block(path, top):
  23. lines = io.open(path, encoding="utf-8", errors="replace").read().split("\n")
  24. start = None
  25. for i, ln in enumerate(lines):
  26. if re.match(r"^%s:\s*$" % re.escape(top), ln):
  27. start = i
  28. break
  29. if start is None:
  30. raise SystemExit("找不到顶层段 %s:" % top)
  31. out = {}
  32. for j in range(start + 1, len(lines)):
  33. ln = lines[j]
  34. if ln.strip() == "" or ln.lstrip().startswith("#"):
  35. continue
  36. if not ln.startswith((" ", "\t")):
  37. break
  38. m = re.match(r"^\s+([A-Za-z0-9_.\-]+):\s*(.*)$", ln)
  39. if m:
  40. out[m.group(1)] = re.split(r"\s+#", m.group(2), maxsplit=1)[0].strip().strip("'\"")
  41. return out
  42. cfg = read_block(YML, "leshouying")
  43. raw = cfg.get("key", "")
  44. m = VAR.match(raw)
  45. KEY = (os.environ.get(m.group(1)) or m.group(2)) if m else raw
  46. needle = KEY.encode("utf-8")
  47. print("密钥长度 = %d, sha256[:16] = %s(只打长度与不可逆指纹)"
  48. % (len(KEY), sha256(needle).hexdigest()[:16]))
  49. pos = io.open(YML, "rb").read().count(needle)
  50. print("正对照 工作区 application.yml 命中 = %d %s"
  51. % (pos, "OK" if pos >= 1 else "!! 探针失效,体检作废"))
  52. if pos < 1:
  53. raise SystemExit(1)
  54. tracked = subprocess.run(["git", "ls-files", "-z"], capture_output=True).stdout.split(b"\0")
  55. tracked = [t.decode("utf-8", "replace") for t in tracked if t]
  56. bad, missing = [], 0
  57. for f in tracked:
  58. try:
  59. c = io.open(f, "rb").read().count(needle)
  60. except Exception:
  61. missing += 1
  62. continue
  63. if c:
  64. bad.append((f, c))
  65. print("扫描 git 跟踪文件数 = %d(其中读不到 %d)" % (len(tracked), missing))
  66. print("工作区版本含密钥的文件数 = %d" % len(bad))
  67. for f, c in bad:
  68. print(" !! %s 命中 %d 次" % (f, c))
  69. head = subprocess.run(["git", "show", "HEAD:java/src/main/resources/application.yml"],
  70. capture_output=True).stdout
  71. print("--- HEAD:application.yml ---")
  72. print("bytes = %d, 命中 = %d" % (len(head), head.count(needle)))
  73. if "--ctx" not in sys.argv:
  74. raise SystemExit(0)
  75. # 定位版:把每个命中处的**整行**打出来(该行里的密钥替换成 <KEY>)
  76. print()
  77. print("=== 命中行定位(密钥已整段替换成 <KEY>)===")
  78. for f, _c in bad:
  79. if f == YML:
  80. continue # 工作区那份单独打在下面
  81. blob = io.open(f, "rb").read()
  82. print("--- %s (%d B)" % (f, len(blob)))
  83. start, k = 0, 0
  84. while True:
  85. i = blob.find(needle, start)
  86. if i < 0:
  87. break
  88. k += 1
  89. ls = blob.rfind(b"\n", 0, i) + 1
  90. le = blob.find(b"\n", i)
  91. if le < 0:
  92. le = len(blob)
  93. line = blob[ls:le].decode("utf-8", "replace").replace(KEY, "<KEY>")
  94. print(" #%d 第 %d 行: %s" % (k, blob.count(b"\n", 0, ls) + 1, line.strip()))
  95. start = i + len(needle)
  96. for label, blob in (("工作区 application.yml", io.open(YML, "rb").read()),
  97. ("HEAD:application.yml", head)):
  98. i = blob.find(needle)
  99. if i < 0:
  100. continue
  101. ls = blob.rfind(b"\n", 0, i) + 1
  102. le = blob.find(b"\n", i)
  103. if le < 0:
  104. le = len(blob)
  105. print("--- %s 第 %d 行: %s"
  106. % (label, blob.count(b"\n", 0, ls) + 1,
  107. blob[ls:le].decode("utf-8", "replace").replace(KEY, "<KEY>").strip()))