Skip to content

Commit

Permalink
support specifying registry ca cert
Browse files Browse the repository at this point in the history
  • Loading branch information
cppforlife committed May 7, 2019
1 parent e9b8844 commit fbaffb3
Show file tree
Hide file tree
Showing 8 changed files with 206 additions and 146 deletions.
4 changes: 4 additions & 0 deletions docs/packaging.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,10 @@ Even though `kbld pkg/unpkg` commands use registry APIs directly, by default the
}
```
### Authenticating to Harbor
You may have to provide `--registry-ca-cert-path` flag with a path to a CA certificate file for Harbor Registry API.
### Notes
- Produced tarball does not have duplicate image layers, as they are named by their digest (see `tar tvf /tmp/packaged-images.tar`).
Expand Down
33 changes: 7 additions & 26 deletions pkg/kbld/cmd/package.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,7 @@ import (
"os"

"github.com/cppforlife/go-cli-ui/ui"
regauthn "github.com/google/go-containerregistry/pkg/authn"
regname "github.com/google/go-containerregistry/pkg/name"
regv1 "github.com/google/go-containerregistry/pkg/v1"
regremote "github.com/google/go-containerregistry/pkg/v1/remote"
cmdcore "github.com/k14s/kbld/pkg/kbld/cmd/core"
ctlimg "github.com/k14s/kbld/pkg/kbld/image"
regtarball "github.com/k14s/kbld/pkg/kbld/imagetarball"
Expand All @@ -20,10 +17,13 @@ type PackageOptions struct {
ui ui.UI
depsFactory cmdcore.DepsFactory

FileFlags FileFlags
OutputPath string
FileFlags FileFlags
RegistryFlags RegistryFlags
OutputPath string
}

var _ regtarball.TarDescriptorsMetadata = ctlimg.Registry{}

func NewPackageOptions(ui ui.UI, depsFactory cmdcore.DepsFactory) *PackageOptions {
return &PackageOptions{ui: ui, depsFactory: depsFactory}
}
Expand All @@ -36,6 +36,7 @@ func NewPackageCmd(o *PackageOptions, flagsFactory cmdcore.FlagsFactory) *cobra.
RunE: func(_ *cobra.Command, _ []string) error { return o.Run() },
}
o.FileFlags.Set(cmd)
o.RegistryFlags.Set(cmd)
cmd.Flags().StringVarP(&o.OutputPath, "output", "o", "", "Output tarball path")
return cmd
}
Expand Down Expand Up @@ -107,30 +108,10 @@ func (o *PackageOptions) exportImages(imgRefsToExport map[string]struct{}, logge

defer outputFile.Close()

tds, err := regtarball.NewTarDescriptors(refs, remoteTarDescritorsMetadata{})
tds, err := regtarball.NewTarDescriptors(refs, ctlimg.NewRegistry(o.RegistryFlags.CACertPaths))
if err != nil {
return fmt.Errorf("Collecting packaging metadata: %s", err)
}

return regtarball.NewTarWriter(tds, outputFile).Write()
}

type remoteTarDescritorsMetadata struct{}

var _ regtarball.TarDescriptorsMetadata = remoteTarDescritorsMetadata{}

func (m remoteTarDescritorsMetadata) Generic(ref regname.Reference) (regv1.Descriptor, error) {
desc, err := regremote.Get(ref, regremote.WithAuthFromKeychain(regauthn.DefaultKeychain))
if err != nil {
return regv1.Descriptor{}, err
}
return desc.Descriptor, nil
}

func (m remoteTarDescritorsMetadata) Index(ref regname.Reference) (regv1.ImageIndex, error) {
return regremote.Index(ref, regremote.WithAuthFromKeychain(regauthn.DefaultKeychain))
}

func (m remoteTarDescritorsMetadata) Image(ref regname.Reference) (regv1.Image, error) {
return regremote.Image(ref, regremote.WithAuthFromKeychain(regauthn.DefaultKeychain))
}
13 changes: 13 additions & 0 deletions pkg/kbld/cmd/registry_flags.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package cmd

import (
"github.com/spf13/cobra"
)

type RegistryFlags struct {
CACertPaths []string
}

func (s *RegistryFlags) Set(cmd *cobra.Command) {
cmd.Flags().StringSliceVar(&s.CACertPaths, "registry-ca-cert-path", nil, "Add CA certificates for registry API (format: /tmp/foo) (can be specified multiple times)")
}
6 changes: 5 additions & 1 deletion pkg/kbld/cmd/resolve.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ type ResolveOptions struct {
depsFactory cmdcore.DepsFactory

FileFlags FileFlags
RegistryFlags RegistryFlags
BuildConcurrency int

ExportImages string
Expand All @@ -41,6 +42,7 @@ func NewResolveCmd(o *ResolveOptions, flagsFactory cmdcore.FlagsFactory) *cobra.
RunE: func(_ *cobra.Command, _ []string) error { return o.Run() },
}
o.FileFlags.Set(cmd)
o.RegistryFlags.Set(cmd)
cmd.Flags().IntVar(&o.BuildConcurrency, "build-concurrency", 4, "Set maximum number of concurrent builds")
return cmd
}
Expand Down Expand Up @@ -94,7 +96,9 @@ func (o *ResolveOptions) resolveImages(
})
}

queue := NewImageBuildQueue(ctlimg.NewFactory(conf, logger))
registry := ctlimg.NewRegistry(o.RegistryFlags.CACertPaths)
factory := ctlimg.NewFactory(conf, registry, logger)
queue := NewImageBuildQueue(factory)

resolvedImages, err := queue.Run(foundImages, o.BuildConcurrency)
if err != nil {
Expand Down
16 changes: 10 additions & 6 deletions pkg/kbld/cmd/unpackage.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,10 @@ type UnpackageOptions struct {
ui ui.UI
depsFactory cmdcore.DepsFactory

FileFlags FileFlags
InputPath string
Repository string
FileFlags FileFlags
RegistryFlags RegistryFlags
InputPath string
Repository string
}

func NewUnpackageOptions(ui ui.UI, depsFactory cmdcore.DepsFactory) *UnpackageOptions {
Expand All @@ -35,6 +36,7 @@ func NewUnpackageCmd(o *UnpackageOptions, flagsFactory cmdcore.FlagsFactory) *co
RunE: func(_ *cobra.Command, _ []string) error { return o.Run() },
}
o.FileFlags.Set(cmd)
o.RegistryFlags.Set(cmd)
cmd.Flags().StringVarP(&o.InputPath, "input", "i", "", "Input tarball path")
cmd.Flags().StringVarP(&o.Repository, "repository", "r", "", "Import images into given image repository (e.g. docker.io/dkalinin/my-project)")
return cmd
Expand Down Expand Up @@ -156,15 +158,17 @@ func (o *UnpackageOptions) importImages(logger *ctlimg.LoggerPrefixWriter) (map[

logger.Write([]byte(fmt.Sprintf("importing %s -> %s...\n", existingRef.Name(), importDigestRef.Name())))

registry := ctlimg.NewRegistry(o.RegistryFlags.CACertPaths)

switch {
case item.Image != nil:
err = ctlimg.ResolvedImage{}.Write(uploadTagRef, *item.Image)
err = registry.WriteImage(uploadTagRef, *item.Image)
if err != nil {
return nil, fmt.Errorf("Importing image as %s: %s", importDigestRef.Name(), err)
}

case item.Index != nil:
err = ctlimg.ResolvedImage{}.WriteIndex(uploadTagRef, *item.Index)
err = registry.WriteIndex(uploadTagRef, *item.Index)
if err != nil {
return nil, fmt.Errorf("Importing image index as %s: %s", importDigestRef.Name(), err)
}
Expand All @@ -189,7 +193,7 @@ func (o *UnpackageOptions) importImages(logger *ctlimg.LoggerPrefixWriter) (map[
}

func (o *UnpackageOptions) verifyTagDigest(uploadTagRef regname.Reference, importDigestRef regname.Digest) error {
resultURL, err := ctlimg.NewResolvedImage(uploadTagRef.Name()).URL()
resultURL, err := ctlimg.NewResolvedImage(uploadTagRef.Name(), ctlimg.NewRegistry(o.RegistryFlags.CACertPaths)).URL()
if err != nil {
return fmt.Errorf("Verifying imported image %s: %s", uploadTagRef.Name(), err)
}
Expand Down
11 changes: 6 additions & 5 deletions pkg/kbld/image/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@ type Image interface {
}

type Factory struct {
conf ctlconf.Conf
logger Logger
conf ctlconf.Conf
registry Registry
logger Logger
}

func NewFactory(conf ctlconf.Conf, logger Logger) Factory {
return Factory{conf, logger}
func NewFactory(conf ctlconf.Conf, registry Registry, logger Logger) Factory {
return Factory{conf, registry, logger}
}

func (f Factory) New(url string) Image {
Expand All @@ -37,7 +38,7 @@ func (f Factory) New(url string) Image {
return digestedImage
}

return ResolvedImage{url}
return NewResolvedImage(url, f.registry)
}

func (f Factory) shouldOverride(url string) (ctlconf.ImageOverride, bool) {
Expand Down
156 changes: 156 additions & 0 deletions pkg/kbld/image/registry.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
package image

import (
"crypto/tls"
"crypto/x509"
"fmt"
"io/ioutil"
"net"
"net/http"
"time"

regauthn "github.com/google/go-containerregistry/pkg/authn"
regname "github.com/google/go-containerregistry/pkg/name"
regv1 "github.com/google/go-containerregistry/pkg/v1"
regremote "github.com/google/go-containerregistry/pkg/v1/remote"
)

type Registry struct {
caCertsPaths []string
}

func NewRegistry(caCertsPaths []string) Registry {
return Registry{caCertsPaths}
}

func (i Registry) Generic(ref regname.Reference) (regv1.Descriptor, error) {
opts, err := i.opts()
if err != nil {
return regv1.Descriptor{}, err
}

desc, err := regremote.Get(ref, opts...)
if err != nil {
return regv1.Descriptor{}, err
}

return desc.Descriptor, nil
}

func (i Registry) Image(ref regname.Reference) (regv1.Image, error) {
opts, err := i.opts()
if err != nil {
return nil, err
}

return regremote.Image(ref, opts...)
}

func (i Registry) WriteImage(ref regname.Reference, img regv1.Image) error {
httpTran, err := i.newHTTPTransport()
if err != nil {
return err
}

authz, err := regauthn.DefaultKeychain.Resolve(ref.Context().Registry)
if err != nil {
return fmt.Errorf("Getting authz details: %s", err)
}

err = i.retry(func() error { return regremote.Write(ref, img, authz, httpTran) })
if err != nil {
return fmt.Errorf("Writing image: %s", err)
}

return nil
}

func (i Registry) Index(ref regname.Reference) (regv1.ImageIndex, error) {
opts, err := i.opts()
if err != nil {
return nil, err
}

return regremote.Index(ref, opts...)
}

func (i Registry) WriteIndex(ref regname.Reference, idx regv1.ImageIndex) error {
httpTran, err := i.newHTTPTransport()
if err != nil {
return err
}

authz, err := regauthn.DefaultKeychain.Resolve(ref.Context().Registry)
if err != nil {
return fmt.Errorf("Getting authz details: %s", err)
}

err = i.retry(func() error { return regremote.WriteIndex(ref, idx, authz, httpTran) })
if err != nil {
return fmt.Errorf("Writing image index: %s", err)
}

return nil
}

func (i Registry) opts() ([]regremote.ImageOption, error) {
httpTran, err := i.newHTTPTransport()
if err != nil {
return nil, err
}

return []regremote.ImageOption{
regremote.WithTransport(httpTran),
regremote.WithAuthFromKeychain(regauthn.DefaultKeychain),
}, nil
}

func (i Registry) newHTTPTransport() (*http.Transport, error) {
pool, err := x509.SystemCertPool()
if err != nil {
pool = x509.NewCertPool()
}

if len(i.caCertsPaths) > 0 {
for _, path := range i.caCertsPaths {
if certs, err := ioutil.ReadFile(path); err != nil {
return nil, fmt.Errorf("Reading CA certificates from '%s': %s", path, err)
} else if ok := pool.AppendCertsFromPEM(certs); !ok {
return nil, fmt.Errorf("Adding CA certificates from '%s': failed", path)
}
}
}

// Copied from https://github.com/golang/go/blob/release-branch.go1.12/src/net/http/transport.go#L42-L53
// We want to use the DefaultTransport but change its TLSClientConfig. There
// isn't a clean way to do this yet: https://github.com/golang/go/issues/26013
return &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
DualStack: true,
}).DialContext,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ResponseHeaderTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
// Use the cert pool with k8s cert bundle appended.
TLSClientConfig: &tls.Config{
RootCAs: pool,
},
}, nil
}

func (i Registry) retry(doFunc func() error) error {
var lastErr error
for i := 0; i < 5; i++ {
lastErr = doFunc()
if lastErr == nil {
return nil
}
time.Sleep(1 * time.Second)
}
return fmt.Errorf("Retried 5 times: %s", lastErr)
}
Loading

0 comments on commit fbaffb3

Please sign in to comment.