Merge pull request #1140 from samos123/fix-1122-public-gcr

Add ability to use public GCR repos without being authenticated
This commit is contained in:
Sam Stoelinga
2020-03-24 10:31:58 -07:00
committed by GitHub
7 changed files with 89 additions and 173 deletions
-2
View File
@@ -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
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
-2
View File
@@ -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
@@ -48,7 +47,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
-2
View File
@@ -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
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
-9
View File
@@ -102,15 +102,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")
-155
View File
@@ -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())
}
+22 -3
View File
@@ -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 == "" {
+67
View File
@@ -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)
}