Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow user to provide registry certificate #1037

Merged
merged 1 commit into from
Mar 12, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
})
}
}