test(*): add tests for new tls support

Adds a testdata directory to hold tls certs at the root
of the project. The tests cover pkg/tlsutil, cmd/helm,
and cmd/helm/installer.

Closes #2289
pull/2291/head
fibonacci1729 8 years ago
parent bba0214ea1
commit 73e6399294

@ -20,6 +20,8 @@ import (
"bytes" "bytes"
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath"
"reflect"
"strings" "strings"
"testing" "testing"
@ -33,6 +35,7 @@ import (
"k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake"
"k8s.io/helm/cmd/helm/installer"
"k8s.io/helm/pkg/helm/helmpath" "k8s.io/helm/pkg/helm/helmpath"
) )
@ -217,3 +220,85 @@ func TestEnsureHome(t *testing.T) {
t.Errorf("%s should not be a directory", fi) t.Errorf("%s should not be a directory", fi)
} }
} }
func TestInitCmd_tlsOptions(t *testing.T) {
const testDir = "../../testdata"
// tls certificates in testDir
var (
testCaCertFile = filepath.Join(testDir, "ca.pem")
testCertFile = filepath.Join(testDir, "crt.pem")
testKeyFile = filepath.Join(testDir, "key.pem")
)
// these tests verify the effects of permuting the "--tls" and "--tls-verify" flags
// and the install options yieled as a result of (*initCmd).tlsOptions()
// during helm init.
var tests = []struct {
certFile string
keyFile string
caFile string
enable bool
verify bool
describe string
}{
{ // --tls and --tls-verify specified (--tls=true,--tls-verify=true)
certFile: testCertFile,
keyFile: testKeyFile,
caFile: testCaCertFile,
enable: true,
verify: true,
describe: "--tls and --tls-verify specified (--tls=true,--tls-verify=true)",
},
{ // --tls-verify implies --tls (--tls=false,--tls-verify=true)
certFile: testCertFile,
keyFile: testKeyFile,
caFile: testCaCertFile,
enable: false,
verify: true,
describe: "--tls-verify implies --tls (--tls=false,--tls-verify=true)",
},
{ // no --tls-verify (--tls=true,--tls-verify=false)
certFile: testCertFile,
keyFile: testKeyFile,
caFile: "",
enable: true,
verify: false,
describe: "no --tls-verify (--tls=true,--tls-verify=false)",
},
{ // tls is disabled (--tls=false,--tls-verify=false)
certFile: "",
keyFile: "",
caFile: "",
enable: false,
verify: false,
describe: "tls is disabled (--tls=false,--tls-verify=false)",
},
}
for _, tt := range tests {
// emulate tls file specific flags
tlsCaCertFile, tlsCertFile, tlsKeyFile = tt.caFile, tt.certFile, tt.keyFile
// emulate tls enable/verify flags
tlsEnable, tlsVerify = tt.enable, tt.verify
cmd := &initCmd{}
if err := cmd.tlsOptions(); err != nil {
t.Fatalf("unexpected error: %v", err)
}
// expected result options
expect := installer.Options{
TLSCaCertFile: tt.caFile,
TLSCertFile: tt.certFile,
TLSKeyFile: tt.keyFile,
VerifyTLS: tt.verify,
EnableTLS: tt.enable || tt.verify,
}
if !reflect.DeepEqual(cmd.opts, expect) {
t.Errorf("%s: got %#+v, want %#+v", tt.describe, cmd.opts, expect)
}
}
}

