Skip to content

Commit

Permalink
Merge pull request #1037 from antechrestos/feature/add_option_to_impo…
Browse files Browse the repository at this point in the history
…rt_registry_certificate

Allow user to provide registry certificate
  • Loading branch information
tejal29 authored Mar 12, 2020
2 parents c718dc6 + b73c2c1 commit 18de5d6
Show file tree
Hide file tree
Showing 9 changed files with 217 additions and 7 deletions.
2 changes: 2 additions & 0 deletions cmd/executor/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -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).")
}
Expand Down
2 changes: 1 addition & 1 deletion deploy/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion deploy/Dockerfile_debug
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion deploy/Dockerfile_warmer
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
31 changes: 31 additions & 0 deletions pkg/config/args.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ limitations under the License.
package config

import (
"fmt"
"strings"

"github.com/sirupsen/logrus"
Expand All @@ -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"
}
Expand All @@ -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 changes: 47 additions & 0 deletions pkg/config/args_test.go
Original file line number Diff line number Diff line change
@@ -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")
}
}
1 change: 1 addition & 0 deletions pkg/config/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ type KanikoOptions struct {
BuildArgs multiArg
InsecureRegistries multiArg
SkipTLSVerifyRegistries multiArg
RegistriesCertificates keyValueArg
Insecure bool
SkipTLSVerify bool
InsecurePull bool
Expand Down
53 changes: 49 additions & 4 deletions pkg/executor/push.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package executor

import (
"crypto/tls"
"crypto/x509"
"encoding/json"
"fmt"
"io/ioutil"
Expand Down Expand Up @@ -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 {
Expand All @@ -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)
}
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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
}
Expand Down
84 changes: 84 additions & 0 deletions pkg/executor/push_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ package executor

import (
"bytes"
"crypto/tls"
"crypto/x509"
"fmt"
"io/ioutil"
"net/http"
Expand Down Expand Up @@ -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)
})
}
}

0 comments on commit 18de5d6

Please sign in to comment.