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  }