github.com/devseccon/trivy@v0.47.1-0.20231123133102-bd902a0bd996/pkg/cloud/report/service_test.go (about)

     1  package report
     2  
     3  import (
     4  	"bytes"
     5  	"github.com/devseccon/trivy/pkg/clock"
     6  	"testing"
     7  	"time"
     8  
     9  	"github.com/aws/aws-sdk-go-v2/aws/arn"
    10  	"github.com/stretchr/testify/assert"
    11  	"github.com/stretchr/testify/require"
    12  
    13  	"github.com/aquasecurity/defsec/pkg/scan"
    14  	defsecTypes "github.com/aquasecurity/defsec/pkg/types"
    15  	"github.com/aquasecurity/trivy-db/pkg/types"
    16  	"github.com/devseccon/trivy/pkg/flag"
    17  )
    18  
    19  func Test_ServiceReport(t *testing.T) {
    20  	tests := []struct {
    21  		name      string
    22  		options   flag.Options
    23  		fromCache bool
    24  		expected  string
    25  	}{
    26  		{
    27  			name: "simple table output",
    28  			options: flag.Options{
    29  				ReportOptions: flag.ReportOptions{
    30  					Format: tableFormat,
    31  					Severities: []types.Severity{
    32  						types.SeverityLow,
    33  						types.SeverityMedium,
    34  						types.SeverityHigh,
    35  						types.SeverityCritical,
    36  					},
    37  				},
    38  			},
    39  			fromCache: false,
    40  			expected: `
    41  Scan Overview for AWS Account 
    42  ┌─────────┬──────────────────────────────────────────────────┬──────────────┐
    43  │         │                Misconfigurations                 │              │
    44  │         ├──────────┬──────────────┬────────┬─────┬─────────┤              │
    45  │ Service │ Critical │     High     │ Medium │ Low │ Unknown │ Last Scanned │
    46  ├─────────┼──────────┼──────────────┼────────┼─────┼─────────┼──────────────┤
    47  │ ec2     │        0 │            1 │      0 │   0 │       0 │ just now     │
    48  │ s3      │        0 │            3 │      0 │   0 │       0 │ just now     │
    49  └─────────┴──────────┴──────────────┴────────┴─────┴─────────┴──────────────┘
    50  `,
    51  		},
    52  		{
    53  			name: "results from cache",
    54  			options: flag.Options{
    55  				ReportOptions: flag.ReportOptions{
    56  					Format: tableFormat,
    57  					Severities: []types.Severity{
    58  						types.SeverityLow,
    59  						types.SeverityMedium,
    60  						types.SeverityHigh,
    61  						types.SeverityCritical,
    62  					},
    63  				},
    64  			},
    65  			fromCache: true,
    66  			expected: `
    67  Scan Overview for AWS Account 
    68  ┌─────────┬──────────────────────────────────────────────────┬──────────────┐
    69  │         │                Misconfigurations                 │              │
    70  │         ├──────────┬──────────────┬────────┬─────┬─────────┤              │
    71  │ Service │ Critical │     High     │ Medium │ Low │ Unknown │ Last Scanned │
    72  ├─────────┼──────────┼──────────────┼────────┼─────┼─────────┼──────────────┤
    73  │ ec2     │        0 │            1 │      0 │   0 │       0 │ just now     │
    74  │ s3      │        0 │            3 │      0 │   0 │       0 │ just now     │
    75  └─────────┴──────────┴──────────────┴────────┴─────┴─────────┴──────────────┘
    76  
    77  This scan report was loaded from cached results. If you'd like to run a fresh scan, use --update-cache.
    78  `,
    79  		},
    80  		{
    81  			name: "filter severities",
    82  			options: flag.Options{
    83  				ReportOptions: flag.ReportOptions{
    84  					Format: tableFormat,
    85  					Severities: []types.Severity{
    86  						types.SeverityMedium,
    87  					},
    88  				},
    89  				AWSOptions: flag.AWSOptions{
    90  					Services: []string{"s3", "ec2"},
    91  				},
    92  			},
    93  			fromCache: false,
    94  			expected: `
    95  Scan Overview for AWS Account 
    96  ┌─────────┬──────────────────────────────────────────────────┬──────────────┐
    97  │         │                Misconfigurations                 │              │
    98  │         ├──────────┬──────────────┬────────┬─────┬─────────┤              │
    99  │ Service │ Critical │     High     │ Medium │ Low │ Unknown │ Last Scanned │
   100  ├─────────┼──────────┼──────────────┼────────┼─────┼─────────┼──────────────┤
   101  │ ec2     │        0 │            0 │      0 │   0 │       0 │ just now     │
   102  │ s3      │        0 │            0 │      0 │   0 │       0 │ just now     │
   103  └─────────┴──────────┴──────────────┴────────┴─────┴─────────┴──────────────┘
   104  `,
   105  		},
   106  		{
   107  			name: "scoped services without results",
   108  			options: flag.Options{
   109  				ReportOptions: flag.ReportOptions{
   110  					Format: tableFormat,
   111  					Severities: []types.Severity{
   112  						types.SeverityLow,
   113  						types.SeverityMedium,
   114  						types.SeverityHigh,
   115  						types.SeverityCritical,
   116  					},
   117  				},
   118  				AWSOptions: flag.AWSOptions{
   119  					Services: []string{"ec2", "s3", "iam"},
   120  				},
   121  			},
   122  			fromCache: false,
   123  			expected: `
   124  Scan Overview for AWS Account 
   125  ┌─────────┬──────────────────────────────────────────────────┬──────────────┐
   126  │         │                Misconfigurations                 │              │
   127  │         ├──────────┬──────────────┬────────┬─────┬─────────┤              │
   128  │ Service │ Critical │     High     │ Medium │ Low │ Unknown │ Last Scanned │
   129  ├─────────┼──────────┼──────────────┼────────┼─────┼─────────┼──────────────┤
   130  │ ec2     │        0 │            1 │      0 │   0 │       0 │ just now     │
   131  │ iam     │        0 │            0 │      0 │   0 │       0 │ just now     │
   132  │ s3      │        0 │            3 │      0 │   0 │       0 │ just now     │
   133  └─────────┴──────────┴──────────────┴────────┴─────┴─────────┴──────────────┘
   134  `,
   135  		},
   136  		{
   137  			name: "json output",
   138  			options: flag.Options{
   139  				ReportOptions: flag.ReportOptions{
   140  					Format: "json",
   141  					Severities: []types.Severity{
   142  						types.SeverityLow,
   143  						types.SeverityMedium,
   144  						types.SeverityHigh,
   145  						types.SeverityCritical,
   146  					},
   147  				},
   148  			},
   149  			fromCache: false,
   150  			expected: `{
   151    "CreatedAt": "2021-08-25T12:20:30.000000005Z",
   152    "ArtifactType": "aws_account",
   153    "Metadata": {
   154      "ImageConfig": {
   155        "architecture": "",
   156        "created": "0001-01-01T00:00:00Z",
   157        "os": "",
   158        "rootfs": {
   159          "type": "",
   160          "diff_ids": null
   161        },
   162        "config": {}
   163      }
   164    },
   165    "Results": [
   166      {
   167        "Target": "arn:aws:ec2:us-east-1:1234567890:instance1",
   168        "Class": "config",
   169        "Type": "cloud",
   170        "MisconfSummary": {
   171          "Successes": 0,
   172          "Failures": 1,
   173          "Exceptions": 0
   174        },
   175        "Misconfigurations": [
   176          {
   177            "Type": "AWS",
   178            "ID": "AVD-AWS-9999",
   179            "AVDID": "AVD-AWS-9999",
   180            "Title": "Do not use bad stuff",
   181            "Description": "Bad stuff is... bad",
   182            "Message": "instance is bad",
   183            "Resolution": "Remove bad stuff",
   184            "Severity": "HIGH",
   185            "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-9999",
   186            "References": [
   187              "https://avd.aquasec.com/misconfig/avd-aws-9999"
   188            ],
   189            "Status": "FAIL",
   190            "Layer": {},
   191            "CauseMetadata": {
   192              "Resource": "arn:aws:ec2:us-east-1:1234567890:instance1",
   193              "Provider": "AWS",
   194              "Service": "ec2",
   195              "Code": {
   196                "Lines": null
   197              }
   198            }
   199          }
   200        ]
   201      },
   202      {
   203        "Target": "arn:aws:s3:us-east-1:1234567890:bucket1",
   204        "Class": "config",
   205        "Type": "cloud",
   206        "MisconfSummary": {
   207          "Successes": 0,
   208          "Failures": 1,
   209          "Exceptions": 0
   210        },
   211        "Misconfigurations": [
   212          {
   213            "Type": "AWS",
   214            "ID": "AVD-AWS-9999",
   215            "AVDID": "AVD-AWS-9999",
   216            "Title": "Do not use bad stuff",
   217            "Description": "Bad stuff is... bad",
   218            "Message": "something failed",
   219            "Resolution": "Remove bad stuff",
   220            "Severity": "HIGH",
   221            "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-9999",
   222            "References": [
   223              "https://avd.aquasec.com/misconfig/avd-aws-9999"
   224            ],
   225            "Status": "FAIL",
   226            "Layer": {},
   227            "CauseMetadata": {
   228              "Resource": "arn:aws:s3:us-east-1:1234567890:bucket1",
   229              "Provider": "AWS",
   230              "Service": "s3",
   231              "Code": {
   232                "Lines": null
   233              }
   234            }
   235          }
   236        ]
   237      },
   238      {
   239        "Target": "arn:aws:s3:us-east-1:1234567890:bucket2",
   240        "Class": "config",
   241        "Type": "cloud",
   242        "MisconfSummary": {
   243          "Successes": 0,
   244          "Failures": 2,
   245          "Exceptions": 0
   246        },
   247        "Misconfigurations": [
   248          {
   249            "Type": "AWS",
   250            "ID": "AVD-AWS-9999",
   251            "AVDID": "AVD-AWS-9999",
   252            "Title": "Do not use bad stuff",
   253            "Description": "Bad stuff is... bad",
   254            "Message": "something else failed",
   255            "Resolution": "Remove bad stuff",
   256            "Severity": "HIGH",
   257            "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-9999",
   258            "References": [
   259              "https://avd.aquasec.com/misconfig/avd-aws-9999"
   260            ],
   261            "Status": "FAIL",
   262            "Layer": {},
   263            "CauseMetadata": {
   264              "Resource": "arn:aws:s3:us-east-1:1234567890:bucket2",
   265              "Provider": "AWS",
   266              "Service": "s3",
   267              "Code": {
   268                "Lines": null
   269              }
   270            }
   271          },
   272          {
   273            "Type": "AWS",
   274            "ID": "AVD-AWS-9999",
   275            "AVDID": "AVD-AWS-9999",
   276            "Title": "Do not use bad stuff",
   277            "Description": "Bad stuff is... bad",
   278            "Message": "something else failed again",
   279            "Resolution": "Remove bad stuff",
   280            "Severity": "HIGH",
   281            "PrimaryURL": "https://avd.aquasec.com/misconfig/avd-aws-9999",
   282            "References": [
   283              "https://avd.aquasec.com/misconfig/avd-aws-9999"
   284            ],
   285            "Status": "FAIL",
   286            "Layer": {},
   287            "CauseMetadata": {
   288              "Resource": "arn:aws:s3:us-east-1:1234567890:bucket2",
   289              "Provider": "AWS",
   290              "Service": "s3",
   291              "Code": {
   292                "Lines": null
   293              }
   294            }
   295          }
   296        ]
   297      },
   298      {
   299        "Target": "arn:aws:s3:us-east-1:1234567890:bucket3",
   300        "Class": "config",
   301        "Type": "cloud",
   302        "MisconfSummary": {
   303          "Successes": 1,
   304          "Failures": 0,
   305          "Exceptions": 0
   306        }
   307      }
   308    ]
   309  }`,
   310  		},
   311  	}
   312  	clock.SetFakeTime(t, time.Date(2021, 8, 25, 12, 20, 30, 5, time.UTC))
   313  	for _, tt := range tests {
   314  		t.Run(tt.name, func(t *testing.T) {
   315  			report := New(
   316  				"AWS",
   317  				tt.options.AWSOptions.Account,
   318  				tt.options.AWSOptions.Region,
   319  				createTestResults(),
   320  				tt.options.AWSOptions.Services,
   321  			)
   322  
   323  			output := bytes.NewBuffer(nil)
   324  			tt.options.SetOutputWriter(output)
   325  			require.NoError(t, Write(report, tt.options, tt.fromCache))
   326  
   327  			assert.Equal(t, "AWS", report.Provider)
   328  			assert.Equal(t, tt.options.AWSOptions.Account, report.AccountID)
   329  			assert.Equal(t, tt.options.AWSOptions.Region, report.Region)
   330  			assert.ElementsMatch(t, tt.options.AWSOptions.Services, report.ServicesInScope)
   331  
   332  			if tt.options.Format == "json" {
   333  				// json output can be formatted/ordered differently - we just care that the data matches
   334  				assert.JSONEq(t, tt.expected, output.String())
   335  			} else {
   336  				assert.Equal(t, tt.expected, output.String())
   337  			}
   338  		})
   339  	}
   340  }
   341  
   342  func createTestResults() scan.Results {
   343  
   344  	baseRule := scan.Rule{
   345  		AVDID:       "AVD-AWS-9999",
   346  		Aliases:     []string{"AWS999"},
   347  		ShortCode:   "no-bad-stuff",
   348  		Summary:     "Do not use bad stuff",
   349  		Explanation: "Bad stuff is... bad",
   350  		Impact:      "Bad things",
   351  		Resolution:  "Remove bad stuff",
   352  		Provider:    "AWS",
   353  		Severity:    "HIGH",
   354  	}
   355  
   356  	var s3Results scan.Results
   357  	s3Results.Add(
   358  		"something failed",
   359  		defsecTypes.NewRemoteMetadata((arn.ARN{
   360  			Partition: "aws",
   361  			Service:   "s3",
   362  			Region:    "us-east-1",
   363  			AccountID: "1234567890",
   364  			Resource:  "bucket1",
   365  		}).String()),
   366  	)
   367  	s3Results.Add(
   368  		"something else failed",
   369  		defsecTypes.NewRemoteMetadata((arn.ARN{
   370  			Partition: "aws",
   371  			Service:   "s3",
   372  			Region:    "us-east-1",
   373  			AccountID: "1234567890",
   374  			Resource:  "bucket2",
   375  		}).String()),
   376  	)
   377  	s3Results.Add(
   378  		"something else failed again",
   379  		defsecTypes.NewRemoteMetadata((arn.ARN{
   380  			Partition: "aws",
   381  			Service:   "s3",
   382  			Region:    "us-east-1",
   383  			AccountID: "1234567890",
   384  			Resource:  "bucket2",
   385  		}).String()),
   386  	)
   387  	s3Results.AddPassed(
   388  		defsecTypes.NewRemoteMetadata((arn.ARN{
   389  			Partition: "aws",
   390  			Service:   "s3",
   391  			Region:    "us-east-1",
   392  			AccountID: "1234567890",
   393  			Resource:  "bucket3",
   394  		}).String()),
   395  	)
   396  	baseRule.Service = "s3"
   397  	s3Results.SetRule(baseRule)
   398  	var ec2Results scan.Results
   399  	ec2Results.Add(
   400  		"instance is bad",
   401  		defsecTypes.NewRemoteMetadata((arn.ARN{
   402  			Partition: "aws",
   403  			Service:   "ec2",
   404  			Region:    "us-east-1",
   405  			AccountID: "1234567890",
   406  			Resource:  "instance1",
   407  		}).String()),
   408  	)
   409  	baseRule.Service = "ec2"
   410  	ec2Results.SetRule(baseRule)
   411  	return append(s3Results, ec2Results...)
   412  }