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 }