@@ -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+
254277func 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 }
0 commit comments