github.com/vchain-us/vcn@v0.9.11-0.20210921212052-a2484d23c0b3/pkg/cmd/verify/bom.go (about) 1 /* 2 * Copyright (c) 2018-2020 vChain, Inc. All Rights Reserved. 3 * This software is released under GPL3. 4 * The full license information can be found under: 5 * https://www.gnu.org/licenses/gpl-3.0.en.html 6 * 7 */ 8 9 package verify 10 11 import ( 12 "context" 13 "encoding/json" 14 "fmt" 15 "path/filepath" 16 "strconv" 17 "strings" 18 19 "github.com/schollz/progressbar/v3" 20 "github.com/spf13/viper" 21 "google.golang.org/grpc/metadata" 22 23 immuschema "github.com/codenotary/immudb/pkg/api/schema" 24 "github.com/vchain-us/vcn/pkg/api" 25 "github.com/vchain-us/vcn/pkg/bom" 26 "github.com/vchain-us/vcn/pkg/bom/artifact" 27 "github.com/vchain-us/vcn/pkg/bom/docker" 28 "github.com/vchain-us/vcn/pkg/meta" 29 "github.com/vchain-us/vcn/pkg/uri" 30 ) 31 32 var trustLevelMap = map[string]artifact.TrustLevel{ 33 "trusted": artifact.Trusted, 34 "t": artifact.Trusted, 35 "unknown": artifact.Unknown, 36 "unk": artifact.Unknown, 37 "uk": artifact.Unknown, 38 "unsupported": artifact.Unsupported, 39 "uns": artifact.Unsupported, 40 "us": artifact.Unsupported, 41 "untrusted": artifact.Untrusted, 42 "unt": artifact.Untrusted, 43 "ut": artifact.Untrusted, 44 } 45 46 var bomTrustLevelToMeta = map[artifact.TrustLevel]meta.Status{ 47 artifact.Untrusted: meta.StatusUntrusted, 48 artifact.Unsupported: meta.StatusUnsupported, 49 artifact.Unknown: meta.StatusUnknown, 50 artifact.Trusted: meta.StatusTrusted, 51 } 52 53 func processBOM(lcUser *api.LcUser, signerID, output, hash, path string) (artifact.Artifact, error) { 54 trustLevel, ok := trustLevelMap[viper.GetString("bom-trust-level")] 55 if !ok { 56 return nil, fmt.Errorf("invalid BoM trust level, supported values are trusted/unknown/unsupported/untrusted") 57 } 58 59 outputOpts := artifact.Progress 60 if viper.GetBool("silent") || output != "" { 61 outputOpts = artifact.Silent 62 } else if viper.GetBool("bom-debug") { 63 outputOpts = artifact.Debug 64 } 65 66 var bomArtifact artifact.Artifact 67 var deps []artifact.Dependency 68 var err error 69 if hash != "" { 70 // hash specified or found by name/version - resolve dependencies from DB 71 bomArtifact, err = loadBomFromDb(hash, signerID, lcUser) 72 if err != nil { 73 return nil, err 74 } 75 deps = bomArtifact.Dependencies() 76 } else { 77 // resolve dependencies from the asset 78 u, err := uri.Parse(path) 79 if err != nil { 80 return nil, err 81 } 82 if _, ok := bom.BomSchemes[u.Scheme]; !ok { 83 return nil, fmt.Errorf("unsupported URI %s for --bom option", path) 84 } 85 if u.Scheme != "" { 86 path = strings.TrimPrefix(u.Opaque, "//") 87 } 88 if u.Scheme == "docker" { 89 bomArtifact, err = docker.New(path, viper.GetStringSlice("bom-container-binary")) 90 if err != nil { 91 return nil, err 92 } 93 } else { 94 path, err = filepath.Abs(path) 95 if err != nil { 96 return nil, err 97 } 98 bomArtifact = bom.New(path) 99 } 100 if bomArtifact == nil { 101 return nil, fmt.Errorf("unsupported artifact format/language") 102 } 103 if signerID == "" { 104 signerID = api.GetSignerIDByApiKey(lcUser.Client.ApiKey) 105 } 106 107 if outputOpts != artifact.Silent { 108 fmt.Printf("Resolving dependencies...\n") 109 } 110 deps, err = bomArtifact.ResolveDependencies(outputOpts) 111 if err != nil { 112 return nil, fmt.Errorf("cannot get dependencies: %w", err) 113 } 114 } 115 116 if outputOpts != artifact.Silent { 117 fmt.Printf("Authenticating dependencies...\n") 118 } 119 threshold := viper.GetFloat64("bom-max-unsupported") 120 unsupportedCount := 0 121 failed := false 122 123 var bar *progressbar.ProgressBar 124 if len(deps) > 1 && output == "" && outputOpts == artifact.Progress { 125 bar = progressbar.Default(int64(len(deps))) 126 } 127 128 progressCallback := func(processedDeps []artifact.Dependency) { 129 switch outputOpts { 130 case artifact.Progress: 131 if bar != nil { 132 bar.Add(len(processedDeps)) 133 } 134 case artifact.Debug: 135 for _, d := range processedDeps { 136 fmt.Printf("%s@%s (%s) - %s\n", d.Name, d.Version, d.Hash, artifact.TrustLevelName(d.TrustLevel)) 137 } 138 } 139 } 140 141 bomBatchSize := int(viper.GetUint("bom-batch-size")) 142 143 errs, err := artifact.AuthenticateDependencies(lcUser, signerID, deps, bomBatchSize, progressCallback) 144 if err != nil { 145 return nil, fmt.Errorf("error authenticating dependencies: %w", err) 146 } 147 148 lowestLevel := artifact.Trusted 149 for i := range deps { // Authenticate mutates the dependency, so use the index 150 if errs[i] != nil { 151 fmt.Printf("cannot authenticate %s@%s dependency: %v\n", 152 deps[i].SignerID, deps[i].Version, errs[i]) 153 continue 154 } 155 if deps[i].TrustLevel < trustLevel { 156 if deps[i].TrustLevel < lowestLevel { 157 lowestLevel = deps[i].TrustLevel 158 } 159 if deps[i].TrustLevel == artifact.Unsupported || deps[i].TrustLevel == artifact.Unknown { 160 unsupportedCount++ 161 } else { 162 failed = true // keep going - process all 163 } 164 } 165 } 166 if outputOpts != artifact.Silent { 167 artifact.Display(bomArtifact, artifact.ColNameVersion|artifact.ColHash|artifact.ColTrustLevel) 168 } 169 if threshold < 100 && unsupportedCount > int(float64(len(deps))*threshold/100) { 170 failed = true // keep going - user still may need output files 171 } 172 173 // store deps in bom file for further processing 174 if bomFile := viper.GetString("bom-file"); bomFile != "" { 175 err = artifact.Store(bomArtifact, bomFile) 176 if err != nil { 177 // show warning, but not error, because authentication finished 178 fmt.Printf("Cannot store actual BoM: %v", err) 179 } 180 } 181 182 err = bom.Output(bomArtifact) 183 if err != nil { 184 // show warning, but not error, because authentication finished 185 fmt.Println(err) 186 } 187 188 if failed { 189 viper.Set("exit-code", strconv.Itoa(bomTrustLevelToMeta[lowestLevel].Int())) 190 return nil, fmt.Errorf("some dependencies have insufficient trust level") 191 } 192 193 return bomArtifact, nil 194 } 195 196 func GetIncluded(hash string, signerID string, lcUser *api.LcUser) ([]api.PackageDetails, error) { 197 md := metadata.Pairs(meta.VcnLCPluginTypeHeaderName, meta.VcnLCPluginTypeHeaderValue) 198 ctx := metadata.NewOutgoingContext(context.Background(), md) 199 if signerID == "" { 200 signerID = api.GetSignerIDByApiKey(lcUser.Client.ApiKey) 201 } 202 203 zItems, err := lcUser.Client.ZScan(ctx, &immuschema.ZScanRequest{ 204 Set: []byte("includes_vcn." + signerID + "." + hash), 205 NoWait: true, 206 }) 207 if err != nil { 208 return nil, err 209 } 210 res := make([]api.PackageDetails, 0, len(zItems.Entries)) 211 for _, v := range zItems.Entries { 212 var p pkg 213 err := json.Unmarshal(v.Entry.Value, &p) 214 if err != nil { 215 fmt.Printf("cannot parse JSON: %v\n", err) 216 continue 217 } 218 res = append(res, api.PackageDetails{ 219 Name: p.Name, 220 Version: p.Md.Version, 221 Hash: p.Hash, 222 Status: meta.Status(p.Status), 223 }) 224 } 225 return res, nil 226 } 227 228 func loadBomFromDb(hash string, signerID string, lcUser *api.LcUser) (artifact.Artifact, error) { 229 md := metadata.Pairs(meta.VcnLCPluginTypeHeaderName, meta.VcnLCPluginTypeHeaderValue) 230 ctx := metadata.NewOutgoingContext(context.Background(), md) 231 if signerID == "" { 232 signerID = api.GetSignerIDByApiKey(lcUser.Client.ApiKey) 233 } 234 235 ar, err := artifact.LoadFromDb(hash, signerID, lcUser) 236 if err != nil { 237 return nil, err 238 } 239 240 zItems, err := lcUser.Client.ZScan(ctx, &immuschema.ZScanRequest{ 241 Set: []byte("included_by_vcn." + signerID + "." + hash), 242 NoWait: true, 243 }) 244 if err != nil { 245 return nil, err 246 } 247 248 trustLevelMap := map[meta.Status]artifact.TrustLevel{ 249 meta.StatusUntrusted: artifact.Untrusted, 250 meta.StatusUnsupported: artifact.Unsupported, 251 meta.StatusUnknown: artifact.Unknown, 252 meta.StatusTrusted: artifact.Trusted, 253 } 254 255 deps := make([]artifact.Dependency, 0, len(zItems.Entries)) 256 for _, v := range zItems.Entries { 257 var p pkg 258 err := json.Unmarshal(v.Entry.Value, &p) 259 if err != nil { 260 fmt.Printf("cannot parse JSON: %v\n", err) 261 continue 262 } 263 level, ok := trustLevelMap[meta.Status(p.Status)] 264 if !ok { 265 level = artifact.Unknown 266 } 267 deps = append(deps, artifact.Dependency{ 268 Name: p.Name, 269 Version: p.Md.Version, 270 SignerID: signerID, 271 HashType: artifact.HashTypeByName(p.Md.HashType), 272 Hash: p.Hash, 273 TrustLevel: level, 274 Kind: p.Kind, 275 }) 276 } 277 ar.Deps = deps 278 279 return ar, nil 280 } 281 282 func DepsToPackageDetails(deps []artifact.Dependency) []api.PackageDetails { 283 res := make([]api.PackageDetails, 0, len(deps)) 284 for _, deps := range deps { 285 res = append(res, api.PackageDetails{ 286 Name: deps.Name, 287 Version: deps.Version, 288 Hash: deps.Hash, 289 Status: bomTrustLevelToMeta[deps.TrustLevel], 290 License: deps.License, 291 }) 292 } 293 return res 294 }