Skip to content

Commit

Permalink
[DEVOPS-123] Added optional ability to perform supported image versio…
Browse files Browse the repository at this point in the history
…ns checks. (#32)

* Added image versions checks to enable optional control over supported packages.

* Adjusted version checks.

* Added Utils function tests.

* Renamed check function to be less ambiguous.

---------

Co-authored-by: Roman Barbun <[email protected]>
  • Loading branch information
barbun and Roman Barbun authored Dec 12, 2023
1 parent 3220c71 commit 5f15b80
Show file tree
Hide file tree
Showing 8 changed files with 139 additions and 12 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ require (
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/hashicorp/go-version v1.6.0 // indirect
github.com/kennygrant/sanitize v1.2.4 // indirect
github.com/klauspost/compress v1.16.3 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ github.com/graph-gophers/graphql-go v1.5.0 h1:fDqblo50TEpD0LY7RXk/LFVYEVqo3+tXMN
github.com/graph-gophers/graphql-go v1.5.0/go.mod h1:YtmJZDLbF1YYNrlNAuiO5zAStUWc3XZT07iGsVqe1Os=
github.com/graph-gophers/graphql-transport-ws v0.0.2 h1:DbmSkbIGzj8SvHei6n8Mh9eLQin8PtA8xY9eCzjRpvo=
github.com/graph-gophers/graphql-transport-ws v0.0.2/go.mod h1:5BVKvFzOd2BalVIBFfnfmHjpJi/MZ5rOj8G55mXvZ8g=
github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek=
github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hasura/go-graphql-client v0.9.2 h1:4FyAeVOu+GcS1BaoELWNyxzaLY7s+g72LLH+qYItdEY=
github.com/hasura/go-graphql-client v0.9.2/go.mod h1:AarJlxO1I59MPqU/TC7gQP0BMFgPEqUTt5LYPvykasw=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
Expand Down
21 changes: 15 additions & 6 deletions pkg/checks/docker/baseimagecheck.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/salsadigitalauorg/shipshape/pkg/config"
"github.com/salsadigitalauorg/shipshape/pkg/result"
"github.com/salsadigitalauorg/shipshape/pkg/utils"

"gopkg.in/yaml.v3"
)

Expand Down Expand Up @@ -78,14 +79,14 @@ func (c *BaseImageCheck) RunCheck() {
defer df.Close()
scanner := bufio.NewScanner(df)
for scanner.Scan() {
from_regex := regexp.MustCompile("^FROM (.*)")
from_regex := regexp.MustCompile("^FROM (.[^:@]*)?[:@]?([^ latest$]*)")
match := from_regex.FindStringSubmatch(scanner.Text())

if len(match) < 1 {
continue
}

if len(c.Allowed) > 0 && !utils.StringSliceContains(c.Allowed, match[1]) {
if len(c.Allowed) > 0 && !utils.PackageCheckString(c.Allowed, match[1], match[2]) {
c.AddFail(name + " is using invalid base image " + match[1])
c.AddBreach(result.KeyValueBreach{
Key: name,
Expand All @@ -99,15 +100,23 @@ func (c *BaseImageCheck) RunCheck() {
}
}
} else {
if !utils.StringSliceMatch(c.Allowed, def.Image) {
c.AddFail(name + " is using invalid base image " + def.Image)
// Extract image package name and optional version from definition.
image_regex := regexp.MustCompile("^(.[^:@]*)?[:@]?([^ latest$]*)")
match := image_regex.FindStringSubmatch(def.Image)

if len(match) < 1 {
continue
}

if !utils.PackageCheckString(c.Allowed, match[1], match[2]) {
c.AddFail(name + " is using invalid base image " + match[1])
c.AddBreach(result.KeyValueBreach{
Key: name,
ValueLabel: "invalid base image",
Value: def.Image,
})
} else if utils.StringSliceMatch(c.Deprecated, def.Image) {
c.AddWarning(name + " is using deprecated image " + def.Image)
} else if utils.StringSliceMatch(c.Deprecated, match[1]) {
c.AddWarning(name + " is using deprecated image " + match[1])
} else {
c.AddPass(name + " is using valid base images")
}
Expand Down
75 changes: 74 additions & 1 deletion pkg/checks/docker/baseimagecheck_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,35 @@ func TestDockerfileCheck(t *testing.T) {
func TestInvalidDockerfileCheck(t *testing.T) {
assert := assert.New(t)
c := docker.BaseImageCheck{
Allowed: []string{"bitnami/redis"},
Allowed: []string{"bitnami/redis@latest"},
Paths: []string{"./fixtures/compose-dockerfile"},
}
c.RunCheck()
assert.Equal(result.Fail, c.Result.Status)
assert.EqualValues(
[]string{"service1 is using invalid base image bitnami/kubectl"},
c.Result.Failures,
)
}

func TestValidDockerfileImageVersion(t *testing.T) {
assert := assert.New(t)
c := docker.BaseImageCheck{
Allowed: []string{"bitnami/[email protected]"},
Paths: []string{"./fixtures/compose-dockerfile"},
}
c.RunCheck()
assert.Equal(result.Pass, c.Result.Status)
assert.EqualValues(
[]string{"service1 is using valid base images"},
c.Result.Passes,
)
}

func TestInvalidDockerfileImageVersion(t *testing.T) {
assert := assert.New(t)
c := docker.BaseImageCheck{
Allowed: []string{"bitnami/kubectl:1.26"},
Paths: []string{"./fixtures/compose-dockerfile"},
}
c.RunCheck()
Expand Down Expand Up @@ -84,6 +112,30 @@ func TestValidImage(t *testing.T) {
)
}

func TestValidImageVersions(t *testing.T) {
assert := assert.New(t)
c := docker.BaseImageCheck{
Allowed: []string{
"bitnami/[email protected]",
"bitnami/postgresql:15",
"bitnami/redis",
"bitnami/mongodb@latest",
},
Paths: []string{"./fixtures/compose-image"},
}
c.RunCheck()
assert.Equal(result.Pass, c.Result.Status)
assert.ElementsMatch(
[]string{
"service1 is using valid base images",
"service2 is using valid base images",
"service3 is using valid base images",
"service4 is using valid base images",
},
c.Result.Passes,
)
}

func TestInvalidImageCheck(t *testing.T) {
assert := assert.New(t)
c := docker.BaseImageCheck{
Expand All @@ -102,6 +154,27 @@ func TestInvalidImageCheck(t *testing.T) {
)
}

func TestInvalidImageVersions(t *testing.T) {
assert := assert.New(t)
c := docker.BaseImageCheck{
Allowed: []string{
"bitnami/kubectl@latest",
"bitnami/postgresql:17",
"bitnami/redis",
},
Paths: []string{"./fixtures/compose-image"},
}
c.RunCheck()
assert.Equal(result.Fail, c.Result.Status)
assert.EqualValues(
[]string{
"service2 is using invalid base image bitnami/postgresql",
"service4 is using invalid base image bitnami/mongodb",
},
c.Result.Failures,
)
}

func TestDockerfileWarning(t *testing.T) {
assert := assert.New(t)
c := docker.BaseImageCheck{
Expand Down
2 changes: 1 addition & 1 deletion pkg/checks/docker/fixtures/compose-dockerfile/Dockerfile
Original file line number Diff line number Diff line change
@@ -1 +1 @@
FROM bitnami/kubectl
FROM bitnami/kubectl:1.25.12-debian-11-r6
8 changes: 4 additions & 4 deletions pkg/checks/docker/fixtures/compose-image/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ version: "3.8"
services:

service1:
image: bitnami/kubectl
image: bitnami/kubectl:1.25.12-debian-11-r5

service2:
image: bitnami/postgresql
image: bitnami/postgresql@16

service3:
image: bitnami/redis
image: bitnami/redis:{MY_VERSION}

service4:
image: bitnami/mongodb
image: bitnami/mongodb:5.0.19-debian-11-r11
33 changes: 33 additions & 0 deletions pkg/utils/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"strings"

"github.com/vmware-labs/yaml-jsonpath/pkg/yamlpath"
"github.com/hashicorp/go-version"
"gopkg.in/yaml.v3"
)

Expand Down Expand Up @@ -302,6 +303,38 @@ func StringSliceMatch(slice []string, item string) bool {
return false
}

// Sift through a slice to determine if it contains eligible package
// with optional version constrains.
func PackageCheckString(slice []string, item string, item_version string) bool {
for _, s := range slice {
// Parse slice with regex to:
// 1 - package name (e.g. "bitnami/kubectl")
// 2 - version (e.g. "8.0")
service_regex := regexp.MustCompile("^(.[^:@]*)?[:@]?([^ latest$]*)")
service_match := service_regex.FindStringSubmatch(s)
// Only proceed if package names were parsed successfully.
if len(service_match[1]) > 0 && len(item) > 0 {
// Check if package name matches.
if service_match[1] == item {
// Package name matched.
// If service does not dictate version than assume any version is allowed.
if len(service_match[2]) < 1 {
return true
} else if len(item_version) > 0 {
// Ensure that item version is not less than slice version.
allowedVersion, err := version.NewVersion(service_match[2])
imageVersion, err := version.NewVersion(item_version)
// Run version comparison.
if err == nil && allowedVersion.LessThanOrEqual(imageVersion) {
return true
}
}
}
}
}
return false
}

func Glob(dir string, match string) ([]string, error) {
files := []string{}
err := filepath.Walk(dir, func(path string, f os.FileInfo, err error) error {
Expand Down
9 changes: 9 additions & 0 deletions pkg/utils/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -477,3 +477,12 @@ func TestHasComposerDependency(t *testing.T) {
t.Errorf("expected file not found got %s", err)
}
}

func TestPackageCheckString(t *testing.T) {
assert := assert.New(t)
assert.False(PackageCheckString([]string{}, "bitnami/kubectl", ""))
assert.False(PackageCheckString([]string{"bitnami/postgresql@16"}, "bitnami/kubectl", "1.24"))
assert.False(PackageCheckString([]string{"bitnami/postgresql@16", "bitnami/[email protected]"}, "bitnami/kubectl", "1.23-alpha"))
assert.True(PackageCheckString([]string{"bitnami/postgresql@16", "bitnami/kubectl"}, "bitnami/kubectl", "1.24"))
assert.True(PackageCheckString([]string{"bitnami/postgresql@16", "bitnami/kubectl:1.24"}, "bitnami/kubectl", "1.25"))
}

0 comments on commit 5f15b80

Please sign in to comment.