@ -17,6 +17,8 @@ limitations under the License.
package installer // import "k8s.io/helm/cmd/helm/installer" package installer // import "k8s.io/helm/cmd/helm/installer"
import ( import (
"os"
"path/filepath"
"reflect" "reflect"
"testing" "testing"
@ -32,7 +34,6 @@ import (
) )
func TestDeploymentManifest(t *testing.T) { func TestDeploymentManifest(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
image string image string
@ -69,6 +70,52 @@ func TestDeploymentManifest(t *testing.T) {
} }
} }
func TestDeploymentManifest_WithTLS(t *testing.T) {
tests := []struct {
opts Options
name string
enable string
verify string
}{
{
Options{Namespace: api.NamespaceDefault, EnableTLS: true, VerifyTLS: true},
"tls enable (true), tls verify (true)",
"1",
"1",
},
{
Options{Namespace: api.NamespaceDefault, EnableTLS: true, VerifyTLS: false},
"tls enable (true), tls verify (false)",
"1",
"",
},
{
Options{Namespace: api.NamespaceDefault, EnableTLS: false, VerifyTLS: true},
"tls enable (false), tls verify (true)",
"1",
"1",
},
}
for _, tt := range tests {
o, err := DeploymentManifest(&tt.opts)
if err != nil {
t.Fatalf("%s: error %q", tt.name, err)
}
var d extensions.Deployment
if err := yaml.Unmarshal([]byte(o), &d); err != nil {
t.Fatalf("%s: error %q", tt.name, err)
}
// verify environment variable in deployment reflect the use of tls being enabled.
if got := d.Spec.Template.Spec.Containers[0].Env[1].Value; got != tt.verify {
t.Errorf("%s: expected tls verify env value %q, got %q", tt.name, tt.verify, got)
}
if got := d.Spec.Template.Spec.Containers[0].Env[2].Value; got != tt.enable {
t.Errorf("%s: expected tls enable env value %q, got %q", tt.name, tt.enable, got)
}
}
}
func TestServiceManifest(t *testing.T) { func TestServiceManifest(t *testing.T) {
o, err := ServiceManifest(api.NamespaceDefault) o, err := ServiceManifest(api.NamespaceDefault)
if err != nil { if err != nil {
@ -84,6 +131,39 @@ func TestServiceManifest(t *testing.T) {
} }
} }
func TestSecretManifest(t *testing.T) {
o, err := SecretManifest(&Options{
VerifyTLS: true,
EnableTLS: true,
Namespace: api.NamespaceDefault,
TLSKeyFile: tlsTestFile(t, "key.pem"),
TLSCertFile: tlsTestFile(t, "crt.pem"),
TLSCaCertFile: tlsTestFile(t, "ca.pem"),
})
if err != nil {
t.Fatalf("error %q", err)
}
var obj api.Secret
if err := yaml.Unmarshal([]byte(o), &obj); err != nil {
t.Fatalf("error %q", err)
}
if got := obj.ObjectMeta.Namespace; got != api.NamespaceDefault {
t.Errorf("expected namespace %s, got %s", api.NamespaceDefault, got)
}
if _, ok := obj.Data["tls.key"]; !ok {
t.Errorf("missing 'tls.key' in generated secret object")
}
if _, ok := obj.Data["tls.crt"]; !ok {
t.Errorf("missing 'tls.crt' in generated secret object")
}
if _, ok := obj.Data["ca.crt"]; !ok {
t.Errorf("missing 'ca.crt' in generated secret object")
}
}
func TestInstall(t *testing.T) { func TestInstall(t *testing.T) {
image := "gcr.io/kubernetes-helm/tiller:v2.0.0" image := "gcr.io/kubernetes-helm/tiller:v2.0.0"
@ -123,6 +203,77 @@ func TestInstall(t *testing.T) {
} }
} }
func TestInstall_WithTLS(t *testing.T) {
image := "gcr.io/kubernetes-helm/tiller:v2.0.0"
name := "tiller-secret"
fc := &fake.Clientset{}
fc.AddReactor("create", "deployments", func(action testcore.Action) (bool, runtime.Object, error) {
obj := action.(testcore.CreateAction).GetObject().(*extensions.Deployment)
l := obj.GetLabels()
if reflect.DeepEqual(l, map[string]string{"app": "helm"}) {
t.Errorf("expected labels = '', got '%s'", l)
}
i := obj.Spec.Template.Spec.Containers[0].Image
if i != image {
t.Errorf("expected image = '%s', got '%s'", image, i)
}
return true, obj, nil
})
fc.AddReactor("create", "services", func(action testcore.Action) (bool, runtime.Object, error) {
obj := action.(testcore.CreateAction).GetObject().(*api.Service)
l := obj.GetLabels()
if reflect.DeepEqual(l, map[string]string{"app": "helm"}) {
t.Errorf("expected labels = '', got '%s'", l)
}
n := obj.ObjectMeta.Namespace
if n != api.NamespaceDefault {
t.Errorf("expected namespace = '%s', got '%s'", api.NamespaceDefault, n)
}
return true, obj, nil
})
fc.AddReactor("create", "secrets", func(action testcore.Action) (bool, runtime.Object, error) {
obj := action.(testcore.CreateAction).GetObject().(*api.Secret)
if l := obj.GetLabels(); reflect.DeepEqual(l, map[string]string{"app": "helm"}) {
t.Errorf("expected labels = '', got '%s'", l)
}
if n := obj.ObjectMeta.Namespace; n != api.NamespaceDefault {
t.Errorf("expected namespace = '%s', got '%s'", api.NamespaceDefault, n)
}
if s := obj.ObjectMeta.Name; s != name {
t.Errorf("expected name = '%s', got '%s'", name, s)
}
if _, ok := obj.Data["tls.key"]; !ok {
t.Errorf("missing 'tls.key' in generated secret object")
}
if _, ok := obj.Data["tls.crt"]; !ok {
t.Errorf("missing 'tls.crt' in generated secret object")
}
if _, ok := obj.Data["ca.crt"]; !ok {
t.Errorf("missing 'ca.crt' in generated secret object")
}
return true, obj, nil
})
opts := &Options{
Namespace: api.NamespaceDefault,
ImageSpec: image,
EnableTLS: true,
VerifyTLS: true,
TLSKeyFile: tlsTestFile(t, "key.pem"),
TLSCertFile: tlsTestFile(t, "crt.pem"),
TLSCaCertFile: tlsTestFile(t, "ca.pem"),
}
if err := Install(fc, opts); err != nil {
t.Errorf("unexpected error: %#+v", err)
}
if actions := fc.Actions(); len(actions) != 3 {
t.Errorf("unexpected actions: %v, expected 3 actions got %d", actions, len(actions))
}
}
func TestInstall_canary(t *testing.T) { func TestInstall_canary(t *testing.T) {
fc := &fake.Clientset{} fc := &fake.Clientset{}
fc.AddReactor("create", "deployments", func(action testcore.Action) (bool, runtime.Object, error) { fc.AddReactor("create", "deployments", func(action testcore.Action) (bool, runtime.Object, error) {
@ -226,3 +377,12 @@ func TestUpgrade_serviceNotFound(t *testing.T) {
t.Errorf("unexpected actions: %v, expected 4 actions got %d", actions, len(actions)) t.Errorf("unexpected actions: %v, expected 4 actions got %d", actions, len(actions))
} }
} }
func tlsTestFile(t *testing.T, path string) string {
const tlsTestDir = "../../../testdata"
path = filepath.Join(tlsTestDir, path)
if _, err := os.Stat(path); os.IsNotExist(err) {
t.Fatalf("tls test file %s does not exist", path)
}
return path
}

@ -44,7 +44,10 @@ func ClientConfig(opts Options) (cfg *tls.Config, err error) {
if opts.CertFile != "" || opts.KeyFile != "" { if opts.CertFile != "" || opts.KeyFile != "" {
if cert, err = CertFromFilePair(opts.CertFile, opts.KeyFile); err != nil { if cert, err = CertFromFilePair(opts.CertFile, opts.KeyFile); err != nil {
return nil, fmt.Errorf("could not load x509 key pair (cert: %q, key: %q): %v", opts.CertFile, opts.KeyFile, err) if os.IsNotExist(err) {
return nil, fmt.Errorf("could not load x509 key pair (cert: %q, key: %q): %v", opts.CertFile, opts.KeyFile, err)
}
return nil, fmt.Errorf("could not read x509 key pair (cert: %q, key: %q): %v", opts.CertFile, opts.KeyFile, err)
} }
} }
if !opts.InsecureSkipVerify && opts.CaCertFile != "" { if !opts.InsecureSkipVerify && opts.CaCertFile != "" {

@ -0,0 +1,83 @@
/*
Copyright 2016 The Kubernetes Authors All rights reserved.
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 tlsutil
import (
"crypto/tls"
"path/filepath"
"testing"
)
const tlsTestDir = "../../testdata"
const (
testCaCertFile = "ca.pem"
testCertFile = "crt.pem"
testKeyFile = "key.pem"
)
func TestClientConfig(t *testing.T) {
opts := Options{
CaCertFile: testfile(t, testCaCertFile),
CertFile: testfile(t, testCertFile),
KeyFile: testfile(t, testKeyFile),
InsecureSkipVerify: false,
}
cfg, err := ClientConfig(opts)
if err != nil {
t.Fatalf("error building tls client config: %v", err)
}
if got := len(cfg.Certificates); got != 1 {
t.Fatalf("expecting 1 client certificates, got %d", got)
}
if cfg.InsecureSkipVerify {
t.Fatalf("insecure skip verify mistmatch, expecting false")
}
if cfg.RootCAs == nil {
t.Fatalf("mismatch tls RootCAs, expecting non-nil")
}
}
func TestServerConfig(t *testing.T) {
opts := Options{
CaCertFile: testfile(t, testCaCertFile),
CertFile: testfile(t, testCertFile),
KeyFile: testfile(t, testKeyFile),
ClientAuth: tls.RequireAndVerifyClientCert,
}
cfg, err := ServerConfig(opts)
if err != nil {
t.Fatalf("error building tls server config: %v", err)
}
if got := cfg.MinVersion; got != tls.VersionTLS12 {
t.Errorf("expecting TLS version 1.2, got %d", got)
}
if got := cfg.ClientCAs; got == nil {
t.Errorf("expecting non-nil CA pool")
}
}
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
}

35
testdata/ca.pem vendored

@ -0,0 +1,35 @@
-----BEGIN CERTIFICATE-----
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
mlK3dw==
-----END CERTIFICATE-----

29
testdata/crt.pem vendored

@ -0,0 +1,29 @@
-----BEGIN CERTIFICATE-----
MIIFCDCCAvCgAwIBAgIJAMADBPQSkgPMMA0GCSqGSIb3DQEBCwUAMF0xCzAJBgNV
BAYTAlVTMQswCQYDVQQIEwJDTzEQMA4GA1UEBxMHQm91bGRlcjEPMA0GA1UEChMG
VGlsbGVyMQ8wDQYDVQQLEwZUaWxsZXIxDTALBgNVBAMTBEhlbG0wHhcNMTcwNDA0
MTYwNzM4WhcNMTgwNDA0MTYwNzM4WjARMQ8wDQYDVQQDEwZjbGllbnQwggIiMA0G
CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDnyxxZtTKZLOYyEDmo1pY8m6A1tot1
UuiSxtwp4rNYIaVyCbpdKrNr68q6dRs40vEWGfH415OzFjK3RpbzdSqeB4U+toUl
bIYjf9N4/ZrAjqBO+Xd+JKUkhKcZIbMJHb2kOzqOL7LSWlKcyGCY/x7Tj4qdka9R
QiXB7zVUEqcTa13A+/rdrPWgzK/xGIYh7cCehOixxXSmfcCHR573BDC5j6s9KozA
T84obBgEgsVgu1+d+n1D+cqAr7ppSZTMWs/f+DwwJG/VWblIYsCuN3yNHLaYsL9M
MTw1ogulcRmFNyw9CSXdyVCxGjh/++sQ2f47TpadI+IzknrBkfPL7+zt2IyaORch
uGsdX+IwQl3aZjayMx7YjYSSbQIfpSF9y4KVPz4RHEUn10hsX/8qXPzitbXVLh7p
b9lUMGPHchTm/dd+oZAbL1TUIJQOJn2vGDMKsuBswBg12YNdhAp55EDZx54CCiM2
sRtlVNTpkatr7Rvd5CDFuLAzwHnrEKTy5EOUrS9aYzqKaGOrMI+k1OCTp3LwLdPX
d7OV9+ZuSLHX6gvF4uAucK8HLp3Visj0GeWL7OzpTv2imjNX5C1wPH7UR6UsF+dg
bzqZOP63e5WR1eEqth5ieE+5jQ8nxvPF//qKHQNlgbD93Y3B3UfmjrnP1chgqFn9
IAXWFsyZ7I8bXQIDAQABoxcwFTATBgNVHSUEDDAKBggrBgEFBQcDAjANBgkqhkiG
9w0BAQsFAAOCAgEAPIXMQOAgb2VlfS59HrvpdqbIapIfs/xBgPKlNfwNO3UpSYyq
XVK1xekLI+mEE639YP/oSc7HX2OrJi3SX5Ofzs0s9h+BNTXPqw1ju+G34cF8MKc0
acynThdcI4eZGc2fKSAIw6RN7iIln74Sf4MNmEuQu6Dnq4QkZKAWtnY7Uq5ooFJS
JA+Joqif8SvEvMgq02XdUhjijlBAanxI/xp64k37k18+pHAxcS22HzrjwDQ4ELqY
gBq9g20JYXoUxjBFUfj+cxBx+LBKfPVTpcbicI4wwP4a2BA6LDUHgcnSMhle1zeq
pHuOIOT6XqYLhO0Yr7WRG9Yzuxs0GV4TH+FlDpDHWL8XG0gjDUZ/2viPlKBr+FoN
inW8jqQ2NYMzYF9zHNzXVGK+5oyH4Y7r/8WxQLfdSR/5S1DXPLSkzkYbduHf9UmF
Dvh6NrCGU0UxypA1NvF5o11cnTQ22GPywVSc0ILKWDRlu8DiGq71bYQu8hTTkTnb
2hOr5JHcGaloms7WM3q0hc2PIhwYXw2V3b9I9lbnvv3Y/yKPNN7IzU5No6siRuIH
paj83V0flMWj1EqJMDxk9ECHgDyl/1ftgJVx1G/f/+UnXoRdR2kFqVVeJTeSIZi7
dSsAOIMN/weZMZF55Q61vgUgYXKp4g2/Zk8BJn0cx9pjEMIw/pc7Eq1x/R8=
-----END CERTIFICATE-----

51
testdata/key.pem vendored

@ -0,0 +1,51 @@
-----BEGIN RSA PRIVATE KEY-----
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
-----END RSA PRIVATE KEY-----
Loading…
Cancel
Save