Skip to content

Commit

Permalink
🐛 Ignore unpinned dependencies in Dockerfiles in vendored directories
Browse files Browse the repository at this point in the history
Signed-off-by: AdamKorcz <adam@adalogics.com>
  • Loading branch information
AdamKorcz committed Nov 14, 2023
1 parent a4ee314 commit 4962379
Show file tree
Hide file tree
Showing 3 changed files with 130 additions and 11 deletions.
31 changes: 31 additions & 0 deletions checks/raw/pinned_dependencies.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package raw
import (
"errors"
"fmt"
"path/filepath"
"reflect"
"regexp"
"strings"
Expand Down Expand Up @@ -111,6 +112,27 @@ func collectDockerfileInsecureDownloads(c *checker.CheckRequest, r *checker.Pinn
}, validateDockerfileInsecureDownloads, r)
}

func fileIsInVendorDir(pathfn string) bool {
cleanedPath := filepath.Clean(pathfn)
splitCleanedPath := strings.Split(cleanedPath, "/")

// If the project vendors, this is likely to be how
// they do it. Therefore, check the first index
// before looping.
if splitCleanedPath[0] == "vendor" {
return true
}
for _, d := range splitCleanedPath {
if strings.EqualFold(d, "vendor") {
return true
}
if strings.EqualFold(d, "third_party") {
return true
}
}
return false
}

var validateDockerfileInsecureDownloads fileparser.DoWhileTrueOnFileContent = func(
pathfn string,
content []byte,
Expand All @@ -122,6 +144,10 @@ var validateDockerfileInsecureDownloads fileparser.DoWhileTrueOnFileContent = fu
len(args), errInvalidArgLength)
}

if fileIsInVendorDir(pathfn) {
return true, nil
}

Check warning on line 149 in checks/raw/pinned_dependencies.go

View check run for this annotation

Codecov / codecov/patch

checks/raw/pinned_dependencies.go#L148-L149

Added lines #L148 - L149 were not covered by tests

pdata := dataAsPinnedDependenciesPointer(args[0])

// Return early if this is not a docker file.
Expand Down Expand Up @@ -219,6 +245,11 @@ var validateDockerfilesPinning fileparser.DoWhileTrueOnFileContent = func(
return false, fmt.Errorf(
"validateDockerfilesPinning requires exactly 2 arguments: got %v: %w", len(args), errInvalidArgLength)
}

if fileIsInVendorDir(pathfn) {
return true, nil
}

pdata := dataAsPinnedDependenciesPointer(args[0])

// Return early if this is not a dockerfile.
Expand Down
78 changes: 67 additions & 11 deletions checks/raw/pinned_dependencies_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -340,41 +340,46 @@ func TestDockerfilePinning(t *testing.T) {
}{
{
name: "invalid dockerfile",
filename: "./testdata/Dockerfile-invalid",
filename: "Dockerfile-invalid",
},
{
name: "invalid dockerfile sh",
filename: "../testdata/script-sh",
filename: "../../testdata/script-sh",
},
{
name: "empty file",
filename: "./testdata/Dockerfile-empty",
filename: "Dockerfile-empty",
},
{
name: "comments only",
filename: "./testdata/Dockerfile-comments",
filename: "Dockerfile-comments",
},
{
name: "Pinned dockerfile",
filename: "./testdata/Dockerfile-pinned",
filename: "Dockerfile-pinned",
},
{
name: "Pinned dockerfile as",
filename: "./testdata/Dockerfile-pinned-as",
filename: "Dockerfile-pinned-as",
},
{
name: "Non-pinned dockerfile as",
filename: "./testdata/Dockerfile-not-pinned-as",
filename: "Dockerfile-not-pinned-as",
warns: 2,
},
{
name: "Non-pinned dockerfile but in vendor, ie: 0 warns",
filename: "vendor/Dockerfile-not-pinned-as",
warns: 0,
},
{
name: "Non-pinned dockerfile",
filename: "./testdata/Dockerfile-not-pinned",
filename: "Dockerfile-not-pinned",
warns: 1,
},
{
name: "Parser error doesn't affect docker image pinning",
filename: "./testdata/Dockerfile-not-pinned-with-parser-error",
filename: "Dockerfile-not-pinned-with-parser-error",
warns: 1,
},
}
Expand All @@ -387,14 +392,14 @@ func TestDockerfilePinning(t *testing.T) {
if tt.filename == "" {
content = make([]byte, 0)
} else {
content, err = os.ReadFile(tt.filename)
content, err = os.ReadFile(filepath.Join("testdata", tt.filename))
if err != nil {
t.Errorf("cannot read file: %v", err)
}
}

var r checker.PinningDependenciesData
_, err = validateDockerfilesPinning(tt.filename, content, &r)
_, err = validateDockerfilesPinning(filepath.Join("testdata", tt.filename), content, &r)
if !errCmp(err, tt.err) {
t.Errorf(cmp.Diff(err, tt.err, cmpopts.EquateErrors()))
}
Expand All @@ -412,6 +417,57 @@ func TestDockerfilePinning(t *testing.T) {
}
}

func TestFileIsInVendorDir(t *testing.T) {
t.Parallel()
tests := []struct {
name string
filename string
expected bool
}{
{
name: "not in vendor or third_party",
filename: "a/b/c/d/Dockerfile",
expected: false,
},
{
name: "is third_party deep in tree",
filename: "a/b/third_party/Dockerfile",
expected: true,
},
{
name: "in vendor",
filename: "vendor/a/b/Dockerfile",
expected: true,
},
{
name: "in third_party",
filename: "third_party/b/c/Dockerfile",
expected: true,
},
{
name: "in deep vendor",
filename: "a/b/c/vendor/Dockerfile",
expected: true,
},
{
name: "misspelled vendor dir",
filename: "a/vendorr/Dockerfile",
expected: false,
},
}

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()
got := fileIsInVendorDir(tt.filename)
if got != tt.expected {
t.Errorf("expected %v. Got %v", tt.expected, got)
}
})
}
}

func TestDockerfilePinningFromLineNumber(t *testing.T) {
t.Parallel()
tests := []struct {
Expand Down
32 changes: 32 additions & 0 deletions checks/raw/testdata/vendor/Dockerfile-not-pinned-as
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@

# Copyright 2021 OpenSSF 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.
FROM python:3.7@sha256:45b23dee08af5e43a7fea6c4cf9c25ccf269ee113168c19722f87876677c5cb2 as base

FROM python:3.7 as build
ARG TARGETOS
ARG TARGETARCH
RUN CGO_ENABLED=0 make build-scorecard

# Add spaces
FROM build
RUN /hello-world

FROM base as base2
RUN ls

FROM base2
RUN ls

FROM python@sha256:45b23dee08af5e43a7fea6c4cf9c25ccf269ee113168c19722f87876677c5cb2

0 comments on commit 4962379

Please sign in to comment.