parent
							
								
									17ceb312c6
								
							
						
					
					
						commit
						b73c2c10c8
					
				|  | @ -154,6 +154,8 @@ func addKanikoOptionsFlags() { | |||
| 	RootCmd.PersistentFlags().DurationVarP(&opts.CacheTTL, "cache-ttl", "", time.Hour*336, "Cache timeout in hours. Defaults to two weeks.") | ||||
| 	RootCmd.PersistentFlags().VarP(&opts.InsecureRegistries, "insecure-registry", "", "Insecure registry using plain HTTP to push and pull. Set it repeatedly for multiple registries.") | ||||
| 	RootCmd.PersistentFlags().VarP(&opts.SkipTLSVerifyRegistries, "skip-tls-verify-registry", "", "Insecure registry ignoring TLS verify to push and pull. Set it repeatedly for multiple registries.") | ||||
| 	opts.RegistriesCertificates = make(map[string]string) | ||||
| 	RootCmd.PersistentFlags().VarP(&opts.RegistriesCertificates, "registry-certificate", "", "Use the provided certificate for TLS communication with the given registry. Expected format is 'my.registry.url=/path/to/the/server/certificate'.") | ||||
| 	RootCmd.PersistentFlags().StringVarP(&opts.RegistryMirror, "registry-mirror", "", "", "Registry mirror to use has pull-through cache instead of docker.io.") | ||||
| 	RootCmd.PersistentFlags().BoolVarP(&opts.WhitelistVarRun, "whitelist-var-run", "", true, "Ignore /var/run directory when taking image snapshot. Set it to false to preserve /var/run/ in destination image. (Default true).") | ||||
| } | ||||
|  |  | |||
|  | @ -14,7 +14,7 @@ | |||
| 
 | ||||
| # Builds the static Go image to execute in a Kubernetes job | ||||
| 
 | ||||
| FROM golang:1.12 | ||||
| FROM golang:1.13 | ||||
| ARG GOARCH=amd64 | ||||
| WORKDIR /go/src/github.com/GoogleContainerTools/kaniko | ||||
| # Get GCR credential helper | ||||
|  |  | |||
|  | @ -15,7 +15,7 @@ | |||
| # Builds the static Go image to execute in a Kubernetes job | ||||
| 
 | ||||
| # Stage 0: Build the executor binary and get credential helpers | ||||
| FROM golang:1.12 | ||||
| FROM golang:1.13 | ||||
| ARG GOARCH=amd64 | ||||
| WORKDIR /go/src/github.com/GoogleContainerTools/kaniko | ||||
| # Get GCR credential helper | ||||
|  |  | |||
|  | @ -14,7 +14,7 @@ | |||
| 
 | ||||
| # Builds the static Go image to execute in a Kubernetes job | ||||
| 
 | ||||
| FROM golang:1.12 | ||||
| FROM golang:1.13 | ||||
| ARG GOARCH=amd64 | ||||
| WORKDIR /go/src/github.com/GoogleContainerTools/kaniko | ||||
| # Get GCR credential helper | ||||
|  |  | |||
|  | @ -17,6 +17,7 @@ limitations under the License. | |||
| package config | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"strings" | ||||
| 
 | ||||
| 	"github.com/sirupsen/logrus" | ||||
|  | @ -39,6 +40,7 @@ func (b *multiArg) Set(value string) error { | |||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // The third is Type() string
 | ||||
| func (b *multiArg) Type() string { | ||||
| 	return "multi-arg type" | ||||
| } | ||||
|  | @ -51,3 +53,32 @@ func (b *multiArg) Contains(v string) bool { | |||
| 	} | ||||
| 	return false | ||||
| } | ||||
| 
 | ||||
| // This type is used to supported passing in multiple key=value flags
 | ||||
| type keyValueArg map[string]string | ||||
| 
 | ||||
| // Now, for our new type, implement the two methods of
 | ||||
| // the flag.Value interface...
 | ||||
