Generalize CheckFileContent functions (#1670)

Co-authored-by: Azeem Shaikh <azeems@google.com>
This commit is contained in:
Azeem Shaikh 2022-02-22 15:40:34 -08:00 committed by GitHub
parent 5656c3ed45
commit e41f8595cb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 223 additions and 238 deletions

View File

@ -98,23 +98,37 @@ func DangerousWorkflow(c *checker.CheckRequest) checker.CheckResult {
data := patternCbData{ data := patternCbData{
workflowPattern: make(map[dangerousResults]bool), workflowPattern: make(map[dangerousResults]bool),
} }
err := fileparser.CheckFilesContent(".github/workflows/*", false, err := fileparser.OnMatchingFileContentDo(c.RepoClient, fileparser.PathMatcher{
c, validateGitHubActionWorkflowPatterns, &data) Pattern: ".github/workflows/*",
CaseSensitive: false,
},
validateGitHubActionWorkflowPatterns, c.Dlogger, &data)
return createResultForDangerousWorkflowPatterns(data, err) return createResultForDangerousWorkflowPatterns(data, err)
} }
// Check file content. // Check file content.
func validateGitHubActionWorkflowPatterns(path string, content []byte, dl checker.DetailLogger, var validateGitHubActionWorkflowPatterns fileparser.DoWhileTrueOnFileContent = func(path string,
data fileparser.FileCbData) (bool, error) { content []byte,
args ...interface{}) (bool, error) {
if !fileparser.IsWorkflowFile(path) { if !fileparser.IsWorkflowFile(path) {
return true, nil return true, nil
} }
if len(args) != 2 {
return false, fmt.Errorf(
"validateGitHubActionWorkflowPatterns requires exactly 2 arguments: %w", errInvalidArgLength)
}
// Verify the type of the data. // Verify the type of the data.
pdata, ok := data.(*patternCbData) pdata, ok := args[1].(*patternCbData)
if !ok { if !ok {
// This never happens. return false, fmt.Errorf(
panic("invalid type") "validateGitHubActionWorkflowPatterns expects arg[0] of type *patternCbData: %w", errInvalidArgType)
}
dl, ok := args[0].(checker.DetailLogger)
if !ok {
return false, fmt.Errorf(
"validateGitHubActionWorkflowPatterns expects arg[1] of type checker.DetailLogger: %w", errInvalidArgType)
} }
if !fileparser.CheckFileContainsCommands(content, "#") { if !fileparser.CheckFileContainsCommands(content, "#") {

View File

@ -20,16 +20,16 @@ import (
"path" "path"
"strings" "strings"
"github.com/ossf/scorecard/v4/checker"
"github.com/ossf/scorecard/v4/clients" "github.com/ossf/scorecard/v4/clients"
sce "github.com/ossf/scorecard/v4/errors" sce "github.com/ossf/scorecard/v4/errors"
) )
// isMatchingPath uses 'pattern' to shell-match the 'path' and its filename // isMatchingPath uses 'pattern' to shell-match the 'path' and its filename
// 'caseSensitive' indicates the match should be case-sensitive. Default: no. // 'caseSensitive' indicates the match should be case-sensitive. Default: no.
func isMatchingPath(pattern, fullpath string, caseSensitive bool) (bool, error) { func isMatchingPath(fullpath string, matchPathTo PathMatcher) (bool, error) {
if !caseSensitive { pattern := matchPathTo.Pattern
pattern = strings.ToLower(pattern) if !matchPathTo.CaseSensitive {
pattern = strings.ToLower(matchPathTo.Pattern)
fullpath = strings.ToLower(fullpath) fullpath = strings.ToLower(fullpath)
} }
@ -55,87 +55,30 @@ func isTestdataFile(fullpath string) bool {
strings.Contains(fullpath, "/testdata/") strings.Contains(fullpath, "/testdata/")
} }
// FileCbData is any data the caller can act upon // PathMatcher represents a query for a filepath.
// to keep state. type PathMatcher struct {
type FileCbData interface{} Pattern string
CaseSensitive bool
// FileContentCb is the callback.
// The bool returned indicates whether the CheckFilesContent2
// should continue iterating over files or not.
type FileContentCb func(path string, content []byte,
dl checker.DetailLogger, data FileCbData) (bool, error)
// CheckFilesContent downloads the tar of the repository and calls the onFileContent() function
// shellPathFnPattern is used for https://golang.org/pkg/path/#Match
// Warning: the pattern is used to match (1) the entire path AND (2) the filename alone. This means:
// - To scope the search to a directory, use "./dirname/*". Example, for the root directory,
// use "./*".
// - A pattern such as "*mypatern*" will match files containing mypattern in *any* directory.
func CheckFilesContent(shellPathFnPattern string,
caseSensitive bool,
c *checker.CheckRequest,
onFileContent FileContentCb,
data FileCbData,
) error {
predicate := func(filepath string) (bool, error) {
// Filter out test files.
if isTestdataFile(filepath) {
return false, nil
}
// Filter out files based on path/names using the pattern.
b, err := isMatchingPath(shellPathFnPattern, filepath, caseSensitive)
if err != nil {
return false, err
}
return b, nil
}
matchedFiles, err := c.RepoClient.ListFiles(predicate)
if err != nil {
// nolint: wrapcheck
return err
}
for _, file := range matchedFiles {
content, err := c.RepoClient.GetFileContent(file)
if err != nil {
//nolint
return err
}
continueIter, err := onFileContent(file, content, c.Dlogger, data)
if err != nil {
return err
}
if !continueIter {
break
}
}
return nil
} }
// FileContentCbV6 is the callback. // DoWhileTrueOnFileContent takes a filepath, its content and
// The bool returned indicates whether the CheckFilesContent2 // optional variadic args. It returns a boolean indicating whether
// should continue iterating over files or not. // iterating over next files should continue.
type FileContentCbV6 func(path string, content []byte, data FileCbData) (bool, error) type DoWhileTrueOnFileContent func(path string, content []byte, args ...interface{}) (bool, error)
// CheckFilesContentV6 is the same as CheckFilesContent // OnMatchingFileContentDo matches all files listed by `repoClient` against `matchPathTo`
// but for use with separated check/policy code. // and on every successful match, runs onFileContent fn on the file's contents.
func CheckFilesContentV6(shellPathFnPattern string, // Continues iterating along the matched files until onFileContent returns
caseSensitive bool, // either a false value or an error.
repoClient clients.RepoClient, func OnMatchingFileContentDo(repoClient clients.RepoClient, matchPathTo PathMatcher,
onFileContent FileContentCbV6, onFileContent DoWhileTrueOnFileContent, args ...interface{}) error {
data FileCbData,
) error {
predicate := func(filepath string) (bool, error) { predicate := func(filepath string) (bool, error) {
// Filter out test files. // Filter out test files.
if isTestdataFile(filepath) { if isTestdataFile(filepath) {
return false, nil return false, nil
} }
// Filter out files based on path/names using the pattern. // Filter out files based on path/names using the pattern.
b, err := isMatchingPath(shellPathFnPattern, filepath, caseSensitive) b, err := isMatchingPath(filepath, matchPathTo)
if err != nil { if err != nil {
return false, err return false, err
} }
@ -144,18 +87,16 @@ func CheckFilesContentV6(shellPathFnPattern string,
matchedFiles, err := repoClient.ListFiles(predicate) matchedFiles, err := repoClient.ListFiles(predicate)
if err != nil { if err != nil {
// nolint: wrapcheck return fmt.Errorf("error during ListFiles: %w", err)
return err
} }
for _, file := range matchedFiles { for _, file := range matchedFiles {
content, err := repoClient.GetFileContent(file) content, err := repoClient.GetFileContent(file)
if err != nil { if err != nil {
//nolint return fmt.Errorf("error during GetFileContent: %w", err)
return err
} }
continueIter, err := onFileContent(file, content, data) continueIter, err := onFileContent(file, content, args...)
if err != nil { if err != nil {
return err return err
} }

View File

@ -20,7 +20,6 @@ import (
"github.com/golang/mock/gomock" "github.com/golang/mock/gomock"
"github.com/ossf/scorecard/v4/checker"
mockrepo "github.com/ossf/scorecard/v4/clients/mockclients" mockrepo "github.com/ossf/scorecard/v4/clients/mockclients"
) )
@ -316,7 +315,10 @@ func Test_isMatchingPath(t *testing.T) {
tt := tt // Re-initializing variable so it is not changed while executing the closure below tt := tt // Re-initializing variable so it is not changed while executing the closure below
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
t.Parallel() t.Parallel()
got, err := isMatchingPath(tt.args.pattern, tt.args.fullpath, tt.args.caseSensitive) got, err := isMatchingPath(tt.args.fullpath, PathMatcher{
Pattern: tt.args.pattern,
CaseSensitive: tt.args.caseSensitive,
})
if (err != nil) != tt.wantErr { if (err != nil) != tt.wantErr {
t.Errorf("isMatchingPath() error = %v, wantErr %v", err, tt.wantErr) t.Errorf("isMatchingPath() error = %v, wantErr %v", err, tt.wantErr)
return return
@ -385,8 +387,8 @@ func Test_isTestdataFile(t *testing.T) {
} }
} }
// TestCheckFilesContentV6 tests the CheckFilesContentV6 function. // TestOnMatchingFileContentDo tests the OnMatchingFileContent function.
func TestCheckFilesContentV6(t *testing.T) { func TestOnMatchingFileContent(t *testing.T) {
t.Parallel() t.Parallel()
//nolint //nolint
tests := []struct { tests := []struct {
@ -448,50 +450,6 @@ func TestCheckFilesContentV6(t *testing.T) {
"Dockerfile.template.template.template.template", "Dockerfile.template.template.template.template",
}, },
}, },
}
for _, tt := range tests {
tt := tt // Re-initializing variable so it is not changed while executing the closure below
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
x := func(path string, content []byte, data FileCbData) (bool, error) {
if tt.shouldFuncFail {
//nolint
return false, errors.New("test error")
}
if tt.shouldGetPredicateFail {
return false, nil
}
return true, nil
}
ctrl := gomock.NewController(t)
mockRepo := mockrepo.NewMockRepoClient(ctrl)
mockRepo.EXPECT().ListFiles(gomock.Any()).Return(tt.files, nil).AnyTimes()
mockRepo.EXPECT().GetFileContent(gomock.Any()).Return(nil, nil).AnyTimes()
result := CheckFilesContentV6(tt.shellPattern, tt.caseSensitive, mockRepo, x, x)
if tt.wantErr && result == nil {
t.Errorf("CheckFilesContentV6() = %v, want %v test name %v", result, tt.wantErr, tt.name)
}
})
}
}
// TestCheckFilesContent tests the CheckFilesContent function.
func TestCheckFilesContent(t *testing.T) {
t.Parallel()
//nolint
tests := []struct {
name string
wantErr bool
shellPattern string
caseSensitive bool
shouldFuncFail bool
shouldGetPredicateFail bool
files []string
}{
{ {
name: "no files", name: "no files",
shellPattern: "Dockerfile", shellPattern: "Dockerfile",
@ -548,8 +506,7 @@ func TestCheckFilesContent(t *testing.T) {
tt := tt // Re-initializing variable so it is not changed while executing the closure below tt := tt // Re-initializing variable so it is not changed while executing the closure below
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
t.Parallel() t.Parallel()
x := func(path string, content []byte, x := func(path string, content []byte, args ...interface{}) (bool, error) {
dl checker.DetailLogger, data FileCbData) (bool, error) {
if tt.shouldFuncFail { if tt.shouldFuncFail {
//nolint //nolint
return false, errors.New("test error") return false, errors.New("test error")
@ -565,14 +522,13 @@ func TestCheckFilesContent(t *testing.T) {
mockRepo.EXPECT().ListFiles(gomock.Any()).Return(tt.files, nil).AnyTimes() mockRepo.EXPECT().ListFiles(gomock.Any()).Return(tt.files, nil).AnyTimes()
mockRepo.EXPECT().GetFileContent(gomock.Any()).Return(nil, nil).AnyTimes() mockRepo.EXPECT().GetFileContent(gomock.Any()).Return(nil, nil).AnyTimes()
c := checker.CheckRequest{ result := OnMatchingFileContentDo(mockRepo, PathMatcher{
RepoClient: mockRepo, Pattern: tt.shellPattern,
} CaseSensitive: tt.caseSensitive,
}, x)
result := CheckFilesContent(tt.shellPattern, tt.caseSensitive, &c, x, x)
if tt.wantErr && result == nil { if tt.wantErr && result == nil {
t.Errorf("CheckFilesContentV6() = %v, want %v test name %v", result, tt.wantErr, tt.name) t.Errorf("OnMatchingFileContentDo() = %v, want %v test name %v", result, tt.wantErr, tt.name)
} }
}) })
} }

View File

@ -36,11 +36,13 @@ func init() {
func checkCFLite(c *checker.CheckRequest) (bool, error) { func checkCFLite(c *checker.CheckRequest) (bool, error) {
result := false result := false
e := fileparser.CheckFilesContent(".clusterfuzzlite/Dockerfile", true, c, e := fileparser.OnMatchingFileContentDo(c.RepoClient, fileparser.PathMatcher{
func(path string, content []byte, dl checker.DetailLogger, data fileparser.FileCbData) (bool, error) { Pattern: ".clusterfuzzlite/Dockerfile",
result = fileparser.CheckFileContainsCommands(content, "#") CaseSensitive: true,
return false, nil }, func(path string, content []byte, args ...interface{}) (bool, error) {
}, nil) result = fileparser.CheckFileContainsCommands(content, "#")
return false, nil
}, nil)
if e != nil { if e != nil {
return result, fmt.Errorf("%w", e) return result, fmt.Errorf("%w", e)
} }

View File

@ -82,11 +82,68 @@ func TokenPermissions(c *checker.CheckRequest) checker.CheckResult {
data := permissionCbData{ data := permissionCbData{
workflows: make(map[string]permissions), workflows: make(map[string]permissions),
} }
err := fileparser.CheckFilesContent(".github/workflows/*", false, err := fileparser.OnMatchingFileContentDo(c.RepoClient, fileparser.PathMatcher{
c, validateGitHubActionTokenPermissions, &data) Pattern: ".github/workflows/*",
CaseSensitive: false,
}, validateGitHubActionTokenPermissions, c.Dlogger, &data)
return createResultForLeastPrivilegeTokens(data, err) return createResultForLeastPrivilegeTokens(data, err)
} }
// Check file content.
var validateGitHubActionTokenPermissions fileparser.DoWhileTrueOnFileContent = func(path string,
content []byte,
args ...interface{}) (bool, error) {
if !fileparser.IsWorkflowFile(path) {
return true, nil
}
// Verify the type of the data.
if len(args) != 2 {
return false, fmt.Errorf(
"validateGitHubActionTokenPermissions requires exactly 2 arguments: %w", errInvalidArgLength)
}
pdata, ok := args[1].(*permissionCbData)
if !ok {
return false, fmt.Errorf(
"validateGitHubActionTokenPermissions requires arg[0] of type *permissionCbData: %w", errInvalidArgType)
}
dl, ok := args[0].(checker.DetailLogger)
if !ok {
return false, fmt.Errorf(
"validateGitHubActionTokenPermissions requires arg[1] of type checker.DetailLogger: %w", errInvalidArgType)
}
if !fileparser.CheckFileContainsCommands(content, "#") {
return true, nil
}
workflow, errs := actionlint.Parse(content)
if len(errs) > 0 && workflow == nil {
return false, fileparser.FormatActionlintError(errs)
}
// 1. Top-level permission definitions.
//nolint
// https://docs.github.com/en/actions/reference/authentication-in-a-workflow#example-1-passing-the-github_token-as-an-input,
// https://github.blog/changelog/2021-04-20-github-actions-control-permissions-for-github_token/,
// https://docs.github.com/en/actions/reference/authentication-in-a-workflow#modifying-the-permissions-for-the-github_token.
if err := validateTopLevelPermissions(workflow, path, dl, pdata); err != nil {
return false, err
}
// 2. Run-level permission definitions,
// see https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idpermissions.
ignoredPermissions := createIgnoredPermissions(workflow, path, dl)
if err := validatejobLevelPermissions(workflow, path, dl, pdata, ignoredPermissions); err != nil {
return false, err
}
// TODO(laurent): 2. Identify github actions that require write and add checks.
// TODO(laurent): 3. Read a few runs and ensures they have the same permissions.
return true, nil
}
func validatePermission(permissionKey permission, permissionValue *actionlint.PermissionScope, func validatePermission(permissionKey permission, permissionValue *actionlint.PermissionScope,
permLevel, path string, dl checker.DetailLogger, pPermissions map[permission]bool, permLevel, path string, dl checker.DetailLogger, pPermissions map[permission]bool,
ignoredPermissions map[permission]bool) error { ignoredPermissions map[permission]bool) error {
@ -378,51 +435,6 @@ func createResultForLeastPrivilegeTokens(result permissionCbData, err error) che
"tokens are read-only in GitHub workflows") "tokens are read-only in GitHub workflows")
} }
// Check file content.
func validateGitHubActionTokenPermissions(path string, content []byte,
dl checker.DetailLogger, data fileparser.FileCbData) (bool, error) {
if !fileparser.IsWorkflowFile(path) {
return true, nil
}
// Verify the type of the data.
pdata, ok := data.(*permissionCbData)
if !ok {
// This never happens.
panic("invalid type")
}
if !fileparser.CheckFileContainsCommands(content, "#") {
return true, nil
}
workflow, errs := actionlint.Parse(content)
if len(errs) > 0 && workflow == nil {
return false, fileparser.FormatActionlintError(errs)
}
// 1. Top-level permission definitions.
//nolint
// https://docs.github.com/en/actions/reference/authentication-in-a-workflow#example-1-passing-the-github_token-as-an-input,
// https://github.blog/changelog/2021-04-20-github-actions-control-permissions-for-github_token/,
// https://docs.github.com/en/actions/reference/authentication-in-a-workflow#modifying-the-permissions-for-the-github_token.
if err := validateTopLevelPermissions(workflow, path, dl, pdata); err != nil {
return false, err
}
// 2. Run-level permission definitions,
// see https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idpermissions.
ignoredPermissions := createIgnoredPermissions(workflow, path, dl)
if err := validatejobLevelPermissions(workflow, path, dl, pdata, ignoredPermissions); err != nil {
return false, err
}
// TODO(laurent): 2. Identify github actions that require write and add checks.
// TODO(laurent): 3. Read a few runs and ensures they have the same permissions.
return true, nil
}
func createIgnoredPermissions(workflow *actionlint.Workflow, fp string, dl checker.DetailLogger) map[permission]bool { func createIgnoredPermissions(workflow *actionlint.Workflow, fp string, dl checker.DetailLogger) map[permission]bool {
ignoredPermissions := make(map[permission]bool) ignoredPermissions := make(map[permission]bool)
if requiresPackagesPermissions(workflow, fp, dl) { if requiresPackagesPermissions(workflow, fp, dl) {

View File

@ -142,7 +142,7 @@ func addPinnedResult(r *pinnedResult, to bool) {
} }
} }
func dataAsWorkflowResultPointer(data fileparser.FileCbData) *worklowPinningResult { func dataAsWorkflowResultPointer(data interface{}) *worklowPinningResult {
pdata, ok := data.(*worklowPinningResult) pdata, ok := data.(*worklowPinningResult)
if !ok { if !ok {
// panic if it is not correct type // panic if it is not correct type
@ -151,6 +151,24 @@ func dataAsWorkflowResultPointer(data fileparser.FileCbData) *worklowPinningResu
return pdata return pdata
} }
func dataAsResultPointer(data interface{}) *pinnedResult {
pdata, ok := data.(*pinnedResult)
if !ok {
// This never happens.
panic("invalid type")
}
return pdata
}
func dataAsDetailLogger(data interface{}) checker.DetailLogger {
pdata, ok := data.(checker.DetailLogger)
if !ok {
// This never happens.
panic("invalid type")
}
return pdata
}
func createReturnValuesForGitHubActionsWorkflowPinned(r worklowPinningResult, infoMsg string, func createReturnValuesForGitHubActionsWorkflowPinned(r worklowPinningResult, infoMsg string,
dl checker.DetailLogger, err error) (int, error) { dl checker.DetailLogger, err error) (int, error) {
if err != nil { if err != nil {
@ -180,15 +198,6 @@ func createReturnValuesForGitHubActionsWorkflowPinned(r worklowPinningResult, in
return score, nil return score, nil
} }
func dataAsResultPointer(data fileparser.FileCbData) *pinnedResult {
pdata, ok := data.(*pinnedResult)
if !ok {
// This never happens.
panic("invalid type")
}
return pdata
}
func createReturnValues(r pinnedResult, infoMsg string, dl checker.DetailLogger, err error) (int, error) { func createReturnValues(r pinnedResult, infoMsg string, dl checker.DetailLogger, err error) (int, error) {
if err != nil { if err != nil {
return checker.InconclusiveResultScore, err return checker.InconclusiveResultScore, err
@ -210,8 +219,10 @@ func createReturnValues(r pinnedResult, infoMsg string, dl checker.DetailLogger,
func isShellScriptFreeOfInsecureDownloads(c *checker.CheckRequest) (int, error) { func isShellScriptFreeOfInsecureDownloads(c *checker.CheckRequest) (int, error) {
var r pinnedResult var r pinnedResult
err := fileparser.CheckFilesContent("*", false, err := fileparser.OnMatchingFileContentDo(c.RepoClient, fileparser.PathMatcher{
c, validateShellScriptIsFreeOfInsecureDownloads, &r) Pattern: "*",
CaseSensitive: false,
}, validateShellScriptIsFreeOfInsecureDownloads, c.Dlogger, &r)
return createReturnForIsShellScriptFreeOfInsecureDownloads(r, c.Dlogger, err) return createReturnForIsShellScriptFreeOfInsecureDownloads(r, c.Dlogger, err)
} }
@ -229,9 +240,16 @@ func testValidateShellScriptIsFreeOfInsecureDownloads(pathfn string,
return createReturnForIsShellScriptFreeOfInsecureDownloads(r, dl, err) return createReturnForIsShellScriptFreeOfInsecureDownloads(r, dl, err)
} }
func validateShellScriptIsFreeOfInsecureDownloads(pathfn string, content []byte, var validateShellScriptIsFreeOfInsecureDownloads fileparser.DoWhileTrueOnFileContent = func(
dl checker.DetailLogger, data fileparser.FileCbData) (bool, error) { pathfn string,
pdata := dataAsResultPointer(data) content []byte,
args ...interface{}) (bool, error) {
if len(args) != 2 {
return false, fmt.Errorf(
"validateShellScriptIsFreeOfInsecureDownloads requires exactly 2 arguments: %w", errInvalidArgLength)
}
pdata := dataAsResultPointer(args[1])
dl := dataAsDetailLogger(args[0])
// Validate the file type. // Validate the file type.
if !isSupportedShellScriptFile(pathfn, content) { if !isSupportedShellScriptFile(pathfn, content) {
@ -250,8 +268,10 @@ func validateShellScriptIsFreeOfInsecureDownloads(pathfn string, content []byte,
func isDockerfileFreeOfInsecureDownloads(c *checker.CheckRequest) (int, error) { func isDockerfileFreeOfInsecureDownloads(c *checker.CheckRequest) (int, error) {
var r pinnedResult var r pinnedResult
err := fileparser.CheckFilesContent("*Dockerfile*", err := fileparser.OnMatchingFileContentDo(c.RepoClient, fileparser.PathMatcher{
false, c, validateDockerfileIsFreeOfInsecureDownloads, &r) Pattern: "*Dockerfile*",
CaseSensitive: false,
}, validateDockerfileIsFreeOfInsecureDownloads, c.Dlogger, &r)
return createReturnForIsDockerfileFreeOfInsecureDownloads(r, c.Dlogger, err) return createReturnForIsDockerfileFreeOfInsecureDownloads(r, c.Dlogger, err)
} }
@ -285,9 +305,16 @@ func isDockerfile(pathfn string, content []byte) bool {
return true return true
} }
func validateDockerfileIsFreeOfInsecureDownloads(pathfn string, content []byte, var validateDockerfileIsFreeOfInsecureDownloads fileparser.DoWhileTrueOnFileContent = func(
dl checker.DetailLogger, data fileparser.FileCbData) (bool, error) { pathfn string,
pdata := dataAsResultPointer(data) content []byte,
args ...interface{}) (bool, error) {
if len(args) != 2 {
return false, fmt.Errorf(
"validateDockerfileIsFreeOfInsecureDownloads requires exactly 2 arguments: %w", errInvalidArgLength)
}
pdata := dataAsResultPointer(args[1])
dl := dataAsDetailLogger(args[0])
// Return early if this is not a docker file. // Return early if this is not a docker file.
if !isDockerfile(pathfn, content) { if !isDockerfile(pathfn, content) {
@ -344,8 +371,10 @@ func validateDockerfileIsFreeOfInsecureDownloads(pathfn string, content []byte,
func isDockerfilePinned(c *checker.CheckRequest) (int, error) { func isDockerfilePinned(c *checker.CheckRequest) (int, error) {
var r pinnedResult var r pinnedResult
err := fileparser.CheckFilesContent("*Dockerfile*", false, err := fileparser.OnMatchingFileContentDo(c.RepoClient, fileparser.PathMatcher{
c, validateDockerfileIsPinned, &r) Pattern: "*Dockerfile*",
CaseSensitive: false,
}, validateDockerfileIsPinned, c.Dlogger, &r)
return createReturnForIsDockerfilePinned(r, c.Dlogger, err) return createReturnForIsDockerfilePinned(r, c.Dlogger, err)
} }
@ -362,12 +391,19 @@ func testValidateDockerfileIsPinned(pathfn string, content []byte, dl checker.De
return createReturnForIsDockerfilePinned(r, dl, err) return createReturnForIsDockerfilePinned(r, dl, err)
} }
func validateDockerfileIsPinned(pathfn string, content []byte, var validateDockerfileIsPinned fileparser.DoWhileTrueOnFileContent = func(
dl checker.DetailLogger, data fileparser.FileCbData) (bool, error) { pathfn string,
content []byte,
args ...interface{}) (bool, error) {
// Users may use various names, e.g., // Users may use various names, e.g.,
// Dockerfile.aarch64, Dockerfile.template, Dockerfile_template, dockerfile, Dockerfile-name.template // Dockerfile.aarch64, Dockerfile.template, Dockerfile_template, dockerfile, Dockerfile-name.template
pdata := dataAsResultPointer(data) if len(args) != 2 {
return false, fmt.Errorf(
"validateDockerfileIsPinned requires exactly 2 arguments: %w", errInvalidArgLength)
}
pdata := dataAsResultPointer(args[1])
dl := dataAsDetailLogger(args[0])
// Return early if this is not a dockerfile. // Return early if this is not a dockerfile.
if !isDockerfile(pathfn, content) { if !isDockerfile(pathfn, content) {
addPinnedResult(pdata, true) addPinnedResult(pdata, true)
@ -472,8 +508,10 @@ func validateDockerfileIsPinned(pathfn string, content []byte,
func isGitHubWorkflowScriptFreeOfInsecureDownloads(c *checker.CheckRequest) (int, error) { func isGitHubWorkflowScriptFreeOfInsecureDownloads(c *checker.CheckRequest) (int, error) {
var r pinnedResult var r pinnedResult
err := fileparser.CheckFilesContent(".github/workflows/*", false, err := fileparser.OnMatchingFileContentDo(c.RepoClient, fileparser.PathMatcher{
c, validateGitHubWorkflowIsFreeOfInsecureDownloads, &r) Pattern: ".github/workflows/*",
CaseSensitive: false,
}, validateGitHubWorkflowIsFreeOfInsecureDownloads, c.Dlogger, &r)
return createReturnForIsGitHubWorkflowScriptFreeOfInsecureDownloads(r, c.Dlogger, err) return createReturnForIsGitHubWorkflowScriptFreeOfInsecureDownloads(r, c.Dlogger, err)
} }
@ -494,14 +532,20 @@ func testValidateGitHubWorkflowScriptFreeOfInsecureDownloads(pathfn string,
// validateGitHubWorkflowIsFreeOfInsecureDownloads checks if the workflow file downloads dependencies that are unpinned. // validateGitHubWorkflowIsFreeOfInsecureDownloads checks if the workflow file downloads dependencies that are unpinned.
// Returns true if the check should continue executing after this file. // Returns true if the check should continue executing after this file.
// nolint: gocognit var validateGitHubWorkflowIsFreeOfInsecureDownloads fileparser.DoWhileTrueOnFileContent = func(
func validateGitHubWorkflowIsFreeOfInsecureDownloads(pathfn string, content []byte, pathfn string,
dl checker.DetailLogger, data fileparser.FileCbData) (bool, error) { content []byte,
args ...interface{}) (bool, error) {
if !fileparser.IsWorkflowFile(pathfn) { if !fileparser.IsWorkflowFile(pathfn) {
return true, nil return true, nil
} }
pdata := dataAsResultPointer(data) if len(args) != 2 {
return false, fmt.Errorf(
"validateGitHubWorkflowIsFreeOfInsecureDownloads requires exactly 2 arguments: %w", errInvalidArgLength)
}
pdata := dataAsResultPointer(args[1])
dl := dataAsDetailLogger(args[0])
if !fileparser.CheckFileContainsCommands(content, "#") { if !fileparser.CheckFileContainsCommands(content, "#") {
addPinnedResult(pdata, true) addPinnedResult(pdata, true)
@ -569,8 +613,10 @@ func validateGitHubWorkflowIsFreeOfInsecureDownloads(pathfn string, content []by
// Check pinning of github actions in workflows. // Check pinning of github actions in workflows.
func isGitHubActionsWorkflowPinned(c *checker.CheckRequest) (int, error) { func isGitHubActionsWorkflowPinned(c *checker.CheckRequest) (int, error) {
var r worklowPinningResult var r worklowPinningResult
err := fileparser.CheckFilesContent(".github/workflows/*", err := fileparser.OnMatchingFileContentDo(c.RepoClient, fileparser.PathMatcher{
true, c, validateGitHubActionWorkflow, &r) Pattern: ".github/workflows/*",
CaseSensitive: true,
}, validateGitHubActionWorkflow, c.Dlogger, &r)
return createReturnForIsGitHubActionsWorkflowPinned(r, c.Dlogger, err) return createReturnForIsGitHubActionsWorkflowPinned(r, c.Dlogger, err)
} }
@ -597,13 +643,20 @@ func generateOwnerToDisplay(gitHubOwned bool) string {
// validateGitHubActionWorkflow checks if the workflow file contains unpinned actions. Returns true if the check // validateGitHubActionWorkflow checks if the workflow file contains unpinned actions. Returns true if the check
// should continue executing after this file. // should continue executing after this file.
func validateGitHubActionWorkflow(pathfn string, content []byte, var validateGitHubActionWorkflow fileparser.DoWhileTrueOnFileContent = func(
dl checker.DetailLogger, data fileparser.FileCbData) (bool, error) { pathfn string,
content []byte,
args ...interface{}) (bool, error) {
if !fileparser.IsWorkflowFile(pathfn) { if !fileparser.IsWorkflowFile(pathfn) {
return true, nil return true, nil
} }
pdata := dataAsWorkflowResultPointer(data) if len(args) != 2 {
return false, fmt.Errorf(
"validateGitHubActionWorkflow requires exactly 2 arguments: %w", errInvalidArgLength)
}
pdata := dataAsWorkflowResultPointer(args[1])
dl := dataAsDetailLogger(args[0])
if !fileparser.CheckFileContainsCommands(content, "#") { if !fileparser.CheckFileContainsCommands(content, "#") {
addWorkflowPinnedResult(pdata, true, true) addWorkflowPinnedResult(pdata, true, true)

View File

@ -31,7 +31,10 @@ import (
// BinaryArtifacts retrieves the raw data for the Binary-Artifacts check. // BinaryArtifacts retrieves the raw data for the Binary-Artifacts check.
func BinaryArtifacts(c clients.RepoClient) (checker.BinaryArtifactData, error) { func BinaryArtifacts(c clients.RepoClient) (checker.BinaryArtifactData, error) {
files := []checker.File{} files := []checker.File{}
err := fileparser.CheckFilesContentV6("*", false, c, checkBinaryFileContent, &files) err := fileparser.OnMatchingFileContentDo(c, fileparser.PathMatcher{
Pattern: "*",
CaseSensitive: false,
}, checkBinaryFileContent, &files)
if err != nil { if err != nil {
return checker.BinaryArtifactData{}, fmt.Errorf("%w", err) return checker.BinaryArtifactData{}, fmt.Errorf("%w", err)
} }
@ -40,12 +43,16 @@ func BinaryArtifacts(c clients.RepoClient) (checker.BinaryArtifactData, error) {
return checker.BinaryArtifactData{Files: files}, nil return checker.BinaryArtifactData{Files: files}, nil
} }
func checkBinaryFileContent(path string, content []byte, var checkBinaryFileContent fileparser.DoWhileTrueOnFileContent = func(path string, content []byte,
data fileparser.FileCbData) (bool, error) { args ...interface{}) (bool, error) {
pfiles, ok := data.(*[]checker.File) if len(args) != 1 {
return false, fmt.Errorf(
"checkBinaryFileContent requires exactly one argument: %w", errInvalidArgLength)
}
pfiles, ok := args[0].(*[]checker.File)
if !ok { if !ok {
// This never happens. return false, fmt.Errorf(
panic("invalid type") "checkBinaryFileContent requires argument of type *[]checker.File: %w", errInvalidArgType)
} }
binaryFileTypes := map[string]bool{ binaryFileTypes := map[string]bool{