Analyze command (fanal#12)

* Extract commands

* Analyze commands

* Add comment

* Resolve dependency
This commit is contained in:
Teppei Fukuda
2019-05-12 11:18:34 +09:00
committed by GitHub
parent 856dd3a464
commit faed25bfec
9 changed files with 76679 additions and 22 deletions

View File

@@ -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
View 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
}

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@@ -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

View File

@@ -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 {

View File

@@ -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
View File

@@ -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
View File

@@ -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=