commit 3c8b011ed44e5712ec151552997bb252dfbfbbc6 Author: chest Date: Sun Nov 30 00:12:48 2025 +0300 init commit diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/CVExplorer.iml b/.idea/CVExplorer.iml new file mode 100644 index 0000000..8c182fa --- /dev/null +++ b/.idea/CVExplorer.iml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..047c58f --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..974faf3 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/main_script.py b/main_script.py new file mode 100644 index 0000000..44ca63b --- /dev/null +++ b/main_script.py @@ -0,0 +1,162 @@ +import docker +import re +from typing import List, Tuple, Optional, Dict + +SAFE_VERSIONS: Dict[str, str] = { #Потом тут будет взятие версий и CVE из базы CVE + "nginx": "1.25.4", + "redis": "7.2.4", + "postgres": "16.2", + "mysql": "8.0.36", +} + + +def parse_image_ref(image_ref: str) -> Tuple[str, str]: + if ":" in image_ref: + repo, tag = image_ref.rsplit(":", 1) + else: + repo, tag = image_ref, "latest" + name = repo.split("/")[-1] + return name, tag + + +def extract_version_from_tag(tag: str) -> Optional[str]: + tag = tag.strip() + if tag in ("latest", "", ""): + return None + base = re.split(r"[^0-9.]", tag)[0] + if not base or not re.search(r"\d", base): + return None + return base + + +def version_tuple(v: str) -> Tuple[int, ...]: + parts = v.split(".") + nums: List[int] = [] + for p in parts: + if p.isdigit(): + nums.append(int(p)) + else: + # На всякий случай вытащим ведущие цифры + m = re.match(r"(\d+)", p) + if m: + nums.append(int(m.group(1))) + return tuple(nums) + + +def compare_versions(v1: str, v2: str) -> int: + + t1 = version_tuple(v1) + t2 = version_tuple(v2) + max_len = max(len(t1), len(t2)) + t1 += (0,) * (max_len - len(t1)) + t2 += (0,) * (max_len - len(t2)) + if t1 < t2: + return -1 + if t1 > t2: + return 1 + return 0 + + +def detect_service(name: str) -> Optional[str]: + lname = name.lower() + for service in SAFE_VERSIONS.keys(): + if service in lname: + return service + return None + + +def main() -> None: + client = docker.from_env() + containers = client.containers.list(all=True) + + rows = [] + for c in containers: + image_tags = c.image.tags + if image_tags: + image_ref = image_tags[0] + else: + image_ref = c.attrs["Config"]["Image"] + + service_name_raw, tag = parse_image_ref(image_ref) + service = detect_service(service_name_raw) + version = extract_version_from_tag(tag) + + status = "UNKNOWN" + note = "" + + if service is None: + status = "SKIP" + note = "Неизвестный сервис (не в списке SAFE_VERSIONS)" + elif version is None: + status = "UNKNOWN" + note = "Не удалось определить версию из тега" + else: + safe_ver = SAFE_VERSIONS.get(service) + if safe_ver is None: + status = "UNKNOWN" + note = "Нет эталонной версии для сравнения" + else: + cmp_res = compare_versions(version, safe_ver) + if cmp_res < 0: + status = "OUTDATED" + note = f"Версия ниже безопасной ({safe_ver})" + elif cmp_res == 0: + status = "SAFE" + note = "Соответствует безопасной версии" + else: + status = "NEWER" + note = f"Версия выше эталонной ({safe_ver})" + + rows.append( + { + "container": c.name, + "image": image_ref, + "service": service or "-", + "version": version or "-", + "status": status, + "note": note, + } + ) + + headers = ["CONTAINER", "IMAGE", "SERVICE", "VERSION", "STATUS", "NOTE"] + col_widths = {h: len(h) for h in headers} + + for r in rows: + col_widths["CONTAINER"] = max(col_widths["CONTAINER"], len(r["container"])) + col_widths["IMAGE"] = max(col_widths["IMAGE"], len(r["image"])) + col_widths["SERVICE"] = max(col_widths["SERVICE"], len(str(r["service"]))) + col_widths["VERSION"] = max(col_widths["VERSION"], len(str(r["version"]))) + col_widths["STATUS"] = max(col_widths["STATUS"], len(r["status"])) + col_widths["NOTE"] = max(col_widths["NOTE"], len(r["note"])) + + header_line = " ".join(h.ljust(col_widths[h]) for h in headers) + sep_line = " ".join("-" * col_widths[h] for h in headers) + print(header_line) + print(sep_line) + + for r in rows: + line = " ".join( + [ + r["container"].ljust(col_widths["CONTAINER"]), + r["image"].ljust(col_widths["IMAGE"]), + str(r["service"]).ljust(col_widths["SERVICE"]), + str(r["version"]).ljust(col_widths["VERSION"]), + r["status"].ljust(col_widths["STATUS"]), + r["note"].ljust(col_widths["NOTE"]), + ] + ) + print(line) + + total = len(rows) + outdated = sum(1 for r in rows if r["status"] == "OUTDATED") + safe = sum(1 for r in rows if r["status"] == "SAFE") + unknown = sum(1 for r in rows if r["status"] == "UNKNOWN") + print() + print(f"Всего контейнеров: {total}") + print(f"Безопасных: {safe}") + print(f"Устаревших: {outdated}") + print(f"Неопределённых: {unknown}") + + +if __name__ == "__main__": + main()