github.com/devseccon/trivy@v0.47.1-0.20231123133102-bd902a0bd996/pkg/k8s/report/report_test.go (about) 1 package report 2 3 import ( 4 "testing" 5 6 "github.com/stretchr/testify/assert" 7 8 dbTypes "github.com/aquasecurity/trivy-db/pkg/types" 9 ftypes "github.com/devseccon/trivy/pkg/fanal/types" 10 "github.com/devseccon/trivy/pkg/types" 11 ) 12 13 var ( 14 deployOrionWithMisconfigs = Resource{ 15 Namespace: "default", 16 Kind: "Deploy", 17 Name: "orion", 18 Metadata: types.Metadata{ 19 RepoTags: []string{ 20 "alpine:3.14", 21 }, 22 RepoDigests: []string{ 23 "alpine:3.14@sha256:8fe1727132b2506c17ba0e1f6a6ed8a016bb1f5735e43b2738cd3fd1979b6260", 24 }, 25 }, 26 Results: types.Results{ 27 { 28 Misconfigurations: []types.DetectedMisconfiguration{ 29 { 30 ID: "ID100", 31 Status: types.StatusFailure, 32 Severity: "LOW", 33 }, 34 { 35 ID: "ID101", 36 Status: types.StatusFailure, 37 Severity: "MEDIUM", 38 }, 39 { 40 ID: "ID102", 41 Status: types.StatusFailure, 42 Severity: "HIGH", 43 }, 44 { 45 ID: "ID103", 46 Status: types.StatusFailure, 47 Severity: "CRITICAL", 48 }, 49 { 50 ID: "ID104", 51 Status: types.StatusFailure, 52 Severity: "UNKNOWN", 53 }, 54 { 55 ID: "ID105", 56 Status: types.StatusFailure, 57 Severity: "LOW", 58 }, 59 { 60 ID: "ID106", 61 Status: types.StatusFailure, 62 Severity: "HIGH", 63 }, 64 }, 65 }, 66 }, 67 } 68 69 deployOrionWithVulns = Resource{ 70 Namespace: "default", 71 Kind: "Deploy", 72 Name: "orion", 73 Metadata: types.Metadata{ 74 RepoTags: []string{ 75 "alpine:3.14", 76 }, 77 RepoDigests: []string{ 78 "alpine:3.14@sha256:8fe1727132b2506c17ba0e1f6a6ed8a016bb1f5735e43b2738cd3fd1979b6260", 79 }, 80 }, 81 Results: types.Results{ 82 { 83 Vulnerabilities: []types.DetectedVulnerability{ 84 { 85 VulnerabilityID: "CVE-2022-1111", 86 Vulnerability: dbTypes.Vulnerability{Severity: "LOW"}, 87 }, 88 { 89 VulnerabilityID: "CVE-2022-2222", 90 Vulnerability: dbTypes.Vulnerability{Severity: "MEDIUM"}, 91 }, 92 { 93 VulnerabilityID: "CVE-2022-3333", 94 Vulnerability: dbTypes.Vulnerability{Severity: "HIGH"}, 95 }, 96 { 97 VulnerabilityID: "CVE-2022-4444", 98 Vulnerability: dbTypes.Vulnerability{Severity: "CRITICAL"}, 99 }, 100 { 101 VulnerabilityID: "CVE-2022-5555", 102 Vulnerability: dbTypes.Vulnerability{Severity: "UNKNOWN"}, 103 }, 104 { 105 VulnerabilityID: "CVE-2022-6666", 106 Vulnerability: dbTypes.Vulnerability{Severity: "CRITICAL"}, 107 }, 108 { 109 VulnerabilityID: "CVE-2022-7777", 110 Vulnerability: dbTypes.Vulnerability{Severity: "MEDIUM"}, 111 }, 112 }, 113 }, 114 }, 115 } 116 117 deployOrionWithBothVulnsAndMisconfigs = Resource{ 118 Namespace: "default", 119 Kind: "Deploy", 120 Name: "orion", 121 Metadata: types.Metadata{ 122 RepoTags: []string{ 123 "alpine:3.14", 124 }, 125 RepoDigests: []string{ 126 "alpine:3.14@sha256:8fe1727132b2506c17ba0e1f6a6ed8a016bb1f5735e43b2738cd3fd1979b6260", 127 }, 128 }, 129 Results: types.Results{ 130 { 131 Misconfigurations: []types.DetectedMisconfiguration{ 132 { 133 ID: "ID100", 134 Status: types.StatusFailure, 135 Severity: "LOW", 136 }, 137 { 138 ID: "ID101", 139 Status: types.StatusFailure, 140 Severity: "MEDIUM", 141 }, 142 { 143 ID: "ID102", 144 Status: types.StatusFailure, 145 Severity: "HIGH", 146 }, 147 { 148 ID: "ID103", 149 Status: types.StatusFailure, 150 Severity: "CRITICAL", 151 }, 152 { 153 ID: "ID104", 154 Status: types.StatusFailure, 155 Severity: "UNKNOWN", 156 }, 157 { 158 ID: "ID105", 159 Status: types.StatusFailure, 160 Severity: "LOW", 161 }, 162 { 163 ID: "ID106", 164 Status: types.StatusFailure, 165 Severity: "HIGH", 166 }, 167 }, 168 }, 169 { 170 Vulnerabilities: []types.DetectedVulnerability{ 171 { 172 VulnerabilityID: "CVE-2022-1111", 173 Vulnerability: dbTypes.Vulnerability{Severity: "LOW"}, 174 }, 175 { 176 VulnerabilityID: "CVE-2022-2222", 177 Vulnerability: dbTypes.Vulnerability{Severity: "MEDIUM"}, 178 }, 179 { 180 VulnerabilityID: "CVE-2022-3333", 181 Vulnerability: dbTypes.Vulnerability{Severity: "HIGH"}, 182 }, 183 { 184 VulnerabilityID: "CVE-2022-4444", 185 Vulnerability: dbTypes.Vulnerability{Severity: "CRITICAL"}, 186 }, 187 { 188 VulnerabilityID: "CVE-2022-5555", 189 Vulnerability: dbTypes.Vulnerability{Severity: "UNKNOWN"}, 190 }, 191 { 192 VulnerabilityID: "CVE-2022-6666", 193 Vulnerability: dbTypes.Vulnerability{Severity: "CRITICAL"}, 194 }, 195 { 196 VulnerabilityID: "CVE-2022-7777", 197 Vulnerability: dbTypes.Vulnerability{Severity: "MEDIUM"}, 198 }, 199 }, 200 }, 201 }, 202 } 203 204 cronjobHelloWithVulns = Resource{ 205 Namespace: "default", 206 Kind: "Cronjob", 207 Name: "hello", 208 Metadata: types.Metadata{ 209 RepoTags: []string{ 210 "alpine:3.14", 211 }, 212 RepoDigests: []string{ 213 "alpine:3.14@sha256:8fe1727132b2506c17ba0e1f6a6ed8a016bb1f5735e43b2738cd3fd1979b6260", 214 }, 215 }, 216 Results: types.Results{ 217 {Vulnerabilities: []types.DetectedVulnerability{{VulnerabilityID: "CVE-2020-9999"}}}, 218 }, 219 } 220 221 podPrometheusWithMisconfigs = Resource{ 222 Namespace: "default", 223 Kind: "Pod", 224 Name: "prometheus", 225 Metadata: types.Metadata{ 226 RepoTags: []string{ 227 "alpine:3.14", 228 }, 229 RepoDigests: []string{ 230 "alpine:3.14@sha256:8fe1727132b2506c17ba0e1f6a6ed8a016bb1f5735e43b2738cd3fd1979b6260", 231 }, 232 }, 233 Results: types.Results{ 234 {Misconfigurations: []types.DetectedMisconfiguration{{ID: "ID100"}}}, 235 }, 236 } 237 238 roleWithMisconfig = Resource{ 239 Namespace: "default", 240 Kind: "Role", 241 Name: "system::leader-locking-kube-controller-manager", 242 Results: types.Results{ 243 { 244 Misconfigurations: []types.DetectedMisconfiguration{ 245 { 246 ID: "ID100", 247 Status: types.StatusFailure, 248 Severity: "MEDIUM", 249 }, 250 }, 251 }, 252 }, 253 } 254 255 deployLuaWithSecrets = Resource{ 256 Namespace: "default", 257 Kind: "Deploy", 258 Name: "lua", 259 Results: types.Results{ 260 { 261 Secrets: []ftypes.SecretFinding{ 262 { 263 RuleID: "secret1", 264 Severity: "CRITICAL", 265 }, 266 { 267 RuleID: "secret2", 268 Severity: "MEDIUM", 269 }, 270 }, 271 }, 272 }, 273 } 274 275 apiseverPodWithMisconfigAndInfra = Resource{ 276 Namespace: "kube-system", 277 Kind: "Pod", 278 Name: "kube-apiserver", 279 Results: types.Results{ 280 { 281 Misconfigurations: []types.DetectedMisconfiguration{ 282 { 283 ID: "KSV-ID100", 284 Status: types.StatusFailure, 285 Severity: "LOW", 286 }, 287 { 288 ID: "KSV-ID101", 289 Status: types.StatusFailure, 290 Severity: "MEDIUM", 291 }, 292 { 293 ID: "KSV-ID102", 294 Status: types.StatusFailure, 295 Severity: "HIGH", 296 }, 297 298 { 299 ID: "KCV-ID100", 300 Status: types.StatusFailure, 301 Severity: "LOW", 302 }, 303 { 304 ID: "KCV-ID101", 305 Status: types.StatusFailure, 306 Severity: "MEDIUM", 307 }, 308 }, 309 }, 310 }, 311 } 312 ) 313 314 func TestReport_consolidate(t *testing.T) { 315 tests := []struct { 316 name string 317 report Report 318 expectedFindings map[string]Resource 319 }{ 320 { 321 name: "report with both misconfigs and vulnerabilities", 322 report: Report{ 323 Resources: []Resource{ 324 deployOrionWithVulns, 325 cronjobHelloWithVulns, 326 deployOrionWithMisconfigs, 327 podPrometheusWithMisconfigs, 328 }, 329 }, 330 expectedFindings: map[string]Resource{ 331 "default/deploy/orion": deployOrionWithBothVulnsAndMisconfigs, 332 "default/cronjob/hello": cronjobHelloWithVulns, 333 "default/pod/prometheus": podPrometheusWithMisconfigs, 334 }, 335 }, 336 { 337 name: "report with only misconfigurations", 338 report: Report{ 339 Resources: []Resource{ 340 deployOrionWithMisconfigs, 341 podPrometheusWithMisconfigs, 342 }, 343 }, 344 expectedFindings: map[string]Resource{ 345 "default/deploy/orion": deployOrionWithMisconfigs, 346 "default/pod/prometheus": podPrometheusWithMisconfigs, 347 }, 348 }, 349 { 350 name: "report with only vulnerabilities", 351 report: Report{ 352 Resources: []Resource{ 353 deployOrionWithVulns, 354 cronjobHelloWithVulns, 355 }, 356 }, 357 expectedFindings: map[string]Resource{ 358 "default/deploy/orion": deployOrionWithVulns, 359 "default/cronjob/hello": cronjobHelloWithVulns, 360 }, 361 }, 362 } 363 364 for _, tt := range tests { 365 t.Run(tt.name, func(t *testing.T) { 366 consolidateReport := tt.report.consolidate() 367 for _, f := range consolidateReport.Findings { 368 key := f.fullname() 369 370 expected, found := tt.expectedFindings[key] 371 if !found { 372 t.Errorf("key not found: %s", key) 373 } 374 375 assert.Equal(t, expected, f) 376 } 377 }) 378 } 379 } 380 381 func TestResource_fullname(t *testing.T) { 382 tests := []struct { 383 expected string 384 resource Resource 385 }{ 386 { 387 "default/deploy/orion", 388 deployOrionWithBothVulnsAndMisconfigs, 389 }, 390 { 391 "default/deploy/orion", 392 deployOrionWithMisconfigs, 393 }, 394 { 395 "default/cronjob/hello", 396 cronjobHelloWithVulns, 397 }, 398 { 399 "default/pod/prometheus", 400 podPrometheusWithMisconfigs, 401 }, 402 } 403 404 for _, tt := range tests { 405 t.Run(tt.expected, func(t *testing.T) { 406 assert.Equal(t, tt.expected, tt.resource.fullname()) 407 }) 408 } 409 } 410 411 func TestResourceFailed(t *testing.T) { 412 tests := []struct { 413 name string 414 report Report 415 expected bool 416 }{ 417 { 418 name: "report with both misconfigs and vulnerabilities", 419 report: Report{ 420 Resources: []Resource{ 421 deployOrionWithVulns, 422 cronjobHelloWithVulns, 423 deployOrionWithMisconfigs, 424 podPrometheusWithMisconfigs, 425 }, 426 }, 427 expected: true, 428 }, 429 { 430 name: "report with only misconfigurations", 431 report: Report{ 432 Resources: []Resource{ 433 deployOrionWithMisconfigs, 434 podPrometheusWithMisconfigs, 435 }, 436 }, 437 expected: true, 438 }, 439 { 440 name: "report with only vulnerabilities", 441 report: Report{ 442 Resources: []Resource{ 443 deployOrionWithVulns, 444 cronjobHelloWithVulns, 445 }, 446 }, 447 expected: true, 448 }, 449 { 450 name: "report without vulnerabilities and misconfigurations", 451 report: Report{}, 452 expected: false, 453 }, 454 } 455 456 for _, tt := range tests { 457 t.Run(tt.name, func(t *testing.T) { 458 assert.Equal(t, tt.expected, tt.report.Failed()) 459 }) 460 } 461 } 462 463 func Test_rbacResource(t *testing.T) { 464 tests := []struct { 465 name string 466 misConfig Resource 467 want bool 468 }{ 469 { 470 name: "rbac Role resources", 471 misConfig: Resource{Kind: "Role"}, 472 want: true, 473 }, 474 { 475 name: "rbac ClusterRole resources", 476 misConfig: Resource{Kind: "ClusterRole"}, 477 want: true, 478 }, 479 { 480 name: "rbac RoleBinding resources", 481 misConfig: Resource{Kind: "RoleBinding"}, 482 want: true, 483 }, 484 { 485 name: "rbac ClusterRoleBinding resources", 486 misConfig: Resource{Kind: "ClusterRoleBinding"}, 487 want: true, 488 }, 489 } 490 for _, test := range tests { 491 t.Run(test.name, func(t *testing.T) { 492 got := rbacResource(test.misConfig) 493 assert.Equal(t, test.want, got) 494 }) 495 } 496 } 497 498 func Test_separateMisconfigReports(t *testing.T) { 499 k8sReport := Report{ 500 Resources: []Resource{ 501 {Kind: "Role"}, 502 {Kind: "Deployment"}, 503 {Kind: "StatefulSet"}, 504 { 505 Kind: "Pod", 506 Namespace: "kube-system", 507 Results: []types.Result{ 508 {Misconfigurations: []types.DetectedMisconfiguration{{ID: "KCV-0001"}}}, 509 {Misconfigurations: []types.DetectedMisconfiguration{{ID: "KSV-0001"}}}, 510 }, 511 }, 512 }, 513 } 514 515 tests := []struct { 516 name string 517 k8sReport Report 518 scanners types.Scanners 519 components []string 520 expectedReports []Report 521 }{ 522 { 523 name: "Config, Rbac, and Infra Reports", 524 k8sReport: k8sReport, 525 scanners: types.Scanners{ 526 types.MisconfigScanner, 527 types.RBACScanner, 528 }, 529 components: []string{ 530 workloadComponent, 531 infraComponent, 532 }, 533 expectedReports: []Report{ 534 // the order matter for the test 535 { 536 Resources: []Resource{ 537 {Kind: "Deployment"}, 538 {Kind: "StatefulSet"}, 539 {Kind: "Pod"}, 540 }, 541 }, 542 {Resources: []Resource{{Kind: "Role"}}}, 543 {Resources: []Resource{{Kind: "Pod"}}}, 544 }, 545 }, 546 { 547 name: "Config and Infra for the same resource", 548 k8sReport: k8sReport, 549 scanners: types.Scanners{types.MisconfigScanner}, 550 components: []string{ 551 workloadComponent, 552 infraComponent, 553 }, 554 expectedReports: []Report{ 555 // the order matter for the test 556 { 557 Resources: []Resource{ 558 {Kind: "Deployment"}, 559 {Kind: "StatefulSet"}, 560 {Kind: "Pod"}, 561 }, 562 }, 563 {Resources: []Resource{{Kind: "Pod"}}}, 564 }, 565 }, 566 { 567 name: "Role Report Only", 568 k8sReport: k8sReport, 569 scanners: types.Scanners{types.RBACScanner}, 570 expectedReports: []Report{ 571 {Resources: []Resource{{Kind: "Role"}}}, 572 }, 573 }, 574 { 575 name: "Config Report Only", 576 k8sReport: k8sReport, 577 scanners: types.Scanners{types.MisconfigScanner}, 578 components: []string{workloadComponent}, 579 expectedReports: []Report{ 580 { 581 Resources: []Resource{ 582 {Kind: "Deployment"}, 583 {Kind: "StatefulSet"}, 584 {Kind: "Pod"}, 585 }, 586 }, 587 }, 588 }, 589 { 590 name: "Infra Report Only", 591 k8sReport: k8sReport, 592 scanners: types.Scanners{types.MisconfigScanner}, 593 components: []string{infraComponent}, 594 expectedReports: []Report{ 595 {Resources: []Resource{{Kind: "Pod"}}}, 596 }, 597 }, 598 599 // TODO: add vuln only 600 // TODO: add secret only 601 } 602 for _, tt := range tests { 603 t.Run(tt.name, func(t *testing.T) { 604 reports := SeparateMisconfigReports(tt.k8sReport, tt.scanners, tt.components) 605 assert.Equal(t, len(tt.expectedReports), len(reports)) 606 607 for i := range reports { 608 assert.Equal(t, len(tt.expectedReports[i].Resources), len(reports[i].Report.Resources)) 609 for j, m := range tt.expectedReports[i].Resources { 610 assert.Equal(t, m.Kind, reports[i].Report.Resources[j].Kind) 611 } 612 } 613 }) 614 } 615 }