-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathauto_patch.py
More file actions
110 lines (93 loc) · 3.5 KB
/
Copy pathauto_patch.py
File metadata and controls
110 lines (93 loc) · 3.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
import os
import json
import docker
import subprocess
# -------------------------------
# Scan container image with Trivy
# -------------------------------
def scan_image(image_name):
print(f"🔍 Scanning {image_name}...")
result = subprocess.run(
["trivy", "image", "--format", "json", image_name],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True
)
if result.returncode != 0:
print("❌ Trivy scan failed:", result.stderr)
return None
return json.loads(result.stdout)
# -------------------------------
# Parse vulnerabilities
# -------------------------------
def get_patchable_vulns(scan_result):
patchable = []
for res in scan_result.get("Results", []):
for vuln in res.get("Vulnerabilities", []):
if vuln.get("FixedVersion"): # only if a fix exists
patchable.append({
"pkg": vuln["PkgName"],
"installed": vuln["InstalledVersion"],
"fixed": vuln["FixedVersion"],
"cve": vuln["VulnerabilityID"],
"desc": vuln.get("Description", "")[:150]
})
return patchable
# -------------------------------
# Generate a patched Dockerfile
# -------------------------------
def generate_dockerfile(base_image, vulns):
print("🛠 Generating patched Dockerfile...")
lines = [f"FROM {base_image}", "RUN apk update || apt-get update -y"]
for v in vulns:
pkg = v["pkg"]
# Alpine packages
if base_image.startswith("alpine"):
line = f"RUN apk add --no-cache --upgrade {pkg}"
else:
# Debian/Ubuntu
line = f"RUN apt-get install -y --only-upgrade {pkg}"
lines.append(line)
lines.append("RUN rm -rf /var/lib/apt/lists/* || true")
dockerfile = "Dockerfile.patched"
with open(dockerfile, "w") as f:
f.write("\n".join(lines))
return dockerfile
# -------------------------------
# Rebuild image
# -------------------------------
def rebuild_image(dockerfile, new_image_name):
print("🔨 Rebuilding patched image...")
client = docker.from_env()
image, logs = client.images.build(path=".", dockerfile=dockerfile, tag=new_image_name)
for chunk in logs:
if "stream" in chunk:
print(chunk["stream"].strip())
return image
# -------------------------------
# Main flow
# -------------------------------
if __name__ == "__main__":
base_image = "alpine:3.12" # 🔹 test image
new_image = base_image.replace(":", "_") + "_patched"
scan_result = scan_image(base_image)
if not scan_result:
exit(1)
vulns = get_patchable_vulns(scan_result)
if not vulns:
print("✅ No patchable vulnerabilities found.")
exit(0)
print("⚠️ Patchable vulnerabilities found:")
for v in vulns:
print(f"{v['cve']} | {v['pkg']} {v['installed']} → {v['fixed']}")
print(f" ➡ {v['desc']}...\n")
dockerfile = generate_dockerfile(base_image, vulns)
patched = rebuild_image(dockerfile, new_image)
print(f"✅ New patched image built: {new_image}")
print("🔍 Re-scanning patched image...")
rescan = scan_image(new_image)
patched_vulns = get_patchable_vulns(rescan)
if not patched_vulns:
print("🎉 All patchable vulnerabilities fixed!")
else:
print("⚠️ Remaining patchable vulns:", patched_vulns)