| // The first method is String() string
 | ||||
| func (a *keyValueArg) String() string { | ||||
| 	var result []string | ||||
| 	for key := range *a { | ||||
| 		result = append(result, fmt.Sprintf("%s=%s", key, (*a)[key])) | ||||
| 	} | ||||
| 	return strings.Join(result, ",") | ||||
| } | ||||
| 
 | ||||
| // The second method is Set(value string) error
 | ||||
| func (a *keyValueArg) Set(value string) error { | ||||
| 	valueSplit := strings.SplitN(value, "=", 2) | ||||
| 	if len(valueSplit) < 2 { | ||||
| 		return fmt.Errorf("invalid argument value. expect key=value, got %s", value) | ||||
| 	} | ||||
| 	(*a)[valueSplit[0]] = valueSplit[1] | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // The third is Type() string
 | ||||
| func (a *keyValueArg) Type() string { | ||||
| 	return "key-value-arg type" | ||||
| } | ||||
|  |  | |||
|  | @ -0,0 +1,47 @@ | |||
| /* | ||||
| Copyright 2020 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 config | ||||
| 
 | ||||
| import "testing" | ||||
| 
 | ||||
| func TestMultiArg_Set_shouldAppendValue(t *testing.T) { | ||||
| 	var arg multiArg | ||||
| 	arg.Set("value1") | ||||
| 	if len(arg) != 1 || arg[0] != "value1" { | ||||
| 		t.Error("Fist value was not appended") | ||||
| 	} | ||||
| 	arg.Set("value2") | ||||
| 	if len(arg) != 2 || arg[1] != "value2" { | ||||
| 		t.Error("Second value was not appended") | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func Test_KeyValueArg_Set_shouldSplitArgument(t *testing.T) { | ||||
| 	arg := make(keyValueArg) | ||||
| 	arg.Set("key=value") | ||||
| 	if arg["key"] != "value" { | ||||
| 		t.Error("Invalid split. key=value should be split to key=>value") | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func Test_KeyValueArg_Set_shouldAcceptEqualAsValue(t *testing.T) { | ||||
| 	arg := make(keyValueArg) | ||||
| 	arg.Set("key=value=something") | ||||
| 	if arg["key"] != "value=something" { | ||||
| 		t.Error("Invalid split. key=value=something should be split to key=>value=something") | ||||
| 	} | ||||
| } | ||||
|  | @ -44,6 +44,7 @@ type KanikoOptions struct { | |||
| 	BuildArgs               multiArg | ||||
| 	InsecureRegistries      multiArg | ||||
| 	SkipTLSVerifyRegistries multiArg | ||||
| 	RegistriesCertificates  keyValueArg | ||||
| 	Insecure                bool | ||||
| 	SkipTLSVerify           bool | ||||
| 	InsecurePull            bool | ||||
|  |  | |||
|  | @ -18,6 +18,7 @@ package executor | |||
| 
 | ||||
| import ( | ||||
| 	"crypto/tls" | ||||
| 	"crypto/x509" | ||||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
| 	"io/ioutil" | ||||
|  | @ -62,6 +63,41 @@ func (w *withUserAgent) RoundTrip(r *http.Request) (*http.Response, error) { | |||
| 	return w.t.RoundTrip(r) | ||||
| } | ||||
| 
 | ||||
| type CertPool interface { | ||||
| 	value() *x509.CertPool | ||||
| 	append(path string) error | ||||
| } | ||||
| 
 | ||||
| type X509CertPool struct { | ||||
| 	inner x509.CertPool | ||||
| } | ||||
| 
 | ||||
| func (p *X509CertPool) value() *x509.CertPool { | ||||
| 	return &p.inner | ||||
| } | ||||
| 
 | ||||
| func (p *X509CertPool) append(path string) error { | ||||
| 	pem, err := ioutil.ReadFile(path) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	p.inner.AppendCertsFromPEM(pem) | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| type systemCertLoader func() CertPool | ||||
| 
 | ||||
| var defaultX509Handler systemCertLoader = func() CertPool { | ||||
| 	systemCertPool, err := x509.SystemCertPool() | ||||
| 	if err != nil { | ||||
| 		logrus.Warn("Failed to load system cert pool. Loading empty one instead.") | ||||
| 		systemCertPool = x509.NewCertPool() | ||||
| 	} | ||||
| 	return &X509CertPool{ | ||||
| 		inner: *systemCertPool, | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // CheckPushPermissions checks that the configured credentials can be used to
 | ||||
| // push to every specified destination.
 | ||||
| func CheckPushPermissions(opts *config.KanikoOptions) error { | ||||
|  | @ -87,7 +123,7 @@ func CheckPushPermissions(opts *config.KanikoOptions) error { | |||
| 			} | ||||
| 			destRef.Repository.Registry = newReg | ||||
| 		} | ||||
| 		tr := makeTransport(opts, registryName) | ||||
| 		tr := makeTransport(opts, registryName, defaultX509Handler) | ||||
| 		if err := remote.CheckPushPermission(destRef, creds.GetKeychain(), tr); err != nil { | ||||
| 			return errors.Wrapf(err, "checking push permission for %q", destRef) | ||||
| 		} | ||||
|  | @ -184,7 +220,7 @@ func DoPush(image v1.Image, opts *config.KanikoOptions) error { | |||
| 			return errors.Wrap(err, "resolving pushAuth") | ||||
| 		} | ||||
| 
 | ||||
| 		tr := makeTransport(opts, registryName) | ||||
| 		tr := makeTransport(opts, registryName, defaultX509Handler) | ||||
| 		rt := &withUserAgent{t: tr} | ||||
| 
 | ||||
| 		if err := remote.Write(destRef, image, remote.WithAuth(pushAuth), remote.WithTransport(rt)); err != nil { | ||||
|  | @ -228,13 +264,22 @@ func writeImageOutputs(image v1.Image, destRefs []name.Tag) error { | |||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func makeTransport(opts *config.KanikoOptions, registryName string) http.RoundTripper { | ||||
| func makeTransport(opts *config.KanikoOptions, registryName string, loader systemCertLoader) http.RoundTripper { | ||||
| 	// Create a transport to set our user-agent.
 | ||||
| 	tr := http.DefaultTransport | ||||
| 	var tr http.RoundTripper = http.DefaultTransport.(*http.Transport).Clone() | ||||
| 	if opts.SkipTLSVerify || opts.SkipTLSVerifyRegistries.Contains(registryName) { | ||||
| 		tr.(*http.Transport).TLSClientConfig = &tls.Config{ | ||||
| 			InsecureSkipVerify: true, | ||||
| 		} | ||||
| 	} else if certificatePath := opts.RegistriesCertificates[registryName]; certificatePath != "" { | ||||
| 		systemCertPool := loader() | ||||
| 		if err := systemCertPool.append(certificatePath); err != nil { | ||||
| 			logrus.WithError(err).Warnf("Failed to load certificate %s for %s\n", certificatePath, registryName) | ||||
| 		} else { | ||||
| 			tr.(*http.Transport).TLSClientConfig = &tls.Config{ | ||||
| 				RootCAs: systemCertPool.value(), | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return tr | ||||
| } | ||||
|  |  | |||
|  | @ -18,6 +18,8 @@ package executor | |||
| 
 | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"crypto/tls" | ||||
| 	"crypto/x509" | ||||
| 	"fmt" | ||||
| 	"io/ioutil" | ||||
| 	"net/http" | ||||
|  | @ -223,3 +225,85 @@ func TestImageNameDigestFile(t *testing.T) { | |||
| 	testutil.CheckErrorAndDeepEqual(t, false, err, want, got) | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| type mockedCertPool struct { | ||||
| 	certificatesPath []string | ||||
| } | ||||
| 
 | ||||
| func (m *mockedCertPool) value() *x509.CertPool { | ||||
| 	return &x509.CertPool{} | ||||
| } | ||||
| 
 | ||||
| func (m *mockedCertPool) append(path string) error { | ||||
| 	m.certificatesPath = append(m.certificatesPath, path) | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func Test_makeTransport(t *testing.T) { | ||||
| 	registryName := "my.registry.name" | ||||
| 
 | ||||
| 	tests := []struct { | ||||
| 		name  string | ||||
| 		opts  *config.KanikoOptions | ||||
| 		check func(*tls.Config, *mockedCertPool) | ||||
| 	}{ | ||||
| 		{ | ||||
| 			name: "SkipTLSVerify set", | ||||
| 			opts: &config.KanikoOptions{SkipTLSVerify: true}, | ||||
| 			check: func(config *tls.Config, pool *mockedCertPool) { | ||||
| 				if !config.InsecureSkipVerify { | ||||
| 					t.Errorf("makeTransport().TLSClientConfig.InsecureSkipVerify not set while SkipTLSVerify set") | ||||
| 				} | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "SkipTLSVerifyRegistries set with expected registry", | ||||
| 			opts: &config.KanikoOptions{SkipTLSVerifyRegistries: []string{registryName}}, | ||||
| 			check: func(config *tls.Config, pool *mockedCertPool) { | ||||
| 				if !config.InsecureSkipVerify { | ||||
| 					t.Errorf("makeTransport().TLSClientConfig.InsecureSkipVerify not set while SkipTLSVerifyRegistries set with registry name") | ||||
| 				} | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "SkipTLSVerifyRegistries set with other registry", | ||||
| 			opts: &config.KanikoOptions{SkipTLSVerifyRegistries: []string{fmt.Sprintf("other.%s", registryName)}}, | ||||
| 			check: func(config *tls.Config, pool *mockedCertPool) { | ||||
| 				if config.InsecureSkipVerify { | ||||
| 					t.Errorf("makeTransport().TLSClientConfig.InsecureSkipVerify set while SkipTLSVerifyRegistries not set with registry name") | ||||
| 				} | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "RegistriesCertificates set for registry", | ||||
| 			opts: &config.KanikoOptions{RegistriesCertificates: map[string]string{registryName: "/path/to/the/certificate.cert"}}, | ||||
| 			check: func(config *tls.Config, pool *mockedCertPool) { | ||||
| 				if len(pool.certificatesPath) != 1 || pool.certificatesPath[0] != "/path/to/the/certificate.cert" { | ||||
| 					t.Errorf("makeTransport().RegistriesCertificates certificate not appended to system certificates") | ||||
| 				} | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "RegistriesCertificates set for another registry", | ||||
| 			opts: &config.KanikoOptions{RegistriesCertificates: map[string]string{fmt.Sprintf("other.%s=", registryName): "/path/to/the/certificate.cert"}}, | ||||
| 			check: func(config *tls.Config, pool *mockedCertPool) { | ||||
| 				if len(pool.certificatesPath) != 0 { | ||||
| 					t.Errorf("makeTransport().RegistriesCertificates certificate appended to system certificates while added for other registry") | ||||
| 				} | ||||
| 			}, | ||||
| 		}, | ||||
| 	} | ||||
| 	for _, tt := range tests { | ||||
| 		t.Run(tt.name, func(t *testing.T) { | ||||
| 			var certificatesPath []string | ||||
| 			certPool := mockedCertPool{ | ||||
| 				certificatesPath: certificatesPath, | ||||
| 			} | ||||
| 			var mockedSystemCertLoader systemCertLoader = func() CertPool { | ||||
| 				return &certPool | ||||
| 			} | ||||
| 			transport := makeTransport(tt.opts, registryName, mockedSystemCertLoader) | ||||
| 			tt.check(transport.(*http.Transport).TLSClientConfig, &certPool) | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue