github.com/lineaje-labs/syft@v0.98.1-0.20231227153149-9e393f60ff1b/cmd/syft/cli/commands/attest_test.go (about)

     1  package commands
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"os/exec"
     7  	"regexp"
     8  	"strings"
     9  	"testing"
    10  
    11  	"github.com/scylladb/go-set/strset"
    12  	"github.com/stretchr/testify/assert"
    13  	"github.com/stretchr/testify/require"
    14  
    15  	"github.com/anchore/clio"
    16  	"github.com/anchore/syft/cmd/syft/cli/options"
    17  	"github.com/anchore/syft/syft/sbom"
    18  	"github.com/anchore/syft/syft/source"
    19  )
    20  
    21  func Test_writeSBOMToFormattedFile(t *testing.T) {
    22  	type args struct {
    23  		s    *sbom.SBOM
    24  		opts *attestOptions
    25  	}
    26  	tests := []struct {
    27  		name         string
    28  		args         args
    29  		wantSbomFile string
    30  		wantErr      bool
    31  	}{
    32  		{
    33  			name: "go case",
    34  			args: args{
    35  				opts: &attestOptions{
    36  					Output: func() options.Output {
    37  						def := defaultAttestOutputOptions()
    38  						def.Outputs = []string{"syft-json"}
    39  						return def
    40  					}(),
    41  				},
    42  				s: &sbom.SBOM{
    43  					Artifacts:     sbom.Artifacts{},
    44  					Relationships: nil,
    45  					Source: source.Description{
    46  						ID:      "source-id",
    47  						Name:    "source-name",
    48  						Version: "source-version",
    49  					},
    50  					Descriptor: sbom.Descriptor{
    51  						Name:    "syft-test",
    52  						Version: "non-version",
    53  					},
    54  				},
    55  			},
    56  			wantSbomFile: `{
    57   "artifacts": [],
    58   "artifactRelationships": [],
    59   "source": {
    60    "id": "source-id",
    61    "name": "source-name",
    62    "version": "source-version",
    63    "type": "",
    64    "metadata": null
    65   },
    66   "distro": {},
    67   "descriptor": {
    68    "name": "syft-test",
    69    "version": "non-version"
    70   },
    71   "schema": {}
    72  }`,
    73  			wantErr: false,
    74  		},
    75  	}
    76  	for _, tt := range tests {
    77  		t.Run(tt.name, func(t *testing.T) {
    78  			sbomFile := &bytes.Buffer{}
    79  
    80  			err := writeSBOMToFormattedFile(tt.args.s, sbomFile, tt.args.opts)
    81  			if (err != nil) != tt.wantErr {
    82  				t.Errorf("writeSBOMToFormattedFile() error = %v, wantErr %v", err, tt.wantErr)
    83  				return
    84  			}
    85  
    86  			// redact the schema block
    87  			re := regexp.MustCompile(`(?s)"schema":\W*\{.*?},?`)
    88  			subject := re.ReplaceAllString(sbomFile.String(), `"schema":{}`)
    89  
    90  			assert.JSONEq(t, tt.wantSbomFile, subject)
    91  		})
    92  	}
    93  }
    94  
    95  func Test_attestCommand(t *testing.T) {
    96  	cmdPrefix := cosignBinName
    97  	lp, err := exec.LookPath(cosignBinName)
    98  	if err == nil {
    99  		cmdPrefix = lp
   100  	}
   101  
   102  	fullCmd := func(args string) string {
   103  		return fmt.Sprintf("%s %s", cmdPrefix, args)
   104  	}
   105  
   106  	type args struct {
   107  		sbomFilepath string
   108  		opts         attestOptions
   109  		userInput    string
   110  	}
   111  	tests := []struct {
   112  		name        string
   113  		args        args
   114  		wantCmd     string
   115  		wantEnvVars map[string]string
   116  		notEnvVars  []string
   117  		wantErr     require.ErrorAssertionFunc
   118  	}{
   119  		{
   120  			name: "with key and password",
   121  			args: args{
   122  				userInput:    "myimage",
   123  				sbomFilepath: "/tmp/sbom-filepath.json",
   124  				opts: func() attestOptions {
   125  					def := defaultAttestOptions()
   126  					def.Outputs = []string{"syft-json"}
   127  					def.Attest.Key = "key"
   128  					def.Attest.Password = "password"
   129  					return def
   130  				}(),
   131  			},
   132  			wantCmd: fullCmd("attest myimage --predicate /tmp/sbom-filepath.json --type custom -y --key key"),
   133  			wantEnvVars: map[string]string{
   134  				"COSIGN_PASSWORD": "password",
   135  			},
   136  			notEnvVars: []string{
   137  				"COSIGN_EXPERIMENTAL", // only for keyless
   138  			},
   139  		},
   140  		{
   141  			name: "keyless",
   142  			args: args{
   143  				userInput:    "myimage",
   144  				sbomFilepath: "/tmp/sbom-filepath.json",
   145  				opts: func() attestOptions {
   146  					def := defaultAttestOptions()
   147  					def.Outputs = []string{"syft-json"}
   148  					return def
   149  				}(),
   150  			},
   151  			wantCmd: fullCmd("attest myimage --predicate /tmp/sbom-filepath.json --type custom -y"),
   152  			wantEnvVars: map[string]string{
   153  				"COSIGN_EXPERIMENTAL": "1",
   154  			},
   155  			notEnvVars: []string{
   156  				"COSIGN_PASSWORD",
   157  			},
   158  		},
   159  	}
   160  	for _, tt := range tests {
   161  		t.Run(tt.name, func(t *testing.T) {
   162  			if tt.wantErr == nil {
   163  				tt.wantErr = require.NoError
   164  			}
   165  
   166  			got, err := attestCommand(tt.args.sbomFilepath, &tt.args.opts, tt.args.userInput)
   167  			tt.wantErr(t, err)
   168  			if err != nil {
   169  				return
   170  			}
   171  
   172  			require.NotNil(t, got)
   173  			assert.Equal(t, tt.wantCmd, got.String())
   174  
   175  			gotEnv := strset.New(got.Env...)
   176  
   177  			for k, v := range tt.wantEnvVars {
   178  				assert.True(t, gotEnv.Has(fmt.Sprintf("%s=%s", k, v)))
   179  			}
   180  
   181  			for _, k := range tt.notEnvVars {
   182  				for _, env := range got.Env {
   183  					fields := strings.Split(env, "=")
   184  					if fields[0] == k {
   185  						t.Errorf("attestCommand() unexpected environment variable %s", k)
   186  					}
   187  				}
   188  			}
   189  		})
   190  	}
   191  }
   192  
   193  func Test_predicateType(t *testing.T) {
   194  	tests := []struct {
   195  		name string
   196  		want string
   197  	}{
   198  		{
   199  			name: "cyclonedx-json",
   200  			want: "cyclonedx",
   201  		},
   202  		{
   203  			name: "spdx-tag-value",
   204  			want: "spdx",
   205  		},
   206  		{
   207  			name: "spdx-tv",
   208  			want: "spdx",
   209  		},
   210  		{
   211  			name: "spdx-json",
   212  			want: "spdxjson",
   213  		},
   214  		{
   215  			name: "json",
   216  			want: "spdxjson",
   217  		},
   218  		{
   219  			name: "syft-json",
   220  			want: "custom",
   221  		},
   222  	}
   223  	for _, tt := range tests {
   224  		t.Run(tt.name, func(t *testing.T) {
   225  			assert.Equalf(t, tt.want, predicateType(tt.name), "predicateType(%v)", tt.name)
   226  		})
   227  	}
   228  }
   229  
   230  func Test_buildSBOMForAttestation(t *testing.T) {
   231  	// note: this test is only meant to test that the filter function is wired
   232  	// and not the correctness of the function in depth
   233  	type args struct {
   234  		id        clio.Identification
   235  		opts      *options.Catalog
   236  		userInput string
   237  	}
   238  	tests := []struct {
   239  		name    string
   240  		args    args
   241  		want    *sbom.SBOM
   242  		wantErr require.ErrorAssertionFunc
   243  	}{
   244  		{
   245  			name: "do not allow directory scans",
   246  			args: args{
   247  				opts: func() *options.Catalog {
   248  					def := defaultAttestOptions()
   249  					return &def.Catalog
   250  				}(),
   251  				userInput: "dir:/tmp/something",
   252  			},
   253  			wantErr: require.Error,
   254  		},
   255  	}
   256  	for _, tt := range tests {
   257  		t.Run(tt.name, func(t *testing.T) {
   258  			if tt.wantErr == nil {
   259  				tt.wantErr = require.NoError
   260  			}
   261  			_, err := generateSBOMForAttestation(tt.args.id, tt.args.opts, tt.args.userInput)
   262  			tt.wantErr(t, err)
   263  			if err != nil {
   264  				return
   265  			}
   266  		})
   267  	}
   268  }