Merge pull request #1037 from antechrestos/feature/add_option_to_import_registry_certificate
Allow user to provide registry certificate
This commit is contained in:
		
						commit
						18de5d6d2d
					
				|  | @ -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().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.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.") | 	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().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).") | 	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 | # Builds the static Go image to execute in a Kubernetes job | ||||||
| 
 | 
 | ||||||
| FROM golang:1.12 | FROM golang:1.13 | ||||||
| ARG GOARCH=amd64 | ARG GOARCH=amd64 | ||||||
| WORKDIR /go/src/github.com/GoogleContainerTools/kaniko | WORKDIR /go/src/github.com/GoogleContainerTools/kaniko | ||||||
| # Get GCR credential helper | # Get GCR credential helper | ||||||
|  |  | ||||||
|  | @ -15,7 +15,7 @@ | ||||||
| # Builds the static Go image to execute in a Kubernetes job | # Builds the static Go image to execute in a Kubernetes job | ||||||
| 
 | 
 | ||||||
| # Stage 0: Build the executor binary and get credential helpers | # Stage 0: Build the executor binary and get credential helpers | ||||||
| FROM golang:1.12 | FROM golang:1.13 | ||||||
| ARG GOARCH=amd64 | ARG GOARCH=amd64 | ||||||
| WORKDIR /go/src/github.com/GoogleContainerTools/kaniko | WORKDIR /go/src/github.com/GoogleContainerTools/kaniko | ||||||
| # Get GCR credential helper | # Get GCR credential helper | ||||||
|  |  | ||||||
|  | @ -14,7 +14,7 @@ | ||||||
| 
 | 
 | ||||||
| # Builds the static Go image to execute in a Kubernetes job | # Builds the static Go image to execute in a Kubernetes job | ||||||
| 
 | 
 | ||||||
| FROM golang:1.12 | FROM golang:1.13 | ||||||
| ARG GOARCH=amd64 | ARG GOARCH=amd64 | ||||||
| WORKDIR /go/src/github.com/GoogleContainerTools/kaniko | WORKDIR /go/src/github.com/GoogleContainerTools/kaniko | ||||||
| # Get GCR credential helper | # Get GCR credential helper | ||||||
|  |  | ||||||
|  | @ -17,6 +17,7 @@ limitations under the License. | ||||||
| package config | package config | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
|  | 	"fmt" | ||||||
| 	"strings" | 	"strings" | ||||||
| 
 | 
 | ||||||
| 	"github.com/sirupsen/logrus" | 	"github.com/sirupsen/logrus" | ||||||
|  | @ -39,6 +40,7 @@ func (b *multiArg) Set(value string) error { | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // The third is Type() string
 | ||||||
| func (b *multiArg) Type() string { | func (b *multiArg) Type() string { | ||||||
| 	return "multi-arg type" | 	return "multi-arg type" | ||||||
| } | } | ||||||
|  | @ -51,3 +53,32 @@ func (b *multiArg) Contains(v string) bool { | ||||||
| 	} | 	} | ||||||
| 	return false | 	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 | 	BuildArgs               multiArg | ||||||
| 	InsecureRegistries      multiArg | 	InsecureRegistries      multiArg | ||||||
| 	SkipTLSVerifyRegistries multiArg | 	SkipTLSVerifyRegistries multiArg | ||||||
|  | 	RegistriesCertificates  keyValueArg | ||||||
| 	Insecure                bool | 	Insecure                bool | ||||||
| 	SkipTLSVerify           bool | 	SkipTLSVerify           bool | ||||||
| 	InsecurePull            bool | 	InsecurePull            bool | ||||||
|  |  | ||||||
|  | @ -18,6 +18,7 @@ package executor | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"crypto/tls" | 	"crypto/tls" | ||||||
|  | 	"crypto/x509" | ||||||
| 	"encoding/json" | 	"encoding/json" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"io/ioutil" | 	"io/ioutil" | ||||||
|  | @ -62,6 +63,41 @@ func (w *withUserAgent) RoundTrip(r *http.Request) (*http.Response, error) { | ||||||
| 	return w.t.RoundTrip(r) | 	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
 | // CheckPushPermissions checks that the configured credentials can be used to
 | ||||||
| // push to every specified destination.
 | // push to every specified destination.
 | ||||||
| func CheckPushPermissions(opts *config.KanikoOptions) error { | func CheckPushPermissions(opts *config.KanikoOptions) error { | ||||||
|  | @ -87,7 +123,7 @@ func CheckPushPermissions(opts *config.KanikoOptions) error { | ||||||
| 			} | 			} | ||||||
| 			destRef.Repository.Registry = newReg | 			destRef.Repository.Registry = newReg | ||||||
| 		} | 		} | ||||||
| 		tr := makeTransport(opts, registryName) | 		tr := makeTransport(opts, registryName, defaultX509Handler) | ||||||
| 		if err := remote.CheckPushPermission(destRef, creds.GetKeychain(), tr); err != nil { | 		if err := remote.CheckPushPermission(destRef, creds.GetKeychain(), tr); err != nil { | ||||||
| 			return errors.Wrapf(err, "checking push permission for %q", destRef) | 			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") | 			return errors.Wrap(err, "resolving pushAuth") | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		tr := makeTransport(opts, registryName) | 		tr := makeTransport(opts, registryName, defaultX509Handler) | ||||||
| 		rt := &withUserAgent{t: tr} | 		rt := &withUserAgent{t: tr} | ||||||
| 
 | 
 | ||||||
| 		if err := remote.Write(destRef, image, remote.WithAuth(pushAuth), remote.WithTransport(rt)); err != nil { | 		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 | 	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.
 | 	// 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) { | 	if opts.SkipTLSVerify || opts.SkipTLSVerifyRegistries.Contains(registryName) { | ||||||
| 		tr.(*http.Transport).TLSClientConfig = &tls.Config{ | 		tr.(*http.Transport).TLSClientConfig = &tls.Config{ | ||||||
| 			InsecureSkipVerify: true, | 			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 | 	return tr | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -18,6 +18,8 @@ package executor | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"bytes" | 	"bytes" | ||||||
|  | 	"crypto/tls" | ||||||
|  | 	"crypto/x509" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"io/ioutil" | 	"io/ioutil" | ||||||
| 	"net/http" | 	"net/http" | ||||||
|  | @ -223,3 +225,85 @@ func TestImageNameDigestFile(t *testing.T) { | ||||||
| 	testutil.CheckErrorAndDeepEqual(t, false, err, want, got) | 	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