Skip to content

Commit 9431d0a

Browse files
feat: made changes to output response (#56)
* feat: made changes to output response Signed-off-by: Rahul Vishwakarma <[email protected]> * --amend --------- Signed-off-by: Rahul Vishwakarma <[email protected]>
1 parent 038c058 commit 9431d0a

File tree

3 files changed

+66
-18
lines changed

3 files changed

+66
-18
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
APP_NAME = k8s-custom-controller
22
DOCKER_USER = manzilrahul
3-
VERSION ?= 1.0.16
3+
VERSION ?= 1.0.18
44
IMAGE_NAME = $(DOCKER_USER)/$(APP_NAME)
55

66
# 🖼️ Logo banner

main.go

Lines changed: 64 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,7 @@ func parseRequest(r *http.Request) (*admissionv1.AdmissionReview, error) {
205205
return &a, nil
206206
}
207207

208-
func scanImageWithTrivy(image string) (bool, string, error) {
208+
func scanImageWithTrivy(image string) (bool, []map[string]string, int, error) {
209209
// cmd := exec.Command("trivy", "image", "--quiet", "--severity", "HIGH,CRITICAL", "--format", "json", image)
210210
// out, err := cmd.Output()
211211
// TODO: Need to make it dynamic to support all trivy server (will go with env)
@@ -220,14 +220,16 @@ func scanImageWithTrivy(image string) (bool, string, error) {
220220
)
221221
out, err := cmd.Output()
222222
if err != nil {
223-
return false, "", fmt.Errorf("trivy scan failed for %s: %v", image, err)
223+
return false, nil, 0, fmt.Errorf("trivy scan failed for %s: %v", image, err)
224224
}
225225
var result map[string]interface{}
226226
if err := json.Unmarshal(out, &result); err != nil {
227-
return false, "", fmt.Errorf("failed to parse trivy output: %v", err)
227+
return false, nil, 0, fmt.Errorf("failed to parse trivy output: %v", err)
228228
}
229229
// Check if vulnerabilities found
230-
vulns := []string{}
230+
// vulns := []string{}
231+
var vulns []map[string]string
232+
var count int = 0
231233
log.Println("❗CVEs Found: ")
232234
if results, ok := result["Results"].([]interface{}); ok {
233235
for _, r := range results {
@@ -237,20 +239,41 @@ func scanImageWithTrivy(image string) (bool, string, error) {
237239
vmap := v.(map[string]interface{})
238240
severity := vmap["Severity"].(string)
239241
// skipping for High CVE > Checking only for CRITICAL
240-
if severity == "CRITICAL" {
241-
msg := fmt.Sprintf(" - 🔥 %s\n", vmap["VulnerabilityID"].(string))
242+
if strings.EqualFold(severity, "CRITICAL") {
243+
count++
244+
// 🔥 CVE-2023-1234 (https://nvd.nist.gov/vuln/detail/CVE-2023-1234)
245+
// msg := fmt.Sprintf(" - 🔥 %s (%s)\n", vmap["VulnerabilityID"], vmap["PrimaryURL"])
242246
//vulns = append(vulns, vmap["VulnerabilityID"].(string))
243-
vulns = append(vulns, msg)
247+
// vulns = append(vulns, msg)
248+
249+
vulns = append(vulns, map[string]string{
250+
"id": vmap["VulnerabilityID"].(string),
251+
"url": vmap["PrimaryURL"].(string),
252+
})
244253
}
245254
}
246255
}
247256
}
248257
}
249258
if len(vulns) > 0 {
250-
return false, strings.Join(vulns, ","), nil
259+
return false, vulns, count, nil
251260
}
252-
return true, "", nil
261+
return true, nil, 0, nil
262+
}
263+
264+
type ImageScanResult struct {
265+
Name string `json:"name"`
266+
CriticalCVEs int `json:"critical_cves"`
267+
CVEs []map[string]string `json:"cves"`
253268
}
269+
270+
type ValidationOutput struct {
271+
Deployment string `json:"deployment"`
272+
Namespace string `json:"namespace"`
273+
Images []ImageScanResult `json:"images"`
274+
Decision string `json:"decision"`
275+
}
276+
254277
func ValidateDeployment(w http.ResponseWriter, r *http.Request) {
255278
log.Println("Received /validate request")
256279
in, err := parseRequest(r)
@@ -279,6 +302,7 @@ func ValidateDeployment(w http.ResponseWriter, r *http.Request) {
279302
}
280303
images = append(images, c.Image)
281304
}
305+
// var frontmsg []string
282306
// Containers
283307
for _, c := range dep.Spec.Template.Spec.Containers {
284308
for _, e := range c.Env {
@@ -288,6 +312,7 @@ func ValidateDeployment(w http.ResponseWriter, r *http.Request) {
288312
}
289313
images = append(images, c.Image)
290314
}
315+
var results []ImageScanResult
291316
for _, image := range images {
292317
log.Println("────────────────────────────────────────────────────")
293318
log.Printf("🛡️ Deployment Image Scanning Started : %s\n", image)
@@ -296,28 +321,51 @@ func ValidateDeployment(w http.ResponseWriter, r *http.Request) {
296321
} else {
297322
log.Println("📦 BYPASS_CVE_DENIED: default(false/no)")
298323
}
299-
ok, vulns, err := scanImageWithTrivy(image)
324+
ok, vulns, count, err := scanImageWithTrivy(image)
300325
if err != nil {
301326
log.Printf("Error scanning image %s: %v", image, err)
302327
continue
303328
}
329+
// Build per-image result
330+
imgResult := ImageScanResult{
331+
Name: image,
332+
CriticalCVEs: count,
333+
CVEs: vulns,
334+
}
335+
results = append(results, imgResult)
336+
if count > 0 {
337+
// Denied: 2 CRITICAL CVEs found in nginx:1.18
338+
// msg := fmt.Sprintf("- 🔖 Denied %v CRITICAL CVEs found in %s \n", count, image)
339+
// frontmsg = append(frontmsg, msg)
340+
denied = true
341+
}
342+
304343
log.Println("────────────────────────────────────────────────────")
305344
if !ok {
306345
denied = true
307346
reasons = append(reasons, fmt.Sprintf("%s (CVE: %s)", image, vulns))
308347
}
309348
}
310-
message := "Images allowed"
349+
// Build final structured validation output
350+
validationOutput := ValidationOutput{
351+
Deployment: dep.Name,
352+
Namespace: dep.Namespace,
353+
Images: results,
354+
Decision: "ALLOWED",
355+
}
356+
311357
if denied {
312-
message = fmt.Sprintf("Denied images due to total CVEs across %v images: %v", len(images), reasons)
313-
log.Printf("Denied images due to CVEs: %v", reasons)
358+
validationOutput.Decision = "DENIED"
314359
}
315360

316-
// look for BYPASS_CVE env - you need to skip
361+
// If BYPASS is set, override
317362
if BYPASS_CVE_DENIED {
318-
log.Printf("It have CVE across all the %v images, but we are skipping as BYPASS_CVE_DENIED set true", len(images))
363+
log.Printf("It has CVEs across %v images, but skipping as BYPASS_CVE_DENIED=true", len(images))
364+
validationOutput.Decision = "ALLOWED"
319365
denied = false
320366
}
367+
// Marshal ValidationOutput into AdmissionResponse message
368+
outputJSON, _ := json.MarshalIndent(validationOutput, "", " ")
321369

322370
log.Printf("Validating Deployment: %s, Images: %v", dep.Name, images)
323371
response := admissionv1.AdmissionReview{
@@ -326,7 +374,7 @@ func ValidateDeployment(w http.ResponseWriter, r *http.Request) {
326374
UID: in.Request.UID,
327375
Allowed: !denied,
328376
Result: &metav1.Status{
329-
Message: message,
377+
Message: string(outputJSON),
330378
},
331379
},
332380
}

manifest/k8s-controller-webhook.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ spec:
2020
spec:
2121
containers:
2222
- name: k8s-controller
23-
image: manzilrahul/k8s-custom-controller:1.0.15
23+
image: manzilrahul/k8s-custom-controller:1.0.18
2424
volumeMounts:
2525
- name: webhook-certs
2626
mountPath: /certs

0 commit comments

Comments
 (0)