From 2d8ec84be42336582a74d54b32a9ee65235a32f2 Mon Sep 17 00:00:00 2001 From: Chris McGehee Date: Mon, 22 Nov 2021 07:40:17 -0800 Subject: [PATCH 1/2] Get OSes from matrix.include if present (#1323) --- checks/fileparser/github_workflow.go | 21 ++++++++++++++++ checks/fileparser/github_workflow_test.go | 10 ++++++++ ...ells-all-windows-matrix-include-empty.yaml | 23 ++++++++++++++++++ ...low-shells-all-windows-matrix-include.yaml | 24 +++++++++++++++++++ 4 files changed, 78 insertions(+) create mode 100644 checks/testdata/github-workflow-shells-all-windows-matrix-include-empty.yaml create mode 100644 checks/testdata/github-workflow-shells-all-windows-matrix-include.yaml diff --git a/checks/fileparser/github_workflow.go b/checks/fileparser/github_workflow.go index 9a110b83b47..724f2ca2b6a 100644 --- a/checks/fileparser/github_workflow.go +++ b/checks/fileparser/github_workflow.go @@ -87,6 +87,14 @@ func getJobStrategyMatrixRows(job *actionlint.Job) map[string]*actionlint.Matrix return nil } +func getJobStrategyMatrixIncludeCombinations(job *actionlint.Job) []*actionlint.MatrixCombination { + if job != nil && job.Strategy != nil && job.Strategy.Matrix != nil && job.Strategy.Matrix.Include != nil && + job.Strategy.Matrix.Include.Combinations != nil { + return job.Strategy.Matrix.Include.Combinations + } + return nil +} + // FormatActionlintError combines the errors into a single one. func FormatActionlintError(errs []*actionlint.Error) error { if len(errs) == 0 { @@ -125,6 +133,19 @@ func GetOSesForJob(job *actionlint.Job) ([]string, error) { } } + matrixCombinations := getJobStrategyMatrixIncludeCombinations(job) + for _, combination := range matrixCombinations { + if combination.Assigns == nil { + continue + } + for _, assign := range combination.Assigns { + if assign.Key == nil || assign.Key.Value != os || assign.Value == nil { + continue + } + jobOSes = append(jobOSes, strings.Trim(assign.Value.String(), "'\"")) + } + } + if len(jobOSes) == 0 { return jobOSes, sce.WithMessage(sce.ErrScorecardInternal, fmt.Sprintf("unable to determine OS for job: %v", GetJobName(job))) diff --git a/checks/fileparser/github_workflow_test.go b/checks/fileparser/github_workflow_test.go index 6a0dc11e234..7817115e4d3 100644 --- a/checks/fileparser/github_workflow_test.go +++ b/checks/fileparser/github_workflow_test.go @@ -49,6 +49,16 @@ func TestGitHubWorkflowShell(t *testing.T) { filename: "../testdata/github-workflow-shells-all-windows-matrix.yaml", expectedShells: []string{"pwsh"}, }, + { + name: "all windows, OSes listed in matrix.include", + filename: "../testdata/github-workflow-shells-all-windows-matrix-include.yaml", + expectedShells: []string{"pwsh"}, + }, + { + name: "all windows, empty matrix.include", + filename: "../testdata/github-workflow-shells-all-windows-matrix-include-empty.yaml", + expectedShells: []string{"pwsh"}, + }, { name: "all windows", filename: "../testdata/github-workflow-shells-all-windows.yaml", diff --git a/checks/testdata/github-workflow-shells-all-windows-matrix-include-empty.yaml b/checks/testdata/github-workflow-shells-all-windows-matrix-include-empty.yaml new file mode 100644 index 00000000000..5245efe8615 --- /dev/null +++ b/checks/testdata/github-workflow-shells-all-windows-matrix-include-empty.yaml @@ -0,0 +1,23 @@ +# Copyright 2021 Security Scorecard Authors +# +# 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. + +jobs: + Job1: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [windows-2019, windows-latest] + include: + steps: + - run: echo "hello, github" diff --git a/checks/testdata/github-workflow-shells-all-windows-matrix-include.yaml b/checks/testdata/github-workflow-shells-all-windows-matrix-include.yaml new file mode 100644 index 00000000000..76ed37a8326 --- /dev/null +++ b/checks/testdata/github-workflow-shells-all-windows-matrix-include.yaml @@ -0,0 +1,24 @@ +# Copyright 2021 Security Scorecard Authors +# +# 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. + +jobs: + Job1: + runs-on: ${{ matrix.os }} + strategy: + matrix: + include: + - {name: Windows, python: '3.9', os: windows-latest} + - {name: Windows, python: '3.8', os: windows-2019} + steps: + - run: echo "hello, github" From 9b600bdc6938ae37b1fc89ba9c3085723b0486b0 Mon Sep 17 00:00:00 2001 From: Chris McGehee Date: Mon, 22 Nov 2021 08:16:03 -0800 Subject: [PATCH 2/2] Skip pinned dependencies check for template Dockerfiles (#1324) Co-authored-by: Azeem Shaikh --- checks/fileparser/listing.go | 19 +++++ checks/fileparser/listing_test.go | 122 ++++++++++++++++++++++++++++++ checks/pinned_dependencies.go | 6 +- 3 files changed, 146 insertions(+), 1 deletion(-) create mode 100644 checks/fileparser/listing_test.go diff --git a/checks/fileparser/listing.go b/checks/fileparser/listing.go index c16895671f2..2f6650db5a8 100644 --- a/checks/fileparser/listing.go +++ b/checks/fileparser/listing.go @@ -222,3 +222,22 @@ func CheckFileContainsCommands(content []byte, comment string) bool { } return false } + +// IsTemplateFile returns true if the file name contains a string commonly used in template files. +func IsTemplateFile(pathfn string) bool { + parts := strings.FieldsFunc(path.Base(pathfn), func(r rune) bool { + switch r { + case '.', '-', '_': + return true + default: + return false + } + }) + for _, part := range parts { + switch strings.ToLower(part) { + case "template", "tmpl", "tpl": + return true + } + } + return false +} diff --git a/checks/fileparser/listing_test.go b/checks/fileparser/listing_test.go new file mode 100644 index 00000000000..1966b444b84 --- /dev/null +++ b/checks/fileparser/listing_test.go @@ -0,0 +1,122 @@ +// Copyright 2021 Security Scorecard Authors +// +// 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 fileparser + +import ( + "testing" +) + +func TestIsTemplateFile(t *testing.T) { + t.Parallel() + + tests := []struct { + filename string + isTemplate bool + }{ + { + filename: "Dockerfile.template", + isTemplate: true, + }, + { + filename: "Dockerfile.tmpl", + isTemplate: true, + }, + { + filename: "Dockerfile.template-debian", + isTemplate: true, + }, + { + filename: "Dockerfile.tmpl.", + isTemplate: true, + }, + { + filename: "Dockerfile-template", + isTemplate: true, + }, + { + filename: "tmpl.Dockerfile", + isTemplate: true, + }, + { + filename: "template.Dockerfile", + isTemplate: true, + }, + { + filename: "Dockerfile_template", + isTemplate: true, + }, + { + filename: "Dockerfile.tmpl.prod", + isTemplate: true, + }, + { + filename: "Dockerfile.Template", + isTemplate: true, + }, + { + filename: "dockerfile.tpl", + isTemplate: true, + }, + { + filename: "build/Dockerfile.tpl", + isTemplate: true, + }, + { + filename: "build/tpl.Dockerfile", + isTemplate: true, + }, + { + filename: "DockerfileTemplate", + isTemplate: false, + }, + { + filename: "Dockerfile.linux", + isTemplate: false, + }, + { + filename: "tmp.Dockerfile", + isTemplate: false, + }, + { + filename: "Dockerfile", + isTemplate: false, + }, + { + filename: "Dockerfile.temp.late", + isTemplate: false, + }, + { + filename: "Dockerfile.temp", + isTemplate: false, + }, + { + filename: "template/Dockerfile", + isTemplate: false, + }, + { + filename: "linux.Dockerfile", + isTemplate: false, + }, + } + for _, tt := range tests { + tt := tt // Re-initializing variable so it is not changed while executing the closure below + t.Run(tt.filename, func(t *testing.T) { + t.Parallel() + if got := IsTemplateFile(tt.filename); got != tt.isTemplate { + t.Errorf("%v: Got (%v) expected (%v)", tt.filename, got, tt.isTemplate) + } + }) + } +} diff --git a/checks/pinned_dependencies.go b/checks/pinned_dependencies.go index 43ee0c95454..152f2ee353a 100644 --- a/checks/pinned_dependencies.go +++ b/checks/pinned_dependencies.go @@ -336,7 +336,6 @@ func validateDockerfileIsPinned(pathfn string, content []byte, dl checker.DetailLogger, data fileparser.FileCbData) (bool, error) { // Users may use various names, e.g., // Dockerfile.aarch64, Dockerfile.template, Dockerfile_template, dockerfile, Dockerfile-name.template - // Templates may trigger false positives, e.g. FROM { NAME }. pdata := dataAsResultPointer(data) // Return early if this is a script, e.g. script_dockerfile_something.sh @@ -350,6 +349,11 @@ func validateDockerfileIsPinned(pathfn string, content []byte, return true, nil } + if fileparser.IsTemplateFile(pathfn) { + addPinnedResult(pdata, true) + return true, nil + } + // We have what looks like a docker file. // Let's interpret the content as utf8-encoded strings. contentReader := strings.NewReader(string(content))