From a8fa090eaa63e26df13ea36720dab7342f27aba7 Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Mon, 26 Aug 2024 01:51:25 +0200 Subject: [PATCH 01/25] Extend utils.MatchImage by add utils.MatchImageDynamic and utils.MatchImageExact to compare container images --- pipeline/frontend/yaml/utils/image.go | 41 ++++- pipeline/frontend/yaml/utils/image_test.go | 188 +++++++++++++++++++++ 2 files changed, 228 insertions(+), 1 deletion(-) diff --git a/pipeline/frontend/yaml/utils/image.go b/pipeline/frontend/yaml/utils/image.go index 63054c21d1..34350a9f2a 100644 --- a/pipeline/frontend/yaml/utils/image.go +++ b/pipeline/frontend/yaml/utils/image.go @@ -14,7 +14,11 @@ package utils -import "github.com/distribution/reference" +import ( + "strings" + + "github.com/distribution/reference" +) // trimImage returns the short image name without tag. func trimImage(name string) string { @@ -57,6 +61,41 @@ func MatchImage(from string, to ...string) bool { return false } +// MatchImageExact returns true if the image name and tag matches +// an image in the list. +func MatchImageExact(from string, to ...string) bool { + from = expandImage(from) + for _, match := range to { + if from == expandImage(match) { + return true + } + } + return false +} + +// MatchImageDynamic check if image is in list based on list. +// If an list entry has a tag specified it only will match if both are the same, else the tag is ignored. +func MatchImageDynamic(from string, to ...string) bool { + fullFrom := expandImage(from) + trimFrom := trimImage(from) + for _, match := range to { + if imageHasTag(match) { + if fullFrom == expandImage(match) { + return true + } + } else { + if trimFrom == trimImage(match) { + return true + } + } + } + return false +} + +func imageHasTag(name string) bool { + return strings.Contains(name, ":") +} + // MatchHostname returns true if the image hostname // matches the specified hostname. func MatchHostname(image, hostname string) bool { diff --git a/pipeline/frontend/yaml/utils/image_test.go b/pipeline/frontend/yaml/utils/image_test.go index 17fab45d5b..9464048209 100644 --- a/pipeline/frontend/yaml/utils/image_test.go +++ b/pipeline/frontend/yaml/utils/image_test.go @@ -113,6 +113,10 @@ func Test_expandImage(t *testing.T) { from: "gcr.io/golang:1.0.0", want: "gcr.io/golang:1.0.0", }, + { + from: "codeberg.org/6543/hello:latest@2c98dce11f78c2b4e40f513ca82f75035eb8cfa4957a6d8eb3f917ecaf77803", + want: "codeberg.org/6543/hello:latest@2c98dce11f78c2b4e40f513ca82f75035eb8cfa4957a6d8eb3f917ecaf77803", + }, // error cases, return input unmodified { from: "foo/bar?baz:boo", @@ -124,6 +128,57 @@ func Test_expandImage(t *testing.T) { } } +func Test_imageHasTag(t *testing.T) { + testdata := []struct { + from string + want bool + }{ + { + from: "golang", + want: false, + }, + { + from: "golang:latest", + want: true, + }, + { + from: "golang:1.0.0", + want: true, + }, + { + from: "library/golang", + want: false, + }, + { + from: "library/golang:latest", + want: true, + }, + { + from: "library/golang:1.0.0", + want: true, + }, + { + from: "index.docker.io/library/golang:1.0.0", + want: true, + }, + { + from: "gcr.io/golang", + want: false, + }, + { + from: "gcr.io/golang:1.0.0", + want: true, + }, + { + from: "codeberg.org/6543/hello:latest@2c98dce11f78c2b4e40f513ca82f75035eb8cfa4957a6d8eb3f917ecaf77803", + want: true, + }, + } + for _, test := range testdata { + assert.Equal(t, test.want, imageHasTag(test.from)) + } +} + func Test_matchImage(t *testing.T) { testdata := []struct { from, to string @@ -205,6 +260,139 @@ func Test_matchImage(t *testing.T) { } } +func Test_matchImageExact(t *testing.T) { + testdata := []struct { + from, to string + want bool + }{ + { + from: "golang", + to: "golang", + want: true, + }, + { + from: "golang:latest", + to: "golang", + want: true, + }, + { + from: "library/golang:latest", + to: "golang", + want: true, + }, + { + from: "index.docker.io/library/golang:1.0.0", + to: "golang", + want: false, + }, + { + from: "golang", + to: "golang:latest", + want: true, + }, + { + from: "library/golang:latest", + to: "library/golang", + want: true, + }, + { + from: "gcr.io/golang", + to: "gcr.io/golang", + want: true, + }, + { + from: "gcr.io/golang:1.0.0", + to: "gcr.io/golang", + want: false, + }, + { + from: "gcr.io/golang:latest", + to: "gcr.io/golang", + want: true, + }, + { + from: "gcr.io/golang", + to: "gcr.io/golang:latest", + want: true, + }, + { + from: "golang", + to: "library/golang", + want: true, + }, + { + from: "golang", + to: "gcr.io/project/golang", + want: false, + }, + { + from: "golang", + to: "gcr.io/library/golang", + want: false, + }, + { + from: "golang", + to: "gcr.io/golang", + want: false, + }, + } + for _, test := range testdata { + if !assert.Equal(t, test.want, MatchImageExact(test.from, test.to)) { + t.Logf("test data: '%s' -> '%s'", test.from, test.to) + } + } +} + +func Test_matchImageDynamic(t *testing.T) { + testdata := []struct { + name, from string + to []string + want bool + }{ + { + name: "simple compare", + from: "golang", + to: []string{"golang"}, + want: true, + }, + { + name: "compare non-taged image whit list who tag requirement", + from: "golang", + to: []string{"golang:v3.0"}, + want: false, + }, + { + name: "compare taged image whit list who tag no requirement", + from: "golang:v3.0", + to: []string{"golang"}, + want: true, + }, + { + name: "compare taged image whit list who has image with no tag requirement", + from: "golang:1.0", + to: []string{"golang", "golang:2.0"}, + want: true, + }, + { + name: "compare taged image whit list who only has images with tag requirement", + from: "golang:1.0", + to: []string{"golang:latest", "golang:2.0"}, + want: false, + }, + { + name: "compare taged image whit list who only has images with tag requirement", + from: "golang:1.0", + to: []string{"golang:latest", "golang:1.0"}, + want: true, + }, + } + for _, test := range testdata { + if !assert.Equal(t, test.want, MatchImageDynamic(test.from, test.to...)) { + t.Logf("test data: '%s' -> '%s'", test.from, test.to) + } + } +} + func Test_matchHostname(t *testing.T) { testdata := []struct { image, hostname string From 9a59c5b62e3193c9da15ab5bc2b95a95c0c0e82b Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Mon, 26 Aug 2024 02:07:59 +0200 Subject: [PATCH 02/25] allow-list trusted- and escalated-plugins by exact match this enforce tag compare of images --- .../30-administration/10-server-config.md | 1 + pipeline/frontend/yaml/compiler/compiler.go | 2 +- pipeline/frontend/yaml/compiler/convert.go | 2 +- pipeline/frontend/yaml/types/container.go | 2 +- shared/constant/constant.go | 37 ++++++++++++++++++- 5 files changed, 40 insertions(+), 4 deletions(-) diff --git a/docs/docs/30-administration/10-server-config.md b/docs/docs/30-administration/10-server-config.md index ed624bf6e6..2771da2e4e 100644 --- a/docs/docs/30-administration/10-server-config.md +++ b/docs/docs/30-administration/10-server-config.md @@ -351,6 +351,7 @@ a user can log into Woodpecker, without re-authentication. > Defaults are defined in [shared/constant/constant.go](https://github.com/woodpecker-ci/woodpecker/blob/main/shared/constant/constant.go) Docker images to run in privileged mode. Only change if you are sure what you do! +You should specify the tag as images are compared via exact matches.