Skip to content

Commit 95d0c1b

Browse files
add image-sha check
1 parent 191de10 commit 95d0c1b

File tree

9 files changed

+326
-0
lines changed

9 files changed

+326
-0
lines changed

docs/generated/checks.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,22 @@ forbiddenServiceTypes:
259259
```yaml
260260
minReplicas: 3
261261
```
262+
## image-sha
263+
264+
**Enabled by default**: No
265+
266+
**Description**: Indicates if there are any image references that are not references by sha256 tags
267+
268+
**Remediation**: Reference all images using their sha256 tags.
269+
270+
**Template**: [image-sha](templates.md#latest-tag)
271+
272+
**Parameters**:
273+
274+
```yaml
275+
AllowList:
276+
- .*:[a-fA-F0-9]{64}$
277+
```
262278
## invalid-target-ports
263279
264280
**Enabled by default**: Yes

docs/generated/templates.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,36 @@ KubeLinter supports the following templates:
407407

408408
## Latest Tag
409409

410+
**Key**: `image-sha`
411+
412+
**Description**: Flag applications running container images that do not satisfies "allowList" & "blockList" parameters criteria.
413+
414+
**Supported Objects**: DeploymentLike
415+
416+
417+
**Parameters**:
418+
419+
```yaml
420+
- arrayElemType: string
421+
description: list of regular expressions specifying pattern(s) for container images
422+
that will be blocked. */
423+
name: blockList
424+
negationAllowed: true
425+
regexAllowed: true
426+
required: false
427+
type: array
428+
- arrayElemType: string
429+
description: list of regular expressions specifying pattern(s) for container images
430+
that will be allowed.
431+
name: allowList
432+
negationAllowed: true
433+
regexAllowed: true
434+
required: false
435+
type: array
436+
```
437+
438+
## Latest Tag
439+
410440
**Key**: `latest-tag`
411441

412442
**Description**: Flag applications running container images that do not satisfies "allowList" & "blockList" parameters criteria.

e2etests/bats-tests.sh

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,23 @@ get_value_from() {
389389
[[ "${count}" == "2" ]]
390390
}
391391

392+
@test "image-sha" {
393+
tmp="tests/checks/image-sha.yml"
394+
cmd="${KUBE_LINTER_BIN} lint --include image-sha --do-not-auto-add-defaults --format json ${tmp}"
395+
run ${cmd}
396+
397+
print_info "${status}" "${output}" "${cmd}" "${tmp}"
398+
[ "$status" -eq 1 ]
399+
400+
message1=$(get_value_from "${lines[0]}" '.Reports[0].Object.K8sObject.GroupVersionKind.Kind + ": " + .Reports[0].Diagnostic.Message')
401+
message2=$(get_value_from "${lines[0]}" '.Reports[1].Object.K8sObject.GroupVersionKind.Kind + ": " + .Reports[1].Diagnostic.Message')
402+
count=$(get_value_from "${lines[0]}" '.Reports | length')
403+
404+
[[ "${message1}" == "Deployment: The container \"app\" is using an invalid container image, \"app:latest\". Please reference the image using a SHA256 tag." ]]
405+
[[ "${message2}" == "DeploymentConfig: The container \"app\" is using an invalid container image, \"app:latest\". Please reference the image using a SHA256 tag." ]]
406+
[[ "${count}" == "2" ]]
407+
}
408+
392409
@test "minimum-three-replicas" {
393410
tmp="tests/checks/minimum-three-replicas.yml"
394411
cmd="${KUBE_LINTER_BIN} lint --include minimum-three-replicas --do-not-auto-add-defaults --format json ${tmp}"
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
name: "image-sha"
2+
description: "Indicates if there are any image references that are not references by sha256 tags"
3+
remediation: "Reference all images using their sha256 tags."
4+
scope:
5+
objectKinds:
6+
- DeploymentLike
7+
template: "image-sha"
8+
params:
9+
AllowList: [".*:[a-fA-F0-9]{64}$"]

pkg/templates/all/all.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
_ "golang.stackrox.io/kube-linter/pkg/templates/hostpid"
2525
_ "golang.stackrox.io/kube-linter/pkg/templates/hpareplicas"
2626
_ "golang.stackrox.io/kube-linter/pkg/templates/imagepullpolicy"
27+
_ "golang.stackrox.io/kube-linter/pkg/templates/imageshatag"
2728
_ "golang.stackrox.io/kube-linter/pkg/templates/latesttag"
2829
_ "golang.stackrox.io/kube-linter/pkg/templates/livenessprobe"
2930
_ "golang.stackrox.io/kube-linter/pkg/templates/memoryrequirements"

pkg/templates/imageshatag/internal/params/gen-params.go

Lines changed: 86 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package params
2+
3+
// Params represents the params accepted by this template.
4+
type Params struct {
5+
6+
// list of regular expressions specifying pattern(s) for container images that will be blocked. */
7+
BlockList []string
8+
9+
// list of regular expressions specifying pattern(s) for container images that will be allowed.
10+
AllowList []string
11+
}

pkg/templates/imageshatag/template.go

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package imageshatag
2+
3+
import (
4+
"fmt"
5+
"regexp"
6+
7+
"github.com/pkg/errors"
8+
"golang.stackrox.io/kube-linter/pkg/check"
9+
"golang.stackrox.io/kube-linter/pkg/config"
10+
"golang.stackrox.io/kube-linter/pkg/diagnostic"
11+
"golang.stackrox.io/kube-linter/pkg/objectkinds"
12+
"golang.stackrox.io/kube-linter/pkg/templates"
13+
"golang.stackrox.io/kube-linter/pkg/templates/imageshatag/internal/params"
14+
"golang.stackrox.io/kube-linter/pkg/templates/util"
15+
v1 "k8s.io/api/core/v1"
16+
)
17+
18+
const (
19+
templateKey = "image-sha"
20+
)
21+
22+
func init() {
23+
templates.Register(check.Template{
24+
HumanName: "Latest Tag",
25+
Key: templateKey,
26+
Description: "Flag applications running container images that do not satisfies \"allowList\" & \"blockList\" parameters criteria.",
27+
SupportedObjectKinds: config.ObjectKindsDesc{
28+
ObjectKinds: []string{objectkinds.DeploymentLike},
29+
},
30+
Parameters: params.ParamDescs,
31+
ParseAndValidateParams: params.ParseAndValidate,
32+
Instantiate: params.WrapInstantiateFunc(func(p params.Params) (check.Func, error) {
33+
34+
blockedRegexes := make([]*regexp.Regexp, 0, len(p.BlockList))
35+
for _, res := range p.BlockList {
36+
rg, err := regexp.Compile(res)
37+
if err != nil {
38+
return nil, errors.Wrapf(err, "invalid regex %s", res)
39+
}
40+
blockedRegexes = append(blockedRegexes, rg)
41+
}
42+
43+
allowedRegexes := make([]*regexp.Regexp, 0, len(p.AllowList))
44+
for _, res := range p.AllowList {
45+
rg, err := regexp.Compile(res)
46+
if err != nil {
47+
return nil, errors.Wrapf(err, "invalid regex %s", res)
48+
}
49+
allowedRegexes = append(allowedRegexes, rg)
50+
}
51+
52+
if len(blockedRegexes) > 0 && len(allowedRegexes) > 0 {
53+
err := fmt.Errorf("check has both \"allowList\" & \"blockList\" parameter's values set")
54+
return nil, errors.Wrapf(err, "only one of the paramater lists can be used at a time")
55+
}
56+
57+
return util.PerContainerCheck(func(container *v1.Container) (results []diagnostic.Diagnostic) {
58+
if len(blockedRegexes) > 0 && isInList(blockedRegexes, container.Image) {
59+
results = append(results, diagnostic.Diagnostic{Message: fmt.Sprintf("The container %q is using an invalid container image, %q. Please reference the image using a SHA256 tag.", container.Name, container.Image)})
60+
} else if len(allowedRegexes) > 0 && !isInList(allowedRegexes, container.Image) {
61+
results = append(results, diagnostic.Diagnostic{Message: fmt.Sprintf("The container %q is using an invalid container image, %q. Please reference the image using a SHA256 tag.", container.Name, container.Image)})
62+
}
63+
return results
64+
}), nil
65+
}),
66+
})
67+
}
68+
69+
// isInList returns true if a match found in the list for the given name
70+
func isInList(regexlist []*regexp.Regexp, name string) bool {
71+
for _, regex := range regexlist {
72+
if regex.MatchString(name) {
73+
return true
74+
}
75+
}
76+
return false
77+
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package imageshatag
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/suite"
7+
8+
"golang.stackrox.io/kube-linter/pkg/diagnostic"
9+
"golang.stackrox.io/kube-linter/pkg/lintcontext/mocks"
10+
"golang.stackrox.io/kube-linter/pkg/templates"
11+
"golang.stackrox.io/kube-linter/pkg/templates/imageshatag/internal/params"
12+
13+
v1 "k8s.io/api/core/v1"
14+
)
15+
16+
var (
17+
containerName = "test-container"
18+
)
19+
20+
func TestContainerImage(t *testing.T) {
21+
suite.Run(t, new(ContainerImageTestSuite))
22+
}
23+
24+
type ContainerImageTestSuite struct {
25+
templates.TemplateTestSuite
26+
27+
ctx *mocks.MockLintContext
28+
}
29+
30+
func (s *ContainerImageTestSuite) SetupTest() {
31+
s.Init(templateKey)
32+
s.ctx = mocks.NewMockContext()
33+
}
34+
35+
func (s *ContainerImageTestSuite) addDeploymentWithContainerImage(name, containerImage string) {
36+
s.ctx.AddMockDeployment(s.T(), name)
37+
s.ctx.AddContainerToDeployment(s.T(), name, v1.Container{Name: containerName, Image: containerImage})
38+
}
39+
40+
func (s *ContainerImageTestSuite) TestImproperContainerImage() {
41+
const (
42+
depwithNotAllowedImageTag = "dep-with-not-allowed-image-tag"
43+
)
44+
45+
s.addDeploymentWithContainerImage(depwithNotAllowedImageTag, "example.com/test:latest")
46+
47+
s.Validate(s.ctx, []templates.TestCase{
48+
{
49+
Param: params.Params{
50+
AllowList: []string{".*:[a-fA-F0-9]{64}$"},
51+
},
52+
Diagnostics: map[string][]diagnostic.Diagnostic{
53+
depwithNotAllowedImageTag: {
54+
{Message: "The container \"test-container\" is using an invalid container image, \"example.com/test:latest\". Please reference the image using a SHA256 tag."},
55+
},
56+
},
57+
ExpectInstantiationError: false,
58+
},
59+
})
60+
}
61+
62+
func (s *ContainerImageTestSuite) TestAcceptableContainerImage() {
63+
const (
64+
depWithAcceptableImageTag = "dep-with-acceptable-container-image"
65+
)
66+
67+
s.addDeploymentWithContainerImage(depWithAcceptableImageTag, "example.com/latest@sha256:75bf9b911b6481dcf29f7942240d1555adaa607eec7fc61bedb7f624f87c36d4")
68+
s.Validate(s.ctx, []templates.TestCase{
69+
{
70+
Param: params.Params{
71+
AllowList: []string{".*:[a-fA-F0-9]{64}$"},
72+
},
73+
Diagnostics: map[string][]diagnostic.Diagnostic{
74+
depWithAcceptableImageTag: nil,
75+
},
76+
ExpectInstantiationError: false,
77+
},
78+
})
79+
}

0 commit comments

Comments
 (0)