Skip to content

Commit

Permalink
Initial commit (#1)
Browse files Browse the repository at this point in the history
* Initial commit

* update

* add apk analyzer

* add dpkg analyzer

* Analyze docker image

* Update README

* Pull image

* Support private registry and use cache

* fix test

* fix for merge

* add rpm analyzer

* add tmp gcr

* Add test

* fix tmp : fix gcr bug on reg package

* fetch gcr container from credential

* use no auth docker token

* update alpine os analyzer

* continue package if no files found

* ignore vendor

* check OS

* add soft link file path

* Add rpm

* update comment

* Support library

* Add rpm analyzer without rpm command
  • Loading branch information
knqyf263 committed May 1, 2019
1 parent 4215cf8 commit 549812a
Show file tree
Hide file tree
Showing 52 changed files with 5,630 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Expand Up @@ -10,3 +10,7 @@

# Output of the go coverage tool, specifically when used with LiteIDE
*.out
.idea
main

vendor
15 changes: 15 additions & 0 deletions .travis.yml
@@ -0,0 +1,15 @@
language: go

go:
- "1.12"
before_install:
- go get github.com/mattn/goveralls
- go get golang.org/x/tools/cmd/cover
script:
- env GO111MODULE=on go test -v -covermode=count -coverprofile=coverage.out ./...
- $HOME/gopath/bin/goveralls -coverprofile=coverage.out -service=travis-ci -repotoken $COVERALLS_TOKEN
addons:
apt:
packages:
- rpm
- libdb-dev
25 changes: 25 additions & 0 deletions Makefile
@@ -0,0 +1,25 @@
export GO111MODULE=on

.PHONY: deps
deps:
go get -d

.PHONY: devel-deps
devel-deps: deps
GO111MODULE=off go get \
golang.org/x/lint/golint \
github.com/mattn/goveralls

.PHONY: test
test: deps
go test ./...

.PHONY: lint
lint: devel-deps
go vet ./...
golint -set_exit_status

.PHONY: cover
cover: devel-deps
goveralls

6 changes: 6 additions & 0 deletions README.md
@@ -1,2 +1,8 @@
# fanal
Static Analysis Library for Containers

[![GoDoc](https://godoc.org/github.com/knqyf263/fanal?status.svg)](https://godoc.org/github.com/knqyf263/fanal)
[![Build Status](https://travis-ci.org/knqyf263/fanal.svg?branch=master)](https://travis-ci.org/knqyf263/fanal)
[![Coverage Status](https://coveralls.io/repos/github/knqyf263/fanal/badge.svg?branch=master)](https://coveralls.io/github/knqyf263/fanal?branch=master)
[![Go Report Card](https://goreportcard.com/badge/github.com/knqyf263/fanal)](https://goreportcard.com/report/github.com/knqyf263/fanal)
[![MIT License](http://img.shields.io/badge/license-MIT-blue.svg?style=flat)](https://github.com/knqyf263/fanal/blob/master/LICENSE)
151 changes: 151 additions & 0 deletions analyzer/analyzer.go
@@ -0,0 +1,151 @@
package analyzer

import (
"context"
"io"
"time"

"golang.org/x/xerrors"

"github.com/knqyf263/fanal/extractor"
"github.com/knqyf263/go-dep-parser/pkg/types"
"github.com/pkg/errors"
)

var (
osAnalyzers []OSAnalyzer
pkgAnalyzers []PkgAnalyzer
libAnalyzers []LibraryAnalyzer

// ErrUnknownOS occurs when unknown OS is analyzed.
ErrUnknownOS = errors.New("Unknown OS")
// ErrPkgAnalysis occurs when the analysis of packages is failed.
ErrPkgAnalysis = errors.New("Failed to analyze packages")
)

type OSAnalyzer interface {
Analyze(extractor.FileMap) (OS, error)
RequiredFiles() []string
}

type PkgAnalyzer interface {
Analyze(extractor.FileMap) ([]Package, error)
RequiredFiles() []string
}

type FilePath string

type LibraryAnalyzer interface {
Analyze(extractor.FileMap) (map[FilePath][]types.Library, error)
RequiredFiles() []string
}

type OS struct {
Name string
Family string
}

type Package struct {
Name string
Version string
Release string
Epoch int
Type string
}

var (
TypeBinary = "binary"
TypeSource = "source"
)

type SrcPackage struct {
Name string `json:"name"`
Version string `json:"version"`
BinaryNames []string `json:"binaryNames"`
}

func RegisterOSAnalyzer(analyzer OSAnalyzer) {
osAnalyzers = append(osAnalyzers, analyzer)
}

func RegisterPkgAnalyzer(analyzer PkgAnalyzer) {
pkgAnalyzers = append(pkgAnalyzers, analyzer)
}

func RegisterLibraryAnalyzer(analyzer LibraryAnalyzer) {
libAnalyzers = append(libAnalyzers, analyzer)
}

func RequiredFilenames() []string {
filenames := []string{}
for _, analyzer := range osAnalyzers {
filenames = append(filenames, analyzer.RequiredFiles()...)
}
for _, analyzer := range pkgAnalyzers {
filenames = append(filenames, analyzer.RequiredFiles()...)
}
for _, analyzer := range libAnalyzers {
filenames = append(filenames, analyzer.RequiredFiles()...)
}
return filenames
}

func Analyze(ctx context.Context, imageName string) (filesMap extractor.FileMap, err error) {
e := extractor.NewDockerExtractor(extractor.DockerOption{Timeout: 600 * time.Second})
filesMap, err = e.Extract(ctx, imageName, RequiredFilenames())
if err != nil {
return nil, errors.Wrap(err, "Failed to extract files")
}
return filesMap, nil
}

func AnalyzeFromFile(ctx context.Context, r io.ReadCloser) (filesMap extractor.FileMap, err error) {
e := extractor.NewDockerExtractor(extractor.DockerOption{})
filesMap, err = e.ExtractFromFile(ctx, r, RequiredFilenames())
if err != nil {
return nil, errors.Wrap(err, "Failed to extract files")
}
return filesMap, nil
}

func GetOS(filesMap extractor.FileMap) (OS, error) {
for _, analyzer := range osAnalyzers {
os, err := analyzer.Analyze(filesMap)
if err != nil {
continue
}
return os, nil
}
return OS{}, ErrUnknownOS

}

func GetPackages(filesMap extractor.FileMap) ([]Package, error) {
for _, analyzer := range pkgAnalyzers {
pkgs, err := analyzer.Analyze(filesMap)
if err != nil {
continue
}
return pkgs, nil
}
return nil, ErrUnknownOS
}

func CheckPackage(pkg *Package) bool {
return pkg.Name != "" && pkg.Version != ""
}

func GetLibraries(filesMap extractor.FileMap) (map[FilePath][]types.Library, error) {
results := map[FilePath][]types.Library{}
for _, analyzer := range libAnalyzers {
libMap, err := analyzer.Analyze(filesMap)
if err != nil {
return nil, xerrors.Errorf("failed to analyze libraries: %w", err)
}

for filePath, libs := range libMap {
results[filePath] = libs
}
}
return results, nil
}
43 changes: 43 additions & 0 deletions analyzer/library/bundler/bundler.go
@@ -0,0 +1,43 @@
package bundler

import (
"bytes"
"path/filepath"

"github.com/knqyf263/fanal/analyzer"
"github.com/knqyf263/fanal/extractor"
"github.com/knqyf263/fanal/utils"
"github.com/knqyf263/go-dep-parser/pkg/bundler"
"github.com/knqyf263/go-dep-parser/pkg/types"
"golang.org/x/xerrors"
)

func init() {
analyzer.RegisterLibraryAnalyzer(&bundlerLibraryAnalyzer{})
}

type bundlerLibraryAnalyzer struct{}

func (a bundlerLibraryAnalyzer) Analyze(fileMap extractor.FileMap) (map[analyzer.FilePath][]types.Library, error) {
libMap := map[analyzer.FilePath][]types.Library{}
requiredFiles := a.RequiredFiles()

for filename, content := range fileMap {
basename := filepath.Base(filename)
if !utils.StringInSlice(basename, requiredFiles) {
continue
}

r := bytes.NewBuffer(content)
libs, err := bundler.Parse(r)
if err != nil {
return nil, xerrors.Errorf("invalid Gemfile.lock format: %w", err)
}
libMap[analyzer.FilePath(filename)] = libs
}
return libMap, nil
}

func (a bundlerLibraryAnalyzer) RequiredFiles() []string {
return []string{"Gemfile.lock"}
}
43 changes: 43 additions & 0 deletions analyzer/library/composer/composer.go
@@ -0,0 +1,43 @@
package composer

import (
"bytes"
"path/filepath"

"github.com/knqyf263/fanal/analyzer"
"github.com/knqyf263/fanal/extractor"
"github.com/knqyf263/fanal/utils"
"github.com/knqyf263/go-dep-parser/pkg/composer"
"github.com/knqyf263/go-dep-parser/pkg/types"
"golang.org/x/xerrors"
)

func init() {
analyzer.RegisterLibraryAnalyzer(&composerLibraryAnalyzer{})
}

type composerLibraryAnalyzer struct{}

func (a composerLibraryAnalyzer) Analyze(fileMap extractor.FileMap) (map[analyzer.FilePath][]types.Library, error) {
libMap := map[analyzer.FilePath][]types.Library{}
requiredFiles := a.RequiredFiles()

for filename, content := range fileMap {
basename := filepath.Base(filename)
if !utils.StringInSlice(basename, requiredFiles) {
continue
}

r := bytes.NewBuffer(content)
libs, err := composer.Parse(r)
if err != nil {
return nil, xerrors.Errorf("invalid composer.lock format: %w", err)
}
libMap[analyzer.FilePath(filename)] = libs
}
return libMap, nil
}

func (a composerLibraryAnalyzer) RequiredFiles() []string {
return []string{"composer.lock"}
}
43 changes: 43 additions & 0 deletions analyzer/library/npm/npm.go
@@ -0,0 +1,43 @@
package npm

import (
"bytes"
"path/filepath"

"github.com/knqyf263/fanal/analyzer"
"github.com/knqyf263/fanal/extractor"
"github.com/knqyf263/fanal/utils"
"github.com/knqyf263/go-dep-parser/pkg/npm"
"github.com/knqyf263/go-dep-parser/pkg/types"
"golang.org/x/xerrors"
)

func init() {
analyzer.RegisterLibraryAnalyzer(&npmLibraryAnalyzer{})
}

type npmLibraryAnalyzer struct{}

func (a npmLibraryAnalyzer) Analyze(fileMap extractor.FileMap) (map[analyzer.FilePath][]types.Library, error) {
libMap := map[analyzer.FilePath][]types.Library{}
requiredFiles := a.RequiredFiles()

for filename, content := range fileMap {
basename := filepath.Base(filename)
if !utils.StringInSlice(basename, requiredFiles) {
continue
}

r := bytes.NewBuffer(content)
libs, err := npm.Parse(r)
if err != nil {
return nil, xerrors.Errorf("invalid package-lock.json format: %w", err)
}
libMap[analyzer.FilePath(filename)] = libs
}
return libMap, nil
}

func (a npmLibraryAnalyzer) RequiredFiles() []string {
return []string{"package-lock.json"}
}
43 changes: 43 additions & 0 deletions analyzer/library/pipenv/pipenv.go
@@ -0,0 +1,43 @@
package pipenv

import (
"bytes"
"path/filepath"

"github.com/knqyf263/fanal/analyzer"
"github.com/knqyf263/fanal/extractor"
"github.com/knqyf263/fanal/utils"
"github.com/knqyf263/go-dep-parser/pkg/pipenv"
"github.com/knqyf263/go-dep-parser/pkg/types"
"golang.org/x/xerrors"
)

func init() {
analyzer.RegisterLibraryAnalyzer(&pipenvLibraryAnalyzer{})
}

type pipenvLibraryAnalyzer struct{}

func (a pipenvLibraryAnalyzer) Analyze(fileMap extractor.FileMap) (map[analyzer.FilePath][]types.Library, error) {
libMap := map[analyzer.FilePath][]types.Library{}
requiredFiles := a.RequiredFiles()

for filename, content := range fileMap {
basename := filepath.Base(filename)
if !utils.StringInSlice(basename, requiredFiles) {
continue
}

r := bytes.NewBuffer(content)
libs, err := pipenv.Parse(r)
if err != nil {
return nil, xerrors.Errorf("invalid Pipfile.lock format: %w", err)
}
libMap[analyzer.FilePath(filename)] = libs
}
return libMap, nil
}

func (a pipenvLibraryAnalyzer) RequiredFiles() []string {
return []string{"Pipfile.lock"}
}

0 comments on commit 549812a

Please sign in to comment.