mirror of
https://github.com/aquasecurity/trivy.git
synced 2025-12-23 07:29:00 -08:00
Analyze command (fanal#12)
* Extract commands * Analyze commands * Add comment * Resolve dependency
This commit is contained in:
@@ -15,9 +15,10 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
osAnalyzers []OSAnalyzer
|
||||
pkgAnalyzers []PkgAnalyzer
|
||||
libAnalyzers []LibraryAnalyzer
|
||||
osAnalyzers []OSAnalyzer
|
||||
pkgAnalyzers []PkgAnalyzer
|
||||
libAnalyzers []LibraryAnalyzer
|
||||
commandAnalyzers []CommandAnalyzer
|
||||
|
||||
// ErrUnknownOS occurs when unknown OS is analyzed.
|
||||
ErrUnknownOS = xerrors.New("Unknown OS")
|
||||
@@ -35,6 +36,11 @@ type PkgAnalyzer interface {
|
||||
RequiredFiles() []string
|
||||
}
|
||||
|
||||
type CommandAnalyzer interface {
|
||||
Analyze(OS, extractor.FileMap) ([]Package, error)
|
||||
RequiredFiles() []string
|
||||
}
|
||||
|
||||
type FilePath string
|
||||
|
||||
type LibraryAnalyzer interface {
|
||||
@@ -74,6 +80,10 @@ func RegisterPkgAnalyzer(analyzer PkgAnalyzer) {
|
||||
pkgAnalyzers = append(pkgAnalyzers, analyzer)
|
||||
}
|
||||
|
||||
func RegisterCommandAnalyzer(analyzer CommandAnalyzer) {
|
||||
commandAnalyzers = append(commandAnalyzers, analyzer)
|
||||
}
|
||||
|
||||
func RegisterLibraryAnalyzer(analyzer LibraryAnalyzer) {
|
||||
libAnalyzers = append(libAnalyzers, analyzer)
|
||||
}
|
||||
@@ -92,7 +102,7 @@ func RequiredFilenames() []string {
|
||||
return filenames
|
||||
}
|
||||
|
||||
func Analyze(ctx context.Context, imageName string, opts ...types.DockerOption) (filesMap extractor.FileMap, err error) {
|
||||
func Analyze(ctx context.Context, imageName string, opts ...types.DockerOption) (fileMap extractor.FileMap, err error) {
|
||||
// default docker option
|
||||
opt := types.DockerOption{
|
||||
Timeout: 600 * time.Second,
|
||||
@@ -105,27 +115,27 @@ func Analyze(ctx context.Context, imageName string, opts ...types.DockerOption)
|
||||
r, err := e.SaveLocalImage(ctx, imageName)
|
||||
if err != nil {
|
||||
// when no docker daemon is installed or no image exists in the local machine
|
||||
filesMap, err = e.Extract(ctx, imageName, RequiredFilenames())
|
||||
fileMap, err = e.Extract(ctx, imageName, RequiredFilenames())
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("failed to extract files: %w", err)
|
||||
}
|
||||
return filesMap, nil
|
||||
return fileMap, nil
|
||||
}
|
||||
|
||||
filesMap, err = e.ExtractFromFile(ctx, r, RequiredFilenames())
|
||||
fileMap, err = e.ExtractFromFile(ctx, r, RequiredFilenames())
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("failed to extract files from saved tar: %w", err)
|
||||
}
|
||||
return filesMap, nil
|
||||
return fileMap, nil
|
||||
}
|
||||
|
||||
func AnalyzeFromFile(ctx context.Context, r io.ReadCloser) (filesMap extractor.FileMap, err error) {
|
||||
func AnalyzeFromFile(ctx context.Context, r io.ReadCloser) (fileMap extractor.FileMap, err error) {
|
||||
e := docker.NewDockerExtractor(types.DockerOption{})
|
||||
filesMap, err = e.ExtractFromFile(ctx, r, RequiredFilenames())
|
||||
fileMap, err = e.ExtractFromFile(ctx, r, RequiredFilenames())
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("failed to extract files from tar: %w", err)
|
||||
}
|
||||
return filesMap, nil
|
||||
return fileMap, nil
|
||||
}
|
||||
|
||||
func GetOS(filesMap extractor.FileMap) (OS, error) {
|
||||
@@ -151,6 +161,17 @@ func GetPackages(filesMap extractor.FileMap) ([]Package, error) {
|
||||
return nil, ErrUnknownOS
|
||||
}
|
||||
|
||||
func GetPackagesFromCommands(targetOS OS, filesMap extractor.FileMap) ([]Package, error) {
|
||||
for _, analyzer := range commandAnalyzers {
|
||||
pkgs, err := analyzer.Analyze(targetOS, filesMap)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
return pkgs, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func CheckPackage(pkg *Package) bool {
|
||||
return pkg.Name != "" && pkg.Version != ""
|
||||
}
|
||||
|
||||
271
analyzer/command/apk/apk.go
Normal file
271
analyzer/command/apk/apk.go
Normal file
@@ -0,0 +1,271 @@
|
||||
package apk
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/knqyf263/fanal/extractor/docker"
|
||||
|
||||
"github.com/knqyf263/fanal/analyzer/os"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/knqyf263/fanal/analyzer"
|
||||
"github.com/knqyf263/fanal/extractor"
|
||||
)
|
||||
|
||||
func init() {
|
||||
analyzer.RegisterCommandAnalyzer(&alpineCmdAnalyzer{})
|
||||
}
|
||||
|
||||
type alpineCmdAnalyzer struct{}
|
||||
|
||||
type apkIndex struct {
|
||||
Package map[string]archive
|
||||
Provide provide
|
||||
}
|
||||
|
||||
type archive struct {
|
||||
Origin string
|
||||
Versions version
|
||||
Dependencies []string
|
||||
Provides []string
|
||||
}
|
||||
|
||||
type provide struct {
|
||||
SO map[string]pkg // package which provides the shared object
|
||||
Package map[string]pkg // package which provides the package
|
||||
}
|
||||
|
||||
type pkg struct {
|
||||
Package string
|
||||
Versions version
|
||||
}
|
||||
|
||||
type version map[string]int
|
||||
|
||||
const (
|
||||
apkIndexArchiveURL = "https://raw.githubusercontent.com/knqyf263/apkIndex-archive/master/alpine/v%s/main/x86_64/history.json"
|
||||
)
|
||||
|
||||
var (
|
||||
apkIndexArchive *apkIndex
|
||||
)
|
||||
|
||||
func (a alpineCmdAnalyzer) Analyze(targetOS analyzer.OS, fileMap extractor.FileMap) (pkgs []analyzer.Package, err error) {
|
||||
if targetOS.Family != os.Alpine {
|
||||
return nil, xerrors.New("not target")
|
||||
}
|
||||
|
||||
if err := a.fetchApkIndexArchive(targetOS); err != nil {
|
||||
log.Println(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, filename := range a.RequiredFiles() {
|
||||
file, ok := fileMap[filename]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
var config docker.Config
|
||||
if err := json.Unmarshal(file, &config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pkgs = append(pkgs, a.parseConfig(config)...)
|
||||
}
|
||||
if len(pkgs) == 0 {
|
||||
return pkgs, errors.New("No package detected")
|
||||
}
|
||||
return pkgs, nil
|
||||
}
|
||||
func (a alpineCmdAnalyzer) fetchApkIndexArchive(targetOS analyzer.OS) (err error) {
|
||||
if apkIndexArchive != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 3.9.3 => 3.9
|
||||
osVer := targetOS.Name
|
||||
if strings.Count(osVer, ".") > 1 {
|
||||
osVer = osVer[:strings.LastIndex(osVer, ".")]
|
||||
}
|
||||
|
||||
url := fmt.Sprintf(apkIndexArchiveURL, osVer)
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("failed to fetch APKINDEX archive: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
apkIndexArchive = &apkIndex{}
|
||||
if err = json.NewDecoder(resp.Body).Decode(apkIndexArchive); err != nil {
|
||||
return xerrors.Errorf("failed to decode APKINDEX JSON: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a alpineCmdAnalyzer) parseConfig(config docker.Config) (packages []analyzer.Package) {
|
||||
envs := map[string]string{}
|
||||
for _, env := range config.ContainerConfig.Env {
|
||||
index := strings.Index(env, "=")
|
||||
envs["$"+env[:index]] = env[index+1:]
|
||||
}
|
||||
|
||||
uniqPkgs := map[string]analyzer.Package{}
|
||||
for _, history := range config.History {
|
||||
pkgs := a.parseCommand(history.CreatedBy, envs)
|
||||
pkgs = a.resolveDependencies(pkgs)
|
||||
results := a.guessVersion(pkgs, history.Created)
|
||||
for _, result := range results {
|
||||
uniqPkgs[result.Name] = result
|
||||
}
|
||||
}
|
||||
for _, pkg := range uniqPkgs {
|
||||
packages = append(packages, pkg)
|
||||
}
|
||||
|
||||
return packages
|
||||
}
|
||||
|
||||
func (a alpineCmdAnalyzer) parseCommand(command string, envs map[string]string) (pkgs []string) {
|
||||
if strings.Contains(command, "#(nop)") {
|
||||
return nil
|
||||
}
|
||||
|
||||
command = strings.TrimPrefix(command, "/bin/sh -c")
|
||||
var commands []string
|
||||
for _, cmd := range strings.Split(command, "&&") {
|
||||
for _, c := range strings.Split(cmd, ";") {
|
||||
commands = append(commands, strings.TrimSpace(c))
|
||||
}
|
||||
}
|
||||
for _, cmd := range commands {
|
||||
if !strings.HasPrefix(cmd, "apk") {
|
||||
continue
|
||||
}
|
||||
|
||||
var add bool
|
||||
for _, field := range strings.Fields(cmd) {
|
||||
if strings.HasPrefix(field, "-") || strings.HasPrefix(field, ".") {
|
||||
continue
|
||||
} else if field == "add" {
|
||||
add = true
|
||||
} else if add {
|
||||
if strings.HasPrefix(field, "$") {
|
||||
for _, pkg := range strings.Fields(envs[field]) {
|
||||
pkgs = append(pkgs, pkg)
|
||||
}
|
||||
continue
|
||||
}
|
||||
pkgs = append(pkgs, field)
|
||||
}
|
||||
}
|
||||
}
|
||||
return pkgs
|
||||
}
|
||||
func (a alpineCmdAnalyzer) resolveDependencies(originalPkgs []string) (pkgs []string) {
|
||||
uniqPkgs := map[string]struct{}{}
|
||||
for _, pkgName := range originalPkgs {
|
||||
if _, ok := uniqPkgs[pkgName]; ok {
|
||||
continue
|
||||
}
|
||||
for _, p := range a.resolveDependency(pkgName) {
|
||||
uniqPkgs[p] = struct{}{}
|
||||
}
|
||||
}
|
||||
for pkg := range uniqPkgs {
|
||||
pkgs = append(pkgs, pkg)
|
||||
}
|
||||
return pkgs
|
||||
}
|
||||
|
||||
func (a alpineCmdAnalyzer) resolveDependency(pkgName string) (pkgNames []string) {
|
||||
pkg, ok := apkIndexArchive.Package[pkgName]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
pkgNames = append(pkgNames, pkgName)
|
||||
for _, dependency := range pkg.Dependencies {
|
||||
// sqlite-libs=3.26.0-r3 => sqlite-libs
|
||||
if strings.Contains(dependency, "=") {
|
||||
dependency = dependency[:strings.Index(dependency, "=")]
|
||||
}
|
||||
|
||||
if strings.HasPrefix(dependency, "so:") {
|
||||
soProvidePkg := apkIndexArchive.Provide.SO[dependency[3:]].Package
|
||||
pkgNames = append(pkgNames, a.resolveDependency(soProvidePkg)...)
|
||||
continue
|
||||
} else if strings.HasPrefix(dependency, "pc:") || strings.HasPrefix(dependency, "cmd:") {
|
||||
continue
|
||||
}
|
||||
pkgProvidePkg, ok := apkIndexArchive.Provide.Package[dependency]
|
||||
if ok {
|
||||
pkgNames = append(pkgNames, a.resolveDependency(pkgProvidePkg.Package)...)
|
||||
continue
|
||||
}
|
||||
pkgNames = append(pkgNames, a.resolveDependency(dependency)...)
|
||||
}
|
||||
return pkgNames
|
||||
}
|
||||
|
||||
type historyVersion struct {
|
||||
Version string
|
||||
BuiltAt int
|
||||
}
|
||||
|
||||
func (a alpineCmdAnalyzer) guessVersion(originalPkgs []string, createdAt time.Time) (pkgs []analyzer.Package) {
|
||||
for _, pkg := range originalPkgs {
|
||||
archive, ok := apkIndexArchive.Package[pkg]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
var historyVersions []historyVersion
|
||||
for version, builtAt := range archive.Versions {
|
||||
historyVersions = append(historyVersions, historyVersion{
|
||||
Version: version,
|
||||
BuiltAt: builtAt,
|
||||
})
|
||||
}
|
||||
sort.Slice(historyVersions, func(i, j int) bool {
|
||||
return historyVersions[i].BuiltAt < historyVersions[j].BuiltAt
|
||||
})
|
||||
|
||||
createdUnix := int(createdAt.Unix())
|
||||
var candidateVersion string
|
||||
for _, historyVersion := range historyVersions {
|
||||
if historyVersion.BuiltAt <= createdUnix {
|
||||
candidateVersion = historyVersion.Version
|
||||
} else if createdUnix < historyVersion.BuiltAt {
|
||||
break
|
||||
}
|
||||
}
|
||||
if candidateVersion == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
pkgs = append(pkgs, analyzer.Package{
|
||||
Name: pkg,
|
||||
Version: candidateVersion,
|
||||
})
|
||||
|
||||
// Add origin package name
|
||||
if archive.Origin != "" && archive.Origin != pkg {
|
||||
pkgs = append(pkgs, analyzer.Package{
|
||||
Name: archive.Origin,
|
||||
Version: candidateVersion,
|
||||
})
|
||||
}
|
||||
}
|
||||
return pkgs
|
||||
}
|
||||
|
||||
func (a alpineCmdAnalyzer) RequiredFiles() []string {
|
||||
return []string{"/config"} // special file
|
||||
}
|
||||
431
analyzer/command/apk/apk_test.go
Normal file
431
analyzer/command/apk/apk_test.go
Normal file
File diff suppressed because one or more lines are too long
75866
analyzer/command/apk/testdata/history_v3.9.json
vendored
Normal file
75866
analyzer/command/apk/testdata/history_v3.9.json
vendored
Normal file
File diff suppressed because it is too large
Load Diff
@@ -12,6 +12,7 @@ import (
|
||||
"github.com/knqyf263/fanal/cache"
|
||||
|
||||
"github.com/knqyf263/fanal/analyzer"
|
||||
_ "github.com/knqyf263/fanal/analyzer/command/apk"
|
||||
_ "github.com/knqyf263/fanal/analyzer/library/bundler"
|
||||
_ "github.com/knqyf263/fanal/analyzer/library/cargo"
|
||||
_ "github.com/knqyf263/fanal/analyzer/library/composer"
|
||||
@@ -79,6 +80,12 @@ func run() (err error) {
|
||||
}
|
||||
fmt.Printf("Packages: %d\n", len(pkgs))
|
||||
|
||||
pkgs, err = analyzer.GetPackagesFromCommands(os, files)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("Packages: %d\n", len(pkgs))
|
||||
|
||||
libs, err := analyzer.GetLibraries(files)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -10,6 +10,9 @@ import (
|
||||
"log"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
digest "github.com/opencontainers/go-digest"
|
||||
|
||||
"github.com/knqyf263/fanal/extractor"
|
||||
"github.com/knqyf263/fanal/extractor/docker/token/ecr"
|
||||
@@ -21,7 +24,6 @@ import (
|
||||
"github.com/genuinetools/reg/registry"
|
||||
"github.com/knqyf263/fanal/cache"
|
||||
"github.com/knqyf263/nested"
|
||||
"github.com/opencontainers/go-digest"
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
@@ -36,6 +38,20 @@ type manifest struct {
|
||||
Layers []string `json:"Layers"`
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
ContainerConfig containerConfig `json:"container_config"`
|
||||
History []History
|
||||
}
|
||||
|
||||
type containerConfig struct {
|
||||
Env []string
|
||||
}
|
||||
|
||||
type History struct {
|
||||
Created time.Time
|
||||
CreatedBy string `json:"created_by"`
|
||||
}
|
||||
|
||||
type layer struct {
|
||||
ID digest.Digest
|
||||
Content io.ReadCloser
|
||||
@@ -219,12 +235,31 @@ func (d DockerExtractor) Extract(ctx context.Context, imageName string, filename
|
||||
opqInLayers[layerID] = opqDirs
|
||||
}
|
||||
|
||||
return applyLayers(layerIDs, filesInLayers, opqInLayers)
|
||||
fileMap, err := applyLayers(layerIDs, filesInLayers, opqInLayers)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("failed to apply layers: %w", err)
|
||||
}
|
||||
|
||||
// download config file
|
||||
rc, err := r.DownloadLayer(ctx, image.Path, m.Manifest.Config.Digest)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("error in layer download: %w", err)
|
||||
}
|
||||
config, err := ioutil.ReadAll(rc)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("failed to decode config JSON: %w", err)
|
||||
}
|
||||
|
||||
// special file for command analyzer
|
||||
fileMap["/config"] = config
|
||||
|
||||
return fileMap, nil
|
||||
}
|
||||
|
||||
func (d DockerExtractor) ExtractFromFile(ctx context.Context, r io.Reader, filenames []string) (extractor.FileMap, error) {
|
||||
manifests := make([]manifest, 0)
|
||||
filesInLayers := make(map[string]extractor.FileMap)
|
||||
filesInLayers := map[string]extractor.FileMap{}
|
||||
tmpJSONs := extractor.FileMap{}
|
||||
opqInLayers := make(map[string]opqDirs)
|
||||
|
||||
tr := tar.NewReader(r)
|
||||
@@ -241,6 +276,12 @@ func (d DockerExtractor) ExtractFromFile(ctx context.Context, r io.Reader, filen
|
||||
if err := json.NewDecoder(tr).Decode(&manifests); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case strings.HasSuffix(header.Name, ".json"):
|
||||
// save all JSON temporarily for config JSON
|
||||
tmpJSONs[header.Name], err = ioutil.ReadAll(tr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case strings.HasSuffix(header.Name, ".tar"):
|
||||
layerID := filepath.Base(filepath.Dir(header.Name))
|
||||
files, opqDirs, err := d.ExtractFiles(tr, filenames)
|
||||
@@ -257,7 +298,15 @@ func (d DockerExtractor) ExtractFromFile(ctx context.Context, r io.Reader, filen
|
||||
return nil, xerrors.New("Invalid image")
|
||||
}
|
||||
|
||||
return applyLayers(manifests[0].Layers, filesInLayers, opqInLayers)
|
||||
fileMap, err := applyLayers(manifests[0].Layers, filesInLayers, opqInLayers)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// special file for command analyzer
|
||||
fileMap["/config"] = tmpJSONs[manifests[0].Config]
|
||||
|
||||
return fileMap, nil
|
||||
}
|
||||
|
||||
func (d DockerExtractor) ExtractFiles(layer io.Reader, filenames []string) (extractor.FileMap, opqDirs, error) {
|
||||
@@ -297,7 +346,6 @@ func (d DockerExtractor) ExtractFiles(layer io.Reader, filenames []string) (extr
|
||||
continue
|
||||
}
|
||||
|
||||
// Extract the element
|
||||
if hdr.Typeflag == tar.TypeSymlink || hdr.Typeflag == tar.TypeLink || hdr.Typeflag == tar.TypeReg {
|
||||
d, err := ioutil.ReadAll(tr)
|
||||
if err != nil {
|
||||
|
||||
@@ -19,20 +19,29 @@ func TestExtractFromFile(t *testing.T) {
|
||||
{
|
||||
file: "testdata/image1.tar",
|
||||
filenames: []string{"var/foo", "etc/test/bar"},
|
||||
FileMap: extractor.FileMap{"etc/test/bar": []byte("bar\n")},
|
||||
err: nil,
|
||||
FileMap: extractor.FileMap{
|
||||
"etc/test/bar": []byte("bar\n"),
|
||||
"/config": []byte(`{"architecture":"amd64","config":{"Hostname":"","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":["/bin/sh"],"ArgsEscaped":true,"Image":"sha256:e641703a6c77abde58a2e2d5e506da5ac61a648bdb17fba7c3325db9d2ba4ded","Volumes":null,"WorkingDir":"","Entrypoint":null,"OnBuild":null,"Labels":null},"container":"7dfcd2c8327651024825c14e0d8752544f59c03efeca291a71e532b7e0ca66bf","container_config":{"Hostname":"","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":["/bin/sh","-c","rm /var/foo \u0026\u0026 rm -rf /etc/test \u0026\u0026 mkdir /etc/test \u0026\u0026 echo bar \u003e /etc/test/bar"],"ArgsEscaped":true,"Image":"sha256:e641703a6c77abde58a2e2d5e506da5ac61a648bdb17fba7c3325db9d2ba4ded","Volumes":null,"WorkingDir":"","Entrypoint":null,"OnBuild":null,"Labels":null},"created":"2019-04-07T04:27:16.291049098Z","docker_version":"18.09.2","history":[{"created":"2019-03-07T22:19:46.661698137Z","created_by":"/bin/sh -c #(nop) ADD file:38bc6b51693b13d84a63e281403e2f6d0218c44b1d7ff12157c4523f9f0ebb1e in / "},{"created":"2019-03-07T22:19:46.815331171Z","created_by":"/bin/sh -c #(nop) CMD [\"/bin/sh\"]","empty_layer":true},{"created":"2019-04-07T04:08:02.548475493Z","created_by":"/bin/sh -c mkdir /etc/test \u0026\u0026 touch /var/foo \u0026\u0026 touch /etc/test/test"},{"created":"2019-04-07T04:27:16.291049098Z","created_by":"/bin/sh -c rm /var/foo \u0026\u0026 rm -rf /etc/test \u0026\u0026 mkdir /etc/test \u0026\u0026 echo bar \u003e /etc/test/bar"}],"os":"linux","rootfs":{"type":"layers","diff_ids":["sha256:d9ff549177a94a413c425ffe14ae1cc0aa254bc9c7df781add08e7d2fba25d27","sha256:f75441026d68038ca80e92f342fb8f3c0f1faeec67b5a80c98f033a65beaef5a","sha256:a8b87ccf2f2f94b9e23308560800afa3f272aa6db5cc7d9b0119b6843889cff2"]}}`),
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
file: "testdata/image2.tar",
|
||||
filenames: []string{"home/app/Gemfile", "home/app2/Gemfile"},
|
||||
FileMap: extractor.FileMap{"home/app2/Gemfile": []byte("gem")},
|
||||
err: nil,
|
||||
FileMap: extractor.FileMap{
|
||||
"home/app2/Gemfile": []byte("gem"),
|
||||
"/config": []byte(`{"architecture":"amd64","config":{"Hostname":"","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":["/bin/sh"],"ArgsEscaped":true,"Image":"sha256:4fe3bbb628df60571f88cb053db9e2c9ec2f1c1e8373db9b026d0e582ef01d6d","Volumes":null,"WorkingDir":"","Entrypoint":null,"OnBuild":null,"Labels":null},"container":"7b1b7a0cfacbce82b51230bf0c6354e64cd0068e4e51180ab717890fc805bdf5","container_config":{"Hostname":"","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":["/bin/sh","-c","mv /home/app /home/app2"],"ArgsEscaped":true,"Image":"sha256:4fe3bbb628df60571f88cb053db9e2c9ec2f1c1e8373db9b026d0e582ef01d6d","Volumes":null,"WorkingDir":"","Entrypoint":null,"OnBuild":null,"Labels":null},"created":"2019-04-07T05:32:59.607884934Z","docker_version":"18.09.2","history":[{"created":"2019-03-07T22:19:46.661698137Z","created_by":"/bin/sh -c #(nop) ADD file:38bc6b51693b13d84a63e281403e2f6d0218c44b1d7ff12157c4523f9f0ebb1e in / "},{"created":"2019-03-07T22:19:46.815331171Z","created_by":"/bin/sh -c #(nop) CMD [\"/bin/sh\"]","empty_layer":true},{"created":"2019-04-07T05:32:58.27180871Z","created_by":"/bin/sh -c mkdir /home/app \u0026\u0026 echo -n gem \u003e /home/app/Gemfile"},{"created":"2019-04-07T05:32:59.607884934Z","created_by":"/bin/sh -c mv /home/app /home/app2"}],"os":"linux","rootfs":{"type":"layers","diff_ids":["sha256:d9ff549177a94a413c425ffe14ae1cc0aa254bc9c7df781add08e7d2fba25d27","sha256:f9e7e541d5be4537a826c4c6cb68b603a8e552c22e28ac726e9be6b22f51af44","sha256:718fb3edf377530e3713cd074d141827d05f654f6389e827c344b7fcff153025"]}}`),
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
file: "testdata/image3.tar",
|
||||
filenames: []string{"home/app/Gemfile", "home/app2/Pipfile", "home/app/Pipfile"},
|
||||
FileMap: extractor.FileMap{"home/app/Pipfile": []byte("pip")},
|
||||
err: nil,
|
||||
FileMap: extractor.FileMap{
|
||||
"home/app/Pipfile": []byte("pip"),
|
||||
"/config": []byte(`{"architecture":"amd64","config":{"Hostname":"","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":["/bin/sh"],"ArgsEscaped":true,"Image":"sha256:53dca1cadfa555151d28ac616df868eed4fc935f21af393118f4fbc36d9fb24a","Volumes":null,"WorkingDir":"","Entrypoint":null,"OnBuild":null,"Labels":null},"container":"42b6c68c1704e06fbffecfee6ae5400978cf508790d563e2bda4d1b20ce93c6d","container_config":{"Hostname":"","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":["/bin/sh","-c","rm -rf /home/app \u0026\u0026 mv /home/app2 /home/app"],"ArgsEscaped":true,"Image":"sha256:53dca1cadfa555151d28ac616df868eed4fc935f21af393118f4fbc36d9fb24a","Volumes":null,"WorkingDir":"","Entrypoint":null,"OnBuild":null,"Labels":null},"created":"2019-04-07T05:36:08.899764053Z","docker_version":"18.09.2","history":[{"created":"2019-03-07T22:19:46.661698137Z","created_by":"/bin/sh -c #(nop) ADD file:38bc6b51693b13d84a63e281403e2f6d0218c44b1d7ff12157c4523f9f0ebb1e in / "},{"created":"2019-03-07T22:19:46.815331171Z","created_by":"/bin/sh -c #(nop) CMD [\"/bin/sh\"]","empty_layer":true},{"created":"2019-04-07T05:32:58.27180871Z","created_by":"/bin/sh -c mkdir /home/app \u0026\u0026 echo -n gem \u003e /home/app/Gemfile"},{"created":"2019-04-07T05:36:07.629894435Z","created_by":"/bin/sh -c mkdir /home/app2 \u0026\u0026 echo -n pip \u003e /home/app2/Pipfile"},{"created":"2019-04-07T05:36:08.899764053Z","created_by":"/bin/sh -c rm -rf /home/app \u0026\u0026 mv /home/app2 /home/app"}],"os":"linux","rootfs":{"type":"layers","diff_ids":["sha256:d9ff549177a94a413c425ffe14ae1cc0aa254bc9c7df781add08e7d2fba25d27","sha256:f9e7e541d5be4537a826c4c6cb68b603a8e552c22e28ac726e9be6b22f51af44","sha256:5a917ce45575a009bb5b4f462ed84522c7f642647b62a9f2b2bdfc2275f85104","sha256:50022087bbe2b08d1ce033122a56c7cf74cc1d1d6dae97a397226dd49a309c3b"]}}`),
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
file: "testdata/image4.tar",
|
||||
@@ -40,6 +49,7 @@ func TestExtractFromFile(t *testing.T) {
|
||||
FileMap: extractor.FileMap{
|
||||
".def": []byte("def"),
|
||||
"foo/.abc": []byte("abc"),
|
||||
"/config": []byte(`{"architecture":"amd64","config":{"Hostname":"","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":["/bin/sh"],"ArgsEscaped":true,"Image":"sha256:cabfb6dd9c622b8cd0efdc7bb38ed9a9d2001a32c2b5d5c174e284784df712e8","Volumes":null,"WorkingDir":"","Entrypoint":null,"OnBuild":null,"Labels":null},"container":"8290b131834ed7ef8c388a290594afeaa5daea024031a2551c8dedfc845fd09e","container_config":{"Hostname":"","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":["/bin/sh","-c","rm -rf /.foo"],"ArgsEscaped":true,"Image":"sha256:cabfb6dd9c622b8cd0efdc7bb38ed9a9d2001a32c2b5d5c174e284784df712e8","Volumes":null,"WorkingDir":"","Entrypoint":null,"OnBuild":null,"Labels":null},"created":"2019-04-07T05:48:16.088980845Z","docker_version":"18.09.2","history":[{"created":"2019-03-07T22:19:46.661698137Z","created_by":"/bin/sh -c #(nop) ADD file:38bc6b51693b13d84a63e281403e2f6d0218c44b1d7ff12157c4523f9f0ebb1e in / "},{"created":"2019-03-07T22:19:46.815331171Z","created_by":"/bin/sh -c #(nop) CMD [\"/bin/sh\"]","empty_layer":true},{"created":"2019-04-07T05:48:10.560447082Z","created_by":"/bin/sh -c echo -n abc \u003e .abc \u0026\u0026 echo -n def \u003e .def"},{"created":"2019-04-07T05:48:11.938256528Z","created_by":"/bin/sh -c mkdir foo \u0026\u0026 echo -n abc \u003e /foo/.abc \u0026\u0026 echo -n def \u003e /foo/.def"},{"created":"2019-04-07T05:48:13.188275588Z","created_by":"/bin/sh -c rm .abc /foo/.def"},{"created":"2019-04-07T05:48:14.569944213Z","created_by":"/bin/sh -c mkdir .foo \u0026\u0026 echo -n abc /.foo/.abc"},{"created":"2019-04-07T05:48:16.088980845Z","created_by":"/bin/sh -c rm -rf /.foo"}],"os":"linux","rootfs":{"type":"layers","diff_ids":["sha256:d9ff549177a94a413c425ffe14ae1cc0aa254bc9c7df781add08e7d2fba25d27","sha256:c42355fdc6d1a90c39b26ae5ac44c85c079f6da260def6bcb781ffcfe45ce6c9","sha256:b16629f22093ce5dfec353149661886cc1ca0c62ff30c450a82eba693eaedbd2","sha256:9717a79724f7114e32f004067a9cf96493812b2772f8a88096d1c43f7898d4f9","sha256:87c73b7beca2340705c988bb35235c66ae16b2ed2a6ce5b37b215f9bb08e7dc9","sha256:99cc8353ab2a712793601465751b9f518a35763db138e8b92b54f13e0c82d8b6"]}}`),
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
|
||||
1
go.mod
1
go.mod
@@ -14,6 +14,7 @@ require (
|
||||
github.com/knqyf263/go-dep-parser v0.0.0-20190511063217-d5d543bfc261
|
||||
github.com/knqyf263/go-rpmdb v0.0.0-20190501070121-10a1c42a10dc
|
||||
github.com/knqyf263/nested v0.0.1
|
||||
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348
|
||||
github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2
|
||||
github.com/pkg/errors v0.8.1
|
||||
golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5
|
||||
|
||||
2
go.sum
2
go.sum
@@ -65,6 +65,7 @@ github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/me
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.0 h1:xU6/SpYbvkNYiptHJYEDRseDLvYE7wSqhYYNy0QSUzI=
|
||||
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
@@ -198,6 +199,7 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2 h1:z99zHgr7hKfrUcX/KsoJk5FJfjTceCKIp96+biqP4To=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c h1:fqgJT0MGcGpPgpWU7VRdRjuArfcOvC4AoJmILihzhDg=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
|
||||
Reference in New Issue
Block a user