2020-02-06 17:16:34 +02:00
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"
2020-08-11 14:42:08 +02:00
"github.com/stretchr/testify/require"
2020-02-06 17:16:34 +02:00
)
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" )
2020-09-02 10:41:12 +02:00
require . NoError ( t , err , "Failed to create temporary directory" )
2020-08-11 14:42:08 +02:00
// clean up tmp dir
2020-08-31 08:45:00 +02:00
defer func ( ) { _ = os . RemoveAll ( dir ) } ( )
2020-02-06 17:16:34 +02:00
testFile , err := ioutil . TempFile ( dir , "t.tar" )
2020-09-02 10:41:12 +02:00
require . NoError ( t , err )
2020-02-06 17:16:34 +02:00
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 )
2020-09-02 10:41:12 +02:00
require . NoErrorf ( t , err , "failed reading %v" , violations )
2020-02-06 17:16:34 +02:00
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 )
2020-09-02 10:41:12 +02:00
require . NoErrorf ( t , err , "failed reading %v" , violations )
2020-02-06 17:16:34 +02:00
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 )
2020-09-02 15:00:55 +02:00
assert . NoError ( t , err )
2020-02-06 17:16:34 +02:00
} )
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 )
2020-09-02 15:00:55 +02:00
assert . NoError ( t , err )
2020-02-06 17:16:34 +02:00
} )
}
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 )
}
}
2020-08-31 08:45:00 +02:00
2020-02-06 17:16:34 +02:00
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" )
}
}
2020-08-31 08:45:00 +02:00
2020-02-06 17:16:34 +02:00
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" )
}
}
func TestUploadScanOrDeclareFetch ( t * testing . T ) {
2020-09-02 10:41:12 +02:00
// init
2020-02-06 17:16:34 +02:00
testFile , err := ioutil . TempFile ( "" , "testFileUpload" )
2020-09-02 10:41:12 +02:00
require . NoError ( t , err )
2020-02-06 17:16:34 +02:00
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 {
2020-09-02 10:41:12 +02:00
// test
2020-02-06 17:16:34 +02:00
config := protecodeExecuteScanOptions { ReuseExisting : c . reuse , CleanupMode : c . clean , Group : c . group , FetchURL : c . fetchURL , FilePath : c . filePath }
got := uploadScanOrDeclareFetch ( config , 0 , pc , fileName )
2020-09-02 10:41:12 +02:00
// assert
2020-02-06 17:16:34 +02:00
assert . Equal ( t , c . want , got )
}
}
func writeReportToFileMock ( resp io . ReadCloser , reportFileName string ) error {
return nil
}
func TestExecuteProtecodeScan ( t * testing . T ) {
2020-08-31 08:45:00 +02:00
testDataFile := filepath . Join ( "testdata" , "TestProtecode" , "protecode_result_violations.json" )
violationsAbsPath , err := filepath . Abs ( testDataFile )
2020-09-02 10:41:12 +02:00
require . NoErrorf ( t , err , "failed to obtain absolute path to test data with violations: %v" , err )
2020-08-31 08:45:00 +02:00
2020-02-06 17:16:34 +02:00
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/" {
2020-08-31 08:45:00 +02:00
byteContent , err := ioutil . ReadFile ( violationsAbsPath )
2020-09-02 10:41:12 +02:00
require . NoErrorf ( t , err , "failed reading %v" , violationsAbsPath )
2020-02-06 17:16:34 +02:00
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 } ,
}
2020-08-31 08:45:00 +02:00
resetDir , err := os . Getwd ( )
2020-09-02 10:41:12 +02:00
require . NoErrorf ( t , err , "Failed to get current directory: %v" , err )
2020-08-31 08:45:00 +02:00
defer func ( ) { _ = os . Chdir ( resetDir ) } ( )
2020-02-06 17:16:34 +02:00
for _ , c := range cases {
2020-09-02 10:41:12 +02:00
// init
2020-02-06 17:16:34 +02:00
dir , err := ioutil . TempDir ( "" , "t" )
2020-09-02 10:41:12 +02:00
require . NoErrorf ( t , err , "Failed to create temporary directory: %v" , err )
2020-08-11 14:42:08 +02:00
// clean up tmp dir
2020-08-31 08:45:00 +02:00
defer func ( ) { _ = os . RemoveAll ( dir ) } ( )
// change into tmp dir and write test data
err = os . Chdir ( dir )
2020-09-02 10:41:12 +02:00
require . NoErrorf ( t , err , "Failed to change into temporary directory: %v" , err )
2020-02-06 17:16:34 +02:00
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" }
2020-09-02 10:41:12 +02:00
influxData := & protecodeExecuteScanInflux { }
// test
executeProtecodeScan ( influxData , pc , & config , "dummy" , writeReportToFileMock )
// assert
assert . Equal ( t , "1125" , influxData . protecode_data . fields . historical_vulnerabilities )
assert . Equal ( t , "0" , influxData . protecode_data . fields . triaged_vulnerabilities )
assert . Equal ( t , "1" , influxData . protecode_data . fields . excluded_vulnerabilities )
assert . Equal ( t , "142" , influxData . protecode_data . fields . major_vulnerabilities )
assert . Equal ( t , "226" , influxData . protecode_data . fields . vulnerabilities )
2020-02-06 17:16:34 +02:00
}
}
2020-08-11 14:42:08 +02:00
func TestCorrectDockerConfigEnvVar ( t * testing . T ) {
2020-08-12 14:57:11 +02:00
t . Run ( "with credentials" , func ( t * testing . T ) {
// init
testDirectory , _ := ioutil . TempDir ( "." , "" )
require . DirExists ( t , testDirectory )
defer os . RemoveAll ( testDirectory )
dockerConfigDir := filepath . Join ( testDirectory , "myConfig" )
os . Mkdir ( dockerConfigDir , 0755 )
require . DirExists ( t , dockerConfigDir )
dockerConfigFile := filepath . Join ( dockerConfigDir , "docker.json" )
file , _ := os . Create ( dockerConfigFile )
defer file . Close ( )
require . FileExists ( t , dockerConfigFile )
resetValue := os . Getenv ( "DOCKER_CONFIG" )
defer os . Setenv ( "DOCKER_CONFIG" , resetValue )
// test
correctDockerConfigEnvVar ( & protecodeExecuteScanOptions { DockerConfigJSON : dockerConfigFile } )
// assert
absolutePath , _ := filepath . Abs ( dockerConfigDir )
assert . Equal ( t , absolutePath , os . Getenv ( "DOCKER_CONFIG" ) )
} )
t . Run ( "without credentials" , func ( t * testing . T ) {
// init
resetValue := os . Getenv ( "DOCKER_CONFIG" )
defer os . Setenv ( "DOCKER_CONFIG" , resetValue )
// test
correctDockerConfigEnvVar ( & protecodeExecuteScanOptions { } )
// assert
assert . Equal ( t , resetValue , os . Getenv ( "DOCKER_CONFIG" ) )
} )
2020-08-11 14:42:08 +02:00
}
2020-09-02 15:00:55 +02:00
func TestGetTarName ( t * testing . T ) {
cases := map [ string ] struct {
image string
version string
expect string
} {
"with version suffix" : {
"com.sap.piper/sample-k8s-app-multistage:1.11-20200902040158_97a5cc34f1796ad735159f020dd55c0f3670a4cb" ,
"1.11-20200902040158_97a5cc34f1796ad735159f020dd55c0f3670a4cb" ,
"com.sap.piper_sample-k8s-app-multistage_1.tar" ,
} ,
"without version suffix" : {
"abc" ,
"3.20.20-20200131085038+eeb7c1033339bfd404d21ec5e7dc05c80e9e985e" ,
"abc_3.tar" ,
} ,
2021-01-26 10:59:10 +02:00
"without version" : {
2020-09-02 15:00:55 +02:00
"abc" ,
"" ,
"abc.tar" ,
} ,
2021-01-26 10:59:10 +02:00
"ScanImage without sha as artifactVersion" : {
"abc@sha256:12345" ,
"" ,
"abc.tar" ,
} ,
"ScanImage with sha as artifactVersion" : {
"ppiper/cf-cli@sha256:c25dbacb9ab6e912afe0fe926d8f9d949c60adfe55d16778bde5941e6c37be11" ,
"c25dbacb9ab6e912afe0fe926d8f9d949c60adfe55d16778bde5941e6c37be11" ,
"ppiper_cf-cli_c25dbacb9ab6e912afe0fe926d8f9d949c60adfe55d16778bde5941e6c37be11.tar" ,
} ,
2020-09-02 15:00:55 +02:00
}
for name , c := range cases {
t . Run ( name , func ( t * testing . T ) {
assert . Equal ( t , c . expect , getTarName ( & protecodeExecuteScanOptions { ScanImage : c . image , ArtifactVersion : c . version } ) )
} )
}
}