diff --git a/cmd/helm/init_test.go b/cmd/helm/init_test.go index f6f93fed95725a7b724316afbf2b7271ee4c1444..3003b3f31841cb32128fa72328b7bb2f61be3133 100644 --- a/cmd/helm/init_test.go +++ b/cmd/helm/init_test.go @@ -20,6 +20,8 @@ import ( "bytes" "io/ioutil" "os" + "path/filepath" + "reflect" "strings" "testing" @@ -33,6 +35,7 @@ import ( "k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake" + "k8s.io/helm/cmd/helm/installer" "k8s.io/helm/pkg/helm/helmpath" ) @@ -217,3 +220,85 @@ func TestEnsureHome(t *testing.T) { 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) + } + } +} diff --git a/cmd/helm/installer/install_test.go b/cmd/helm/installer/install_test.go index 4ea6efc3c170c61974b16719917c9218e456f28c..63f8419b3d7416834cd8bfde097d472a28eeb114 100644 --- a/cmd/helm/installer/install_test.go +++ b/cmd/helm/installer/install_test.go @@ -17,6 +17,8 @@ limitations under the License. package installer // import "k8s.io/helm/cmd/helm/installer" import ( + "os" + "path/filepath" "reflect" "testing" @@ -32,7 +34,6 @@ import ( ) func TestDeploymentManifest(t *testing.T) { - tests := []struct { name 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) { o, err := ServiceManifest(api.NamespaceDefault) 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) { 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) { fc := &fake.Clientset{} 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)) } } + +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 +} diff --git a/pkg/tlsutil/cfg.go b/pkg/tlsutil/cfg.go index b755ca8cac5cac7160d93ca384d6207d4dc68a22..ee90935155f24992d320ad54bf239cbaf19e7385 100644 --- a/pkg/tlsutil/cfg.go +++ b/pkg/tlsutil/cfg.go @@ -44,7 +44,10 @@ func ClientConfig(opts Options) (cfg *tls.Config, err error) { if opts.CertFile != "" || opts.KeyFile != "" { 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 != "" { diff --git a/pkg/tlsutil/tlsutil_test.go b/pkg/tlsutil/tlsutil_test.go new file mode 100644 index 0000000000000000000000000000000000000000..4f04d50ab46868d3ca6de73dada80a39cfa7f785 --- /dev/null +++ b/pkg/tlsutil/tlsutil_test.go @@ -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 +} diff --git a/testdata/ca.pem b/testdata/ca.pem new file mode 100644 index 0000000000000000000000000000000000000000..79d854a8de0d3f169781e731dcb3e84c0605a4eb --- /dev/null +++ b/testdata/ca.pem @@ -0,0 +1,35 @@ +-----BEGIN CERTIFICATE----- +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 +mlK3dw== +-----END CERTIFICATE----- diff --git a/testdata/crt.pem b/testdata/crt.pem new file mode 100644 index 0000000000000000000000000000000000000000..226b3a71b7b510e66fae3e617108a6fec700ad49 --- /dev/null +++ b/testdata/crt.pem @@ -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----- diff --git a/testdata/key.pem b/testdata/key.pem new file mode 100644 index 0000000000000000000000000000000000000000..8b2cde82ed024f08eba4eee5f2e8ba0ea49b324b --- /dev/null +++ b/testdata/key.pem @@ -0,0 +1,51 @@ +-----BEGIN RSA PRIVATE KEY----- +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 +-----END RSA PRIVATE KEY-----