Skip to content

Commit 47be523

Browse files
🐛 Retain tag when remediating unpinned docker images. (#2595)
Signed-off-by: Spencer Schrock <sschrock@google.com> Signed-off-by: Spencer Schrock <sschrock@google.com>
1 parent b30bc79 commit 47be523

File tree

3 files changed

+116
-6
lines changed

3 files changed

+116
-6
lines changed

checks/evaluation/pinned_dependencies.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ func generateRemediation(remediaitonMd remediation.RemediationMetadata, rr *chec
141141
case checker.DependencyUseTypeGHAction:
142142
return remediaitonMd.CreateWorkflowPinningRemediation(rr.Location.Path)
143143
case checker.DependencyUseTypeDockerfileContainerImage:
144-
return remediation.CreateDockerfilePinningRemediation(rr.Name)
144+
return remediation.CreateDockerfilePinningRemediation(rr, remediation.CraneDigester{})
145145
default:
146146
return nil
147147
}
@@ -183,7 +183,6 @@ func generateOwnerToDisplay(gitHubOwned bool) string {
183183
}
184184

185185
// TODO(laurent): need to support GCB pinning.
186-
//nolint
187186
func maxScore(s1, s2 int) int {
188187
if s1 > s2 {
189188
return s1

remediation/remediations.go

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -85,17 +85,38 @@ func (r *RemediationMetadata) createWorkflowRemediation(path, t string) *checker
8585
}
8686
}
8787

88+
func dockerImageName(d *checker.Dependency) (name string, ok bool) {
89+
if d.Name == nil || *d.Name == "" {
90+
return "", false
91+
}
92+
if d.PinnedAt != nil && *d.PinnedAt != "" {
93+
return fmt.Sprintf("%s:%s", *d.Name, *d.PinnedAt), true
94+
}
95+
return *d.Name, true
96+
}
97+
98+
type Digester interface{ Digest(string) (string, error) }
99+
100+
type CraneDigester struct{}
101+
102+
func (c CraneDigester) Digest(name string) (string, error) {
103+
//nolint:wrapcheck // error value not used
104+
return crane.Digest(name)
105+
}
106+
88107
// CreateDockerfilePinningRemediation create remediaiton for pinning Dockerfile images.
89-
func CreateDockerfilePinningRemediation(name *string) *checker.Remediation {
90-
if name == nil {
108+
func CreateDockerfilePinningRemediation(dep *checker.Dependency, digester Digester) *checker.Remediation {
109+
name, ok := dockerImageName(dep)
110+
if !ok {
91111
return nil
92112
}
93-
hash, err := crane.Digest(*name)
113+
114+
hash, err := digester.Digest(name)
94115
if err != nil {
95116
return nil
96117
}
97118

98-
text := fmt.Sprintf(dockerfilePinText, *name, hash)
119+
text := fmt.Sprintf(dockerfilePinText, name, hash)
99120
markdown := text
100121

101122
return &checker.Remediation{

remediation/remediations_test.go

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"testing"
2020

2121
"github.com/golang/mock/gomock"
22+
"github.com/google/go-cmp/cmp"
2223

2324
"github.com/ossf/scorecard/v4/checker"
2425
mockrepo "github.com/ossf/scorecard/v4/clients/mockclients"
@@ -49,3 +50,92 @@ func TestRepeatedSetup(t *testing.T) {
4950
}
5051
}
5152
}
53+
54+
func asPointer(s string) *string {
55+
return &s
56+
}
57+
58+
type stubDigester struct{}
59+
60+
func (s stubDigester) Digest(name string) (string, error) {
61+
m := map[string]string{
62+
"foo": "2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae",
63+
"baz": "fcde2b2edba56bf408601fb721fe9b5c338d10ee429ea04fae5511b68fbf8fb9",
64+
"amazoncorretto:11": "b1a711069b801a325a30885f08f5067b2b102232379750dda4d25a016afd9a88",
65+
}
66+
hash, ok := m[name]
67+
if !ok {
68+
//nolint:goerr113
69+
return "", fmt.Errorf("no hash for image: %q", name)
70+
}
71+
return fmt.Sprintf("sha256:%s", hash), nil
72+
}
73+
74+
func TestCreateDockerfilePinningRemediation(t *testing.T) {
75+
t.Parallel()
76+
77+
//nolint:govet,lll
78+
tests := []struct {
79+
name string
80+
dep checker.Dependency
81+
expected *checker.Remediation
82+
}{
83+
{
84+
name: "no depdendency",
85+
dep: checker.Dependency{},
86+
expected: nil,
87+
},
88+
{
89+
name: "image name no tag",
90+
dep: checker.Dependency{
91+
Name: asPointer("foo"),
92+
Type: checker.DependencyUseTypeDockerfileContainerImage,
93+
},
94+
expected: &checker.Remediation{
95+
HelpText: "pin your Docker image by updating foo to foo@sha256:2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae",
96+
HelpMarkdown: "pin your Docker image by updating foo to foo@sha256:2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae",
97+
},
98+
},
99+
{
100+
// github.com/ossf/scorecard/issues/2581
101+
name: "image name with tag",
102+
dep: checker.Dependency{
103+
Name: asPointer("amazoncorretto"),
104+
PinnedAt: asPointer("11"),
105+
Type: checker.DependencyUseTypeDockerfileContainerImage,
106+
},
107+
expected: &checker.Remediation{
108+
HelpText: "pin your Docker image by updating amazoncorretto:11 to amazoncorretto:11@sha256:b1a711069b801a325a30885f08f5067b2b102232379750dda4d25a016afd9a88",
109+
HelpMarkdown: "pin your Docker image by updating amazoncorretto:11 to amazoncorretto:11@sha256:b1a711069b801a325a30885f08f5067b2b102232379750dda4d25a016afd9a88",
110+
},
111+
},
112+
{
113+
name: "unknown image",
114+
dep: checker.Dependency{
115+
Name: asPointer("not-found"),
116+
Type: checker.DependencyUseTypeDockerfileContainerImage,
117+
},
118+
expected: nil,
119+
},
120+
{
121+
name: "unknown tag",
122+
dep: checker.Dependency{
123+
Name: asPointer("foo"),
124+
PinnedAt: asPointer("not-found"),
125+
Type: checker.DependencyUseTypeDockerfileContainerImage,
126+
},
127+
expected: nil,
128+
},
129+
}
130+
131+
for _, tt := range tests {
132+
tt := tt
133+
t.Run(tt.name, func(t *testing.T) {
134+
t.Parallel()
135+
got := CreateDockerfilePinningRemediation(&tt.dep, stubDigester{})
136+
if !cmp.Equal(got, tt.expected) {
137+
t.Errorf(cmp.Diff(got, tt.expected))
138+
}
139+
})
140+
}
141+
}

0 commit comments

Comments
 (0)