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