mirror of
https://github.com/anchore/syft.git
synced 2026-10-30 14:02:26 +01:00
add file source digest support (#1914)
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
This commit is contained in:
@@ -61,8 +61,7 @@ type Application struct {
|
||||
Exclusions []string `yaml:"exclude" json:"exclude" mapstructure:"exclude"`
|
||||
Platform string `yaml:"platform" json:"platform" mapstructure:"platform"`
|
||||
Name string `yaml:"name" json:"name" mapstructure:"name"`
|
||||
SourceName string `yaml:"source-name" json:"source-name" mapstructure:"source-name"`
|
||||
SourceVersion string `yaml:"source-version" json:"source-version" mapstructure:"source-version"`
|
||||
Source sourceCfg `yaml:"source" json:"source" mapstructure:"source"`
|
||||
Parallelism int `yaml:"parallelism" json:"parallelism" mapstructure:"parallelism"` // the number of catalog workers to run in parallel
|
||||
DefaultImagePullSource string `yaml:"default-image-pull-source" json:"default-image-pull-source" mapstructure:"default-image-pull-source"` // specify default image pull source
|
||||
}
|
||||
@@ -147,8 +146,8 @@ func (cfg *Application) parseConfigValues() error {
|
||||
|
||||
if cfg.Name != "" {
|
||||
log.Warnf("name parameter is deprecated. please use: source-name. name will be removed in a future version")
|
||||
if cfg.SourceName == "" {
|
||||
cfg.SourceName = cfg.Name
|
||||
if cfg.Source.Name == "" {
|
||||
cfg.Source.Name = cfg.Name
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
package config
|
||||
|
||||
import "github.com/spf13/viper"
|
||||
|
||||
type sourceCfg struct {
|
||||
Name string `json:"name" yaml:"name" mapstructure:"name"`
|
||||
Version string `json:"version" yaml:"version" mapstructure:"version"`
|
||||
File fileSource `json:"file" yaml:"file" mapstructure:"file"`
|
||||
}
|
||||
|
||||
type fileSource struct {
|
||||
Digests []string `json:"digests" yaml:"digests" mapstructure:"digests"`
|
||||
}
|
||||
|
||||
func (cfg sourceCfg) loadDefaultValues(v *viper.Viper) {
|
||||
v.SetDefault("source.file.digests", []string{"sha256"})
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package file
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"fmt"
|
||||
"hash"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/anchore/syft/syft/file"
|
||||
)
|
||||
|
||||
func supportedHashAlgorithms() []crypto.Hash {
|
||||
return []crypto.Hash{
|
||||
crypto.MD5,
|
||||
crypto.SHA1,
|
||||
crypto.SHA224,
|
||||
crypto.SHA256,
|
||||
crypto.SHA384,
|
||||
crypto.SHA512,
|
||||
}
|
||||
}
|
||||
|
||||
func NewDigestsFromFile(closer io.ReadCloser, hashes []crypto.Hash) ([]file.Digest, error) {
|
||||
// create a set of hasher objects tied together with a single writer to feed content into
|
||||
hashers := make([]hash.Hash, len(hashes))
|
||||
writers := make([]io.Writer, len(hashes))
|
||||
for idx, hashObj := range hashes {
|
||||
hashers[idx] = hashObj.New()
|
||||
writers[idx] = hashers[idx]
|
||||
}
|
||||
|
||||
size, err := io.Copy(io.MultiWriter(writers...), closer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if size == 0 {
|
||||
return make([]file.Digest, 0), nil
|
||||
}
|
||||
|
||||
result := make([]file.Digest, len(hashes))
|
||||
// only capture digests when there is content. It is important to do this based on SIZE and not
|
||||
// FILE TYPE. The reasoning is that it is possible for a tar to be crafted with a header-only
|
||||
// file type but a body is still allowed.
|
||||
for idx, hasher := range hashers {
|
||||
result[idx] = file.Digest{
|
||||
Algorithm: CleanDigestAlgorithmName(hashes[idx].String()),
|
||||
Value: fmt.Sprintf("%+x", hasher.Sum(nil)),
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func Hashers(names ...string) ([]crypto.Hash, error) {
|
||||
hashByName := make(map[string]crypto.Hash)
|
||||
for _, h := range supportedHashAlgorithms() {
|
||||
hashByName[CleanDigestAlgorithmName(h.String())] = h
|
||||
}
|
||||
|
||||
var hashers []crypto.Hash
|
||||
for _, hashStr := range names {
|
||||
hashObj, ok := hashByName[CleanDigestAlgorithmName(hashStr)]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unsupported hash algorithm: %s", hashStr)
|
||||
}
|
||||
hashers = append(hashers, hashObj)
|
||||
}
|
||||
return hashers, nil
|
||||
}
|
||||
|
||||
func CleanDigestAlgorithmName(name string) string {
|
||||
lower := strings.ToLower(name)
|
||||
return strings.ReplaceAll(lower, "-", "")
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
package file
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anchore/syft/syft/file"
|
||||
)
|
||||
|
||||
func TestCleanDigestAlgorithmName(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "go case",
|
||||
input: "SHA-256",
|
||||
want: "sha256",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert.Equal(t, tt.want, CleanDigestAlgorithmName(tt.input))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewDigestsFromFile(t *testing.T) {
|
||||
require.NotEmpty(t, supportedHashAlgorithms())
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
fixture string
|
||||
hashes []crypto.Hash
|
||||
want []file.Digest
|
||||
wantErr require.ErrorAssertionFunc
|
||||
}{
|
||||
{
|
||||
name: "check supported hash algorithms",
|
||||
fixture: "test-fixtures/digest.txt",
|
||||
hashes: supportedHashAlgorithms(),
|
||||
want: []file.Digest{
|
||||
{
|
||||
Algorithm: "md5",
|
||||
Value: "e8818a24402ae7f8b874cdd9350c1b51",
|
||||
},
|
||||
{
|
||||
Algorithm: "sha1",
|
||||
Value: "eea4671d168c81fd52e615ed9fb3531a526f4748",
|
||||
},
|
||||
{
|
||||
Algorithm: "sha224",
|
||||
Value: "fd993e84c7afb449d34bcae7c5ee118f5c73b50170da05171523b22c",
|
||||
},
|
||||
{
|
||||
Algorithm: "sha256",
|
||||
Value: "cbf1a703b7e4a67529d6e17114880dfa9f879f3749872e1a9d4a20ac509165ad",
|
||||
},
|
||||
{
|
||||
Algorithm: "sha384",
|
||||
Value: "1eaded3f17fb8d7b731c9175a0f355d3a35575c3cb6cdda46a5272b632968d7257a5e6437d0efae599a81a1b2dcc81ba",
|
||||
},
|
||||
{
|
||||
Algorithm: "sha512",
|
||||
Value: "b49d5995456edba144dce750eaa8eae12af8fd08c076d401fcf78aac4172080feb70baaa5ed8c1b05046ec278446330fbf77e8ca9e60c03945ded761a641a7e1",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.wantErr == nil {
|
||||
tt.wantErr = require.NoError
|
||||
}
|
||||
|
||||
fh, err := os.Open(tt.fixture)
|
||||
require.NoError(t, err)
|
||||
|
||||
got, err := NewDigestsFromFile(fh, tt.hashes)
|
||||
tt.wantErr(t, err)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHashers(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
names []string
|
||||
want []crypto.Hash
|
||||
wantErr require.ErrorAssertionFunc
|
||||
}{
|
||||
{
|
||||
name: "check supported hash algorithms",
|
||||
names: []string{"MD-5", "shA1", "sHa224", "sha---256", "sha384", "sha512"},
|
||||
want: []crypto.Hash{
|
||||
crypto.MD5,
|
||||
crypto.SHA1,
|
||||
crypto.SHA224,
|
||||
crypto.SHA256,
|
||||
crypto.SHA384,
|
||||
crypto.SHA512,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "error on unsupported hash algorithm",
|
||||
names: []string{"made-up"},
|
||||
wantErr: require.Error,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.wantErr == nil {
|
||||
tt.wantErr = require.NoError
|
||||
}
|
||||
got, err := Hashers(tt.names...)
|
||||
tt.wantErr(t, err)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
hello, file!
|
||||
Reference in New Issue
Block a user