From c2445c76da13ee3be9664e3740ea23fb6f6176d4 Mon Sep 17 00:00:00 2001 From: Fernando Giannetti Date: Wed, 26 Jul 2023 17:05:37 -0300 Subject: [PATCH] Allows to disable the fallback to the default registry on image pull (#2637) * Allow to disable the fallback to the default registry on image pull When one or more registry mirror(s) are deffined with the 'registry-mirror' argument, if none of those mirrors include the image, the current behavior is to fallback to the default registry. If a whitelist (or some image restriction) is applied at the mirror side, fallbacking to the default registry makes that restriction useless. This new argument allows to skip the fallback and abort the build if the mirror rejects an image. If it is not set, is completelly transparent. * fix typo on command help --- README.md | 7 +++++++ cmd/executor/cmd/root.go | 1 + cmd/warmer/cmd/root.go | 1 + integration/integration_test.go | 23 +++++++++++++++++++++++ pkg/config/options.go | 1 + pkg/image/remote/remote.go | 12 +++++++++--- pkg/image/remote/remote_test.go | 32 ++++++++++++++++++++++++++++++++ 7 files changed, 74 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 33a8f58a5..3a9eb63ec 100644 --- a/README.md +++ b/README.md @@ -96,6 +96,7 @@ _If you are interested in contributing to kaniko, see - [Flag `--registry-certificate`](#flag---registry-certificate) - [Flag `--registry-client-cert`](#flag---registry-client-cert) - [Flag `--registry-mirror`](#flag---registry-mirror) + - [Flag `--skip-default-registry-fallback`](#flag---skip-default-registry-fallback) - [Flag `--reproducible`](#flag---reproducible) - [Flag `--single-snapshot`](#flag---single-snapshot) - [Flag `--skip-tls-verify`](#flag---skip-tls-verify) @@ -992,6 +993,12 @@ are: - `192.168.0.1:5000` - `mycompany-docker-virtual.jfrog.io` +#### Flag `--skip-default-registry-fallback` + +Set this flag if you want the build process to fail if none of the mirrors listed in flag [registry-mirror](#flag---registry-mirror) can pull some image. This should be used with mirrors that implements a whitelist or some image restrictions. + +If [registry-mirror](#flag---registry-mirror) is not set or is empty, this flag is ignored. + #### Flag `--reproducible` Set this flag to strip timestamps out of the built image and make it diff --git a/cmd/executor/cmd/root.go b/cmd/executor/cmd/root.go index 1b40f6d90..774c877ba 100644 --- a/cmd/executor/cmd/root.go +++ b/cmd/executor/cmd/root.go @@ -238,6 +238,7 @@ func addKanikoOptionsFlags() { opts.RegistriesClientCertificates = make(map[string]string) RootCmd.PersistentFlags().VarP(&opts.RegistriesClientCertificates, "registry-client-cert", "", "Use the provided client certificate for mutual TLS (mTLS) communication with the given registry. Expected format is 'my.registry.url=/path/to/client/cert,/path/to/client/key'.") RootCmd.PersistentFlags().VarP(&opts.RegistryMirrors, "registry-mirror", "", "Registry mirror to use as pull-through cache instead of docker.io. Set it repeatedly for multiple mirrors.") + RootCmd.PersistentFlags().BoolVarP(&opts.SkipDefaultRegistryFallback, "skip-default-registry-fallback", "", false, "If an image is not found on any mirrors (defined with registry-mirror) do not fallback to the default registry. If registry-mirror is not defined, this flag is ignored.") RootCmd.PersistentFlags().BoolVarP(&opts.IgnoreVarRun, "ignore-var-run", "", true, "Ignore /var/run directory when taking image snapshot. Set it to false to preserve /var/run/ in destination image.") RootCmd.PersistentFlags().VarP(&opts.Labels, "label", "", "Set metadata for an image. Set it repeatedly for multiple labels.") RootCmd.PersistentFlags().BoolVarP(&opts.SkipUnusedStages, "skip-unused-stages", "", false, "Build only used stages if defined to true. Otherwise it builds by default all stages, even the unnecessaries ones until it reaches the target stage / end of Dockerfile") diff --git a/cmd/warmer/cmd/root.go b/cmd/warmer/cmd/root.go index 699373260..6ac7f7d18 100644 --- a/cmd/warmer/cmd/root.go +++ b/cmd/warmer/cmd/root.go @@ -98,6 +98,7 @@ func addKanikoOptionsFlags() { opts.RegistriesClientCertificates = make(map[string]string) RootCmd.PersistentFlags().VarP(&opts.RegistriesClientCertificates, "registry-client-cert", "", "Use the provided client certificate for mutual TLS (mTLS) communication with the given registry. Expected format is 'my.registry.url=/path/to/client/cert,/path/to/client/key'.") RootCmd.PersistentFlags().VarP(&opts.RegistryMirrors, "registry-mirror", "", "Registry mirror to use as pull-through cache instead of docker.io. Set it repeatedly for multiple mirrors.") + RootCmd.PersistentFlags().BoolVarP(&opts.SkipDefaultRegistryFallback, "skip-default-registry-fallback", "", false, "If an image is not found on any mirrors (defined with registry-mirror) do not fallback to the default registry. If registry-mirror is not defined, this flag is ignored.") RootCmd.PersistentFlags().StringVarP(&opts.CustomPlatform, "customPlatform", "", "", "Specify the build platform if different from the current host") RootCmd.PersistentFlags().StringVarP(&opts.DockerfilePath, "dockerfile", "d", "", "Path to the dockerfile to be cached. The kaniko warmer will parse and write out each stage's base image layers to the cache-dir. Using the same dockerfile path as what you plan to build in the kaniko executor is the expected usage.") RootCmd.PersistentFlags().VarP(&opts.BuildArgs, "build-arg", "", "This flag should be used in conjunction with the dockerfile flag for scenarios where dynamic replacement of the base image is required.") diff --git a/integration/integration_test.go b/integration/integration_test.go index b4785ea30..ab5977165 100644 --- a/integration/integration_test.go +++ b/integration/integration_test.go @@ -395,6 +395,29 @@ func TestBuildViaRegistryMirrors(t *testing.T) { checkContainerDiffOutput(t, diff, expected) } +func TestBuildSkipFallback(t *testing.T) { + repo := getGitRepo(false) + dockerfile := fmt.Sprintf("%s/%s/Dockerfile_registry_mirror", integrationPath, dockerfilesPath) + + // Build with kaniko + kanikoImage := GetKanikoImage(config.imageRepo, "Dockerfile_registry_mirror") + dockerRunFlags := []string{"run", "--net=host"} + dockerRunFlags = addServiceAccountFlags(dockerRunFlags, config.serviceAccount) + dockerRunFlags = append(dockerRunFlags, ExecutorImage, + "-f", dockerfile, + "-d", kanikoImage, + "--registry-mirror", "doesnotexist.example.com", + "--skip-default-registry-fallback", + "-c", fmt.Sprintf("git://%s", repo)) + + kanikoCmd := exec.Command("docker", dockerRunFlags...) + + _, err := RunCommandWithoutTest(kanikoCmd) + if err == nil { + t.Errorf("Build should fail after using skip-default-registry-fallback and registry-mirror fail to pull") + } +} + // TestKanikoDir tests that a build that sets --kaniko-dir produces the same output as the equivalent docker build. func TestKanikoDir(t *testing.T) { repo := getGitRepo(false) diff --git a/pkg/config/options.go b/pkg/config/options.go index 37044898c..5ad9a0e54 100644 --- a/pkg/config/options.go +++ b/pkg/config/options.go @@ -37,6 +37,7 @@ type RegistryOptions struct { SkipTLSVerifyRegistries multiArg RegistriesCertificates keyValueArg RegistriesClientCertificates keyValueArg + SkipDefaultRegistryFallback bool Insecure bool SkipTLSVerify bool InsecurePull bool diff --git a/pkg/image/remote/remote.go b/pkg/image/remote/remote.go index e0ecff97b..b47cbb2f0 100644 --- a/pkg/image/remote/remote.go +++ b/pkg/image/remote/remote.go @@ -17,6 +17,7 @@ limitations under the License. package remote import ( + "errors" "strings" "github.com/GoogleContainerTools/kaniko/pkg/config" @@ -31,7 +32,8 @@ import ( ) var ( - manifestCache = make(map[string]v1.Image) + manifestCache = make(map[string]v1.Image) + remoteImageFunc = remote.Image ) // RetrieveRemoteImage retrieves the manifest for the specified image from the specified registry @@ -68,7 +70,7 @@ func RetrieveRemoteImage(image string, opts config.RegistryOptions, customPlatfo ref := setNewRegistry(ref, newReg) logrus.Infof("Retrieving image %s from registry mirror %s", ref, registryMirror) - remoteImage, err := remote.Image(ref, remoteOptions(registryMirror, opts, customPlatform)...) + remoteImage, err := remoteImageFunc(ref, remoteOptions(registryMirror, opts, customPlatform)...) if err != nil { logrus.Warnf("Failed to retrieve image %s from registry mirror %s: %s. Will try with the next mirror, or fallback to the default registry.", ref, registryMirror, err) continue @@ -78,6 +80,10 @@ func RetrieveRemoteImage(image string, opts config.RegistryOptions, customPlatfo return remoteImage, nil } + + if len(opts.RegistryMirrors) > 0 && opts.SkipDefaultRegistryFallback { + return nil, errors.New("image not found on any configured mirror(s)") + } } registryName := ref.Context().RegistryStr() @@ -91,7 +97,7 @@ func RetrieveRemoteImage(image string, opts config.RegistryOptions, customPlatfo logrus.Infof("Retrieving image %s from registry %s", ref, registryName) - remoteImage, err := remote.Image(ref, remoteOptions(registryName, opts, customPlatform)...) + remoteImage, err := remoteImageFunc(ref, remoteOptions(registryName, opts, customPlatform)...) if remoteImage != nil { manifestCache[image] = remoteImage diff --git a/pkg/image/remote/remote_test.go b/pkg/image/remote/remote_test.go index 3879f017d..0a8103c51 100644 --- a/pkg/image/remote/remote_test.go +++ b/pkg/image/remote/remote_test.go @@ -17,11 +17,13 @@ limitations under the License. package remote import ( + "errors" "testing" "github.com/GoogleContainerTools/kaniko/pkg/config" "github.com/google/go-containerregistry/pkg/name" v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/google/go-containerregistry/pkg/v1/remote" "github.com/google/go-containerregistry/pkg/v1/types" ) @@ -108,3 +110,33 @@ func Test_RetrieveRemoteImage_manifestCache(t *testing.T) { t.Fatal("Expected call to succeed because there is a manifest for this image in the cache.") } } + +func Test_RetrieveRemoteImage_skipFallback(t *testing.T) { + image := "debian" + registryMirror := "some-registry" + + opts := config.RegistryOptions{ + RegistryMirrors: []string{registryMirror}, + SkipDefaultRegistryFallback: false, + } + + remoteImageFunc = func(ref name.Reference, options ...remote.Option) (v1.Image, error) { + if ref.Context().Registry.Name() == registryMirror { + return nil, errors.New("no image found") + } + + return &mockImage{}, nil + } + + if _, err := RetrieveRemoteImage(image, opts, ""); err != nil { + t.Fatal("Expected call to succeed because fallback to default registry") + } + + opts.SkipDefaultRegistryFallback = true + //clean cached image + manifestCache = make(map[string]v1.Image) + + if _, err := RetrieveRemoteImage(image, opts, ""); err == nil { + t.Fatal("Expected call to fail because fallback to default registry is skipped") + } +}