github.com/vchain-us/vcn@v0.9.11-0.20210921212052-a2484d23c0b3/pkg/bom/docker/rpm.go (about)

     1  package docker
     2  
     3  import (
     4  	"encoding/hex"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"os"
     8  
     9  	rpmdb "github.com/anchore/go-rpmdb/pkg"
    10  
    11  	"github.com/vchain-us/vcn/pkg/bom/artifact"
    12  	"github.com/vchain-us/vcn/pkg/bom/executor"
    13  )
    14  
    15  type rpm struct {
    16  	db     *rpmdb.RpmDB
    17  	byFile map[string]*rpmdb.PackageInfo
    18  }
    19  
    20  var hashTypeMaps = map[rpmdb.DigestAlgorithm]artifact.HashType{
    21  	rpmdb.PGPHASHALGO_MD5:    artifact.HashMD5,
    22  	rpmdb.PGPHASHALGO_SHA1:   artifact.HashSHA1,
    23  	rpmdb.PGPHASHALGO_MD2:    artifact.HashMD2,
    24  	rpmdb.PGPHASHALGO_SHA256: artifact.HashSHA256,
    25  	rpmdb.PGPHASHALGO_SHA384: artifact.HashSHA384,
    26  	rpmdb.PGPHASHALGO_SHA512: artifact.HashSHA512,
    27  	rpmdb.PGPHASHALGO_SHA224: artifact.HashSHA224,
    28  }
    29  
    30  func (pkg rpm) Type() string {
    31  	return RPM
    32  }
    33  
    34  func (pkg *rpm) PackageByFile(e executor.Executor, file string, output artifact.OutputOptions) (artifact.Dependency, error) {
    35  	var err error
    36  	if pkg.db == nil {
    37  		pkg.db, err = openDb(e)
    38  		if err != nil {
    39  			return artifact.Dependency{}, err
    40  		}
    41  	}
    42  
    43  	if pkg.byFile == nil {
    44  		pkg.byFile = make(map[string]*rpmdb.PackageInfo)
    45  		pkgList, err := pkg.db.ListPackages()
    46  		if err != nil {
    47  			return artifact.Dependency{}, fmt.Errorf("cannot read RPM database: %w", err)
    48  		}
    49  
    50  		for i := range pkgList {
    51  			for _, f := range pkgList[i].Files {
    52  				pkg.byFile[f.Path] = pkgList[i]
    53  			}
    54  		}
    55  	}
    56  
    57  	p, ok := pkg.byFile[file]
    58  	if !ok {
    59  		return artifact.Dependency{}, ErrNotFound
    60  	}
    61  
    62  	hashtype, ok := hashTypeMaps[p.DigestAlgorithm]
    63  	if !ok {
    64  		hashtype = artifact.HashInvalid
    65  	}
    66  	hash, err := combineHashesFromSlice(p.Files, p.Name)
    67  	if err != nil {
    68  		fmt.Printf("Cannot combine hashes: %v\n", err)
    69  		// ignore error
    70  	}
    71  	if p.License == "" {
    72  		p.License = "NONE"
    73  	}
    74  	return artifact.Dependency{
    75  		Name:     p.Name,
    76  		Version:  p.Version,
    77  		HashType: hashtype,
    78  		Hash:     hash,
    79  		License:  p.License,
    80  	}, nil
    81  }
    82  
    83  func (pkg *rpm) AllPackages(e executor.Executor, output artifact.OutputOptions) ([]artifact.Dependency, error) {
    84  	var err error
    85  	if pkg.db == nil {
    86  		pkg.db, err = openDb(e)
    87  		if err != nil {
    88  			return nil, err
    89  		}
    90  	}
    91  
    92  	pkgList, err := pkg.db.ListPackages()
    93  	if err != nil {
    94  		return nil, fmt.Errorf("cannot read RPM database: %w", err)
    95  	}
    96  
    97  	res := make([]artifact.Dependency, 0, len(pkgList))
    98  	for _, p := range pkgList {
    99  		hashtype, ok := hashTypeMaps[p.DigestAlgorithm]
   100  		if !ok {
   101  			hashtype = artifact.HashInvalid
   102  		}
   103  		hash, err := combineHashesFromSlice(p.Files, p.Name)
   104  		if err != nil {
   105  			fmt.Printf("Cannot combine hashes: %v\n", err)
   106  			// ignore error
   107  		}
   108  		if hash == "" {
   109  			continue
   110  		}
   111  		if p.License == "" {
   112  			p.License = "NONE"
   113  		}
   114  		res = append(res, artifact.Dependency{
   115  			Name:     p.Name,
   116  			Version:  p.Version,
   117  			HashType: hashtype,
   118  			Hash:     hash,
   119  			License:  p.License,
   120  		})
   121  	}
   122  
   123  	return res, nil
   124  }
   125  
   126  func openDb(e executor.Executor) (*rpmdb.RpmDB, error) {
   127  	buf, err := e.ReadFile("/var/lib/rpm/Packages")
   128  	if err != nil {
   129  		return nil, fmt.Errorf("error reading file from container: %w", err)
   130  	}
   131  
   132  	f, err := ioutil.TempFile("", "vcn.rpmdb")
   133  	if err != nil {
   134  		return nil, fmt.Errorf("cannot create temporary file: %w", err)
   135  	}
   136  	rpmFile := f.Name()
   137  	defer os.Remove(rpmFile) // clean up
   138  
   139  	_, err = f.Write(buf)
   140  	if err != nil {
   141  		return nil, fmt.Errorf("cannot write temporary file: %w", err)
   142  	}
   143  	f.Close()
   144  
   145  	db, err := rpmdb.Open(rpmFile)
   146  	if err != nil {
   147  		return nil, fmt.Errorf("cannot read RPM database: %w", err)
   148  	}
   149  
   150  	return db, err
   151  }
   152  
   153  func combineHashesFromSlice(files []rpmdb.FileInfo, pkgName string) (string, error) {
   154  	var hash []byte
   155  	for _, file := range files {
   156  		if file.Digest == "" {
   157  			// some files don't have digests, like symbolic links
   158  			continue
   159  		}
   160  		comp, err := hex.DecodeString(file.Digest)
   161  		if err != nil {
   162  			return "", fmt.Errorf("malformed hash for package %s", pkgName)
   163  		}
   164  		if hash == nil {
   165  			hash = comp
   166  		} else {
   167  			if len(comp) != len(hash) {
   168  				// should never happen - all hashes must be of the same length
   169  				return "", fmt.Errorf("malformed hash for package %s", pkgName)
   170  			}
   171  			// XOR hash
   172  			for i := 0; i < len(hash); i++ {
   173  				hash[i] ^= comp[i]
   174  			}
   175  		}
   176  	}
   177  
   178  	return hex.EncodeToString(hash), nil
   179  }