Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: add check during image discovery to make sure images are valid #3234

Merged
merged 8 commits into from
Feb 19, 2025
17 changes: 12 additions & 5 deletions src/pkg/packager/prepare.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,15 @@ import (
"context"
"errors"
"fmt"
"github.com/zarf-dev/zarf/src/pkg/logger"
"os"
"path/filepath"
"regexp"
"sort"
"strings"
"time"

"github.com/zarf-dev/zarf/src/pkg/logger"

"github.com/defenseunicorns/pkg/helpers/v2"
"github.com/goccy/go-yaml"
"github.com/google/go-containerregistry/pkg/crane"
Expand All @@ -37,7 +38,7 @@ import (
"github.com/zarf-dev/zarf/src/types"
)

var imageCheck = regexp.MustCompile(`(?mi)"image":"([^"]+)"`)
var imageCheck = regexp.MustCompile(`(?mi)"image":"((([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.
Expand Down Expand Up @@ -454,13 +455,19 @@ func findWhyResources(resources []*unstructured.Unstructured, whyImage, componen

func appendToImageMap(imgMap map[string]bool, pod corev1.PodSpec) map[string]bool {
for _, container := range pod.InitContainers {
imgMap[container.Image] = true
if ReferenceRegexp.MatchString(container.Image) {
imgMap[container.Image] = true
}
}
for _, container := range pod.Containers {
imgMap[container.Image] = true
if ReferenceRegexp.MatchString(container.Image) {
imgMap[container.Image] = true
}
}
for _, container := range pod.EphemeralContainers {
imgMap[container.Image] = true
if ReferenceRegexp.MatchString(container.Image) {
imgMap[container.Image] = true
}
}
return imgMap
}
Expand Down
22 changes: 22 additions & 0 deletions src/pkg/packager/prepare_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,28 @@ func TestFindImages(t *testing.T) {
},
},
},
{
name: "valid-image-uri",
cfg: &types.PackagerConfig{
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",
"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",
},
},
},
{
name: "image not found",
cfg: &types.PackagerConfig{
Expand Down
101 changes: 101 additions & 0 deletions src/pkg/packager/regex.go
Original file line number Diff line number Diff line change
@@ -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...) + `$`
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
image:
repository: docker.io*
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ metadata:
version: 1.0.0
components:
- name: baseline
required: true
required: true
charts:
- name: with-values
version: 0.1.0
Expand All @@ -16,3 +16,9 @@ components:
version: 0.1.0
namespace: without-values
localPath: chart
- name: invalid-image
version: 0.1.0
namespace: invalid-image
localPath: chart
valuesFiles:
- values-invalid-image.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: agent
spec:
selector:
matchLabels:
app: agent
template:
metadata:
labels:
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*
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- deployment.yaml
- policy.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: restrict-image-registries
spec:
background: true
failurePolicy: Ignore
rules:
- exclude:
any:
- resources:
namespaces:
- kube-system
match:
all:
- resources:
kinds:
- Pod
name: validate-registries
validate:
foreach:
- list: request.object.spec.[ephemeralContainers, initContainers, containers][]
pattern:
image: docker.io*
12 changes: 12 additions & 0 deletions src/pkg/packager/testdata/find-images/valid-image-uri/zarf.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
kind: ZarfPackageConfig
metadata:
name: valid-image-uri
version: 1.0.0
components:
- name: baseline
required: true
manifests:
- name: valid-image-uri
namespace: default
kustomizations:
- ./.
Loading