Two-way TLS support added for oci pull.

Signed-off-by: Sunil Kumar <suryakn27@outlook.com>

Signed-off-by: suryatech27-cloud <sunil.techno27@gmail.com>
pull/10808/head
suryatech27-cloud 4 years ago
parent a30b0a10d9
commit e3e77cbb05

@ -19,7 +19,13 @@ package tlsutil
import ( import (
"crypto/tls" "crypto/tls"
"crypto/x509" "crypto/x509"
"fmt"
"io/ioutil"
"os" "os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
@ -56,3 +62,77 @@ func ClientConfig(opts Options) (cfg *tls.Config, err error) {
cfg = &tls.Config{InsecureSkipVerify: opts.InsecureSkipVerify, Certificates: []tls.Certificate{*cert}, RootCAs: pool} cfg = &tls.Config{InsecureSkipVerify: opts.InsecureSkipVerify, Certificates: []tls.Certificate{*cert}, RootCAs: pool}
return cfg, nil return cfg, nil
} }
func ReadCertFromSecDir(host string) (opts Options, err error) {
if runtime.GOOS == "windows" || runtime.GOOS == "unix" {
fmt.Printf("%v OS not supported for this oci pull. Contact your administrator for more information !!!", runtime.GOOS)
os.Exit(1)
} else {
cmd, err := exec.Command("helm", "env", "HELM_CLIENT_TLS_CERT_DIR").Output()
if err != nil {
fmt.Printf("Error : %s", err)
os.Exit(1)
}
clientCertDir := strings.TrimSuffix(string(cmd), "\n")
if clientCertDir == "" {
fmt.Printf("Please configure client certificate directory for tls connection set/export HELM_CLIENT_TLS_CERT_DIR='/etc/docker/certs.d/'\n")
os.Exit(1)
}
if clientCertDir[len(clientCertDir)-1] != '/' {
clientCertDir = fmt.Sprintf("%s/%s", clientCertDir, host)
} else {
clientCertDir = fmt.Sprintf("%s%s", clientCertDir, host)
}
if _, err := os.Stat(clientCertDir); err != nil {
if os.IsNotExist(err) {
os.MkdirAll(clientCertDir, os.ModePerm)
return opts, errors.Wrapf(err, clientCertDir, "%v\n%v Directory created.")
}
} else {
if files, err := ioutil.ReadDir(clientCertDir); err == nil {
for _, file := range files {
if filepath.Ext(file.Name()) == ".crt" {
opts.CaCertFile = fmt.Sprintf("%s/%s", clientCertDir, file.Name())
} else if filepath.Ext(file.Name()) == ".pem" {
opts.CertFile = fmt.Sprintf("%s/%s", clientCertDir, file.Name())
} else if filepath.Ext(file.Name()) == ".key" {
opts.KeyFile = fmt.Sprintf("%s/%s", clientCertDir, file.Name())
}
}
} else {
fmt.Printf(" Certificate not found in current directory - %v\n ", err)
os.Exit(1)
}
switch {
case opts.CaCertFile == "" && opts.CertFile == "" && opts.KeyFile == "":
fmt.Printf("Error : Missing certificate (cacerts.crt,client.pem,client.key) required !!\n")
os.Exit(1)
case opts.CaCertFile == "" && opts.CertFile == "":
fmt.Printf("Error : Missing certificate : Root-CA and client certificate (cacerts.crt,client.pem) required !!\n")
os.Exit(1)
case opts.CaCertFile == "" && opts.KeyFile == "":
fmt.Printf("Error : Missing Certificate : Root-CA and and client key (cacerts.crt,client.key) required.\n")
os.Exit(1)
case opts.CertFile == "" && opts.KeyFile == "":
fmt.Printf("Error : Missing Certificate : Client certificate and client key (client.pem,client.key) required.\n")
os.Exit(1)
}
switch {
case opts.CaCertFile == "":
fmt.Printf("Error : Missing Certificate : Client Root-CA (cacerts.crt) required.\n")
os.Exit(1)
case opts.CertFile == "":
fmt.Printf("Error : Missing Certificate : Client certificate(client.pem) required.\n")
os.Exit(1)
case opts.KeyFile == "":
fmt.Printf("Error : Missing Certificate : Client keyfile (client.key) required.\n")
os.Exit(1)
}
}
}
return opts, nil
}

@ -124,9 +124,28 @@ func (p *Pull) Run(chartRef string) (string, error) {
} }
saved, v, err := c.DownloadTo(chartRef, p.Version, dest) saved, v, err := c.DownloadTo(chartRef, p.Version, dest)
if err != nil {
if registry.IsOCI(chartRef) && strings.Contains(fmt.Sprint(err), "remote error: tls: handshake failure") {
registryClient, err := registry.NewCrosClient(chartRef,
registry.ClientOptDebug(p.Settings.Debug),
registry.ClientOptCredentialsFile(p.Settings.RegistryConfig),
registry.ClientOptWriter(&out),
)
if err != nil {
return out.String(), err
}
c.Options = append(c.Options,
getter.WithRegistryClient(registryClient),
getter.WithTagName(p.Version))
saved, v, err = c.DownloadTo(chartRef, p.Version, dest)
if err != nil { if err != nil {
return out.String(), err return out.String(), err
} }
} else {
return out.String(), err
}
}
if p.Verify { if p.Verify {
for name := range v.SignedBy.Identities { for name := range v.SignedBy.Identities {

@ -68,6 +68,8 @@ type EnvSettings struct {
PluginsDirectory string PluginsDirectory string
// MaxHistory is the max release history maintained. // MaxHistory is the max release history maintained.
MaxHistory int MaxHistory int
// Secondary Certificate directory for helm oci pull
ClientSecCertDirectory string
} }
func New() *EnvSettings { func New() *EnvSettings {
@ -80,6 +82,7 @@ func New() *EnvSettings {
KubeAsGroups: envCSV("HELM_KUBEASGROUPS"), KubeAsGroups: envCSV("HELM_KUBEASGROUPS"),
KubeAPIServer: os.Getenv("HELM_KUBEAPISERVER"), KubeAPIServer: os.Getenv("HELM_KUBEAPISERVER"),
KubeCaFile: os.Getenv("HELM_KUBECAFILE"), KubeCaFile: os.Getenv("HELM_KUBECAFILE"),
ClientSecCertDirectory: envOr("HELM_CLIENT_TLS_CERT_DIR", ""),
PluginsDirectory: envOr("HELM_PLUGINS", helmpath.DataPath("plugins")), PluginsDirectory: envOr("HELM_PLUGINS", helmpath.DataPath("plugins")),
RegistryConfig: envOr("HELM_REGISTRY_CONFIG", helmpath.ConfigPath("registry/config.json")), RegistryConfig: envOr("HELM_REGISTRY_CONFIG", helmpath.ConfigPath("registry/config.json")),
RepositoryConfig: envOr("HELM_REPOSITORY_CONFIG", helmpath.ConfigPath("repositories.yaml")), RepositoryConfig: envOr("HELM_REPOSITORY_CONFIG", helmpath.ConfigPath("repositories.yaml")),
@ -157,6 +160,7 @@ func (s *EnvSettings) EnvVars() map[string]string {
"HELM_REPOSITORY_CONFIG": s.RepositoryConfig, "HELM_REPOSITORY_CONFIG": s.RepositoryConfig,
"HELM_NAMESPACE": s.Namespace(), "HELM_NAMESPACE": s.Namespace(),
"HELM_MAX_HISTORY": strconv.Itoa(s.MaxHistory), "HELM_MAX_HISTORY": strconv.Itoa(s.MaxHistory),
"HELM_CLIENT_TLS_CERT_DIR": s.ClientSecCertDirectory,
// broken, these are populated from helm flags and not kubeconfig. // broken, these are populated from helm flags and not kubeconfig.
"HELM_KUBECONTEXT": s.KubeContext, "HELM_KUBECONTEXT": s.KubeContext,

@ -22,9 +22,11 @@ import (
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
"net"
"net/http" "net/http"
"sort" "sort"
"strings" "strings"
"time"
"github.com/Masterminds/semver/v3" "github.com/Masterminds/semver/v3"
"github.com/containerd/containerd/remotes" "github.com/containerd/containerd/remotes"
@ -38,6 +40,8 @@ import (
registryremote "oras.land/oras-go/pkg/registry/remote" registryremote "oras.land/oras-go/pkg/registry/remote"
registryauth "oras.land/oras-go/pkg/registry/remote/auth" registryauth "oras.land/oras-go/pkg/registry/remote/auth"
"helm.sh/helm/v3/internal/tlsutil"
"helm.sh/helm/v3/internal/urlutil"
"helm.sh/helm/v3/internal/version" "helm.sh/helm/v3/internal/version"
"helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v3/pkg/chart"
"helm.sh/helm/v3/pkg/helmpath" "helm.sh/helm/v3/pkg/helmpath"
@ -131,6 +135,94 @@ func NewClient(options ...ClientOption) (*Client, error) {
return client, nil return client, nil
} }
func NewCrosClient(chartref string, options ...ClientOption) (*Client, error) {
client := &Client{
out: ioutil.Discard,
}
for _, option := range options {
option(client)
}
if client.credentialsFile == "" {
client.credentialsFile = helmpath.ConfigPath(CredentialsFileBasename)
}
if client.authorizer == nil {
authClient, err := dockerauth.NewClientWithDockerFallback(client.credentialsFile)
if err != nil {
return nil, err
}
client.authorizer = authClient
}
if client.resolver == nil {
host, err := urlutil.ExtractHostname(chartref)
if err != nil {
fmt.Printf("error :%v\n", err)
}
clientOpts, err := tlsutil.ReadCertFromSecDir(host)
if err != nil {
return client, errors.Wrapf(err, "Client certificate/directory Not Exist !!")
}
cfgtls, err := tlsutil.ClientConfig(clientOpts)
if err != nil {
fmt.Printf("error :%v\n", err)
}
var rt http.RoundTripper = &http.Transport{
Dial: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}).Dial,
TLSHandshakeTimeout: 30 * time.Second,
TLSClientConfig: cfgtls,
ResponseHeaderTimeout: time.Duration(3 * time.Second),
DisableKeepAlives: true,
}
crosclient := http.Client{Transport: rt, Timeout: 30 * time.Second}
headers := http.Header{}
headers.Set("User-Agent", version.GetUserAgent())
opts := []auth.ResolverOption{auth.WithResolverHeaders(headers), auth.WithResolverClient(&crosclient)}
resolver, err := client.authorizer.ResolverWithOpts(opts...)
if err != nil {
return nil, err
}
client.resolver = resolver
}
if client.registryAuthorizer == nil {
client.registryAuthorizer = &registryauth.Client{
Header: http.Header{
"User-Agent": {version.GetUserAgent()},
},
Cache: registryauth.DefaultCache,
Credential: func(ctx context.Context, reg string) (registryauth.Credential, error) {
dockerClient, ok := client.authorizer.(*dockerauth.Client)
if !ok {
return registryauth.EmptyCredential, errors.New("unable to obtain docker client")
}
username, password, err := dockerClient.Credential(reg)
if err != nil {
return registryauth.EmptyCredential, errors.New("unable to retrieve credentials")
}
// A blank returned username and password value is a bearer token
if username == "" && password != "" {
return registryauth.Credential{
RefreshToken: password,
}, nil
}
return registryauth.Credential{
Username: username,
Password: password,
}, nil
},
}
}
return client, nil
}
// ClientOptDebug returns a function that sets the debug setting on client options set // ClientOptDebug returns a function that sets the debug setting on client options set
func ClientOptDebug(debug bool) ClientOption { func ClientOptDebug(debug bool) ClientOption {
return func(client *Client) { return func(client *Client) {
@ -303,9 +395,8 @@ func (c *Client) Pull(ref string, options ...PullOption) (*PullResult, error) {
numDescriptors := len(descriptors) numDescriptors := len(descriptors)
if numDescriptors < minNumDescriptors { if numDescriptors < minNumDescriptors {
return nil, errors.New( return nil, fmt.Errorf("manifest does not contain minimum number of descriptors (%d), descriptors found: %d",
fmt.Sprintf("manifest does not contain minimum number of descriptors (%d), descriptors found: %d", minNumDescriptors, numDescriptors)
minNumDescriptors, numDescriptors))
} }
var configDescriptor *ocispec.Descriptor var configDescriptor *ocispec.Descriptor
var chartDescriptor *ocispec.Descriptor var chartDescriptor *ocispec.Descriptor
@ -325,22 +416,19 @@ func (c *Client) Pull(ref string, options ...PullOption) (*PullResult, error) {
} }
} }
if configDescriptor == nil { if configDescriptor == nil {
return nil, errors.New( return nil, fmt.Errorf("could not load config with mediatype %s", ConfigMediaType)
fmt.Sprintf("could not load config with mediatype %s", ConfigMediaType))
} }
if operation.withChart && chartDescriptor == nil { if operation.withChart && chartDescriptor == nil {
return nil, errors.New( return nil, fmt.Errorf("manifest does not contain a layer with mediatype %s",
fmt.Sprintf("manifest does not contain a layer with mediatype %s", ChartLayerMediaType)
ChartLayerMediaType))
} }
var provMissing bool var provMissing bool
if operation.withProv && provDescriptor == nil { if operation.withProv && provDescriptor == nil {
if operation.ignoreMissingProv { if operation.ignoreMissingProv {
provMissing = true provMissing = true
} else { } else {
return nil, errors.New( return nil, fmt.Errorf("manifest does not contain a layer with mediatype %s",
fmt.Sprintf("manifest does not contain a layer with mediatype %s", ProvLayerMediaType)
ProvLayerMediaType))
} }
} }
result := &PullResult{ result := &PullResult{

Loading…
Cancel
Save