diff --git a/src/pkg/packager/prepare.go b/src/pkg/packager/prepare.go index 7bbac66030..a1ea95cba6 100644 --- a/src/pkg/packager/prepare.go +++ b/src/pkg/packager/prepare.go @@ -39,7 +39,6 @@ import ( ) var imageCheck = regexp.MustCompile(`(?mi)"image":"((([a-z0-9._-]+)/)?([a-z0-9._-]+)(:([a-z0-9._-]+))?)"`) -var imagePodCheck = regexp.MustCompile(`(?mi)^(([a-z0-9._-]+)/)?([a-z0-9._-]+)(:([a-z0-9._-]+))?$`) var imageFuzzyCheck = regexp.MustCompile(`(?mi)["|=]([a-z0-9\-.\/:]+:[\w.\-]*[a-z\.\-][\w.\-]*)"`) // FindImages iterates over a Zarf.yaml and attempts to parse any images. @@ -456,17 +455,17 @@ func findWhyResources(resources []*unstructured.Unstructured, whyImage, componen func appendToImageMap(imgMap map[string]bool, pod corev1.PodSpec) map[string]bool { for _, container := range pod.InitContainers { - if imagePodCheck.MatchString(container.Image) { + if ReferenceRegexp.MatchString(container.Image) { imgMap[container.Image] = true } } for _, container := range pod.Containers { - if imagePodCheck.MatchString(container.Image) { + if ReferenceRegexp.MatchString(container.Image) { imgMap[container.Image] = true } } for _, container := range pod.EphemeralContainers { - if imagePodCheck.MatchString(container.Image) { + if ReferenceRegexp.MatchString(container.Image) { imgMap[container.Image] = true } } diff --git a/src/pkg/packager/prepare_test.go b/src/pkg/packager/prepare_test.go index d9dbab1434..30eaf22b26 100644 --- a/src/pkg/packager/prepare_test.go +++ b/src/pkg/packager/prepare_test.go @@ -75,11 +75,19 @@ func TestFindImages(t *testing.T) { CreateOpts: types.ZarfCreateOptions{ BaseDir: "./testdata/find-images/valid-image-uri", }, + FindImagesOpts: types.ZarfFindImagesOptions{ + SkipCosign: true, + }, }, expectedImages: map[string][]string{ "baseline": { "ghcr.io/zarf-dev/zarf/agent:v0.38.1", - "ghcr.io/zarf-dev/zarf/agent:sha256-f8b1c2f99349516ae1bd0711a19697abcc41555076b0ae90f1a70ca6b50dcbd8.sig", + "10.0.0.1:443/zarf-dev/zarf/agent:v0.38.1", + "alpine", + "xn--7o8h.com/myimage:9.8.7", + "registry.io/foo/project--id.module--name.ver---sion--name", + "foo_bar:latest", + "foo.com:8080/bar:1.2.3", }, }, }, diff --git a/src/pkg/packager/regex.go b/src/pkg/packager/regex.go new file mode 100644 index 0000000000..d20dc5f734 --- /dev/null +++ b/src/pkg/packager/regex.go @@ -0,0 +1,101 @@ +package packager + +// Borrow from "github.com/containers/image" with love <3 +// https://github.com/containers/image/blob/aa915b75e867d14f6cb486a4fcc7d7c91cf4ca0a/docker/reference/regexp.go + +import ( + "regexp" + "strings" +) + +const ( + // alphaNumeric defines the alpha numeric atom, typically a + // component of names. This only allows lower case characters and digits. + alphaNumeric = `[a-z0-9]+` + + // separator defines the separators allowed to be embedded in name + // components. This allow one period, one or two underscore and multiple + // dashes. Repeated dashes and underscores are intentionally treated + // differently. In order to support valid hostnames as name components, + // supporting repeated dash was added. Additionally double underscore is + // now allowed as a separator to loosen the restriction for previously + // supported names. + separator = `(?:[._]|__|[-]*)` + + // repository name to start with a component as defined by DomainRegexp + // and followed by an optional port. + domainComponent = `(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])` + + // The string counterpart for TagRegexp. + tag = `[\w][\w.-]{0,127}` + + // The string counterpart for DigestRegexp. + digestPat = `[A-Za-z][A-Za-z0-9]*(?:[-_+.][A-Za-z][A-Za-z0-9]*)*[:][[:xdigit:]]{32,}` +) + +var ( + // nameComponent restricts registry path component names to start + // with at least one letter or number, with following parts able to be + // separated by one period, one or two underscore and multiple dashes. + nameComponent = expression( + alphaNumeric, + optional(repeated(separator, alphaNumeric))) + + domain = expression( + domainComponent, + optional(repeated(literal(`.`), domainComponent)), + optional(literal(`:`), `[0-9]+`)) + + namePat = expression( + optional(domain, literal(`/`)), + nameComponent, + optional(repeated(literal(`/`), nameComponent))) + + referencePat = anchored(capture(namePat), + optional(literal(":"), capture(tag)), + optional(literal("@"), capture(digestPat))) + + ReferenceRegexp = re(referencePat) +) + +// re compiles the string to a regular expression. +var re = regexp.MustCompile + +// literal compiles s into a literal regular expression, escaping any regexp +// reserved characters. +func literal(s string) string { + return regexp.QuoteMeta(s) +} + +// expression defines a full expression, where each regular expression must +// follow the previous. +func expression(res ...string) string { + return strings.Join(res, "") +} + +// optional wraps the expression in a non-capturing group and makes the +// production optional. +func optional(res ...string) string { + return group(expression(res...)) + `?` +} + +// repeated wraps the regexp in a non-capturing group to get one or more +// matches. +func repeated(res ...string) string { + return group(expression(res...)) + `+` +} + +// group wraps the regexp in a non-capturing group. +func group(res ...string) string { + return `(?:` + expression(res...) + `)` +} + +// capture wraps the expression in a capturing group. +func capture(res ...string) string { + return `(` + expression(res...) + `)` +} + +// anchored anchors the regular expression by adding start and end delimiters. +func anchored(res ...string) string { + return `^` + expression(res...) + `$` +} diff --git a/src/pkg/packager/testdata/find-images/valid-image-uri/deployment.yaml b/src/pkg/packager/testdata/find-images/valid-image-uri/deployment.yaml index d972da08b3..a0221ca48a 100644 --- a/src/pkg/packager/testdata/find-images/valid-image-uri/deployment.yaml +++ b/src/pkg/packager/testdata/find-images/valid-image-uri/deployment.yaml @@ -12,7 +12,29 @@ spec: app: agent spec: containers: + # these should be detected - name: agent image: ghcr.io/zarf-dev/zarf/agent:v0.38.1 + - name: port + image: 10.0.0.1:443/zarf-dev/zarf/agent:v0.38.1 + - name: alpine + image: alpine + - name: punycode + image: xn--7o8h.com/myimage:9.8.7 + - name: project + image: registry.io/foo/project--id.module--name.ver---sion--name + - name: seperate + image: foo_bar:latest + - name: domain-port + image: foo.com:8080/bar:1.2.3 + # these should NOT be detected + - name: under + image: _docker/_docker + - name: quad-under + image: ____/____ + - name: dash-namespace + image: foo/-bar + - name: slash-tag + image: foo.com:http/bar - name: bad-image image: registry1.dso.mil*