github.com/lineaje-labs/syft@v0.98.1-0.20231227153149-9e393f60ff1b/test/cli/packages_cmd_test.go (about) 1 package cli 2 3 import ( 4 "fmt" 5 "path/filepath" 6 "testing" 7 ) 8 9 const ( 10 // this is the number of packages that should be found in the image-pkg-coverage fixture image 11 // when analyzed with the squashed scope. 12 coverageImageSquashedPackageCount = 25 13 ) 14 15 func TestPackagesCmdFlags(t *testing.T) { 16 hiddenPackagesImage := "docker-archive:" + getFixtureImage(t, "image-hidden-packages") 17 coverageImage := "docker-archive:" + getFixtureImage(t, "image-pkg-coverage") 18 nodeBinaryImage := "docker-archive:" + getFixtureImage(t, "image-node-binary") 19 // badBinariesImage := "docker-archive:" + getFixtureImage(t, "image-bad-binaries") 20 tmp := t.TempDir() + "/" 21 22 tests := []struct { 23 name string 24 args []string 25 env map[string]string 26 assertions []traitAssertion 27 }{ 28 { 29 name: "no-args-shows-help", 30 args: []string{"packages"}, 31 assertions: []traitAssertion{ 32 assertInOutput("an image/directory argument is required"), // specific error that should be shown 33 assertInOutput("Generate a packaged-based Software Bill Of Materials"), // excerpt from help description 34 assertFailingReturnCode, 35 }, 36 }, 37 { 38 name: "json-output-flag", 39 args: []string{"packages", "-o", "json", coverageImage}, 40 assertions: []traitAssertion{ 41 assertJsonReport, 42 assertInOutput(`"metadataType":"apk-db-entry"`), 43 assertNotInOutput(`"metadataType":"ApkMetadata"`), 44 assertSuccessfulReturnCode, 45 }, 46 }, 47 { 48 name: "quiet-flag-with-logger", 49 args: []string{"packages", "-qvv", "-o", "json", coverageImage}, 50 assertions: []traitAssertion{ 51 assertJsonReport, 52 assertNoStderr, 53 assertSuccessfulReturnCode, 54 }, 55 }, 56 { 57 name: "quiet-flag-with-tui", 58 args: []string{"packages", "-q", "-o", "json", coverageImage}, 59 assertions: []traitAssertion{ 60 assertJsonReport, 61 assertNoStderr, 62 assertSuccessfulReturnCode, 63 }, 64 }, 65 { 66 name: "multiple-output-flags", 67 args: []string{"packages", "-o", "table", "-o", "json=" + tmp + ".tmp/multiple-output-flag-test.json", coverageImage}, 68 assertions: []traitAssertion{ 69 assertTableReport, 70 assertFileExists(tmp + ".tmp/multiple-output-flag-test.json"), 71 assertSuccessfulReturnCode, 72 }, 73 }, 74 // I haven't been able to reproduce locally yet, but in CI this has proven to be unstable: 75 // For the same commit: 76 // pass: https://github.com/anchore/syft/runs/4611344142?check_suite_focus=true 77 // fail: https://github.com/anchore/syft/runs/4611343586?check_suite_focus=true 78 // For the meantime this test will be commented out, but should be added back in as soon as possible. 79 // 80 // { 81 // name: "regression-survive-bad-binaries", 82 // // this image has all sorts of rich binaries from the clang-13 test suite that should do pretty bad things 83 // // to the go cataloger binary path. We should NEVER let a panic stop the cataloging process for these 84 // // specific cases. 85 // 86 // // this is more of an integration test, however, to assert the output we want to see from the application 87 // // a CLI test is much easier. 88 // args: []string{"packages", "-vv", badBinariesImage}, 89 // assertions: []traitAssertion{ 90 // assertInOutput("could not parse possible go binary"), 91 // assertSuccessfulReturnCode, 92 // }, 93 // }, 94 { 95 name: "output-env-binding", 96 env: map[string]string{ 97 "SYFT_OUTPUT": "json", 98 }, 99 args: []string{"packages", coverageImage}, 100 assertions: []traitAssertion{ 101 assertJsonReport, 102 assertSuccessfulReturnCode, 103 }, 104 }, 105 { 106 name: "table-output-flag", 107 args: []string{"packages", "-o", "table", coverageImage}, 108 assertions: []traitAssertion{ 109 assertTableReport, 110 assertSuccessfulReturnCode, 111 }, 112 }, 113 { 114 name: "default-output-flag", 115 args: []string{"packages", coverageImage}, 116 assertions: []traitAssertion{ 117 assertTableReport, 118 assertSuccessfulReturnCode, 119 }, 120 }, 121 { 122 name: "legacy-json-output-flag", 123 args: []string{"packages", "-o", "json", coverageImage}, 124 env: map[string]string{ 125 "SYFT_FORMAT_JSON_LEGACY": "true", 126 }, 127 assertions: []traitAssertion{ 128 assertJsonReport, 129 assertNotInOutput(`"metadataType":"apk-db-entry"`), 130 assertInOutput(`"metadataType":"ApkMetadata"`), 131 assertSuccessfulReturnCode, 132 }, 133 }, 134 { 135 name: "squashed-scope-flag", 136 args: []string{"packages", "-o", "json", "-s", "squashed", coverageImage}, 137 assertions: []traitAssertion{ 138 assertPackageCount(coverageImageSquashedPackageCount), 139 assertSuccessfulReturnCode, 140 }, 141 }, 142 { 143 name: "squashed-scope-flag-hidden-packages", 144 args: []string{"packages", "-o", "json", "-s", "squashed", hiddenPackagesImage}, 145 assertions: []traitAssertion{ 146 assertPackageCount(162), 147 assertNotInOutput("vsftpd"), // hidden package 148 assertSuccessfulReturnCode, 149 }, 150 }, 151 { 152 name: "all-layers-scope-flag", 153 args: []string{"packages", "-o", "json", "-s", "all-layers", hiddenPackagesImage}, 154 assertions: []traitAssertion{ 155 assertPackageCount(163), // packages are now deduplicated for this case 156 assertInOutput("all-layers"), 157 assertInOutput("vsftpd"), // hidden package 158 assertSuccessfulReturnCode, 159 }, 160 }, 161 { 162 name: "all-layers-scope-flag-by-env", 163 args: []string{"packages", "-o", "json", hiddenPackagesImage}, 164 env: map[string]string{ 165 "SYFT_PACKAGE_CATALOGER_SCOPE": "all-layers", 166 }, 167 assertions: []traitAssertion{ 168 assertPackageCount(163), // packages are now deduplicated for this case 169 assertInOutput("all-layers"), 170 assertInOutput("vsftpd"), // hidden package 171 assertSuccessfulReturnCode, 172 }, 173 }, 174 { 175 // we want to make certain that syft can catalog a single go binary and get a SBOM report that is not empty 176 name: "catalog-single-go-binary", 177 args: []string{"packages", "-o", "json", getSyftBinaryLocation(t)}, 178 assertions: []traitAssertion{ 179 assertJsonReport, 180 assertStdoutLengthGreaterThan(1000), 181 assertSuccessfulReturnCode, 182 }, 183 }, 184 { 185 name: "catalog-node-js-binary", 186 args: []string{"packages", "-o", "json", nodeBinaryImage}, 187 assertions: []traitAssertion{ 188 assertJsonReport, 189 assertInOutput("node.js"), 190 assertSuccessfulReturnCode, 191 }, 192 }, 193 { 194 name: "responds-to-package-cataloger-search-options", 195 args: []string{"--help"}, 196 env: map[string]string{ 197 "SYFT_PACKAGE_SEARCH_UNINDEXED_ARCHIVES": "true", 198 "SYFT_PACKAGE_SEARCH_INDEXED_ARCHIVES": "false", 199 }, 200 assertions: []traitAssertion{ 201 // the application config in the log matches that of what we expect to have been configured. Note: 202 // we are not testing further wiring of this option, only that the config responds to 203 // package-cataloger-level options. 204 assertInOutput("search-unindexed-archives: true"), 205 assertInOutput("search-indexed-archives: false"), 206 }, 207 }, 208 { 209 name: "platform-option-wired-up", 210 args: []string{"packages", "--platform", "arm64", "-o", "json", "registry:busybox:1.31"}, 211 assertions: []traitAssertion{ 212 assertInOutput("sha256:1ee006886991ad4689838d3a288e0dd3fd29b70e276622f16b67a8922831a853"), // linux/arm64 image digest 213 assertSuccessfulReturnCode, 214 }, 215 }, 216 { 217 name: "json-file-flag", 218 args: []string{"packages", "-o", "json", "--file", filepath.Join(tmp, "output-1.json"), coverageImage}, 219 assertions: []traitAssertion{ 220 assertSuccessfulReturnCode, 221 assertFileOutput(t, filepath.Join(tmp, "output-1.json"), 222 assertJsonReport, 223 ), 224 }, 225 }, 226 { 227 name: "json-output-flag-to-file", 228 args: []string{"packages", "-o", fmt.Sprintf("json=%s", filepath.Join(tmp, "output-2.json")), coverageImage}, 229 assertions: []traitAssertion{ 230 assertSuccessfulReturnCode, 231 assertFileOutput(t, filepath.Join(tmp, "output-2.json"), 232 assertJsonReport, 233 ), 234 }, 235 }, 236 { 237 name: "catalogers-option", 238 // This will detect enable python-package-cataloger, python-installed-package-cataloger and ruby-gemspec cataloger 239 args: []string{"packages", "-o", "json", "--catalogers", "python,ruby-gemspec", coverageImage}, 240 assertions: []traitAssertion{ 241 assertPackageCount(13), 242 assertSuccessfulReturnCode, 243 }, 244 }, 245 { 246 name: "override-default-parallelism", 247 args: []string{"packages", "-vvv", "-o", "json", coverageImage}, 248 env: map[string]string{ 249 "SYFT_PARALLELISM": "2", 250 }, 251 assertions: []traitAssertion{ 252 // the application config in the log matches that of what we expect to have been configured. 253 assertInOutput("parallelism: 2"), 254 assertInOutput("parallelism=2"), 255 assertPackageCount(coverageImageSquashedPackageCount), 256 assertSuccessfulReturnCode, 257 }, 258 }, 259 { 260 name: "default-parallelism", 261 args: []string{"packages", "-vvv", "-o", "json", coverageImage}, 262 assertions: []traitAssertion{ 263 // the application config in the log matches that of what we expect to have been configured. 264 assertInOutput("parallelism: 1"), 265 assertInOutput("parallelism=1"), 266 assertPackageCount(coverageImageSquashedPackageCount), 267 assertSuccessfulReturnCode, 268 }, 269 }, 270 { 271 name: "password and key not in config output", 272 args: []string{"packages", "-vvv", "-o", "json", coverageImage}, 273 env: map[string]string{ 274 "SYFT_ATTEST_PASSWORD": "secret_password", 275 "SYFT_ATTEST_KEY": "secret_key_path", 276 }, 277 assertions: []traitAssertion{ 278 assertNotInOutput("secret_password"), 279 assertNotInOutput("secret_key_path"), 280 assertPackageCount(coverageImageSquashedPackageCount), 281 assertSuccessfulReturnCode, 282 }, 283 }, 284 } 285 286 for _, test := range tests { 287 t.Run(test.name, func(t *testing.T) { 288 cmd, stdout, stderr := runSyft(t, test.env, test.args...) 289 for _, traitFn := range test.assertions { 290 traitFn(t, stdout, stderr, cmd.ProcessState.ExitCode()) 291 } 292 logOutputOnFailure(t, cmd, stdout, stderr) 293 }) 294 } 295 } 296 297 func TestRegistryAuth(t *testing.T) { 298 host := "localhost:17" 299 image := fmt.Sprintf("%s/something:latest", host) 300 args := []string{"packages", "-vvv", fmt.Sprintf("registry:%s", image)} 301 302 tests := []struct { 303 name string 304 args []string 305 env map[string]string 306 assertions []traitAssertion 307 }{ 308 { 309 name: "fallback to keychain", 310 args: args, 311 assertions: []traitAssertion{ 312 assertInOutput("source=OciRegistry"), 313 assertInOutput(image), 314 assertInOutput(fmt.Sprintf("no registry credentials configured for %q, using the default keychain", host)), 315 }, 316 }, 317 { 318 name: "use creds", 319 args: args, 320 env: map[string]string{ 321 "SYFT_REGISTRY_AUTH_AUTHORITY": host, 322 "SYFT_REGISTRY_AUTH_USERNAME": "username", 323 "SYFT_REGISTRY_AUTH_PASSWORD": "password", 324 }, 325 assertions: []traitAssertion{ 326 assertInOutput("source=OciRegistry"), 327 assertInOutput(image), 328 assertInOutput(fmt.Sprintf(`using basic auth for registry "%s"`, host)), 329 }, 330 }, 331 { 332 name: "use token", 333 args: args, 334 env: map[string]string{ 335 "SYFT_REGISTRY_AUTH_AUTHORITY": host, 336 "SYFT_REGISTRY_AUTH_TOKEN": "my-token", 337 }, 338 assertions: []traitAssertion{ 339 assertInOutput("source=OciRegistry"), 340 assertInOutput(image), 341 assertInOutput(fmt.Sprintf(`using token for registry "%s"`, host)), 342 }, 343 }, 344 { 345 name: "not enough info fallback to keychain", 346 args: args, 347 env: map[string]string{ 348 "SYFT_REGISTRY_AUTH_AUTHORITY": host, 349 }, 350 assertions: []traitAssertion{ 351 assertInOutput("source=OciRegistry"), 352 assertInOutput(image), 353 assertInOutput(fmt.Sprintf(`no registry credentials configured for %q, using the default keychain`, host)), 354 }, 355 }, 356 { 357 name: "allows insecure http flag", 358 args: args, 359 env: map[string]string{ 360 "SYFT_REGISTRY_INSECURE_USE_HTTP": "true", 361 }, 362 assertions: []traitAssertion{ 363 assertInOutput("insecure-use-http: true"), 364 }, 365 }, 366 { 367 name: "use tls configuration", 368 args: args, 369 env: map[string]string{ 370 "SYFT_REGISTRY_AUTH_TLS_CERT": "place.crt", 371 "SYFT_REGISTRY_AUTH_TLS_KEY": "place.key", 372 }, 373 assertions: []traitAssertion{ 374 assertInOutput("using custom TLS credentials from"), 375 }, 376 }, 377 } 378 379 for _, test := range tests { 380 t.Run(test.name, func(t *testing.T) { 381 cmd, stdout, stderr := runSyft(t, test.env, test.args...) 382 for _, traitAssertionFn := range test.assertions { 383 traitAssertionFn(t, stdout, stderr, cmd.ProcessState.ExitCode()) 384 } 385 logOutputOnFailure(t, cmd, stdout, stderr) 386 }) 387 } 388 }