mirror of
https://github.com/SAP/jenkins-library.git
synced 2024-12-12 10:55:20 +02:00
2ebf2010b7
* Protecode as go implementation Co-authored-by: Sven Merk <33895725+nevskrem@users.noreply.github.com> Co-authored-by: Oliver Nocon <33484802+OliverNocon@users.noreply.github.com>
378 lines
11 KiB
Go
378 lines
11 KiB
Go
package cmd
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"net/url"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
|
|
pkgutil "github.com/GoogleContainerTools/container-diff/pkg/util"
|
|
"github.com/SAP/jenkins-library/pkg/protecode"
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
type DockerClientMock struct {
|
|
imageName string
|
|
registryURL string
|
|
localPath string
|
|
includeLayers bool
|
|
}
|
|
|
|
//Download interface for download an image to a local path
|
|
type Download interface {
|
|
GetImageSource() (string, error)
|
|
DownloadImageToPath(imageSource, filePath string) (pkgutil.Image, error)
|
|
TarImage(writer io.Writer, image pkgutil.Image) error
|
|
}
|
|
|
|
const (
|
|
daemonPrefix = "daemon://"
|
|
remotePrefix = "remote://"
|
|
)
|
|
|
|
func (c *DockerClientMock) GetImageSource() (string, error) {
|
|
|
|
imageSource := c.imageName
|
|
|
|
if len(c.registryURL) > 0 && len(c.localPath) <= 0 {
|
|
registry := c.registryURL
|
|
|
|
url, _ := url.Parse(c.registryURL)
|
|
//remove protocoll from registryURL to get registry
|
|
if len(url.Scheme) > 0 {
|
|
registry = strings.Replace(c.registryURL, fmt.Sprintf("%v://", url.Scheme), "", 1)
|
|
}
|
|
|
|
if strings.HasSuffix(registry, "/") {
|
|
imageSource = fmt.Sprintf("%v%v%v", remotePrefix, registry, c.imageName)
|
|
} else {
|
|
imageSource = fmt.Sprintf("%v%v/%v", remotePrefix, registry, c.imageName)
|
|
}
|
|
} else if len(c.localPath) > 0 {
|
|
imageSource = c.localPath
|
|
if !pkgutil.IsTar(c.localPath) {
|
|
imageSource = fmt.Sprintf("%v%v", daemonPrefix, c.localPath)
|
|
}
|
|
}
|
|
|
|
if len(imageSource) <= 0 {
|
|
return imageSource, fmt.Errorf("There is no image source for the parameters: (Name: %v, Registry: %v, local Path: %v)", c.imageName, c.registryURL, c.localPath)
|
|
}
|
|
|
|
return imageSource, nil
|
|
}
|
|
|
|
//DownloadImageToPath download the image to the specified path
|
|
func (c *DockerClientMock) DownloadImageToPath(imageSource, filePath string) (pkgutil.Image, error) {
|
|
|
|
return pkgutil.Image{}, nil
|
|
}
|
|
|
|
//TarImage write a tar from the given image
|
|
func (c *DockerClientMock) TarImage(writer io.Writer, image pkgutil.Image) error {
|
|
|
|
return nil
|
|
}
|
|
|
|
func TestRunProtecodeScan(t *testing.T) {
|
|
|
|
requestURI := ""
|
|
dir, err := ioutil.TempDir("", "t")
|
|
if err != nil {
|
|
t.Fatal("Failed to create temporary directory")
|
|
}
|
|
// clean up tmp dir
|
|
defer os.RemoveAll(dir)
|
|
testFile, err := ioutil.TempFile(dir, "t.tar")
|
|
if err != nil {
|
|
t.FailNow()
|
|
}
|
|
fileName := filepath.Base(testFile.Name())
|
|
path := strings.ReplaceAll(testFile.Name(), fileName, "")
|
|
|
|
server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
|
requestURI = req.RequestURI
|
|
var b bytes.Buffer
|
|
|
|
if requestURI == "/api/product/4486/" || requestURI == "/api/product/4711/" {
|
|
violations := filepath.Join("testdata/TestProtecode", "protecode_result_violations.json")
|
|
byteContent, err := ioutil.ReadFile(violations)
|
|
if err != nil {
|
|
t.Fatalf("failed reading %v", violations)
|
|
}
|
|
response := protecode.ResultData{Result: protecode.Result{ProductID: 4711, ReportURL: requestURI}}
|
|
err = json.Unmarshal(byteContent, &response)
|
|
|
|
json.NewEncoder(&b).Encode(response)
|
|
|
|
} else if requestURI == "/api/fetch/" {
|
|
violations := filepath.Join("testdata/TestProtecode", "protecode_result_violations.json")
|
|
byteContent, err := ioutil.ReadFile(violations)
|
|
if err != nil {
|
|
t.Fatalf("failed reading %v", violations)
|
|
}
|
|
response := protecode.ResultData{Result: protecode.Result{ProductID: 4486, ReportURL: requestURI}}
|
|
err = json.Unmarshal(byteContent, &response)
|
|
|
|
json.NewEncoder(&b).Encode(response)
|
|
|
|
} else if requestURI == "/api/product/4486/pdf-report" {
|
|
|
|
} else if requestURI == "/api/upload/t.tar" {
|
|
response := protecode.ResultData{Result: protecode.Result{ProductID: 4486, ReportURL: requestURI}}
|
|
|
|
var b bytes.Buffer
|
|
json.NewEncoder(&b).Encode(&response)
|
|
rw.Write([]byte(b.Bytes()))
|
|
} else {
|
|
response := protecode.Result{ProductID: 4486, ReportURL: requestURI}
|
|
json.NewEncoder(&b).Encode(&response)
|
|
}
|
|
|
|
rw.Write([]byte(b.Bytes()))
|
|
}))
|
|
|
|
// Close the server when test finishes
|
|
defer server.Close()
|
|
|
|
po := protecode.Options{ServerURL: server.URL}
|
|
pc := protecode.Protecode{}
|
|
pc.SetOptions(po)
|
|
|
|
dClient := &DockerClientMock{imageName: "t", registryURL: "", localPath: path, includeLayers: false}
|
|
|
|
influx := protecodeExecuteScanInflux{}
|
|
reportPath = dir
|
|
cachePath = dir
|
|
|
|
t.Run("With tar as scan image", func(t *testing.T) {
|
|
config := protecodeExecuteScanOptions{ServerURL: server.URL, TimeoutMinutes: "1", ReuseExisting: false, CleanupMode: "none", Group: "13", FetchURL: "/api/fetch/", ExcludeCVEs: "CVE-2018-1, CVE-2017-1000382", ReportFileName: "./cache/report-file.txt"}
|
|
err = runProtecodeScan(&config, &influx, dClient)
|
|
assert.Nil(t, err, "There should be no Error")
|
|
})
|
|
|
|
t.Run("Without tar as scan image", func(t *testing.T) {
|
|
config := protecodeExecuteScanOptions{ServerURL: server.URL, ScanImage: "t", FilePath: path, TimeoutMinutes: "1", ReuseExisting: false, CleanupMode: "none", Group: "13", ExcludeCVEs: "CVE-2018-1, CVE-2017-1000382", ReportFileName: "./cache/report-file.txt"}
|
|
err = runProtecodeScan(&config, &influx, dClient)
|
|
assert.Nil(t, err, "There should be no Error")
|
|
})
|
|
|
|
}
|
|
|
|
func TestHandleArtifactVersion(t *testing.T) {
|
|
cases := []struct {
|
|
version string
|
|
want string
|
|
}{
|
|
|
|
{"1.0.0-20200131085038+eeb7c1033339bfd404d21ec5e7dc05c80e9e985e", "1"},
|
|
{"2.20.20-20200131085038+eeb7c1033339bfd404d21ec5e7dc05c80e9e985e", "2"},
|
|
{"3.20.20-20200131085038+eeb7c1033339bfd404d21ec5e7dc05c80e9e985e", "3"},
|
|
{"4.20.20-20200131085038", "4"},
|
|
{"5.20.20-20200131085038+", "5"},
|
|
{"6.00", "6.00"},
|
|
{"7.20.20", "7.20.20"},
|
|
}
|
|
|
|
for _, c := range cases {
|
|
|
|
got := handleArtifactVersion(c.version)
|
|
assert.Equal(t, c.want, got)
|
|
}
|
|
}
|
|
func TestCreateClient(t *testing.T) {
|
|
cases := []struct {
|
|
timeout string
|
|
}{
|
|
{""},
|
|
{"1"},
|
|
}
|
|
|
|
for _, c := range cases {
|
|
config := protecodeExecuteScanOptions{TimeoutMinutes: c.timeout}
|
|
|
|
client := createClient(&config)
|
|
assert.NotNil(t, client, "client should not be empty")
|
|
}
|
|
}
|
|
func TestCreateDockerClient(t *testing.T) {
|
|
cases := []struct {
|
|
scanImage string
|
|
dockerRegistryURL string
|
|
filePath string
|
|
includeLayers bool
|
|
}{
|
|
{"test", "url", "path", false},
|
|
{"", "", "", true},
|
|
}
|
|
|
|
for _, c := range cases {
|
|
config := protecodeExecuteScanOptions{ScanImage: c.scanImage, DockerRegistryURL: c.dockerRegistryURL, FilePath: c.filePath, IncludeLayers: c.includeLayers}
|
|
client := createDockerClient(&config)
|
|
assert.NotNil(t, client, "client should not be empty")
|
|
}
|
|
}
|
|
|
|
var fileContent string
|
|
|
|
func writeToFileMock(f string, d []byte, p os.FileMode) error {
|
|
fileContent = string(d)
|
|
return nil
|
|
}
|
|
|
|
func TestWriteReportDataToJSONFile(t *testing.T) {
|
|
|
|
expected := "{\"target\":\"REPORTFILENAME\",\"mandatory\":true,\"productID\":\"4711\",\"serverUrl\":\"DUMMYURL\",\"count\":\"0\",\"cvss2GreaterOrEqualSeven\":\"4\",\"cvss3GreaterOrEqualSeven\":\"3\",\"excludedVulnerabilities\":\"2\",\"triagedVulnerabilities\":\"0\",\"historicalVulnerabilities\":\"1\",\"Vulnerabilities\":[{\"cve\":\"Vulnerability\",\"cvss\":2.5,\"cvss3_score\":\"5.5\"}]}"
|
|
|
|
var parsedResult map[string]int = make(map[string]int)
|
|
parsedResult["historical_vulnerabilities"] = 1
|
|
parsedResult["excluded_vulnerabilities"] = 2
|
|
parsedResult["cvss3GreaterOrEqualSeven"] = 3
|
|
parsedResult["cvss2GreaterOrEqualSeven"] = 4
|
|
parsedResult["vulnerabilities"] = 5
|
|
|
|
config := protecodeExecuteScanOptions{ServerURL: "DUMMYURL", ReportFileName: "REPORTFILENAME"}
|
|
|
|
writeReportDataToJSONFile(&config, parsedResult, 4711, []protecode.Vuln{{"Vulnerability", 2.5, "5.5"}}, writeToFileMock)
|
|
assert.Equal(t, fileContent, expected, "content should be not empty")
|
|
}
|
|
|
|
func TestUploadScanOrDeclareFetch(t *testing.T) {
|
|
|
|
testFile, err := ioutil.TempFile("", "testFileUpload")
|
|
if err != nil {
|
|
t.FailNow()
|
|
}
|
|
defer os.RemoveAll(testFile.Name()) // clean up
|
|
fileName := filepath.Base(testFile.Name())
|
|
path := strings.ReplaceAll(testFile.Name(), fileName, "")
|
|
|
|
requestURI := ""
|
|
server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
|
requestURI = req.RequestURI
|
|
|
|
if requestURI == "/api/fetch/" {
|
|
response := protecode.ResultData{Result: protecode.Result{ProductID: 4711, ReportURL: requestURI}}
|
|
var b bytes.Buffer
|
|
json.NewEncoder(&b).Encode(&response)
|
|
rw.Write([]byte(b.Bytes()))
|
|
}
|
|
if requestURI == fmt.Sprintf("/api/upload/%v", fileName) || requestURI == fmt.Sprintf("/api/upload/PR_4711_%v", fileName) {
|
|
response := protecode.ResultData{Result: protecode.Result{ProductID: 4711, ReportURL: requestURI}}
|
|
|
|
var b bytes.Buffer
|
|
json.NewEncoder(&b).Encode(&response)
|
|
rw.Write([]byte(b.Bytes()))
|
|
}
|
|
}))
|
|
|
|
// Close the server when test finishes
|
|
defer server.Close()
|
|
|
|
po := protecode.Options{ServerURL: server.URL}
|
|
pc := protecode.Protecode{}
|
|
pc.SetOptions(po)
|
|
|
|
cases := []struct {
|
|
reuse bool
|
|
clean string
|
|
group string
|
|
fetchURL string
|
|
filePath string
|
|
prName string
|
|
want int
|
|
}{
|
|
{false, "test", "group1", "/api/fetch/", "", "", 4711},
|
|
{false, "test", "group1", "", path, "", 4711},
|
|
{false, "test", "group1", "", path, "PR_4711", 4711},
|
|
}
|
|
|
|
for _, c := range cases {
|
|
|
|
config := protecodeExecuteScanOptions{ReuseExisting: c.reuse, CleanupMode: c.clean, Group: c.group, FetchURL: c.fetchURL, FilePath: c.filePath}
|
|
got := uploadScanOrDeclareFetch(config, 0, pc, fileName)
|
|
|
|
assert.Equal(t, c.want, got)
|
|
}
|
|
}
|
|
|
|
func writeReportToFileMock(resp io.ReadCloser, reportFileName string) error {
|
|
return nil
|
|
}
|
|
|
|
func TestExecuteProtecodeScan(t *testing.T) {
|
|
requestURI := ""
|
|
server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
|
requestURI = req.RequestURI
|
|
var b bytes.Buffer
|
|
|
|
if requestURI == "/api/product/4711/" {
|
|
violations := filepath.Join("testdata/TestProtecode", "protecode_result_violations.json")
|
|
byteContent, err := ioutil.ReadFile(violations)
|
|
if err != nil {
|
|
t.Fatalf("failed reading %v", violations)
|
|
}
|
|
response := protecode.ResultData{}
|
|
err = json.Unmarshal(byteContent, &response)
|
|
|
|
json.NewEncoder(&b).Encode(response)
|
|
|
|
} else if requestURI == "/api/product/4711/pdf-report" {
|
|
|
|
} else {
|
|
response := protecode.ResultData{Result: protecode.Result{ProductID: 4711, ReportURL: requestURI}}
|
|
json.NewEncoder(&b).Encode(&response)
|
|
}
|
|
|
|
rw.Write([]byte(b.Bytes()))
|
|
}))
|
|
|
|
// Close the server when test finishes
|
|
defer server.Close()
|
|
|
|
po := protecode.Options{ServerURL: server.URL, Duration: time.Minute * 3}
|
|
pc := protecode.Protecode{}
|
|
pc.SetOptions(po)
|
|
|
|
cases := []struct {
|
|
reuse bool
|
|
clean string
|
|
group string
|
|
fetchURL string
|
|
want int
|
|
}{
|
|
{false, "binary", "group1", "/api/fetch/", 4711},
|
|
}
|
|
|
|
for _, c := range cases {
|
|
|
|
dir, err := ioutil.TempDir("", "t")
|
|
if err != nil {
|
|
t.Fatal("Failed to create temporary directory")
|
|
}
|
|
// clean up tmp dir
|
|
defer os.RemoveAll(dir)
|
|
reportPath = dir
|
|
config := protecodeExecuteScanOptions{ReuseExisting: c.reuse, CleanupMode: c.clean, Group: c.group, FetchURL: c.fetchURL, TimeoutMinutes: "3", ExcludeCVEs: "CVE-2018-1, CVE-2017-1000382", ReportFileName: "./cache/report-file.txt"}
|
|
|
|
got := executeProtecodeScan(pc, &config, "dummy", writeReportToFileMock)
|
|
|
|
assert.Equal(t, 1125, got["historical_vulnerabilities"])
|
|
assert.Equal(t, 0, got["triaged_vulnerabilities"])
|
|
assert.Equal(t, 1, got["excluded_vulnerabilities"])
|
|
assert.Equal(t, 129, got["cvss3GreaterOrEqualSeven"])
|
|
assert.Equal(t, 13, got["cvss2GreaterOrEqualSeven"])
|
|
assert.Equal(t, 226, got["vulnerabilities"])
|
|
}
|
|
}
|