github.com/ishita82/trivy-gitaction@v0.0.0-20240206054925-e937cc05f8e3/integration/integration_test.go (about) 1 //go:build integration || vm_integration || module_integration || k8s_integration 2 3 package integration 4 5 import ( 6 "context" 7 "encoding/json" 8 "flag" 9 "fmt" 10 "github.com/aquasecurity/trivy/pkg/clock" 11 "io" 12 "net" 13 "os" 14 "path/filepath" 15 "sort" 16 "strings" 17 "testing" 18 "time" 19 20 cdx "github.com/CycloneDX/cyclonedx-go" 21 "github.com/samber/lo" 22 spdxjson "github.com/spdx/tools-golang/json" 23 "github.com/spdx/tools-golang/spdx" 24 "github.com/spdx/tools-golang/spdxlib" 25 "github.com/stretchr/testify/assert" 26 "github.com/stretchr/testify/require" 27 "github.com/xeipuuv/gojsonschema" 28 29 "github.com/aquasecurity/trivy-db/pkg/db" 30 "github.com/aquasecurity/trivy-db/pkg/metadata" 31 "github.com/aquasecurity/trivy/pkg/commands" 32 "github.com/aquasecurity/trivy/pkg/dbtest" 33 "github.com/aquasecurity/trivy/pkg/types" 34 35 _ "modernc.org/sqlite" 36 ) 37 38 var update = flag.Bool("update", false, "update golden files") 39 40 const SPDXSchema = "https://raw.githubusercontent.com/spdx/spdx-spec/development/v%s/schemas/spdx-schema.json" 41 42 func initDB(t *testing.T) string { 43 fixtureDir := filepath.Join("testdata", "fixtures", "db") 44 entries, err := os.ReadDir(fixtureDir) 45 require.NoError(t, err) 46 47 var fixtures []string 48 for _, entry := range entries { 49 if entry.IsDir() { 50 continue 51 } 52 fixtures = append(fixtures, filepath.Join(fixtureDir, entry.Name())) 53 } 54 55 cacheDir := dbtest.InitDB(t, fixtures) 56 defer db.Close() 57 58 dbDir := filepath.Dir(db.Path(cacheDir)) 59 60 metadataFile := filepath.Join(dbDir, "metadata.json") 61 f, err := os.Create(metadataFile) 62 require.NoError(t, err) 63 64 err = json.NewEncoder(f).Encode(metadata.Metadata{ 65 Version: db.SchemaVersion, 66 NextUpdate: time.Now().Add(24 * time.Hour), 67 UpdatedAt: time.Now(), 68 }) 69 require.NoError(t, err) 70 71 dbtest.InitJavaDB(t, cacheDir) 72 return cacheDir 73 } 74 75 func getFreePort() (int, error) { 76 addr, err := net.ResolveTCPAddr("tcp", "localhost:0") 77 if err != nil { 78 return 0, err 79 } 80 81 l, err := net.ListenTCP("tcp", addr) 82 if err != nil { 83 return 0, err 84 } 85 defer l.Close() 86 return l.Addr().(*net.TCPAddr).Port, nil 87 } 88 89 func waitPort(ctx context.Context, addr string) error { 90 for { 91 conn, err := net.Dial("tcp", addr) 92 if err == nil && conn != nil { 93 return nil 94 } 95 select { 96 case <-ctx.Done(): 97 return err 98 default: 99 time.Sleep(1 * time.Second) 100 } 101 } 102 } 103 104 func readReport(t *testing.T, filePath string) types.Report { 105 t.Helper() 106 107 f, err := os.Open(filePath) 108 require.NoError(t, err, filePath) 109 defer f.Close() 110 111 var report types.Report 112 err = json.NewDecoder(f).Decode(&report) 113 require.NoError(t, err, filePath) 114 115 // We don't compare history because the nano-seconds in "created" don't match 116 report.Metadata.ImageConfig.History = nil 117 118 // We don't compare repo tags because the archive doesn't support it 119 report.Metadata.RepoTags = nil 120 report.Metadata.RepoDigests = nil 121 122 for i, result := range report.Results { 123 for j := range result.Vulnerabilities { 124 report.Results[i].Vulnerabilities[j].Layer.Digest = "" 125 } 126 127 sort.Slice(result.CustomResources, func(i, j int) bool { 128 if result.CustomResources[i].Type != result.CustomResources[j].Type { 129 return result.CustomResources[i].Type < result.CustomResources[j].Type 130 } 131 return result.CustomResources[i].FilePath < result.CustomResources[j].FilePath 132 }) 133 } 134 135 return report 136 } 137 138 func readCycloneDX(t *testing.T, filePath string) *cdx.BOM { 139 f, err := os.Open(filePath) 140 require.NoError(t, err) 141 defer f.Close() 142 143 bom := cdx.NewBOM() 144 decoder := cdx.NewBOMDecoder(f, cdx.BOMFileFormatJSON) 145 err = decoder.Decode(bom) 146 require.NoError(t, err) 147 148 // Sort components 149 if bom.Components != nil { 150 sort.Slice(*bom.Components, func(i, j int) bool { 151 return (*bom.Components)[i].Name < (*bom.Components)[j].Name 152 }) 153 for i := range *bom.Components { 154 (*bom.Components)[i].BOMRef = "" 155 sort.Slice(*(*bom.Components)[i].Properties, func(ii, jj int) bool { 156 return (*(*bom.Components)[i].Properties)[ii].Name < (*(*bom.Components)[i].Properties)[jj].Name 157 }) 158 } 159 sort.Slice(*bom.Vulnerabilities, func(i, j int) bool { 160 return (*bom.Vulnerabilities)[i].ID < (*bom.Vulnerabilities)[j].ID 161 }) 162 } 163 164 return bom 165 } 166 167 func readSpdxJson(t *testing.T, filePath string) *spdx.Document { 168 f, err := os.Open(filePath) 169 require.NoError(t, err) 170 defer f.Close() 171 172 bom, err := spdxjson.Read(f) 173 require.NoError(t, err) 174 175 sort.Slice(bom.Relationships, func(i, j int) bool { 176 if bom.Relationships[i].RefA.ElementRefID != bom.Relationships[j].RefA.ElementRefID { 177 return bom.Relationships[i].RefA.ElementRefID < bom.Relationships[j].RefA.ElementRefID 178 } 179 return bom.Relationships[i].RefB.ElementRefID < bom.Relationships[j].RefB.ElementRefID 180 }) 181 182 sort.Slice(bom.Files, func(i, j int) bool { 183 return bom.Files[i].FileSPDXIdentifier < bom.Files[j].FileSPDXIdentifier 184 }) 185 186 // We don't compare values which change each time an SBOM is generated 187 bom.CreationInfo.Created = "" 188 bom.DocumentNamespace = "" 189 190 return bom 191 } 192 193 func execute(osArgs []string) error { 194 // Set a fake time 195 ctx := clock.With(context.Background(), time.Date(2021, 8, 25, 12, 20, 30, 5, time.UTC)) 196 197 // Setup CLI App 198 app := commands.NewApp() 199 app.SetOut(io.Discard) 200 app.SetArgs(osArgs) 201 202 // Run Trivy 203 return app.ExecuteContext(ctx) 204 } 205 206 func compareReports(t *testing.T, wantFile, gotFile string, override func(*types.Report)) { 207 want := readReport(t, wantFile) 208 got := readReport(t, gotFile) 209 if override != nil { 210 override(&want) 211 } 212 assert.Equal(t, want, got) 213 } 214 215 func compareCycloneDX(t *testing.T, wantFile, gotFile string) { 216 want := readCycloneDX(t, wantFile) 217 got := readCycloneDX(t, gotFile) 218 assert.Equal(t, want, got) 219 220 // Validate CycloneDX output against the JSON schema 221 validateReport(t, got.JSONSchema, got) 222 } 223 224 func compareSPDXJson(t *testing.T, wantFile, gotFile string) { 225 want := readSpdxJson(t, wantFile) 226 got := readSpdxJson(t, gotFile) 227 assert.Equal(t, want, got) 228 229 SPDXVersion, ok := strings.CutPrefix(want.SPDXVersion, "SPDX-") 230 assert.True(t, ok) 231 232 assert.NoError(t, spdxlib.ValidateDocument(got)) 233 234 // Validate SPDX output against the JSON schema 235 validateReport(t, fmt.Sprintf(SPDXSchema, SPDXVersion), got) 236 } 237 238 func validateReport(t *testing.T, schema string, report any) { 239 schemaLoader := gojsonschema.NewReferenceLoader(schema) 240 documentLoader := gojsonschema.NewGoLoader(report) 241 result, err := gojsonschema.Validate(schemaLoader, documentLoader) 242 require.NoError(t, err) 243 244 if valid := result.Valid(); !valid { 245 errs := lo.Map(result.Errors(), func(err gojsonschema.ResultError, _ int) string { 246 return err.String() 247 }) 248 assert.True(t, valid, strings.Join(errs, "\n")) 249 } 250 }