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  }