blob: ea426ff1ce663d36be5caacefeb34acc32083be0 [file] [edit]
// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package scannerlib_test
import (
"bytes"
"context"
"errors"
"fmt"
"io"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/testing/protocmp"
cpb "github.com/google/localtoast/scannerlib/proto/compliance_go_proto"
"github.com/google/localtoast/scanapi"
apb "github.com/google/localtoast/scannerlib/proto/api_go_proto"
ipb "github.com/google/localtoast/scannerlib/proto/scan_instructions_go_proto"
"github.com/google/localtoast/scannerlib"
"github.com/google/localtoast/scannerlib/testconfigcreator"
)
const (
testFilePath1 = "/path/to/test/file1"
testFileContent1 = "File content 1"
testFilePath2 = "/path/to/test/file2"
testFileContent2 = "File content 2"
regexMatchingTestFiles = "/path/to/test.*"
testQueryNoRows = "SELECT 1 WHERE FALSE"
testQueryOneRow = "SELECT 1"
)
type fakeAPIProvider struct{}
func (fakeAPIProvider) OpenFile(ctx context.Context, path string) (io.ReadCloser, error) {
if path == testFilePath1 {
return io.NopCloser(bytes.NewReader([]byte(testFileContent1))), nil
} else if path == testFilePath2 {
return io.NopCloser(bytes.NewReader([]byte(testFileContent2))), nil
}
return nil, errors.New("File not found")
}
func (fakeAPIProvider) OpenDir(ctx context.Context, path string) (scanapi.DirReader, error) {
return nil, errors.New("not implemented")
}
func (fakeAPIProvider) FilePermissions(ctx context.Context, path string) (*apb.PosixPermissions, error) {
return nil, errors.New("not implemented")
}
func (fakeAPIProvider) SQLQuery(ctx context.Context, query string) (int, error) {
switch query {
case testQueryNoRows:
return 0, nil
case testQueryOneRow:
return 1, nil
default:
return 0, fmt.Errorf("the query %q is not supported by fakeAPIProvider", query)
}
}
func (fakeAPIProvider) SQLQueryWithResponse(ctx context.Context, query string) (string, error) {
return "", errors.New("not implemented")
}
func (fakeAPIProvider) SupportedDatabase() (ipb.SQLCheck_SQLDatabase, error) {
return ipb.SQLCheck_DB_MYSQL, nil
}
func TestScannerVersion(t *testing.T) {
config := &apb.ScanConfig{}
result, err := scannerlib.Scanner{}.Scan(context.Background(), config, fakeAPIProvider{})
if err != nil {
t.Fatalf("scannerlib.Scan() had unexpected error: %v", err)
}
if result.GetStatus().GetStatus() != apb.ScanStatus_SUCCEEDED {
t.Fatalf("scannerlib.Scan() returned unsuccessful scan status: %v",
result.GetStatus().GetStatus())
}
if result.GetScannerVersion() != scannerlib.ScannerVersion {
t.Errorf("scannerlib.Scan() returned scanner version: %s, expected %s",
result.GetScannerVersion(), scannerlib.ScannerVersion)
}
}
func TestCompliantScan(t *testing.T) {
compliantCheck := []*ipb.FileCheck{
&ipb.FileCheck{
FilesToCheck: []*ipb.FileSet{testconfigcreator.SingleFileWithPath(testFilePath1)},
CheckType: &ipb.FileCheck_Content{Content: &ipb.ContentCheck{Content: testFileContent1}},
},
}
config := &apb.ScanConfig{
BenchmarkConfigs: []*apb.BenchmarkConfig{
testconfigcreator.NewBenchmarkConfig(
t, "id", testconfigcreator.NewFileScanInstruction(compliantCheck)),
},
}
result, err := scannerlib.Scanner{}.Scan(context.Background(), config, fakeAPIProvider{})
if err != nil {
t.Fatalf("scannerlib.Scan(%v) had unexpected error: %v", config, err)
}
if result.GetStatus().GetStatus() != apb.ScanStatus_SUCCEEDED {
t.Fatalf("scannerlib.Scan(%v) returned unsuccessful scan status: %v",
config, result.GetStatus().GetStatus())
}
if len(result.GetCompliantBenchmarks()) != 1 {
t.Errorf("scannerlib.Scan(%v) returned check result: %v, expected 1 compliant check.",
config, result)
}
if len(result.GetNonCompliantBenchmarks()) != 0 {
t.Errorf("scannerlib.Scan(%v) returned check result: %v, expected 0 non-compliant configchecks.",
config, result)
}
}
func TestNonCompliantScan(t *testing.T) {
testCases := []struct {
desc string
config *apb.ScanConfig
}{
{
desc: "non compliant files",
config: &apb.ScanConfig{
BenchmarkConfigs: []*apb.BenchmarkConfig{
testconfigcreator.NewBenchmarkConfig(
t, "id", testconfigcreator.NewFileScanInstruction([]*ipb.FileCheck{
{
FilesToCheck: []*ipb.FileSet{testconfigcreator.SingleFileWithPath(testFilePath1)},
CheckType: &ipb.FileCheck_Content{Content: &ipb.ContentCheck{Content: "Different content"}},
},
})),
},
},
},
{
desc: "non compliance reason",
config: &apb.ScanConfig{
BenchmarkConfigs: []*apb.BenchmarkConfig{
testconfigcreator.NewBenchmarkConfig(
t, "id", testconfigcreator.NewSQLScanInstruction([]*ipb.SQLCheck{
&ipb.SQLCheck{
TargetDatabase: ipb.SQLCheck_DB_MYSQL,
Query: testQueryOneRow,
ExpectResults: false,
},
})),
},
},
},
}
for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
result, err := scannerlib.Scanner{}.Scan(context.Background(), tc.config, fakeAPIProvider{})
if err != nil {
t.Fatalf("scannerlib.Scan(%v) had unexpected error: %v", tc.config, err)
}
if result.GetStatus().GetStatus() != apb.ScanStatus_SUCCEEDED {
t.Fatalf("scannerlib.Scan(%v) returned unsuccessful scan status: %v",
tc.config, result.GetStatus().GetStatus())
}
if len(result.GetCompliantBenchmarks()) != 0 {
t.Errorf("scannerlib.Scan(%v) returned check result: %v, expected 0 compliant configchecks.", tc.config, result)
}
if len(result.GetNonCompliantBenchmarks()) != 1 {
t.Errorf("scannerlib.Scan(%v) returned check result: %v, expected 1 non-compliant check.",
tc.config, result)
}
})
}
}
func TestFailingScan(t *testing.T) {
nonExistentPath := "/non/existent/file"
failingCheck := []*ipb.FileCheck{
&ipb.FileCheck{
FilesToCheck: []*ipb.FileSet{testconfigcreator.SingleFileWithPath(nonExistentPath)},
CheckType: &ipb.FileCheck_Content{Content: &ipb.ContentCheck{Content: "Content"}},
},
}
config := &apb.ScanConfig{
BenchmarkConfigs: []*apb.BenchmarkConfig{
testconfigcreator.NewBenchmarkConfig(
t, "id", testconfigcreator.NewFileScanInstruction(failingCheck)),
},
}
result, err := scannerlib.Scanner{}.Scan(context.Background(), config, fakeAPIProvider{})
if err != nil {
t.Fatalf("scannerlib.Scan(%v) had unexpected error: %v", config, err)
}
if result.GetStatus().GetStatus() != apb.ScanStatus_FAILED {
t.Errorf("scannerlib.Scan(%v) returned scan status: %v, expected ScanStatus_FAILED",
config, result.GetStatus().GetStatus())
}
expectedFailureReason := fmt.Sprintf(
"Compliance state of the following benchmarks couldn't be determined: [id]\n"+
"The following errors were encountered while running the checks:\n"+
"[file check on single_file:{path:%q}]: api.OpenFile(%q): File not found\n",
nonExistentPath, nonExistentPath)
if diff := cmp.Diff(expectedFailureReason, result.GetStatus().GetFailureReason(),
protocmp.Transform()); diff != "" {
t.Errorf("scannerlib.Scan(%v) returned unexpected failure reason, (-want +got):\n%s", config, diff)
}
}
func TestBenchmarkDocumentInScanResults(t *testing.T) {
document := "CIS document"
check := []*ipb.FileCheck{
&ipb.FileCheck{
FilesToCheck: []*ipb.FileSet{testconfigcreator.SingleFileWithPath(testFilePath1)},
CheckType: &ipb.FileCheck_Content{Content: &ipb.ContentCheck{Content: testFileContent1}},
},
}
bc := testconfigcreator.NewBenchmarkConfig(t, "id", testconfigcreator.NewFileScanInstruction(check))
bc.GetComplianceNote().Version = []*cpb.ComplianceVersion{{BenchmarkDocument: document}}
config := &apb.ScanConfig{BenchmarkConfigs: []*apb.BenchmarkConfig{bc}}
result, err := scannerlib.Scanner{}.Scan(context.Background(), config, fakeAPIProvider{})
if err != nil {
t.Fatalf("scannerlib.Scan(%v) had unexpected error: %v", config, err)
}
if result.GetBenchmarkDocument() != document {
t.Errorf("scannerlib.Scan(%v) expected to return document %q, got %q", config, document, result.GetBenchmarkDocument())
}
}
func TestNonCompliantFileCheckResultsAreAggregated(t *testing.T) {
testCases := []struct {
desc string
checkAlternative *ipb.CheckAlternative
expectedNonCompliantBenchmarks []*apb.ComplianceResult
}{
{
desc: "File checks",
checkAlternative: &ipb.CheckAlternative{
FileChecks: []*ipb.FileCheck{
&ipb.FileCheck{
FilesToCheck: []*ipb.FileSet{testconfigcreator.SingleFileWithPath(testFilePath1)},
CheckType: &ipb.FileCheck_Content{Content: &ipb.ContentCheck{Content: "Different content 1"}},
},
&ipb.FileCheck{
FilesToCheck: []*ipb.FileSet{testconfigcreator.SingleFileWithPath(testFilePath2)},
CheckType: &ipb.FileCheck_Content{Content: &ipb.ContentCheck{Content: "Different content 2"}},
},
},
},
expectedNonCompliantBenchmarks: []*apb.ComplianceResult{
&apb.ComplianceResult{
Id: "id",
ComplianceOccurrence: &cpb.ComplianceOccurrence{
NonCompliantFiles: []*cpb.NonCompliantFile{
&cpb.NonCompliantFile{
Path: testFilePath1,
Reason: fmt.Sprintf("Got content %q, expected \"Different content 1\"", testFileContent1),
},
&cpb.NonCompliantFile{
Path: testFilePath2,
Reason: fmt.Sprintf("Got content %q, expected \"Different content 2\"", testFileContent2),
},
},
},
},
},
},
{
desc: "SQL checks",
checkAlternative: &ipb.CheckAlternative{
SqlChecks: []*ipb.SQLCheck{
&ipb.SQLCheck{
TargetDatabase: ipb.SQLCheck_DB_MYSQL,
Query: testQueryOneRow,
ExpectResults: false,
},
&ipb.SQLCheck{
TargetDatabase: ipb.SQLCheck_DB_MYSQL,
Query: testQueryNoRows,
ExpectResults: true,
},
},
},
expectedNonCompliantBenchmarks: []*apb.ComplianceResult{
&apb.ComplianceResult{
Id: "id",
ComplianceOccurrence: &cpb.ComplianceOccurrence{
NonCompliantFiles: []*cpb.NonCompliantFile{},
NonComplianceReason: fmt.Sprintf("Expected no results for query %q, but got 1 rows.\nExpected results for query %q, but got none.", testQueryOneRow, testQueryNoRows),
},
},
},
},
}
for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
config := &apb.ScanConfig{
BenchmarkConfigs: []*apb.BenchmarkConfig{
testconfigcreator.NewBenchmarkConfig(
t, "id", &ipb.BenchmarkScanInstruction{
CheckAlternatives: []*ipb.CheckAlternative{tc.checkAlternative},
}),
},
}
result, err := scannerlib.Scanner{}.Scan(context.Background(), config, fakeAPIProvider{})
if err != nil {
t.Fatalf("scannerlib.Scan(%v) had unexpected error: %v", config, err)
}
if result.GetStatus().GetStatus() != apb.ScanStatus_SUCCEEDED {
t.Fatalf("scannerlib.Scan(%v) returned unsuccessful scan status: %v",
config, result.GetStatus().GetStatus())
}
if len(result.GetCompliantBenchmarks()) != 0 {
t.Errorf("scannerlib.Scan(%v) returned check result: %v, expected 0 compliant configchecks.",
config, result)
}
sortProtos := cmpopts.SortSlices(func(m1, m2 protocmp.Message) bool { return m1.String() < m2.String() })
if diff := cmp.Diff(tc.expectedNonCompliantBenchmarks, result.GetNonCompliantBenchmarks(),
protocmp.Transform(), sortProtos); diff != "" {
t.Errorf("scannerlib.Scan(%v) returned unexpected results (-want +got):\n%s", config, diff)
}
})
}
}
func TestBenchmarkIsNonCompliantIfOneCheckIsNonCompliant(t *testing.T) {
nonCompliantCheck := &ipb.FileCheck{
FilesToCheck: []*ipb.FileSet{testconfigcreator.SingleFileWithPath(testFilePath1)},
CheckType: &ipb.FileCheck_Content{Content: &ipb.ContentCheck{Content: "Different content 1"}},
}
compliantCheck := &ipb.FileCheck{
FilesToCheck: []*ipb.FileSet{testconfigcreator.SingleFileWithPath(testFilePath2)},
CheckType: &ipb.FileCheck_Content{Content: &ipb.ContentCheck{Content: testFileContent2}},
}
checks := []*ipb.FileCheck{nonCompliantCheck, compliantCheck}
config := &apb.ScanConfig{
BenchmarkConfigs: []*apb.BenchmarkConfig{
testconfigcreator.NewBenchmarkConfig(
t, "id", testconfigcreator.NewFileScanInstruction(checks)),
},
}
result, err := scannerlib.Scanner{}.Scan(context.Background(), config, fakeAPIProvider{})
if err != nil {
t.Fatalf("scannerlib.Scan(%v) had unexpected error: %v", config, err)
}
if result.GetStatus().GetStatus() != apb.ScanStatus_SUCCEEDED {
t.Fatalf("scannerlib.Scan(%v) returned unsuccessful scan status: %v",
config, result.GetStatus().GetStatus())
}
if len(result.GetCompliantBenchmarks()) != 0 {
t.Errorf("scannerlib.Scan(%v) returned check result: %v, expected 0 compliant configchecks.",
config, result)
}
if len(result.GetNonCompliantBenchmarks()) != 1 {
t.Errorf("scannerlib.Scan(%v) returned check result: %v, expected 1 non-compliant check.",
config, result)
}
}
func TestDuplicateBenchmarkIDs(t *testing.T) {
check := []*ipb.FileCheck{&ipb.FileCheck{
FilesToCheck: []*ipb.FileSet{testconfigcreator.SingleFileWithPath(testFilePath1)},
CheckType: &ipb.FileCheck_Content{Content: &ipb.ContentCheck{Content: testFileContent1}},
}}
config := &apb.ScanConfig{
BenchmarkConfigs: []*apb.BenchmarkConfig{
testconfigcreator.NewBenchmarkConfig(
t, "id", testconfigcreator.NewFileScanInstruction(check)),
testconfigcreator.NewBenchmarkConfig(
t, "id", testconfigcreator.NewFileScanInstruction(check)),
},
}
if _, err := (scannerlib.Scanner{}).Scan(context.Background(), config, fakeAPIProvider{}); err == nil {
t.Fatalf("scannerlib.Scan(%v) didn't return an error", config)
}
}
func TestAlternativeWithFileAndDBChecks(t *testing.T) {
testCases := []struct {
desc string
fileCheck *ipb.FileCheck
sqlCheck *ipb.SQLCheck
expectCompliance bool
}{
{
desc: "Both checks compliant",
fileCheck: &ipb.FileCheck{
FilesToCheck: []*ipb.FileSet{testconfigcreator.SingleFileWithPath(testFilePath1)},
CheckType: &ipb.FileCheck_Existence{Existence: &ipb.ExistenceCheck{ShouldExist: true}},
},
sqlCheck: &ipb.SQLCheck{
TargetDatabase: ipb.SQLCheck_DB_MYSQL,
Query: "SELECT 1",
ExpectResults: true,
},
expectCompliance: true,
},
{
desc: "One check non-compliant",
fileCheck: &ipb.FileCheck{
FilesToCheck: []*ipb.FileSet{testconfigcreator.SingleFileWithPath(testFilePath1)},
CheckType: &ipb.FileCheck_Existence{Existence: &ipb.ExistenceCheck{ShouldExist: true}},
},
sqlCheck: &ipb.SQLCheck{
TargetDatabase: ipb.SQLCheck_DB_MYSQL,
Query: "SELECT 1",
ExpectResults: false,
},
expectCompliance: false,
},
{
desc: "Both checks non-compliant",
fileCheck: &ipb.FileCheck{
FilesToCheck: []*ipb.FileSet{testconfigcreator.SingleFileWithPath(testFilePath1)},
CheckType: &ipb.FileCheck_Existence{Existence: &ipb.ExistenceCheck{ShouldExist: false}},
},
sqlCheck: &ipb.SQLCheck{
TargetDatabase: ipb.SQLCheck_DB_MYSQL,
Query: "SELECT 1",
ExpectResults: false,
},
expectCompliance: false,
},
}
for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
config := &apb.ScanConfig{
BenchmarkConfigs: []*apb.BenchmarkConfig{
testconfigcreator.NewBenchmarkConfig(
t, "id", &ipb.BenchmarkScanInstruction{
CheckAlternatives: []*ipb.CheckAlternative{
&ipb.CheckAlternative{
FileChecks: []*ipb.FileCheck{tc.fileCheck},
SqlChecks: []*ipb.SQLCheck{tc.sqlCheck},
},
},
}),
},
}
result, err := scannerlib.Scanner{}.Scan(context.Background(), config, fakeAPIProvider{})
if err != nil {
t.Fatalf("scannerlib.Scan(%v) returned an error: %v", config, err)
}
if result.GetStatus().GetStatus() != apb.ScanStatus_SUCCEEDED {
t.Fatalf("scannerlib.Scan(%v) returned unsuccessful scan status: %v",
config, result.GetStatus().GetStatus())
}
compliant := len(result.GetNonCompliantBenchmarks()) == 0
if tc.expectCompliance != compliant {
t.Errorf("scannerlib.Scan(%v) expected to return compliance status %t, got %t:\n%v", config, tc.expectCompliance, compliant, result)
}
})
}
}
func TestInstructionsWithUnknownField(t *testing.T) {
instructions := fmt.Sprintf("check_alternatives:{unknown_field: \"value\" file_checks:{files_to_check:{single_file:{path:%q}} existence:{should_exist: true}}}", testFilePath1)
config := &apb.ScanConfig{
BenchmarkConfigs: []*apb.BenchmarkConfig{
&apb.BenchmarkConfig{
Id: "test",
ComplianceNote: &cpb.ComplianceNote{
ScanInstructions: []byte(instructions),
},
},
},
}
result, err := scannerlib.Scanner{}.Scan(context.Background(), config, fakeAPIProvider{})
if err != nil {
t.Fatalf("scannerlib.Scan(%v) returned an error: %v", config, err)
}
if result.GetStatus().GetStatus() != apb.ScanStatus_SUCCEEDED {
t.Errorf("scannerlib.Scan(%v) returned unsuccessful scan status: %v", config, result.GetStatus().GetStatus())
}
}
func TestInstructionsWithBinarySerialization(t *testing.T) {
compliantCheck := []*ipb.FileCheck{{
FilesToCheck: []*ipb.FileSet{testconfigcreator.SingleFileWithPath(testFilePath1)},
CheckType: &ipb.FileCheck_Content{Content: &ipb.ContentCheck{Content: testFileContent1}},
}}
instructions := testconfigcreator.NewFileScanInstruction(compliantCheck)
serInstructions, err := proto.Marshal(instructions)
if err != nil {
t.Fatalf("proto.Marshal(%v) returned error: %v", instructions, err)
}
config := &apb.ScanConfig{
BenchmarkConfigs: []*apb.BenchmarkConfig{
&apb.BenchmarkConfig{
Id: "test",
ComplianceNote: &cpb.ComplianceNote{
ScanInstructions: serInstructions,
},
},
},
}
result, err := scannerlib.Scanner{}.Scan(context.Background(), config, fakeAPIProvider{})
if err != nil {
t.Fatalf("scannerlib.Scan(%v) returned an error: %v", config, err)
}
if result.GetStatus().GetStatus() != apb.ScanStatus_SUCCEEDED {
t.Errorf("scannerlib.Scan(%v) returned unsuccessful scan status: %v", config, result.GetStatus().GetStatus())
}
if len(result.GetNonCompliantBenchmarks()) > 0 {
t.Errorf("scannerlib.Scan(%v) returned non-compliant benchmarks, expected none: %v", config, result.GetNonCompliantBenchmarks())
}
}
func TestCheckAlternativeAggregation(t *testing.T) {
compliantChecks := []*ipb.FileCheck{&ipb.FileCheck{
FilesToCheck: []*ipb.FileSet{testconfigcreator.SingleFileWithPath(testFilePath2)},
CheckType: &ipb.FileCheck_Content{Content: &ipb.ContentCheck{Content: testFileContent2}},
}}
nonCompliantChecks := []*ipb.FileCheck{&ipb.FileCheck{
FilesToCheck: []*ipb.FileSet{testconfigcreator.SingleFileWithPath(testFilePath1)},
CheckType: &ipb.FileCheck_Content{Content: &ipb.ContentCheck{Content: "Different content"}},
}}
nonCompliantChecks2 := []*ipb.FileCheck{&ipb.FileCheck{
FilesToCheck: []*ipb.FileSet{testconfigcreator.SingleFileWithPath(testFilePath2)},
CheckType: &ipb.FileCheck_Content{Content: &ipb.ContentCheck{Content: "Different content"}},
}}
failingChecks := []*ipb.FileCheck{&ipb.FileCheck{
FilesToCheck: []*ipb.FileSet{testconfigcreator.SingleFileWithPath("/non/existent/file")},
CheckType: &ipb.FileCheck_Content{Content: &ipb.ContentCheck{Content: "Content"}},
}}
testCases := []struct {
desc string
instructions *ipb.BenchmarkScanInstruction
expectedCompliantBenchmarks []*apb.ComplianceResult
expectedNonCompliantBenchmarks []*apb.ComplianceResult
expectedScanStatus apb.ScanStatus_ScanStatusEnum
}{
{
desc: "Two compliant alternatives",
instructions: &ipb.BenchmarkScanInstruction{
CheckAlternatives: []*ipb.CheckAlternative{
&ipb.CheckAlternative{
FileChecks: compliantChecks,
},
&ipb.CheckAlternative{
FileChecks: compliantChecks,
},
},
},
expectedCompliantBenchmarks: []*apb.ComplianceResult{&apb.ComplianceResult{
Id: "id",
ComplianceOccurrence: &cpb.ComplianceOccurrence{
NonCompliantFiles: []*cpb.NonCompliantFile{},
},
}},
expectedNonCompliantBenchmarks: []*apb.ComplianceResult{},
expectedScanStatus: apb.ScanStatus_SUCCEEDED,
},
{
desc: "One compliant and one non-compliant alternative",
instructions: &ipb.BenchmarkScanInstruction{
CheckAlternatives: []*ipb.CheckAlternative{
&ipb.CheckAlternative{
FileChecks: compliantChecks,
},
&ipb.CheckAlternative{
FileChecks: nonCompliantChecks,
},
},
},
expectedCompliantBenchmarks: []*apb.ComplianceResult{&apb.ComplianceResult{
Id: "id",
ComplianceOccurrence: &cpb.ComplianceOccurrence{
NonCompliantFiles: []*cpb.NonCompliantFile{},
},
}},
expectedNonCompliantBenchmarks: []*apb.ComplianceResult{},
expectedScanStatus: apb.ScanStatus_SUCCEEDED,
},
{
desc: "Two non-compliant alternatives",
instructions: &ipb.BenchmarkScanInstruction{
CheckAlternatives: []*ipb.CheckAlternative{
&ipb.CheckAlternative{
FileChecks: nonCompliantChecks,
},
&ipb.CheckAlternative{
FileChecks: nonCompliantChecks2,
},
},
},
expectedCompliantBenchmarks: []*apb.ComplianceResult{},
expectedNonCompliantBenchmarks: []*apb.ComplianceResult{&apb.ComplianceResult{
Id: "id",
ComplianceOccurrence: &cpb.ComplianceOccurrence{
NonCompliantFiles: []*cpb.NonCompliantFile{
&cpb.NonCompliantFile{
Path: testFilePath1,
Reason: fmt.Sprintf("Got content %q, expected \"Different content\"", testFileContent1),
},
&cpb.NonCompliantFile{
Path: testFilePath2,
Reason: fmt.Sprintf("Got content %q, expected \"Different content\"", testFileContent2),
},
},
},
}},
expectedScanStatus: apb.ScanStatus_SUCCEEDED,
},
{
desc: "One failing, one compliant, one non-compliant alternative",
instructions: &ipb.BenchmarkScanInstruction{
CheckAlternatives: []*ipb.CheckAlternative{
&ipb.CheckAlternative{
FileChecks: compliantChecks,
},
&ipb.CheckAlternative{
FileChecks: nonCompliantChecks,
},
&ipb.CheckAlternative{
FileChecks: failingChecks,
},
},
},
expectedCompliantBenchmarks: []*apb.ComplianceResult{},
expectedNonCompliantBenchmarks: []*apb.ComplianceResult{},
expectedScanStatus: apb.ScanStatus_FAILED,
},
}
for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
config := &apb.ScanConfig{
BenchmarkConfigs: []*apb.BenchmarkConfig{
testconfigcreator.NewBenchmarkConfig(t, "id", tc.instructions),
},
}
result, err := scannerlib.Scanner{}.Scan(context.Background(), config, fakeAPIProvider{})
if err != nil {
t.Fatalf("scannerlib.Scan(%v) had unexpected error: %v", config, err)
}
if result.GetStatus().GetStatus() != tc.expectedScanStatus {
t.Fatalf("expected scan status %v, scannerlib.Scan(%v) returned: %v",
tc.expectedScanStatus, config, result.GetStatus().GetStatus())
}
if diff := cmp.Diff(tc.expectedCompliantBenchmarks, result.GetCompliantBenchmarks(), protocmp.Transform()); diff != "" {
t.Errorf("scannerlib.Scan(%v) returned unexpected compliant files (-want +got):\n%s", config, diff)
}
if diff := cmp.Diff(tc.expectedNonCompliantBenchmarks, result.GetNonCompliantBenchmarks(), protocmp.Transform()); diff != "" {
t.Errorf("scannerlib.Scan(%v) returned unexpected non-compliant files (-want +got):\n%s", config, diff)
}
})
}
}
func TestDuplicateFindingsRemoved(t *testing.T) {
nonComplianceMsg := "unexpected content"
displayCmd := "ls /path/to/test"
checks := []*ipb.FileCheck{
&ipb.FileCheck{
FilesToCheck: []*ipb.FileSet{testconfigcreator.SingleFileWithPath(testFilePath1)},
CheckType: &ipb.FileCheck_Content{Content: &ipb.ContentCheck{Content: "Different content"}},
NonComplianceMsg: nonComplianceMsg,
FileDisplayCommand: displayCmd,
},
&ipb.FileCheck{
FilesToCheck: []*ipb.FileSet{testconfigcreator.SingleFileWithPath(testFilePath2)},
CheckType: &ipb.FileCheck_Content{Content: &ipb.ContentCheck{Content: "Different content"}},
NonComplianceMsg: nonComplianceMsg,
FileDisplayCommand: displayCmd,
},
}
config := &apb.ScanConfig{
BenchmarkConfigs: []*apb.BenchmarkConfig{
testconfigcreator.NewBenchmarkConfig(t, "id", testconfigcreator.NewFileScanInstruction(checks)),
},
}
result, err := scannerlib.Scanner{}.Scan(context.Background(), config, fakeAPIProvider{})
if err != nil {
t.Fatalf("scannerlib.Scan(%v) had unexpected error: %v", config, err)
}
// Expect only a single non-compliance finding.
want := []*apb.ComplianceResult{&apb.ComplianceResult{
Id: "id",
ComplianceOccurrence: &cpb.ComplianceOccurrence{
NonCompliantFiles: []*cpb.NonCompliantFile{&cpb.NonCompliantFile{
DisplayCommand: displayCmd,
Reason: nonComplianceMsg,
}},
},
}}
if diff := cmp.Diff(want, result.GetNonCompliantBenchmarks(), protocmp.Transform()); diff != "" {
t.Errorf("scannerlib.Scan(%v) returned unexpected non-compliant files (-want +got):\n%s", config, diff)
}
}
func benchmarkConfigsWithVersions(t *testing.T, versions [][]string) []*apb.BenchmarkConfig {
instruction := testconfigcreator.NewFileScanInstruction([]*ipb.FileCheck{
&ipb.FileCheck{
FilesToCheck: []*ipb.FileSet{testconfigcreator.SingleFileWithPath(testFilePath1)},
CheckType: &ipb.FileCheck_Content{Content: &ipb.ContentCheck{Content: testFileContent1}},
},
})
benchmarkConfigs := make([]*apb.BenchmarkConfig, 0, len(versions))
for i, v := range versions {
versions := make([]*cpb.ComplianceVersion, 0, len(v))
for _, vv := range v {
versions = append(versions, &cpb.ComplianceVersion{Version: vv})
}
bc := testconfigcreator.NewBenchmarkConfig(t, fmt.Sprintf("id%d", i+1), instruction)
bc.GetComplianceNote().Version = versions
benchmarkConfigs = append(benchmarkConfigs, bc)
}
return benchmarkConfigs
}
func TestOldestBenchmarkVersionInScanResult(t *testing.T) {
testCases := []struct {
desc string
versions [][]string
expectedVersion string
}{
{
desc: "basic version test",
versions: [][]string{{"1.0.1"}, {"1.1.2"}},
expectedVersion: "1.0.1",
},
{
desc: "multiple-digit versions",
versions: [][]string{{"2.11.14"}, {"2.11.12"}},
expectedVersion: "2.11.12",
},
{
desc: "versions substrings of each other",
versions: [][]string{{"2.1"}, {"2.1.1"}},
expectedVersion: "2.1",
},
{
desc: "benchmarks with multiple versions",
versions: [][]string{
{"1.0.0", "1.0.1", "1.1.0"},
{"1.2.0"},
},
expectedVersion: "1.1.0",
},
{
desc: "empty version",
versions: [][]string{{""}, {"2.1.1"}},
expectedVersion: "0.0.0",
},
{
desc: "invalid format",
versions: [][]string{{"invalid"}, {"2.1.1"}},
expectedVersion: "0.0.0",
},
{
desc: "non-numeric version fragment",
versions: [][]string{{"1.2.invalid"}, {"1.2.3"}},
expectedVersion: "0.0.0",
},
}
for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
config := &apb.ScanConfig{
BenchmarkConfigs: benchmarkConfigsWithVersions(t, tc.versions),
}
result, err := scannerlib.Scanner{}.Scan(context.Background(), config, fakeAPIProvider{})
if err != nil {
t.Fatalf("scannerlib.Scan(%v) had unexpected error: %v", config, err)
}
if result.GetStatus().GetStatus() != apb.ScanStatus_SUCCEEDED {
t.Fatalf("scannerlib.Scan(%v) returned unsuccessful scan status: %v",
config, result.GetStatus().GetStatus())
}
if result.GetBenchmarkVersion() != tc.expectedVersion {
t.Errorf("scannerlib.Scan(%v) returned benchmark version %s, expected %s",
config, result.GetBenchmarkVersion(), tc.expectedVersion)
}
})
}
}