Allow user to provide registry certificate

Fixes #1100
Fixes #1101
This commit is contained in:
Ben Einaudi 2020-02-07 17:18:16 +01:00
parent 17ceb312c6
commit b73c2c10c8
9 changed files with 217 additions and 7 deletions

View File

@ -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).")
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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"
}

47
pkg/config/args_test.go Normal file
View File

@ -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")
}
}

View File

@ -44,6 +44,7 @@ type KanikoOptions struct {
BuildArgs multiArg
InsecureRegistries multiArg
SkipTLSVerifyRegistries multiArg
RegistriesCertificates keyValueArg
Insecure bool
SkipTLSVerify bool
InsecurePull bool

View File

@ -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
}

View File

@ -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)
})
}
}