diff --git a/deploy/Dockerfile b/deploy/Dockerfile index 2e1b1fb9ba..4a9ec93acb 100644 --- a/deploy/Dockerfile +++ b/deploy/Dockerfile @@ -20,7 +20,6 @@ WORKDIR /go/src/github.com/GoogleContainerTools/kaniko # Get GCR credential helper ADD https://github.com/GoogleCloudPlatform/docker-credential-gcr/releases/download/v1.5.0/docker-credential-gcr_linux_amd64-1.5.0.tar.gz /usr/local/bin/ RUN tar -C /usr/local/bin/ -xvzf /usr/local/bin/docker-credential-gcr_linux_amd64-1.5.0.tar.gz -RUN docker-credential-gcr configure-docker # Get Amazon ECR credential helper RUN go get -u github.com/awslabs/amazon-ecr-credential-helper/ecr-login/cli/docker-credential-ecr-login RUN make -C /go/src/github.com/awslabs/amazon-ecr-credential-helper linux-amd64 @@ -37,7 +36,6 @@ COPY --from=0 /usr/local/bin/docker-credential-gcr /kaniko/docker-credential-gcr COPY --from=0 /go/src/github.com/awslabs/amazon-ecr-credential-helper/bin/linux-amd64/docker-credential-ecr-login /kaniko/docker-credential-ecr-login COPY --from=0 /usr/local/bin/docker-credential-acr-linux /kaniko/docker-credential-acr-linux COPY files/ca-certificates.crt /kaniko/ssl/certs/ -COPY --from=0 /root/.docker/config.json /kaniko/.docker/config.json ENV HOME /root ENV USER /root ENV PATH /usr/local/bin:/kaniko diff --git a/deploy/Dockerfile_debug b/deploy/Dockerfile_debug index e9ccf15e3f..197c808419 100644 --- a/deploy/Dockerfile_debug +++ b/deploy/Dockerfile_debug @@ -21,7 +21,6 @@ WORKDIR /go/src/github.com/GoogleContainerTools/kaniko # Get GCR credential helper ADD https://github.com/GoogleCloudPlatform/docker-credential-gcr/releases/download/v1.5.0/docker-credential-gcr_linux_amd64-1.5.0.tar.gz /usr/local/bin/ RUN tar -C /usr/local/bin/ -xvzf /usr/local/bin/docker-credential-gcr_linux_amd64-1.5.0.tar.gz -RUN docker-credential-gcr configure-docker # Get Amazon ECR credential helper RUN go get -u github.com/awslabs/amazon-ecr-credential-helper/ecr-login/cli/docker-credential-ecr-login RUN make -C /go/src/github.com/awslabs/amazon-ecr-credential-helper linux-amd64 @@ -43,7 +42,6 @@ COPY --from=1 /distroless/bazel-bin/experimental/busybox/busybox/ /busybox/ # Declare /busybox as a volume to get it automatically whitelisted VOLUME /busybox COPY files/ca-certificates.crt /kaniko/ssl/certs/ -COPY --from=0 /root/.docker/config.json /kaniko/.docker/config.json ENV HOME /root ENV USER /root ENV PATH /usr/local/bin:/kaniko:/busybox diff --git a/deploy/Dockerfile_warmer b/deploy/Dockerfile_warmer index d33d2d74b1..3163e746ad 100644 --- a/deploy/Dockerfile_warmer +++ b/deploy/Dockerfile_warmer @@ -20,7 +20,6 @@ WORKDIR /go/src/github.com/GoogleContainerTools/kaniko # Get GCR credential helper ADD https://github.com/GoogleCloudPlatform/docker-credential-gcr/releases/download/v1.5.0/docker-credential-gcr_linux_amd64-1.5.0.tar.gz /usr/local/bin/ RUN tar -C /usr/local/bin/ -xvzf /usr/local/bin/docker-credential-gcr_linux_amd64-1.5.0.tar.gz -RUN docker-credential-gcr configure-docker # Get Amazon ECR credential helper RUN go get -u github.com/awslabs/amazon-ecr-credential-helper/ecr-login/cli/docker-credential-ecr-login RUN make -C /go/src/github.com/awslabs/amazon-ecr-credential-helper linux-amd64 @@ -33,7 +32,6 @@ COPY --from=0 /go/src/github.com/GoogleContainerTools/kaniko/out/warmer /kaniko/ COPY --from=0 /usr/local/bin/docker-credential-gcr /kaniko/docker-credential-gcr COPY --from=0 /go/src/github.com/awslabs/amazon-ecr-credential-helper/bin/linux-amd64/docker-credential-ecr-login /kaniko/docker-credential-ecr-login COPY files/ca-certificates.crt /kaniko/ssl/certs/ -COPY --from=0 /root/.docker/config.json /kaniko/.docker/config.json ENV HOME /root ENV USER /root ENV PATH /usr/local/bin:/kaniko diff --git a/integration/integration_test.go b/integration/integration_test.go index 357b46cb0f..730b4b2538 100644 --- a/integration/integration_test.go +++ b/integration/integration_test.go @@ -103,15 +103,6 @@ func launchTests(m *testing.M) (int, error) { RunOnInterrupt(func() { DeleteFromBucket(fileInBucket) }) defer DeleteFromBucket(fileInBucket) - } else { - var err error - var migratedFiles []string - if migratedFiles, err = MigrateGCRRegistry(dockerfilesPath, allDockerfiles, config.imageRepo); err != nil { - RollbackMigratedFiles(dockerfilesPath, migratedFiles) - return 1, errors.Wrap(err, "Fail to migrate dockerfiles from gcs") - } - RunOnInterrupt(func() { RollbackMigratedFiles(dockerfilesPath, migratedFiles) }) - defer RollbackMigratedFiles(dockerfilesPath, migratedFiles) } if err := buildRequiredImages(); err != nil { return 1, errors.Wrap(err, "Error while building images") diff --git a/integration/migrate_gcr.go b/integration/migrate_gcr.go deleted file mode 100644 index c056f5f6eb..0000000000 --- a/integration/migrate_gcr.go +++ /dev/null @@ -1,155 +0,0 @@ -/* -Copyright 2018 Google LLC - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package integration - -import ( - "bufio" - "fmt" - "os" - "os/exec" - "path" - "regexp" - "strings" -) - -// This function crawl through dockerfiles and replace all reference to "gcr.io/" in FROM instruction and replace it targetRepo -// The result is the array containing all modified file -// Each of this file is saved -func MigrateGCRRegistry(dockerfilesPath string, dockerfiles []string, targetRepo string) ([]string, error) { - var savedFiles []string - importedImages := map[string]interface{}{} - - for _, dockerfile := range dockerfiles { - if referencedImages, savedFile, err := migrateFile(dockerfilesPath, dockerfile, targetRepo); err != nil { - if savedFile { - savedFiles = append(savedFiles, dockerfile) - } - return savedFiles, err - } else if savedFile { - savedFiles = append(savedFiles, dockerfile) - for _, referencedImage := range referencedImages { - importedImages[referencedImage] = nil - } - } - } - for image := range importedImages { - if err := importImage(image, targetRepo); err != nil { - return savedFiles, err - } - } - return savedFiles, nil -} - -// This function rollback all previously modified files -func RollbackMigratedFiles(dockerfilesPath string, dockerfiles []string) []error { - var result []error - for _, dockerfile := range dockerfiles { - fmt.Printf("Rolling back %s\n", dockerfile) - if err := recoverDockerfile(dockerfilesPath, dockerfile); err != nil { - result = append(result, err) - } - } - return result -} - -// Import the gcr.io image such as gcr.io/my-image to targetRepo -func importImage(image string, targetRepo string) error { - fmt.Printf("Importing %s to %s\n", image, targetRepo) - targetImage := strings.ReplaceAll(image, "gcr.io/", targetRepo) - pullCmd := exec.Command("docker", "pull", image) - if out, err := RunCommandWithoutTest(pullCmd); err != nil { - return fmt.Errorf("Failed to pull image %s with docker command \"%s\": %s %s", image, pullCmd.Args, err, string(out)) - } - - tagCmd := exec.Command("docker", "tag", image, targetImage) - if out, err := RunCommandWithoutTest(tagCmd); err != nil { - return fmt.Errorf("Failed to tag image %s to %s with docker command \"%s\": %s %s", image, targetImage, tagCmd.Args, err, string(out)) - } - - pushCmd := exec.Command("docker", "push", targetImage) - if out, err := RunCommandWithoutTest(pushCmd); err != nil { - return fmt.Errorf("Failed to push image %s with docker command \"%s\": %s %s", targetImage, pushCmd.Args, err, string(out)) - } - return nil -} - -// takes a dockerfile and replace each gcr.io/ occurrence in FROM instruction and replace it with imageRepo -// return true if the file was saved -// if so, the array is non nil and contains each gcr image name -func migrateFile(dockerfilesPath string, dockerfile string, imageRepo string) ([]string, bool, error) { - var input *os.File - var output *os.File - var err error - var referencedImages []string - - if input, err = os.Open(path.Join(dockerfilesPath, dockerfile)); err != nil { - return nil, false, err - } - defer input.Close() - - var lines []string - scanner := bufio.NewScanner(input) - scanner.Split(bufio.ScanLines) - for scanner.Scan() { - line := scanner.Text() - if isFromGcrBaseImageInstruction(line) { - referencedImages = append(referencedImages, strings.Trim(strings.Split(line, " ")[1], " ")) - lines = append(lines, strings.ReplaceAll(line, gcrRepoPrefix, imageRepo)) - } else { - lines = append(lines, line) - } - } - rawOutput := []byte(strings.Join(append(lines, ""), "\n")) - - if len(referencedImages) == 0 { - return nil, false, nil - } - - if err = saveDockerfile(dockerfilesPath, dockerfile); err != nil { - return nil, false, err - } - - if output, err = os.Create(path.Join(dockerfilesPath, dockerfile)); err != nil { - return nil, true, err - } - defer output.Close() - - if written, err := output.Write(rawOutput); err != nil { - return nil, true, err - } else if written != len(rawOutput) { - return nil, true, fmt.Errorf("invalid number of byte written. Got %d, expected %d", written, len(rawOutput)) - } - return referencedImages, true, nil - -} - -func isFromGcrBaseImageInstruction(line string) bool { - result, _ := regexp.MatchString(fmt.Sprintf("FROM +%s", gcrRepoPrefix), line) - return result -} - -func saveDockerfile(dockerfilesPath string, dockerfile string) error { - return os.Rename(path.Join(dockerfilesPath, dockerfile), path.Join(dockerfilesPath, saveName(dockerfile))) -} - -func recoverDockerfile(dockerfilesPath string, dockerfile string) error { - return os.Rename(path.Join(dockerfilesPath, saveName(dockerfile)), path.Join(dockerfilesPath, dockerfile)) -} - -func saveName(dockerfile string) string { - return fmt.Sprintf("%s_save_%d", dockerfile, os.Getpid()) -} diff --git a/pkg/executor/push.go b/pkg/executor/push.go index 1141563bc5..f4483de160 100644 --- a/pkg/executor/push.go +++ b/pkg/executor/push.go @@ -24,6 +24,7 @@ import ( "io/ioutil" "net/http" "os" + "os/exec" "path/filepath" "strings" "time" @@ -52,6 +53,7 @@ type withUserAgent struct { const ( UpstreamClientUaKey = "UPSTREAM_CLIENT_TYPE" + DockerConfLocation = "/kaniko/.docker/config.json" ) func (w *withUserAgent) RoundTrip(r *http.Request) (*http.Response, error) { @@ -98,6 +100,13 @@ var defaultX509Handler systemCertLoader = func() CertPool { } } +// for testing +var ( + fs = afero.NewOsFs() + execCommand = exec.Command + checkRemotePushPermission = remote.CheckPushPermission +) + // CheckPushPermissions checks that the configured credentials can be used to // push to every specified destination. func CheckPushPermissions(opts *config.KanikoOptions) error { @@ -115,6 +124,18 @@ func CheckPushPermissions(opts *config.KanikoOptions) error { continue } + // Historically kaniko was pre-configured by default with gcr credential helper, + // in here we keep the backwards compatibility by enabling the GCR helper only + // when gcr.io is in one of the destinations. + if strings.Contains(destRef.RegistryStr(), "gcr.io") { + // Checking for existence of docker.config as it's normally required for + // authenticated registries and prevent overwriting user provided docker conf + if _, err := fs.Stat(DockerConfLocation); os.IsNotExist(err) { + if err := execCommand("docker-credential-gcr", "configure-docker").Run(); err != nil { + return errors.Wrap(err, "error while configuring docker-credential-gcr helper") + } + } + } registryName := destRef.Repository.Registry.Name() if opts.Insecure || opts.InsecureRegistries.Contains(registryName) { newReg, err := name.NewRegistry(registryName, name.WeakValidation, name.Insecure) @@ -124,7 +145,7 @@ func CheckPushPermissions(opts *config.KanikoOptions) error { destRef.Repository.Registry = newReg } tr := makeTransport(opts, registryName, defaultX509Handler) - if err := remote.CheckPushPermission(destRef, creds.GetKeychain(), tr); err != nil { + if err := checkRemotePushPermission(destRef, creds.GetKeychain(), tr); err != nil { return errors.Wrapf(err, "checking push permission for %q", destRef) } checked[destRef.Context().RepositoryStr()] = true @@ -231,8 +252,6 @@ func DoPush(image v1.Image, opts *config.KanikoOptions) error { return writeImageOutputs(image, destRefs) } -var fs = afero.NewOsFs() - func writeImageOutputs(image v1.Image, destRefs []name.Tag) error { dir := os.Getenv("BUILDER_OUTPUT") if dir == "" { diff --git a/pkg/executor/push_test.go b/pkg/executor/push_test.go index 3659f85148..54eda4654b 100644 --- a/pkg/executor/push_test.go +++ b/pkg/executor/push_test.go @@ -24,11 +24,13 @@ import ( "io/ioutil" "net/http" "os" + "os/exec" "path/filepath" "testing" "github.com/GoogleContainerTools/kaniko/pkg/config" "github.com/GoogleContainerTools/kaniko/testutil" + "github.com/google/go-containerregistry/pkg/authn" "github.com/google/go-containerregistry/pkg/name" "github.com/google/go-containerregistry/pkg/v1/layout" "github.com/google/go-containerregistry/pkg/v1/random" @@ -307,3 +309,68 @@ func Test_makeTransport(t *testing.T) { }) } } + +var calledExecCommand = false +var calledCheckPushPermission = false + +func setCalledFalse() { + calledExecCommand = false + calledCheckPushPermission = false +} + +func fakeExecCommand(command string, args ...string) *exec.Cmd { + calledExecCommand = true + cs := []string{"-test.run=TestHelperProcess", "--", command} + cs = append(cs, args...) + cmd := exec.Command(os.Args[0], cs...) + cmd.Env = []string{"GO_WANT_HELPER_PROCESS=1"} + return cmd +} + +func fakeCheckPushPermission(ref name.Reference, kc authn.Keychain, t http.RoundTripper) error { + calledCheckPushPermission = true + return nil +} + +func TestCheckPushPermissions(t *testing.T) { + tests := []struct { + Destination string + ShouldCallExecCommand bool + ExistingConfig bool + }{ + {"gcr.io/test-image", true, false}, + {"gcr.io/test-image", false, true}, + {"localhost:5000/test-image", false, false}, + {"localhost:5000/test-image", false, true}, + } + + execCommand = fakeExecCommand + checkRemotePushPermission = fakeCheckPushPermission + for _, test := range tests { + testName := fmt.Sprintf("%s_ExistingDockerConf_%v", test.Destination, test.ExistingConfig) + t.Run(testName, func(t *testing.T) { + fs = afero.NewMemMapFs() + opts := config.KanikoOptions{ + Destinations: []string{test.Destination}, + } + if test.ExistingConfig { + afero.WriteFile(fs, DockerConfLocation, []byte(""), os.FileMode(0644)) + defer fs.Remove(DockerConfLocation) + } + CheckPushPermissions(&opts) + if test.ShouldCallExecCommand != calledExecCommand { + t.Errorf("Expected calledExecCommand to be %v however it was %v", + calledExecCommand, test.ShouldCallExecCommand) + } + setCalledFalse() + }) + } +} + +func TestHelperProcess(t *testing.T) { + if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" { + return + } + fmt.Fprintf(os.Stdout, "fake result") + os.Exit(0) +}