mirror of https://github.com/helm/helm
Fix for (#10597) Added 2-way TLS Support for oci pull for artifact repository which causes TLS handshake failure error. Special notes for your reviewer: Added flag for two-way authentication (--mtls-enabled) . eg: helm pull oci://nginx.testharbor.com:9443/testrepo/sslcharttest --version 0.1.0 --ca-file /etc/docker/certs.d/nginx.testharbor.com/ca.crt --cert-file /etc/docker/certs.d/nginx.testharbor.com/root_client.crt --key-file /etc/docker/certs.d/nginx.testharbor.com/root_client.key --mtls-enabledpull/11174/head
parent
9377988685
commit
04e772d801
@ -0,0 +1,136 @@
|
|||||||
|
/*
|
||||||
|
Copyright The Helm Authors.
|
||||||
|
|
||||||
|
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 main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"helm.sh/helm/v3/pkg/repo/repotest"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMutualtlsPull(t *testing.T) {
|
||||||
|
srv, err := repotest.NewTempmtlsServerWithCleanup(t, "testdata/testcharts/*.tgz*")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer srv.Stopmtls()
|
||||||
|
|
||||||
|
ociSrv, err := repotest.NewOCImtlsServer(t, srv.Rootmtls())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
ociSrv.Run(t)
|
||||||
|
|
||||||
|
if err := srv.LinkIndicesmtls(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
helmTestKeyOut := "Signed by: Helm Testing (This key should only be used for testing. DO NOT TRUST.) <helm-testing@helm.sh>\n" +
|
||||||
|
"Using Key With Fingerprint: 5E615389B53CA37F0EE60BD3843BBF981FC18762\n" +
|
||||||
|
"Chart Hash Verified: "
|
||||||
|
|
||||||
|
// all flags will get "-d outdir" appended.
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args string
|
||||||
|
existFile string
|
||||||
|
existDir string
|
||||||
|
wantError bool
|
||||||
|
wantErrorMsg string
|
||||||
|
failExpect string
|
||||||
|
expectFile string
|
||||||
|
expectDir bool
|
||||||
|
expectVerify bool
|
||||||
|
expectSha string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Fetch OCI Chart",
|
||||||
|
args: fmt.Sprintf("oci://%s/u/ocitestuser/oci-dependent-chart --version 0.1.0 --ca-file ../../testdata/rootca.crt --cert-file ../../testdata/rootca.crt --key-file ../../testdata/rootca.key --tls-enabled", ociSrv.RegistryURL),
|
||||||
|
expectFile: "./oci-dependent-chart-0.1.0.tgz",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Fail fetching non-existent OCI chart with mutual tls enabled",
|
||||||
|
args: fmt.Sprintf("oci://%s/u/ocitestuser/nosuchthing --version 0.1.0 --tls-enabled", ociSrv.RegistryURL),
|
||||||
|
failExpect: "Failed to fetch",
|
||||||
|
wantError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Fail fetching OCI chart without version specified with mutual tls enabled",
|
||||||
|
args: fmt.Sprintf("oci://%s/u/ocitestuser/nosuchthing --tls-enabled", ociSrv.RegistryURL),
|
||||||
|
wantErrorMsg: "Error: --version flag is explicitly required for OCI registries",
|
||||||
|
wantError: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
outdir := srv.Rootmtls()
|
||||||
|
cmd := fmt.Sprintf("fetch %s -d '%s' --repository-config %s --repository-cache %s --registry-config %s",
|
||||||
|
tt.args,
|
||||||
|
outdir,
|
||||||
|
filepath.Join(outdir, "repositories.yaml"),
|
||||||
|
outdir,
|
||||||
|
filepath.Join(outdir, "config.json"),
|
||||||
|
)
|
||||||
|
// Create file or Dir before helm pull --untar, see: https://github.com/helm/helm/issues/7182
|
||||||
|
if tt.existFile != "" {
|
||||||
|
file := filepath.Join(outdir, tt.existFile)
|
||||||
|
_, err := os.Create(file)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if tt.existDir != "" {
|
||||||
|
file := filepath.Join(outdir, tt.existDir)
|
||||||
|
err := os.Mkdir(file, 0755)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_, out, err := executeActionCommand(cmd)
|
||||||
|
if err != nil {
|
||||||
|
if tt.wantError {
|
||||||
|
if tt.wantErrorMsg != "" && tt.wantErrorMsg == err.Error() {
|
||||||
|
t.Fatalf("Actual error %s, not equal to expected error %s", err, tt.wantErrorMsg)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t.Fatalf("%q reported error: %s", tt.name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tt.expectVerify {
|
||||||
|
outString := helmTestKeyOut + tt.expectSha + "\n"
|
||||||
|
if out != outString {
|
||||||
|
t.Errorf("%q: expected verification output %q, got %q", tt.name, outString, out)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
ef := filepath.Join(outdir, tt.expectFile)
|
||||||
|
fi, err := os.Stat(ef)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%q: expected a file at %s. %s", tt.name, ef, err)
|
||||||
|
}
|
||||||
|
if fi.IsDir() != tt.expectDir {
|
||||||
|
t.Errorf("%q: expected directory=%t, but it's not.", tt.name, tt.expectDir)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,445 @@
|
|||||||
|
/*
|
||||||
|
Copyright The Helm Authors.
|
||||||
|
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 repotest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/distribution/distribution/v3/configuration"
|
||||||
|
"github.com/distribution/distribution/v3/registry"
|
||||||
|
_ "github.com/distribution/distribution/v3/registry/auth/htpasswd" // used for docker test registry
|
||||||
|
_ "github.com/distribution/distribution/v3/registry/storage/driver/inmemory" // used for docker test registry
|
||||||
|
"github.com/phayes/freeport"
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
"sigs.k8s.io/yaml"
|
||||||
|
|
||||||
|
"helm.sh/helm/v3/internal/tlsutil"
|
||||||
|
"helm.sh/helm/v3/pkg/chart"
|
||||||
|
"helm.sh/helm/v3/pkg/chart/loader"
|
||||||
|
"helm.sh/helm/v3/pkg/chartutil"
|
||||||
|
ociRegistry "helm.sh/helm/v3/pkg/registry"
|
||||||
|
"helm.sh/helm/v3/pkg/repo"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewTempServerWithCleanup creates a server inside of a temp dir.
|
||||||
|
//
|
||||||
|
// If the passed in string is not "", it will be treated as a shell glob, and files
|
||||||
|
// will be copied from that path to the server's docroot.
|
||||||
|
//
|
||||||
|
// The caller is responsible for stopping the server.
|
||||||
|
// The temp dir will be removed by testing package automatically when test finished.
|
||||||
|
const (
|
||||||
|
testCaCertFile = "crt.pem"
|
||||||
|
testCertFile = "rootca.crt"
|
||||||
|
testKeyFile = "rootca.key"
|
||||||
|
)
|
||||||
|
|
||||||
|
const tlsTestDir = "../../testdata"
|
||||||
|
|
||||||
|
func NewTempmtlsServerWithCleanup(t *testing.T, glob string) (*mtlsServer, error) {
|
||||||
|
srv, err := NewTempmtlsServer(glob)
|
||||||
|
t.Cleanup(func() { os.RemoveAll(srv.docroot) })
|
||||||
|
return srv, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up a fake repo with basic auth enabled
|
||||||
|
func NewTempmtlsServerWithCleanupAndBasicAuth(t *testing.T, glob string) *mtlsServer {
|
||||||
|
srv, err := NewTempmtlsServerWithCleanup(t, glob)
|
||||||
|
srv.Stopmtls()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
srv.WithMiddlewaremtls(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
username, password, ok := r.BasicAuth()
|
||||||
|
if !ok || username != "username" || password != "password" {
|
||||||
|
t.Errorf("Expected request to use basic auth and for username == 'username' and password == 'password', got '%v', '%s', '%s'", ok, username, password)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
srv.mtlsStart()
|
||||||
|
return srv
|
||||||
|
}
|
||||||
|
|
||||||
|
type mtlsOCIServer struct {
|
||||||
|
*registry.Registry
|
||||||
|
RegistryURL string
|
||||||
|
Dir string
|
||||||
|
TestUsername string
|
||||||
|
TestPassword string
|
||||||
|
Client *ociRegistry.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
type mtlsOCIServerRunConfig struct {
|
||||||
|
DependingChart *chart.Chart
|
||||||
|
}
|
||||||
|
|
||||||
|
type mtlsOCIServerOpt func(config *mtlsOCIServerRunConfig)
|
||||||
|
|
||||||
|
func NewOCImtlsServer(t *testing.T, dir string) (*mtlsOCIServer, error) {
|
||||||
|
testHtpasswdFileBasename := "authtest.htpasswd"
|
||||||
|
testUsername, testPassword := "username", "password"
|
||||||
|
|
||||||
|
pwBytes, err := bcrypt.GenerateFromPassword([]byte(testPassword), bcrypt.DefaultCost)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("error generating bcrypt password for test htpasswd file")
|
||||||
|
}
|
||||||
|
htpasswdPath := filepath.Join(dir, testHtpasswdFileBasename)
|
||||||
|
err = ioutil.WriteFile(htpasswdPath, []byte(fmt.Sprintf("%s:%s\n", testUsername, string(pwBytes))), 0644)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error creating test htpasswd file")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Registry config
|
||||||
|
config := &configuration.Configuration{}
|
||||||
|
port, err := freeport.GetFreePort()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error finding free port for test registry")
|
||||||
|
}
|
||||||
|
|
||||||
|
config.HTTP.Addr = fmt.Sprintf(":%d", port)
|
||||||
|
config.HTTP.DrainTimeout = time.Duration(10) * time.Second
|
||||||
|
config.Storage = map[string]configuration.Parameters{"inmemory": map[string]interface{}{}}
|
||||||
|
config.Auth = configuration.Auth{
|
||||||
|
"htpasswd": configuration.Parameters{
|
||||||
|
"realm": "localhost",
|
||||||
|
"path": htpasswdPath,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
registryURL := fmt.Sprintf("localhost:%d", port)
|
||||||
|
|
||||||
|
r, err := registry.NewRegistry(context.Background(), config)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &mtlsOCIServer{
|
||||||
|
Registry: r,
|
||||||
|
RegistryURL: registryURL,
|
||||||
|
TestUsername: testUsername,
|
||||||
|
TestPassword: testPassword,
|
||||||
|
Dir: dir,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (srv *mtlsOCIServer) Run(t *testing.T, opts ...mtlsOCIServerOpt) {
|
||||||
|
cfg := &mtlsOCIServerRunConfig{}
|
||||||
|
for _, fn := range opts {
|
||||||
|
fn(cfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
go srv.ListenAndServe()
|
||||||
|
|
||||||
|
credentialsFile := filepath.Join(srv.Dir, "config.json")
|
||||||
|
|
||||||
|
optns := tlsutil.Options{
|
||||||
|
CaCertFile: testfile(t, testCaCertFile),
|
||||||
|
CertFile: testfile(t, testCertFile),
|
||||||
|
KeyFile: testfile(t, testKeyFile),
|
||||||
|
InsecureSkipVerify: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
// init test client
|
||||||
|
registryClient, err := ociRegistry.NewClient(
|
||||||
|
ociRegistry.ClientOptDebug(true),
|
||||||
|
ociRegistry.ClientOptEnableCache(true),
|
||||||
|
ociRegistry.ClientOptWriter(os.Stdout),
|
||||||
|
ociRegistry.ClientOptTwoWayTLSEnable(true),
|
||||||
|
ociRegistry.ClientOptCredentialsFile(credentialsFile),
|
||||||
|
ociRegistry.ClientOptWithTLSOpts(optns),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error creating registry client")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = registryClient.Login(
|
||||||
|
srv.RegistryURL,
|
||||||
|
ociRegistry.LoginOptBasicAuth(srv.TestUsername, srv.TestPassword),
|
||||||
|
ociRegistry.LoginOptInsecure(false))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error logging into registry with good credentials")
|
||||||
|
}
|
||||||
|
|
||||||
|
ref := fmt.Sprintf("%s/u/ocitestuser/oci-dependent-chart:0.1.0", srv.RegistryURL)
|
||||||
|
|
||||||
|
err = chartutil.ExpandFile(srv.Dir, filepath.Join(srv.Dir, "oci-dependent-chart-0.1.0.tgz"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// valid chart
|
||||||
|
ch, err := loader.LoadDir(filepath.Join(srv.Dir, "oci-dependent-chart"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("error loading chart")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.RemoveAll(filepath.Join(srv.Dir, "oci-dependent-chart"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("error removing chart before push")
|
||||||
|
}
|
||||||
|
|
||||||
|
// save it back to disk..
|
||||||
|
absPath, err := chartutil.Save(ch, srv.Dir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("could not create chart archive")
|
||||||
|
}
|
||||||
|
|
||||||
|
// load it into memory...
|
||||||
|
contentBytes, err := ioutil.ReadFile(absPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("could not load chart into memory")
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := registryClient.Push(contentBytes, ref)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error pushing dependent chart: %s", err)
|
||||||
|
}
|
||||||
|
t.Logf("Manifest.Digest: %s, Manifest.Size: %d, "+
|
||||||
|
"Config.Digest: %s, Config.Size: %d, "+
|
||||||
|
"Chart.Digest: %s, Chart.Size: %d",
|
||||||
|
result.Manifest.Digest, result.Manifest.Size,
|
||||||
|
result.Config.Digest, result.Config.Size,
|
||||||
|
result.Chart.Digest, result.Chart.Size)
|
||||||
|
|
||||||
|
srv.Client = registryClient
|
||||||
|
c := cfg.DependingChart
|
||||||
|
if c == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
dependingRef := fmt.Sprintf("%s/u/ocitestuser/%s:%s",
|
||||||
|
srv.RegistryURL, c.Metadata.Name, c.Metadata.Version)
|
||||||
|
|
||||||
|
// load it into memory...
|
||||||
|
absPath = filepath.Join(srv.Dir,
|
||||||
|
fmt.Sprintf("%s-%s.tgz", c.Metadata.Name, c.Metadata.Version))
|
||||||
|
contentBytes, err = ioutil.ReadFile(absPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("could not load chart into memory")
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err = registryClient.Push(contentBytes, dependingRef)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error pushing depending chart: %s", err)
|
||||||
|
}
|
||||||
|
t.Logf("Manifest.Digest: %s, Manifest.Size: %d, "+
|
||||||
|
"Config.Digest: %s, Config.Size: %d, "+
|
||||||
|
"Chart.Digest: %s, Chart.Size: %d",
|
||||||
|
result.Manifest.Digest, result.Manifest.Size,
|
||||||
|
result.Config.Digest, result.Config.Size,
|
||||||
|
result.Chart.Digest, result.Chart.Size)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testfile(t *testing.T, file string) (path string) {
|
||||||
|
var err error
|
||||||
|
if path, err = filepath.Abs(filepath.Join(tlsTestDir, file)); err != nil {
|
||||||
|
t.Fatalf("error getting absolute path to test file %q: %v", file, err)
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTempServer creates a server inside of a temp dir.
|
||||||
|
//
|
||||||
|
// If the passed in string is not "", it will be treated as a shell glob, and files
|
||||||
|
// will be copied from that path to the server's docroot.
|
||||||
|
//
|
||||||
|
// The caller is responsible for destroying the temp directory as well as stopping
|
||||||
|
// the server.
|
||||||
|
//
|
||||||
|
// Deprecated: use NewTempServerWithCleanup
|
||||||
|
func NewTempmtlsServer(glob string) (*mtlsServer, error) {
|
||||||
|
tdir, err := ioutil.TempDir("", "helm-repotest-")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
srv := NewmtlsServer(tdir)
|
||||||
|
|
||||||
|
if glob != "" {
|
||||||
|
if _, err := srv.mtlsCopyCharts(glob); err != nil {
|
||||||
|
srv.Stopmtls()
|
||||||
|
return srv, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return srv, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewServer creates a repository server for testing.
|
||||||
|
//
|
||||||
|
// docroot should be a temp dir managed by the caller.
|
||||||
|
//
|
||||||
|
// This will start the server, serving files off of the docroot.
|
||||||
|
//
|
||||||
|
// Use CopyCharts to move charts into the repository and then index them
|
||||||
|
// for service.
|
||||||
|
func NewmtlsServer(docroot string) *mtlsServer {
|
||||||
|
root, err := filepath.Abs(docroot)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
srv := &mtlsServer{
|
||||||
|
docroot: root,
|
||||||
|
}
|
||||||
|
srv.mtlsStart()
|
||||||
|
// Add the testing repository as the only repo.
|
||||||
|
if err := setmtlsTestingRepository(srv.mtlsURL(), filepath.Join(root, "repositories.yaml")); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return srv
|
||||||
|
}
|
||||||
|
|
||||||
|
// Server is an implementation of a repository server for testing.
|
||||||
|
type mtlsServer struct {
|
||||||
|
docroot string
|
||||||
|
srv *httptest.Server
|
||||||
|
middleware http.HandlerFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithMiddleware injects middleware in front of the server. This can be used to inject
|
||||||
|
// additional functionality like layering in an authentication frontend.
|
||||||
|
func (s *mtlsServer) WithMiddlewaremtls(middleware http.HandlerFunc) {
|
||||||
|
s.middleware = middleware
|
||||||
|
}
|
||||||
|
|
||||||
|
// Root gets the docroot for the server.
|
||||||
|
func (s *mtlsServer) Rootmtls() string {
|
||||||
|
return s.docroot
|
||||||
|
}
|
||||||
|
|
||||||
|
// CopyCharts takes a glob expression and copies those charts to the server root.
|
||||||
|
func (s *mtlsServer) mtlsCopyCharts(origin string) ([]string, error) {
|
||||||
|
files, err := filepath.Glob(origin)
|
||||||
|
if err != nil {
|
||||||
|
return []string{}, err
|
||||||
|
}
|
||||||
|
copied := make([]string, len(files))
|
||||||
|
for i, f := range files {
|
||||||
|
base := filepath.Base(f)
|
||||||
|
newname := filepath.Join(s.docroot, base)
|
||||||
|
data, err := ioutil.ReadFile(f)
|
||||||
|
if err != nil {
|
||||||
|
return []string{}, err
|
||||||
|
}
|
||||||
|
if err := ioutil.WriteFile(newname, data, 0644); err != nil {
|
||||||
|
return []string{}, err
|
||||||
|
}
|
||||||
|
copied[i] = newname
|
||||||
|
}
|
||||||
|
|
||||||
|
err = s.mtlsCreateIndex()
|
||||||
|
return copied, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateIndex will read docroot and generate an index.yaml file.
|
||||||
|
func (s *mtlsServer) mtlsCreateIndex() error {
|
||||||
|
// generate the index
|
||||||
|
index, err := repo.IndexDirectory(s.docroot, s.mtlsURL())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
d, err := yaml.Marshal(index)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ifile := filepath.Join(s.docroot, "index.yaml")
|
||||||
|
return ioutil.WriteFile(ifile, d, 0644)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *mtlsServer) mtlsStart() {
|
||||||
|
s.srv = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if s.middleware != nil {
|
||||||
|
s.middleware.ServeHTTP(w, r)
|
||||||
|
}
|
||||||
|
http.FileServer(http.Dir(s.docroot)).ServeHTTP(w, r)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *mtlsServer) StartmTLS() {
|
||||||
|
cd := "../../testdata"
|
||||||
|
ca, pub, priv := filepath.Join(cd, "rootca.crt"), filepath.Join(cd, "crt.pem"), filepath.Join(cd, "key.pem")
|
||||||
|
|
||||||
|
s.srv = httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if s.middleware != nil {
|
||||||
|
s.middleware.ServeHTTP(w, r)
|
||||||
|
}
|
||||||
|
http.FileServer(http.Dir(s.Rootmtls())).ServeHTTP(w, r)
|
||||||
|
}))
|
||||||
|
tlsConf, err := tlsutil.NewClientTLS(pub, priv, ca)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
tlsConf.BuildNameToCertificate()
|
||||||
|
tlsConf.ServerName = "helm.sh"
|
||||||
|
s.srv.TLS = tlsConf
|
||||||
|
s.srv.StartTLS()
|
||||||
|
|
||||||
|
// Set up repositories config with ca file
|
||||||
|
repoConfig := filepath.Join(s.Rootmtls(), "repositories.yaml")
|
||||||
|
|
||||||
|
r := repo.NewFile()
|
||||||
|
r.Add(&repo.Entry{
|
||||||
|
Name: "test",
|
||||||
|
URL: s.mtlsURL(),
|
||||||
|
CAFile: filepath.Join("../../testdata", "rootca.crt"),
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := r.WriteFile(repoConfig, 0644); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop stops the server and closes all connections.
|
||||||
|
//
|
||||||
|
// It should be called explicitly.
|
||||||
|
func (s *mtlsServer) Stopmtls() {
|
||||||
|
s.srv.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// URL returns the URL of the server.
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
// http://localhost:1776
|
||||||
|
func (s *mtlsServer) mtlsURL() string {
|
||||||
|
return s.srv.URL
|
||||||
|
}
|
||||||
|
|
||||||
|
// LinkIndices links the index created with CreateIndex and makes a symbolic link to the cache index.
|
||||||
|
//
|
||||||
|
// This makes it possible to simulate a local cache of a repository.
|
||||||
|
func (s *mtlsServer) LinkIndicesmtls() error {
|
||||||
|
lstart := filepath.Join(s.docroot, "index.yaml")
|
||||||
|
ldest := filepath.Join(s.docroot, "test-index.yaml")
|
||||||
|
return os.Symlink(lstart, ldest)
|
||||||
|
}
|
||||||
|
|
||||||
|
// setTestingRepository sets up a testing repository.yaml with only the given URL.
|
||||||
|
func setmtlsTestingRepository(url, fname string) error {
|
||||||
|
r := repo.NewFile()
|
||||||
|
r.Add(&repo.Entry{
|
||||||
|
Name: "test",
|
||||||
|
URL: url,
|
||||||
|
})
|
||||||
|
return r.WriteFile(fname, 0644)
|
||||||
|
}
|
@ -0,0 +1,118 @@
|
|||||||
|
/*
|
||||||
|
Copyright The Helm Authors.
|
||||||
|
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 repotest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"sigs.k8s.io/yaml"
|
||||||
|
|
||||||
|
"helm.sh/helm/v3/internal/test/ensure"
|
||||||
|
"helm.sh/helm/v3/pkg/repo"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Young'n, in these here parts, we test our tests.
|
||||||
|
|
||||||
|
func TestMtlsServer(t *testing.T) {
|
||||||
|
defer ensure.HelmHome(t)()
|
||||||
|
|
||||||
|
rootDir := ensure.TempDir(t)
|
||||||
|
defer os.RemoveAll(rootDir)
|
||||||
|
|
||||||
|
srv := NewServer(rootDir)
|
||||||
|
defer srv.Stop()
|
||||||
|
|
||||||
|
c, err := srv.CopyCharts("testdata/*.tgz")
|
||||||
|
if err != nil {
|
||||||
|
// Some versions of Go don't correctly fire defer on Fatal.
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(c) != 1 {
|
||||||
|
t.Errorf("Unexpected chart count: %d", len(c))
|
||||||
|
}
|
||||||
|
|
||||||
|
if filepath.Base(c[0]) != "examplechart-0.1.0.tgz" {
|
||||||
|
t.Errorf("Unexpected chart: %s", c[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := http.Get(srv.URL() + "/examplechart-0.1.0.tgz")
|
||||||
|
res.Body.Close()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if res.ContentLength < 500 {
|
||||||
|
t.Errorf("Expected at least 500 bytes of data, got %d", res.ContentLength)
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err = http.Get(srv.URL() + "/index.yaml")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := ioutil.ReadAll(res.Body)
|
||||||
|
res.Body.Close()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
m := repo.NewIndexFile()
|
||||||
|
if err := yaml.Unmarshal(data, m); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if l := len(m.Entries); l != 1 {
|
||||||
|
t.Fatalf("Expected 1 entry, got %d", l)
|
||||||
|
}
|
||||||
|
|
||||||
|
expect := "examplechart"
|
||||||
|
if !m.Has(expect, "0.1.0") {
|
||||||
|
t.Errorf("missing %q", expect)
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err = http.Get(srv.URL() + "/index.yaml-nosuchthing")
|
||||||
|
res.Body.Close()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if res.StatusCode != 404 {
|
||||||
|
t.Fatalf("Expected 404, got %d", res.StatusCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewmtlsTempServer(t *testing.T) {
|
||||||
|
defer ensure.HelmHome(t)()
|
||||||
|
|
||||||
|
srv, err := NewTempServerWithCleanup(t, "testdata/examplechart-0.1.0.tgz")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer srv.Stop()
|
||||||
|
|
||||||
|
res, err := http.Head(srv.URL() + "/examplechart-0.1.0.tgz")
|
||||||
|
res.Body.Close()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if res.StatusCode != 200 {
|
||||||
|
t.Errorf("Expected 200, got %d", res.StatusCode)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in new issue