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