diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 01135a11..25b28de4 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -29,19 +29,27 @@ jobs: os: [ubuntu-latest, macos-latest, windows-latest] steps: - uses: actions/checkout@v3 + with: + submodules: recursive + - name: Set up Go uses: actions/setup-go@v3 with: go-version: 1.18 # - name: Test cmd pkg # run: cd cmd && go test -v ./... + + - name: Install libgit2 + run: make libgit2 + if: matrix.os != 'windows-latest' + - name: Test core pkg env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: go test -v ./... + run: go test -tags=static -v ./... - name: Test httphandler pkg - run: cd httphandler && go test -v ./... + run: cd httphandler && go test -tags=static -v ./... - name: Build env: @@ -51,7 +59,7 @@ jobs: ArmoAuthServer: auth.armo.cloud ArmoERServer: report.armo.cloud ArmoWebsite: portal.armo.cloud - CGO_ENABLED: 0 + CGO_ENABLED: 1 run: python3 --version && python3 build.py - name: Smoke Testing @@ -93,6 +101,8 @@ jobs: steps: - uses: actions/checkout@v2 + with: + submodules: recursive - name: Set image version id: image-version diff --git a/.github/workflows/build_dev.yaml b/.github/workflows/build_dev.yaml index ef50af89..153bab25 100644 --- a/.github/workflows/build_dev.yaml +++ b/.github/workflows/build_dev.yaml @@ -12,6 +12,8 @@ jobs: os: [ubuntu-latest, macos-latest, windows-latest] steps: - uses: actions/checkout@v3 + with: + submodules: recursive - name: Set up Go uses: actions/setup-go@v3 with: @@ -28,13 +30,17 @@ jobs: # - name: Test cmd pkg # run: cd cmd && go test -v ./... + - name: Install libgit2 + run: make libgit2 + if: matrix.os != 'windows-latest' + - name: Test core pkg env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: go test -v ./... + run: go test -tags=static -v ./... - name: Test httphandler pkg - run: cd httphandler && go test -v ./... + run: cd httphandler && go test -tags=static -v ./... - name: Build env: @@ -44,7 +50,7 @@ jobs: ArmoAuthServer: auth.armo.cloud ArmoERServer: report.armo.cloud ArmoWebsite: portal.armo.cloud - CGO_ENABLED: 0 + CGO_ENABLED: 1 run: python3 --version && python3 build.py - name: Smoke Testing @@ -71,6 +77,8 @@ jobs: steps: - uses: actions/checkout@v2 + with: + submodules: recursive - name: Set image version id: image-version diff --git a/.github/workflows/master_pr_checks.yaml b/.github/workflows/pr_checks.yaml similarity index 78% rename from .github/workflows/master_pr_checks.yaml rename to .github/workflows/pr_checks.yaml index ae9a279e..a49c7833 100644 --- a/.github/workflows/master_pr_checks.yaml +++ b/.github/workflows/pr_checks.yaml @@ -1,8 +1,8 @@ -name: master-pr +name: pr-checks on: pull_request: - branches: [ master ] + branches: [ master, dev ] types: [ edited, opened, synchronize, reopened ] jobs: build: @@ -13,21 +13,28 @@ jobs: os: [ubuntu-latest, macos-latest, windows-latest] steps: - uses: actions/checkout@v3 + with: + submodules: recursive + - name: Set up Go uses: actions/setup-go@v3 with: go-version: 1.18 + - name: Install libgit2 + run: make libgit2 + if: matrix.os != 'windows-latest' + # - name: Test cmd pkg # run: cd cmd && go test -v ./... - name: Test core pkg env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: go test -v ./... + run: go test -tags=static -v ./... - name: Test httphandler pkg - run: cd httphandler && go test -v ./... + run: cd httphandler && go test -tags=static -v ./... - name: Build env: @@ -37,7 +44,7 @@ jobs: ArmoAuthServer: auth.armo.cloud ArmoERServer: report.armo.cloud ArmoWebsite: portal.armo.cloud - CGO_ENABLED: 0 + CGO_ENABLED: 1 run: python3 --version && python3 build.py - name: Smoke Testing diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..0cda0556 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "git2go"] + path = git2go + url = https://github.com/libgit2/git2go.git diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..1fd42de0 --- /dev/null +++ b/Makefile @@ -0,0 +1,20 @@ +.PHONY: test all build libgit2 + +# default task invoked while running make +all: libgit2 build + +export CGO_ENABLED=1 + +# build and install libgit2 +libgit2: + git submodule update --init --recursive + cd git2go; make install-static + +# go build tags +TAGS = "static" + +build: + go build -v -tags=$(TAGS) . + +test: + go test -v -tags=$(TAGS) ./... diff --git a/README.md b/README.md index 7d656a41..5202fa97 100644 --- a/README.md +++ b/README.md @@ -144,9 +144,6 @@ home-manager: Or to your profile (not preferred): `nix-env --install -A nixpkgs.kubescape` -## Install using Go - -With a sufficient version of `go` you can install and build with `go install github.com/armosec/kubescape/v2@latest` ## Usage & Examples @@ -302,7 +299,6 @@ Now you can submit the results to the Kubescape SaaS version - kubescape submit results path/to/results.json ``` - # Integrations ## VS Code Extension @@ -315,6 +311,78 @@ Scan the YAML files while writing them using the [vs code extension](https://git View Kubescape scan results directly in [Lens IDE](https://k8slens.dev/) using kubescape [Lens extension](https://github.com/armosec/lens-kubescape/blob/master/README.md) + +# Building Kubescape + +## Windows + +``` +go build . +``` +OR +``` +make build +``` + +## Linux / MacOS + +1. Install libgit2 dependency + + ``` + make libgit2 + ``` + +> `cmake` is required to build libgit2. You can install it by running `sudo apt-get install cmake` (Linux) or `brew install cmake` (MacOS) + +2. Build + + ``` + make build + ``` +3. Test + + ``` + make test + ``` + +## VS code configuration samples + +You can use the samples files below to setup your VS code environment for building and debugging purposes. + + +```json5 +// .vscode/settings.json +{ + "go.testTags": "static", + "go.buildTags": "static", + "go.toolsEnvVars": { + "CGO_ENABLED": "1" + } +} +``` + +```json5 +// .vscode/launch.json +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Launch Package", + "type": "go", + "request": "launch", + "mode": "auto", + "program": "${workspaceFolder}/main.go", + "args": [ + "scan", + "--logger", + "debug" + ], + "buildFlags": "-tags=static" + } + ] +} +``` + # Under the hood ## Technology diff --git a/build.py b/build.py index 56e2d646..f035cd4a 100644 --- a/build.py +++ b/build.py @@ -73,7 +73,7 @@ def main(): if armo_auth_server: ldflags += " -X {}={}".format(AUTH_SERVER_CONST, armo_auth_server) - build_command = ["go", "build", "-o", ks_file, "-ldflags" ,ldflags] + build_command = ["go", "build", "-tags=static", "-o", ks_file, "-ldflags" ,ldflags] print("Building kubescape and saving here: {}".format(ks_file)) print("Build command: {}".format(" ".join(build_command))) diff --git a/build/Dockerfile b/build/Dockerfile index d67403cc..542bc210 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -8,17 +8,21 @@ ENV CLIENT=$client ENV GO111MODULE= -ENV CGO_ENABLED=0 +ENV CGO_ENABLED=1 # Install required python/pip ENV PYTHONUNBUFFERED=1 -RUN apk add --update --no-cache python3 && ln -sf python3 /usr/bin/python +RUN apk add --update --no-cache python3 git openssl-dev musl-dev gcc make cmake pkgconfig && ln -sf python3 /usr/bin/python RUN python3 -m ensurepip RUN pip3 install --no-cache --upgrade pip setuptools WORKDIR /work ADD . . +# install libgit2 +WORKDIR /work +RUN rm -rf git2go && make libgit2 + # build kubescape server WORKDIR /work/httphandler RUN python build.py diff --git a/core/cautils/localgitrepository.go b/core/cautils/localgitrepository.go index 1f6463d9..23496edd 100644 --- a/core/cautils/localgitrepository.go +++ b/core/cautils/localgitrepository.go @@ -1,29 +1,36 @@ +//go:build !windows +// +build !windows + package cautils import ( "fmt" "path" "strings" + "time" "github.com/armosec/go-git-url/apis" gitv5 "github.com/go-git/go-git/v5" configv5 "github.com/go-git/go-git/v5/config" plumbingv5 "github.com/go-git/go-git/v5/plumbing" + git2go "github.com/libgit2/git2go/v33" ) type LocalGitRepository struct { - repo *gitv5.Repository - head *plumbingv5.Reference - config *configv5.Config + goGitRepo *gitv5.Repository + git2GoRepo *git2go.Repository + head *plumbingv5.Reference + config *configv5.Config + fileToLastCommit map[string]*git2go.Commit } func NewLocalGitRepository(path string) (*LocalGitRepository, error) { - gitRepo, err := gitv5.PlainOpenWithOptions(path, &gitv5.PlainOpenOptions{DetectDotGit: true}) + goGitRepo, err := gitv5.PlainOpenWithOptions(path, &gitv5.PlainOpenOptions{DetectDotGit: true}) if err != nil { return nil, err } - head, err := gitRepo.Head() + head, err := goGitRepo.Head() if err != nil { return nil, err } @@ -32,15 +39,25 @@ func NewLocalGitRepository(path string) (*LocalGitRepository, error) { return nil, fmt.Errorf("current HEAD reference is not a branch") } - config, err := gitRepo.Config() + config, err := goGitRepo.Config() + if err != nil { + return nil, err + } + + if len(config.Remotes) == 0 { + return nil, fmt.Errorf("no remotes found") + } + + git2GoRepo, err := git2go.OpenRepository(path) if err != nil { return nil, err } return &LocalGitRepository{ - repo: gitRepo, - head: head, - config: config, + goGitRepo: goGitRepo, + head: head, + config: config, + git2GoRepo: git2GoRepo, }, nil } @@ -80,20 +97,7 @@ func (g *LocalGitRepository) GetName() (string, error) { // GetLastCommit get latest commit object func (g *LocalGitRepository) GetLastCommit() (*apis.Commit, error) { - return g.GetFileLastCommit("") -} - -// GetFileLastCommit get file latest commit object, if empty will return latest commit -func (g *LocalGitRepository) GetFileLastCommit(filePath string) (*apis.Commit, error) { - // By default, returns commit information from current HEAD - logOptions := &gitv5.LogOptions{} - - if filePath != "" { - logOptions.FileName = &filePath - logOptions.Order = gitv5.LogOrderCommitterTime // faster -> LogOrderDFSPost - } - - cIter, err := g.repo.Log(logOptions) + cIter, err := g.goGitRepo.Log(&gitv5.LogOptions{}) if err != nil { return nil, err } @@ -117,8 +121,122 @@ func (g *LocalGitRepository) GetFileLastCommit(filePath string) (*apis.Commit, e }, nil } +func (g *LocalGitRepository) getAllCommits() ([]*git2go.Commit, error) { + logItr, itrErr := g.git2GoRepo.Walk() + if itrErr != nil { + + return nil, itrErr + } + + pushErr := logItr.PushHead() + if pushErr != nil { + return nil, pushErr + } + + var allCommits []*git2go.Commit + err := logItr.Iterate(func(commit *git2go.Commit) bool { + if commit != nil { + allCommits = append(allCommits, commit) + return true + } + return false + }) + + if err != nil { + return nil, err + } + + if err != nil { + return nil, err + } + + return allCommits, nil +} + +func (g *LocalGitRepository) GetFileLastCommit(filePath string) (*apis.Commit, error) { + if g.fileToLastCommit == nil { + filePathToCommitTime := map[string]time.Time{} + filePathToCommit := map[string]*git2go.Commit{} + allCommits, _ := g.getAllCommits() + + // builds a map of all files to their last commit + for _, commit := range allCommits { + // Ignore merge commits (2+ parents) + if commit.ParentCount() <= 1 { + tree, err := commit.Tree() + if err != nil { + continue + } + + // ParentCount can be either 1 or 0 (initial commit) + // In case it's the initial commit, prevTree is nil + var prevTree *git2go.Tree + if commit.ParentCount() == 1 { + prevCommit := commit.Parent(0) + prevTree, err = prevCommit.Tree() + if err != nil { + continue + } + } + + diff, err := g.git2GoRepo.DiffTreeToTree(prevTree, tree, nil) + if err != nil { + continue + } + + numDeltas, err := diff.NumDeltas() + if err != nil { + continue + } + + for i := 0; i < numDeltas; i++ { + delta, err := diff.Delta(i) + if err != nil { + continue + } + + deltaFilePath := delta.NewFile.Path + commitTime := commit.Author().When + + // In case we have the commit information for the file which is not the latest - we override it + if currentCommitTime, exists := filePathToCommitTime[deltaFilePath]; exists { + if currentCommitTime.Before(commitTime) { + filePathToCommitTime[deltaFilePath] = commitTime + filePathToCommit[deltaFilePath] = commit + } + } else { + filePathToCommitTime[deltaFilePath] = commitTime + filePathToCommit[deltaFilePath] = commit + } + } + } + } + g.fileToLastCommit = filePathToCommit + } + + if relevantCommit, exists := g.fileToLastCommit[filePath]; exists { + return g.getCommit(relevantCommit), nil + } + + return nil, fmt.Errorf("failed to get commit information for file: %s", filePath) +} + +func (g *LocalGitRepository) getCommit(commit *git2go.Commit) *apis.Commit { + return &apis.Commit{ + SHA: commit.Id().String(), + Author: apis.Committer{ + Name: commit.Author().Name, + Email: commit.Author().Email, + Date: commit.Author().When, + }, + Message: commit.Message(), + Committer: apis.Committer{}, + Files: []apis.Files{}, + } +} + func (g *LocalGitRepository) GetRootDir() (string, error) { - wt, err := g.repo.Worktree() + wt, err := g.goGitRepo.Worktree() if err != nil { return "", fmt.Errorf("failed to get repo root") } diff --git a/core/cautils/localgitrepository_test.go b/core/cautils/localgitrepository_test.go index 1ce827d2..d7a63f7e 100644 --- a/core/cautils/localgitrepository_test.go +++ b/core/cautils/localgitrepository_test.go @@ -6,17 +6,20 @@ import ( "io" "os" "path/filepath" + "runtime" "strings" "testing" "github.com/stretchr/testify/suite" ) +var TEST_REPOS = [...]string{"localrepo", "withoutremotes"} + type LocalGitRepositoryTestSuite struct { suite.Suite - archive *zip.ReadCloser - gitRepositoryPath string - destinationPath string + archives map[string]*zip.ReadCloser + gitRepositoryPaths map[string]string + destinationPath string } func unzipFile(zipPath, destinationFolder string) (*zip.ReadCloser, error) { @@ -61,17 +64,21 @@ func unzipFile(zipPath, destinationFolder string) (*zip.ReadCloser, error) { } func (s *LocalGitRepositoryTestSuite) SetupSuite() { - zippedFixturePath := filepath.Join(".", "testdata", "localrepo.git") + s.archives = make(map[string]*zip.ReadCloser) + s.gitRepositoryPaths = make(map[string]string) + destinationPath := filepath.Join(".", "testdata", "temp") - gitRepositoryPath := filepath.Join(destinationPath, "localrepo") - + s.destinationPath = destinationPath os.RemoveAll(destinationPath) - archive, err := unzipFile(zippedFixturePath, destinationPath) + for _, repo := range TEST_REPOS { + zippedFixturePath := filepath.Join(".", "testdata", repo+".git") + gitRepositoryPath := filepath.Join(destinationPath, repo) + archive, err := unzipFile(zippedFixturePath, destinationPath) - if err == nil { - s.archive = archive - s.gitRepositoryPath = gitRepositoryPath - s.destinationPath = destinationPath + if err == nil { + s.archives[repo] = archive + s.gitRepositoryPaths[repo] = gitRepositoryPath + } } } @@ -80,9 +87,14 @@ func TestLocalGitRepositoryTestSuite(t *testing.T) { } func (s *LocalGitRepositoryTestSuite) TearDownSuite() { - if s.archive != nil { - s.archive.Close() + if s.archives != nil { + for _, archive := range s.archives { + if archive != nil { + archive.Close() + } + } } + os.RemoveAll(s.destinationPath) } @@ -92,14 +104,20 @@ func (s *LocalGitRepositoryTestSuite) TestInvalidRepositoryPath() { } } +func (s *LocalGitRepositoryTestSuite) TestRepositoryWithoutRemotes() { + if _, err := NewLocalGitRepository(s.gitRepositoryPaths["withoutremotes"]); s.Error(err) { + s.Equal("no remotes found", err.Error()) + } +} + func (s *LocalGitRepositoryTestSuite) TestGetBranchName() { - if localRepo, err := NewLocalGitRepository(s.gitRepositoryPath); s.NoError(err) { + if localRepo, err := NewLocalGitRepository(s.gitRepositoryPaths["localrepo"]); s.NoError(err) { s.Equal("master", localRepo.GetBranchName()) } } func (s *LocalGitRepositoryTestSuite) TestGetName() { - if localRepo, err := NewLocalGitRepository(s.gitRepositoryPath); s.NoError(err) { + if localRepo, err := NewLocalGitRepository(s.gitRepositoryPaths["localrepo"]); s.NoError(err) { if name, err := localRepo.GetName(); s.NoError(err) { s.Equal("localrepo", name) } @@ -108,7 +126,7 @@ func (s *LocalGitRepositoryTestSuite) TestGetName() { } func (s *LocalGitRepositoryTestSuite) TestGetOriginUrl() { - if localRepo, err := NewLocalGitRepository(s.gitRepositoryPath); s.NoError(err) { + if localRepo, err := NewLocalGitRepository(s.gitRepositoryPaths["localrepo"]); s.NoError(err) { if url, err := localRepo.GetRemoteUrl(); s.NoError(err) { s.Equal("git@github.com:testuser/localrepo", url) } @@ -116,7 +134,7 @@ func (s *LocalGitRepositoryTestSuite) TestGetOriginUrl() { } func (s *LocalGitRepositoryTestSuite) TestGetLastCommit() { - if localRepo, err := NewLocalGitRepository(s.gitRepositoryPath); s.NoError(err) { + if localRepo, err := NewLocalGitRepository(s.gitRepositoryPaths["localrepo"]); s.NoError(err) { if commit, err := localRepo.GetLastCommit(); s.NoError(err) { s.Equal("7e09312b8017695fadcd606882e3779f10a5c832", commit.SHA) s.Equal("Amir Malka", commit.Author.Name) @@ -129,25 +147,29 @@ func (s *LocalGitRepositoryTestSuite) TestGetLastCommit() { func (s *LocalGitRepositoryTestSuite) TestGetFileLastCommit() { s.Run("fileA", func() { - if localRepo, err := NewLocalGitRepository(s.gitRepositoryPath); s.NoError(err) { - if commit, err := localRepo.GetFileLastCommit("fileA"); s.NoError(err) { - s.Equal("9fae4be19624297947d2b605cefbff516628612d", commit.SHA) - s.Equal("Amir Malka", commit.Author.Name) - s.Equal("amirm@armosec.io", commit.Author.Email) - s.Equal("2022-05-22 18:55:48 +0300 +0300", commit.Author.Date.String()) - s.Equal("added file A\n", commit.Message) + if localRepo, err := NewLocalGitRepository(s.gitRepositoryPaths["localrepo"]); s.NoError(err) { + if runtime.GOOS != "windows" { + if commit, err := localRepo.GetFileLastCommit("fileA"); s.NoError(err) { + s.Equal("9fae4be19624297947d2b605cefbff516628612d", commit.SHA) + s.Equal("Amir Malka", commit.Author.Name) + s.Equal("amirm@armosec.io", commit.Author.Email) + s.Equal("2022-05-22 18:55:48 +0300 +0300", commit.Author.Date.String()) + s.Equal("added file A\n", commit.Message) + } } } }) s.Run("fileB", func() { - if localRepo, err := NewLocalGitRepository(s.gitRepositoryPath); s.NoError(err) { - if commit, err := localRepo.GetFileLastCommit("dirA/fileB"); s.NoError(err) { - s.Equal("7e09312b8017695fadcd606882e3779f10a5c832", commit.SHA) - s.Equal("Amir Malka", commit.Author.Name) - s.Equal("amirm@armosec.io", commit.Author.Email) - s.Equal("2022-05-22 19:11:57 +0300 +0300", commit.Author.Date.String()) - s.Equal("added file B\n", commit.Message) + if localRepo, err := NewLocalGitRepository(s.gitRepositoryPaths["localrepo"]); s.NoError(err) { + if runtime.GOOS != "windows" { + if commit, err := localRepo.GetFileLastCommit("dirA/fileB"); s.NoError(err) { + s.Equal("7e09312b8017695fadcd606882e3779f10a5c832", commit.SHA) + s.Equal("Amir Malka", commit.Author.Name) + s.Equal("amirm@armosec.io", commit.Author.Email) + s.Equal("2022-05-22 19:11:57 +0300 +0300", commit.Author.Date.String()) + s.Equal("added file B\n", commit.Message) + } } } }) diff --git a/core/cautils/localgitrepository_windows.go b/core/cautils/localgitrepository_windows.go new file mode 100644 index 00000000..5a72f597 --- /dev/null +++ b/core/cautils/localgitrepository_windows.go @@ -0,0 +1,125 @@ +//go:build windows +// +build windows + +package cautils + +import ( + "fmt" + "path" + "strings" + + "github.com/armosec/go-git-url/apis" + gitv5 "github.com/go-git/go-git/v5" + configv5 "github.com/go-git/go-git/v5/config" + plumbingv5 "github.com/go-git/go-git/v5/plumbing" +) + +type LocalGitRepository struct { + goGitRepo *gitv5.Repository + head *plumbingv5.Reference + config *configv5.Config +} + +func NewLocalGitRepository(path string) (*LocalGitRepository, error) { + goGitRepo, err := gitv5.PlainOpenWithOptions(path, &gitv5.PlainOpenOptions{DetectDotGit: true}) + if err != nil { + return nil, err + } + + head, err := goGitRepo.Head() + if err != nil { + return nil, err + } + + if !head.Name().IsBranch() { + return nil, fmt.Errorf("current HEAD reference is not a branch") + } + + config, err := goGitRepo.Config() + if err != nil { + return nil, err + } + + if len(config.Remotes) == 0 { + return nil, fmt.Errorf("no remotes found") + } + + return &LocalGitRepository{ + goGitRepo: goGitRepo, + head: head, + config: config, + }, nil +} + +// GetBranchName get current branch name +func (g *LocalGitRepository) GetBranchName() string { + return g.head.Name().Short() +} + +// GetRemoteUrl get default remote URL +func (g *LocalGitRepository) GetRemoteUrl() (string, error) { + branchName := g.GetBranchName() + if branchRef, branchFound := g.config.Branches[branchName]; branchFound { + remoteName := branchRef.Remote + if len(g.config.Remotes[remoteName].URLs) == 0 { + return "", fmt.Errorf("expected to find URLs for remote '%s', branch '%s'", remoteName, branchName) + } + return g.config.Remotes[remoteName].URLs[0], nil + } + + const defaultRemoteName string = "origin" + if len(g.config.Remotes[defaultRemoteName].URLs) == 0 { + return "", fmt.Errorf("expected to find URLs for remote '%s'", defaultRemoteName) + } + return g.config.Remotes[defaultRemoteName].URLs[0], nil +} + +// GetName get origin name without the .git suffix +func (g *LocalGitRepository) GetName() (string, error) { + originUrl, err := g.GetRemoteUrl() + if err != nil { + return "", err + } + baseName := path.Base(originUrl) + // remove .git + return strings.TrimSuffix(baseName, ".git"), nil +} + +// GetLastCommit get latest commit object +func (g *LocalGitRepository) GetLastCommit() (*apis.Commit, error) { + cIter, err := g.goGitRepo.Log(&gitv5.LogOptions{}) + if err != nil { + return nil, err + } + + commit, err := cIter.Next() + defer cIter.Close() + if err != nil { + return nil, err + } + + return &apis.Commit{ + SHA: commit.Hash.String(), + Author: apis.Committer{ + Name: commit.Author.Name, + Email: commit.Author.Email, + Date: commit.Author.When, + }, + Message: commit.Message, + Committer: apis.Committer{}, + Files: []apis.Files{}, + }, nil +} + +func (g *LocalGitRepository) GetFileLastCommit(filePath string) (*apis.Commit, error) { + return nil, nil +} + +func (g *LocalGitRepository) GetRootDir() (string, error) { + wt, err := g.goGitRepo.Worktree() + if err != nil { + return "", fmt.Errorf("failed to get repo root") + } + + return wt.Filesystem.Root(), nil +} diff --git a/core/cautils/testdata/withoutremotes.git b/core/cautils/testdata/withoutremotes.git new file mode 100644 index 00000000..6d78412d Binary files /dev/null and b/core/cautils/testdata/withoutremotes.git differ diff --git a/core/pkg/resourcehandler/filesloader.go b/core/pkg/resourcehandler/filesloader.go index 8af20ffe..0008476c 100644 --- a/core/pkg/resourcehandler/filesloader.go +++ b/core/pkg/resourcehandler/filesloader.go @@ -57,9 +57,9 @@ func (fileHandler *FileResourceHandler) GetResources(sessionObj *cautils.OPASess // Get repo root repoRoot := "" - giRepo, err := cautils.NewLocalGitRepository(path) + gitRepo, err := cautils.NewLocalGitRepository(path) if err == nil { - repoRoot, _ = giRepo.GetRootDir() + repoRoot, _ = gitRepo.GetRootDir() } // load resource from local file system @@ -75,19 +75,36 @@ func (fileHandler *FileResourceHandler) GetResources(sessionObj *cautils.OPASess if err == nil { source = relSource } + + var filetype string + if cautils.IsYaml(source) { + filetype = reporthandling.SourceTypeYaml + } else if cautils.IsJson(source) { + filetype = reporthandling.SourceTypeJson + } else { + continue + } + + var lastCommit reporthandling.LastCommit + commitInfo, _ := gitRepo.GetFileLastCommit(source) + if commitInfo != nil { + lastCommit = reporthandling.LastCommit{ + Hash: commitInfo.SHA, + Date: commitInfo.Author.Date, + CommitterName: commitInfo.Author.Name, + CommitterEmail: commitInfo.Author.Email, + Message: commitInfo.Message, + } + } + + workloadSource := reporthandling.Source{ + RelativePath: source, + FileType: filetype, + LastCommit: lastCommit, + } + for i := range ws { - var filetype string - if cautils.IsYaml(source) { - filetype = reporthandling.SourceTypeYaml - } else if cautils.IsJson(source) { - filetype = reporthandling.SourceTypeJson - } else { - continue - } - workloadIDToSource[ws[i].GetID()] = reporthandling.Source{ - RelativePath: source, - FileType: filetype, - } + workloadIDToSource[ws[i].GetID()] = workloadSource } } @@ -104,12 +121,28 @@ func (fileHandler *FileResourceHandler) GetResources(sessionObj *cautils.OPASess if err == nil { source = relSource } - for i := range ws { - workloadIDToSource[ws[i].GetID()] = reporthandling.Source{ - RelativePath: source, - FileType: reporthandling.SourceTypeHelmChart, + + var lastCommit reporthandling.LastCommit + commitInfo, _ := gitRepo.GetFileLastCommit(source) + if commitInfo != nil { + lastCommit = reporthandling.LastCommit{ + Hash: commitInfo.SHA, + Date: commitInfo.Author.Date, + CommitterName: commitInfo.Author.Name, + CommitterEmail: commitInfo.Author.Email, + Message: commitInfo.Message, } } + + workloadSource := reporthandling.Source{ + RelativePath: source, + FileType: reporthandling.SourceTypeHelmChart, + LastCommit: lastCommit, + } + + for i := range ws { + workloadIDToSource[ws[i].GetID()] = workloadSource + } } if len(helmSourceToWorkloads) > 0 { diff --git a/git2go b/git2go new file mode 160000 index 00000000..eae00773 --- /dev/null +++ b/git2go @@ -0,0 +1 @@ +Subproject commit eae00773cce87d5282a8ac7c10b5c1961ee6f9cb diff --git a/go.mod b/go.mod index 1c9d9655..3845820f 100644 --- a/go.mod +++ b/go.mod @@ -17,6 +17,7 @@ require ( github.com/go-git/go-git/v5 v5.4.2 github.com/google/uuid v1.3.0 github.com/johnfercher/maroto v0.37.0 + github.com/libgit2/git2go/v33 v33.0.9 github.com/mattn/go-isatty v0.0.14 github.com/olekukonko/tablewriter v0.0.5 github.com/open-policy-agent/opa v0.41.0 @@ -162,3 +163,5 @@ require ( sigs.k8s.io/json v0.0.0-20220525155127-227cbc7cc124 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect ) + +replace github.com/libgit2/git2go/v33 => ./git2go diff --git a/go.sum b/go.sum index 7574832f..7bc527ea 100644 --- a/go.sum +++ b/go.sum @@ -717,6 +717,8 @@ github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -1356,6 +1358,7 @@ golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= @@ -1608,6 +1611,7 @@ golang.org/x/sys v0.0.0-20201117170446-d9b008d0a637/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201202213521-69691e467435/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/httphandler/build.py b/httphandler/build.py index 7be5c512..b5f01c16 100644 --- a/httphandler/build.py +++ b/httphandler/build.py @@ -69,7 +69,7 @@ def main(): if ArmoAuthServer: ldflags += " -X {}={}".format(AUTH_SERVER_CONST, ArmoAuthServer) - build_command = ["go", "build", "-o", ks_file, "-ldflags" ,ldflags] + build_command = ["go", "build", "-tags=static", "-o", ks_file, "-ldflags" ,ldflags] print("Building kubescape and saving here: {}".format(ks_file)) print("Build command: {}".format(" ".join(build_command))) diff --git a/httphandler/go.mod b/httphandler/go.mod index 01489569..f30e39b6 100644 --- a/httphandler/go.mod +++ b/httphandler/go.mod @@ -99,6 +99,7 @@ require ( github.com/json-iterator/go v1.1.12 // indirect github.com/jung-kurt/gofpdf v1.16.2 // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect + github.com/libgit2/git2go/v33 v33.0.9 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.12 // indirect github.com/mattn/go-isatty v0.0.14 // indirect @@ -165,3 +166,5 @@ require ( sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect sigs.k8s.io/yaml v1.3.0 // indirect ) + +replace github.com/libgit2/git2go/v33 => ../git2go diff --git a/httphandler/go.sum b/httphandler/go.sum index 0077f8b2..7f67971b 100644 --- a/httphandler/go.sum +++ b/httphandler/go.sum @@ -717,6 +717,8 @@ github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -1357,6 +1359,7 @@ golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= @@ -1609,6 +1612,7 @@ golang.org/x/sys v0.0.0-20201117170446-d9b008d0a637/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201202213521-69691e467435/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=