diff --git a/integrations/kube-agent-updater/cmd/teleport-kube-agent-updater/main.go b/integrations/kube-agent-updater/cmd/teleport-kube-agent-updater/main.go
index a6ad8b3f540e7..30e2f485cffc0 100644
--- a/integrations/kube-agent-updater/cmd/teleport-kube-agent-updater/main.go
+++ b/integrations/kube-agent-updater/cmd/teleport-kube-agent-updater/main.go
@@ -64,6 +64,7 @@ func main() {
var versionServer string
var versionChannel string
var insecureNoVerify bool
+ var insecureNoResolve bool
var disableLeaderElection bool
flag.StringVar(&agentName, "agent-name", "", "The name of the agent that should be updated. This is mandatory.")
@@ -71,7 +72,8 @@ func main() {
flag.StringVar(&metricsAddr, "metrics-addr", ":8080", "The address the metric endpoint binds to.")
flag.StringVar(&probeAddr, "healthz-addr", ":8081", "The address the probe endpoint binds to.")
flag.DurationVar(&syncPeriod, "sync-period", 10*time.Hour, "Operator sync period (format: https://pkg.go.dev/time#ParseDuration)")
- flag.BoolVar(&insecureNoVerify, "insecure-no-verify-image", false, "Disable image signature verification.")
+ flag.BoolVar(&insecureNoVerify, "insecure-no-verify-image", false, "Disable image signature verification. The image tag is still resolved and image must exist.")
+ flag.BoolVar(&insecureNoResolve, "insecure-no-resolve-image", false, "Disable image signature verification AND resolution. The updater can update to non-existing images.")
flag.BoolVar(&disableLeaderElection, "disable-leader-election", false, "Disable leader election, used when running the kube-agent-updater outside of Kubernetes.")
flag.StringVar(&versionServer, "version-server", "https://updates.releases.teleport.dev/v1/", "URL of the HTTP server advertising target version and critical maintenances. Trailing slash is optional.")
flag.StringVar(&versionChannel, "version-channel", "cloud/stable", "Version channel to get updates from.")
@@ -130,10 +132,14 @@ func main() {
}
var imageValidators img.Validators
- if insecureNoVerify {
+ switch {
+ case insecureNoResolve:
+ ctrl.Log.Info("INSECURE: Image validation and resolution disabled")
+ imageValidators = append(imageValidators, img.NewNopValidator("insecure no resolution"))
+ case insecureNoVerify:
ctrl.Log.Info("INSECURE: Image validation disabled")
- imageValidators = append(imageValidators, img.NewInsecureValidator("insecure always verify"))
- } else {
+ imageValidators = append(imageValidators, img.NewInsecureValidator("insecure always verified"))
+ default:
validator, err := img.NewCosignSingleKeyValidator(teleportProdOCIPubKey, "cosign signature validator")
if err != nil {
ctrl.Log.Error(err, "failed to build image validator, exiting")
diff --git a/integrations/kube-agent-updater/pkg/img/insecure.go b/integrations/kube-agent-updater/pkg/img/insecure.go
index d8d435712e963..a02cc17e2473e 100644
--- a/integrations/kube-agent-updater/pkg/img/insecure.go
+++ b/integrations/kube-agent-updater/pkg/img/insecure.go
@@ -55,7 +55,8 @@ func (v *insecureValidator) ValidateAndResolveDigest(ctx context.Context, image
}
// NewInsecureValidator returns an img.Validator that only resolves the image
-// but does not check its signature.
+// but does not check its signature. This must not be confused with
+// NewNopValidator that returns a validator that always validate without resolving.
func NewInsecureValidator(name string) Validator {
return &insecureValidator{
name: name,
diff --git a/integrations/kube-agent-updater/pkg/img/nop.go b/integrations/kube-agent-updater/pkg/img/nop.go
new file mode 100644
index 0000000000000..da95a364be91d
--- /dev/null
+++ b/integrations/kube-agent-updater/pkg/img/nop.go
@@ -0,0 +1,112 @@
+/*
+ * Teleport
+ * Copyright (C) 2023 Gravitational, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package img
+
+import (
+ "context"
+
+ "github.com/distribution/reference"
+ "github.com/google/go-containerregistry/pkg/name"
+ "github.com/gravitational/trace"
+ "github.com/opencontainers/go-digest"
+)
+
+type nopValidator struct {
+ name string
+}
+
+// Name returns the validator name
+func (v *nopValidator) Name() string {
+ return v.name
+}
+
+// ValidateAndResolveDigest of the nopValidator does not resolve nor
+// validate the image. It always says the image is valid, and returns it as-is.
+// Using this validator makes you vulnerable in case of image
+// registry compromise.
+func (v *nopValidator) ValidateAndResolveDigest(_ context.Context, image reference.NamedTagged) (NamedTaggedDigested, error) {
+ ref, err := name.NewTag(image.String())
+ if err != nil {
+ return nil, trace.Wrap(err)
+ }
+
+ digestedImage := newUnresolvedImageRef(ref.RegistryStr(), ref.RepositoryStr(), image.Tag())
+ return digestedImage, nil
+}
+
+// NewNopValidator returns an img.Validator that only resolves the image
+// but does not check its signature.
+func NewNopValidator(name string) Validator {
+ return &nopValidator{
+ name: name,
+ }
+}
+
+// unresolvedImageRef is the insecure version of imageRef. It does not contain the
+// digest, which means we cannot enforce that the image ran by Kubernetes is
+// the same that was validated.
+// This should only be used with the insecure validator.
+type unresolvedImageRef struct {
+ repository struct {
+ domain string
+ path string
+ }
+ tag string
+}
+
+// String returns the string representation of the image
+func (i unresolvedImageRef) String() string {
+ return i.Name() + ":" + i.tag
+}
+
+// Name returns the image Name (repo domain and image path)
+func (i unresolvedImageRef) Name() string {
+ if i.repository.domain == "" {
+ return i.repository.path
+ }
+ return i.repository.domain + "/" + i.repository.path
+
+}
+
+// Tag returns the image tag
+func (i unresolvedImageRef) Tag() string {
+ return i.tag
+}
+
+// Digest returns nothing. It's here to implement the NamedTaggedDigested interface.
+func (i unresolvedImageRef) Digest() digest.Digest {
+ return ""
+}
+
+// newUnresolvedImageRef returns an image reference that both Named, Tagged but not
+// Digested. This is insecure because we cannot enforce that the image ran by Kubernetes is
+// the same that was validated.
+// This should only be used by the nopValidator.
+func newUnresolvedImageRef(domain, path, tag string) NamedTaggedDigested {
+ return unresolvedImageRef{
+ repository: struct {
+ domain string
+ path string
+ }{
+ domain: domain,
+ path: path,
+ },
+ tag: tag,
+ }
+}
diff --git a/integrations/kube-agent-updater/pkg/img/nop_test.go b/integrations/kube-agent-updater/pkg/img/nop_test.go
new file mode 100644
index 0000000000000..fa83a95977a94
--- /dev/null
+++ b/integrations/kube-agent-updater/pkg/img/nop_test.go
@@ -0,0 +1,46 @@
+/*
+ * Teleport
+ * Copyright (C) 2023 Gravitational, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package img
+
+import (
+ "context"
+ "testing"
+
+ "github.com/distribution/reference"
+ "github.com/stretchr/testify/require"
+)
+
+func TestNewNopValidator(t *testing.T) {
+ // Test setup
+ v := NewNopValidator("test")
+ ctx := context.Background()
+ baseImageName := "registry.example.com/teleport-distroless"
+ baseImage, err := reference.ParseNamed(baseImageName)
+ require.NoError(t, err)
+ testVersionTag := "15.1.2"
+
+ image, err := reference.WithTag(baseImage, testVersionTag)
+ require.NoError(t, err)
+
+ // Test execution: we check that the image is not resolved, validated
+ // and that the output image is the same as the input one.
+ result, err := v.ValidateAndResolveDigest(ctx, image)
+ require.NoError(t, err)
+ require.Equal(t, image.String(), result.String())
+}