diff --git a/cmd/helm/get.go b/cmd/helm/get.go index a2eb1d137..5ae143dae 100644 --- a/cmd/helm/get.go +++ b/cmd/helm/get.go @@ -72,9 +72,9 @@ func newGetCmd(client helm.Interface, out io.Writer) *cobra.Command { cmd.Flags().Int32Var(&get.version, "revision", 0, "get the named release with revision") - cmd.AddCommand(addFlagsTLS(newGetValuesCmd(nil, out))) - cmd.AddCommand(addFlagsTLS(newGetManifestCmd(nil, out))) - cmd.AddCommand(addFlagsTLS(newGetHooksCmd(nil, out))) + cmd.AddCommand(newGetValuesCmd(nil, out)) + cmd.AddCommand(newGetManifestCmd(nil, out)) + cmd.AddCommand(newGetHooksCmd(nil, out)) return cmd } diff --git a/cmd/helm/helm.go b/cmd/helm/helm.go index 4c7ca9290..288d63b49 100644 --- a/cmd/helm/helm.go +++ b/cmd/helm/helm.go @@ -40,16 +40,6 @@ import ( ) var ( - tlsCaCertFile string // path to TLS CA certificate file - tlsCertFile string // path to TLS certificate file - tlsKeyFile string // path to TLS key file - tlsVerify bool // enable TLS and verify remote certificates - tlsEnable bool // enable TLS - - tlsCaCertDefault = "$HELM_HOME/ca.pem" - tlsCertDefault = "$HELM_HOME/cert.pem" - tlsKeyDefault = "$HELM_HOME/key.pem" - tillerTunnel *kube.Tunnel settings helm_env.EnvSettings ) @@ -76,6 +66,11 @@ Environment: $HELM_NO_PLUGINS disable plugins. Set HELM_NO_PLUGINS=1 to disable plugins. $TILLER_NAMESPACE set an alternative Tiller namespace (default "kube-system") $KUBECONFIG set an alternative Kubernetes configuration file (default "~/.kube/config") + $HELM_TLS_CA_CERT path to TLS CA certificate used to verify the Helm client and Tiller server certificates (default "$HELM_HOME/ca.pem") + $HELM_TLS_CERT path to TLS client certificate file for authenticating to Tiller (default "$HELM_HOME/cert.pem") + $HELM_TLS_KEY path to TLS client key file for authenticating to Tiller (default "$HELM_HOME/key.pem") + $HELM_TLS_VERIFY enable TLS connection between Helm and Tiller and verify Tiller server certificate (default "false") + $HELM_TLS_ENABLE enable TLS connection between Helm and Tiller (default "false") ` func newRootCmd(args []string) *cobra.Command { @@ -84,11 +79,6 @@ func newRootCmd(args []string) *cobra.Command { Short: "The Helm package manager for Kubernetes.", Long: globalUsage, SilenceUsage: true, - PersistentPreRun: func(*cobra.Command, []string) { - tlsCaCertFile = os.ExpandEnv(tlsCaCertFile) - tlsCertFile = os.ExpandEnv(tlsCertFile) - tlsKeyFile = os.ExpandEnv(tlsKeyFile) - }, PersistentPostRun: func(*cobra.Command, []string) { teardown() }, @@ -113,18 +103,18 @@ func newRootCmd(args []string) *cobra.Command { newVerifyCmd(out), // release commands - addFlagsTLS(newDeleteCmd(nil, out)), - addFlagsTLS(newGetCmd(nil, out)), - addFlagsTLS(newHistoryCmd(nil, out)), - addFlagsTLS(newInstallCmd(nil, out)), - addFlagsTLS(newListCmd(nil, out)), - addFlagsTLS(newRollbackCmd(nil, out)), - addFlagsTLS(newStatusCmd(nil, out)), - addFlagsTLS(newUpgradeCmd(nil, out)), - - addFlagsTLS(newReleaseTestCmd(nil, out)), - addFlagsTLS(newResetCmd(nil, out)), - addFlagsTLS(newVersionCmd(nil, out)), + newDeleteCmd(nil, out), + newGetCmd(nil, out), + newHistoryCmd(nil, out), + newInstallCmd(nil, out), + newListCmd(nil, out), + newRollbackCmd(nil, out), + newStatusCmd(nil, out), + newUpgradeCmd(nil, out), + + newReleaseTestCmd(nil, out), + newResetCmd(nil, out), + newVersionCmd(nil, out), newCompletionCmd(out), newHomeCmd(out), @@ -270,20 +260,11 @@ func ensureHelmClient(h helm.Interface) helm.Interface { func newClient() helm.Interface { options := []helm.Option{helm.Host(settings.TillerHost), helm.ConnectTimeout(settings.TillerConnectionTimeout)} - if tlsVerify || tlsEnable { - if tlsCaCertFile == "" { - tlsCaCertFile = settings.Home.TLSCaCert() - } - if tlsCertFile == "" { - tlsCertFile = settings.Home.TLSCert() - } - if tlsKeyFile == "" { - tlsKeyFile = settings.Home.TLSKey() - } - debug("Key=%q, Cert=%q, CA=%q\n", tlsKeyFile, tlsCertFile, tlsCaCertFile) - tlsopts := tlsutil.Options{KeyFile: tlsKeyFile, CertFile: tlsCertFile, InsecureSkipVerify: true} - if tlsVerify { - tlsopts.CaCertFile = tlsCaCertFile + if settings.TLSVerify || settings.TLSEnable { + debug("Key=%q, Cert=%q, CA=%q\n", settings.TLSKeyFile, settings.TLSCertFile, settings.TLSCaCertFile) + tlsopts := tlsutil.Options{KeyFile: settings.TLSKeyFile, CertFile: settings.TLSCertFile, InsecureSkipVerify: true} + if settings.TLSVerify { + tlsopts.CaCertFile = settings.TLSCaCertFile tlsopts.InsecureSkipVerify = false } tlscfg, err := tlsutil.ClientConfig(tlsopts) @@ -295,16 +276,3 @@ func newClient() helm.Interface { } return helm.NewClient(options...) } - -// addFlagsTLS adds the flags for supporting client side TLS to the -// helm command (only those that invoke communicate to Tiller.) -func addFlagsTLS(cmd *cobra.Command) *cobra.Command { - - // add flags - cmd.Flags().StringVar(&tlsCaCertFile, "tls-ca-cert", tlsCaCertDefault, "path to TLS CA certificate file") - cmd.Flags().StringVar(&tlsCertFile, "tls-cert", tlsCertDefault, "path to TLS certificate file") - cmd.Flags().StringVar(&tlsKeyFile, "tls-key", tlsKeyDefault, "path to TLS key file") - cmd.Flags().BoolVar(&tlsVerify, "tls-verify", false, "enable TLS for request and verify remote") - cmd.Flags().BoolVar(&tlsEnable, "tls", false, "enable TLS for request") - return cmd -} diff --git a/cmd/helm/init.go b/cmd/helm/init.go index 92f4c3794..8acd4b454 100644 --- a/cmd/helm/init.go +++ b/cmd/helm/init.go @@ -90,6 +90,11 @@ type initCmd struct { maxHistory int replicas int wait bool + tlsEnable bool + tlsVerify bool + tlsKeyFile string + tlsCertFile string + tlsCaCertFile string } func newInitCmd(out io.Writer) *cobra.Command { @@ -105,6 +110,7 @@ func newInitCmd(out io.Writer) *cobra.Command { } i.namespace = settings.TillerNamespace i.home = settings.Home + i.tlsCaCertFile = settings.TLSCaCertFile i.client = ensureHelmClient(i.client) return i.run() @@ -121,11 +127,10 @@ func newInitCmd(out io.Writer) *cobra.Command { f.BoolVar(&i.skipRefresh, "skip-refresh", false, "do not refresh (download) the local repository cache") f.BoolVar(&i.wait, "wait", false, "block until Tiller is running and ready to receive requests") - f.BoolVar(&tlsEnable, "tiller-tls", false, "install Tiller with TLS enabled") - f.BoolVar(&tlsVerify, "tiller-tls-verify", false, "install Tiller with TLS enabled and to verify remote certificates") - f.StringVar(&tlsKeyFile, "tiller-tls-key", "", "path to TLS key file to install with Tiller") - f.StringVar(&tlsCertFile, "tiller-tls-cert", "", "path to TLS certificate file to install with Tiller") - f.StringVar(&tlsCaCertFile, "tls-ca-cert", "", "path to CA root certificate") + f.BoolVar(&i.tlsEnable, "tiller-tls", false, "install Tiller with TLS enabled") + f.BoolVar(&i.tlsVerify, "tiller-tls-verify", false, "install Tiller with TLS and Helm client certificate verification enabled") + f.StringVar(&i.tlsKeyFile, "tiller-tls-key", "", "path to Tiller TLS server key file") + f.StringVar(&i.tlsCertFile, "tiller-tls-cert", "", "path to Tiller TLS server certificate file") f.StringVar(&stableRepositoryURL, "stable-repo-url", stableRepositoryURL, "URL for stable repository") f.StringVar(&localRepositoryURL, "local-repo-url", localRepositoryURL, "URL for local repository") @@ -145,22 +150,22 @@ func newInitCmd(out io.Writer) *cobra.Command { // tlsOptions sanitizes the tls flags as well as checks for the existence of required // tls files indicated by those flags, if any. func (i *initCmd) tlsOptions() error { - i.opts.EnableTLS = tlsEnable || tlsVerify - i.opts.VerifyTLS = tlsVerify + i.opts.EnableTLS = i.tlsEnable || i.tlsVerify + i.opts.VerifyTLS = i.tlsVerify if i.opts.EnableTLS { missing := func(file string) bool { _, err := os.Stat(file) return os.IsNotExist(err) } - if i.opts.TLSKeyFile = tlsKeyFile; i.opts.TLSKeyFile == "" || missing(i.opts.TLSKeyFile) { - return errors.New("missing required TLS key file") + if i.opts.TLSKeyFile = i.tlsKeyFile; i.opts.TLSKeyFile == "" || missing(i.opts.TLSKeyFile) { + return errors.New("missing required TLS server key file") } - if i.opts.TLSCertFile = tlsCertFile; i.opts.TLSCertFile == "" || missing(i.opts.TLSCertFile) { - return errors.New("missing required TLS certificate file") + if i.opts.TLSCertFile = i.tlsCertFile; i.opts.TLSCertFile == "" || missing(i.opts.TLSCertFile) { + return errors.New("missing required TLS server certificate file") } if i.opts.VerifyTLS { - if i.opts.TLSCaCertFile = tlsCaCertFile; i.opts.TLSCaCertFile == "" || missing(i.opts.TLSCaCertFile) { + if i.opts.TLSCaCertFile = i.tlsCaCertFile; i.opts.TLSCaCertFile == "" || missing(i.opts.TLSCaCertFile) { return errors.New("missing required TLS CA file") } } diff --git a/cmd/helm/init_test.go b/cmd/helm/init_test.go index 6a5767fca..91c34a753 100644 --- a/cmd/helm/init_test.go +++ b/cmd/helm/init_test.go @@ -280,13 +280,14 @@ func TestInitCmd_tlsOptions(t *testing.T) { } 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{} + // emulate tls flags + cmd := &initCmd{ + tlsCaCertFile: tt.caFile, + tlsCertFile: tt.certFile, + tlsKeyFile: tt.keyFile, + tlsVerify: tt.verify, + tlsEnable: tt.enable, + } if err := cmd.tlsOptions(); err != nil { t.Fatalf("unexpected error: %v", err) } diff --git a/pkg/helm/environment/environment.go b/pkg/helm/environment/environment.go index 2980e6dc9..19227ae47 100644 --- a/pkg/helm/environment/environment.go +++ b/pkg/helm/environment/environment.go @@ -49,6 +49,16 @@ type EnvSettings struct { Debug bool // KubeContext is the name of the kubeconfig context. KubeContext string + // TLSCaCertFile is the path to TLS CA certificate file used to verify the Helm client and Tiller server certificates + TLSCaCertFile string + // TLSCertFile is the path to Helm TLS client certificate file for authenticating to Tiller + TLSCertFile string + // TLSKeyFile is the path to Helm TLS client key file for authenticating to Tiller + TLSKeyFile string + // TLSVerify enables TLS between Helm and Tiller and verification of the Tiller server certificate + TLSVerify bool + // TLSEnable enables TLS between Helm and Tiller + TLSEnable bool } // AddFlags binds flags to the given flagset. @@ -59,6 +69,11 @@ func (s *EnvSettings) AddFlags(fs *pflag.FlagSet) { fs.BoolVar(&s.Debug, "debug", false, "enable verbose output") fs.StringVar(&s.TillerNamespace, "tiller-namespace", "kube-system", "namespace of Tiller") fs.Int64Var(&s.TillerConnectionTimeout, "tiller-connection-timeout", int64(300), "the duration (in seconds) Helm will wait to establish a connection to tiller") + fs.StringVar(&s.TLSCaCertFile, "tls-ca-cert", "", "path to TLS CA certificate file used to verify the Helm client and Tiller server certificates") + fs.StringVar(&s.TLSCertFile, "tls-cert", "", "path to Helm TLS client certificate file for authenticating to Tiller") + fs.StringVar(&s.TLSKeyFile, "tls-key", "", "path to Helm TLS client key file for authenticating to Tiller") + fs.BoolVar(&s.TLSVerify, "tls-verify", false, "enable TLS connection between Helm and Tiller and verify Tiller server certificate") + fs.BoolVar(&s.TLSEnable, "tls", false, "enable TLS connection between Helm and Tiller") } // Init sets values from the environment. @@ -66,6 +81,16 @@ func (s *EnvSettings) Init(fs *pflag.FlagSet) { for name, envar := range envMap { setFlagFromEnv(name, envar, fs) } + // TLS defaults that depend on Home value + if s.TLSCaCertFile == "" { + s.TLSCaCertFile = s.Home.TLSCaCert() + } + if s.TLSCertFile == "" { + s.TLSCertFile = s.Home.TLSCert() + } + if s.TLSKeyFile == "" { + s.TLSKeyFile = s.Home.TLSKey() + } } // PluginDirs is the path to the plugin directories. @@ -82,6 +107,11 @@ var envMap = map[string]string{ "home": "HELM_HOME", "host": "HELM_HOST", "tiller-namespace": "TILLER_NAMESPACE", + "tls-ca-cert": "HELM_TLS_CA_CERT", + "tls-cert": "HELM_TLS_CERT", + "tls-key": "HELM_TLS_KEY", + "tls-verify": "HELM_TLS_VERIFY", + "tls": "HELM_TLS_ENABLE", } func setFlagFromEnv(name, envar string, fs *pflag.FlagSet) { @@ -89,7 +119,7 @@ func setFlagFromEnv(name, envar string, fs *pflag.FlagSet) { return } if v, ok := os.LookupEnv(envar); ok { - fs.Set(name, v) + fs.Set(name, os.ExpandEnv(v)) } } diff --git a/pkg/helm/environment/environment_test.go b/pkg/helm/environment/environment_test.go index 8f0caa388..19ccd9b40 100644 --- a/pkg/helm/environment/environment_test.go +++ b/pkg/helm/environment/environment_test.go @@ -37,42 +37,103 @@ func TestEnvSettings(t *testing.T) { // expected values home, host, ns, kcontext, plugins string debug bool + tlsca, tlscert, tlskey string + tlsenable, tlsverify bool }{ { - name: "defaults", - args: []string{}, - home: DefaultHelmHome, - plugins: helmpath.Home(DefaultHelmHome).Plugins(), - ns: "kube-system", + name: "defaults", + args: []string{}, + home: DefaultHelmHome, + plugins: helmpath.Home(DefaultHelmHome).Plugins(), + ns: "kube-system", + tlsca: helmpath.Home(DefaultHelmHome).TLSCaCert(), + tlscert: helmpath.Home(DefaultHelmHome).TLSCert(), + tlskey: helmpath.Home(DefaultHelmHome).TLSKey(), + tlsenable: false, + tlsverify: false, }, { - name: "with flags set", - args: []string{"--home", "/foo", "--host=here", "--debug", "--tiller-namespace=myns"}, - home: "/foo", - plugins: helmpath.Home("/foo").Plugins(), - host: "here", - ns: "myns", - debug: true, + name: "with flags set", + args: []string{"--home", "/foo", "--host=here", "--debug", "--tiller-namespace=myns"}, + home: "/foo", + plugins: helmpath.Home("/foo").Plugins(), + host: "here", + ns: "myns", + debug: true, + tlsca: helmpath.Home("/foo").TLSCaCert(), + tlscert: helmpath.Home("/foo").TLSCert(), + tlskey: helmpath.Home("/foo").TLSKey(), + tlsenable: false, + tlsverify: false, }, { - name: "with envvars set", - args: []string{}, - envars: map[string]string{"HELM_HOME": "/bar", "HELM_HOST": "there", "HELM_DEBUG": "1", "TILLER_NAMESPACE": "yourns"}, - home: "/bar", - plugins: helmpath.Home("/bar").Plugins(), - host: "there", - ns: "yourns", - debug: true, + name: "with envvars set", + args: []string{}, + envars: map[string]string{"HELM_HOME": "/bar", "HELM_HOST": "there", "HELM_DEBUG": "1", "TILLER_NAMESPACE": "yourns"}, + home: "/bar", + plugins: helmpath.Home("/bar").Plugins(), + host: "there", + ns: "yourns", + debug: true, + tlsca: helmpath.Home("/bar").TLSCaCert(), + tlscert: helmpath.Home("/bar").TLSCert(), + tlskey: helmpath.Home("/bar").TLSKey(), + tlsenable: false, + tlsverify: false, }, { - name: "with flags and envvars set", - args: []string{"--home", "/foo", "--host=here", "--debug", "--tiller-namespace=myns"}, - envars: map[string]string{"HELM_HOME": "/bar", "HELM_HOST": "there", "HELM_DEBUG": "1", "TILLER_NAMESPACE": "yourns", "HELM_PLUGIN": "glade"}, - home: "/foo", - plugins: "glade", - host: "here", - ns: "myns", - debug: true, + name: "with flags and envvars set", + args: []string{"--home", "/foo", "--host=here", "--debug", "--tiller-namespace=myns"}, + envars: map[string]string{"HELM_HOME": "/bar", "HELM_HOST": "there", "HELM_DEBUG": "1", "TILLER_NAMESPACE": "yourns", "HELM_PLUGIN": "glade"}, + home: "/foo", + plugins: "glade", + host: "here", + ns: "myns", + debug: true, + tlsca: helmpath.Home("/foo").TLSCaCert(), + tlscert: helmpath.Home("/foo").TLSCert(), + tlskey: helmpath.Home("/foo").TLSKey(), + tlsenable: false, + tlsverify: false, + }, + { + name: "with TLS flags set", + args: []string{"--home", "/bar", "--tls-ca-cert", "/a/ca.crt", "--tls-cert=/a/client.crt", "--tls-key", "/a/client.key", "--tls-verify", "--tls"}, + home: "/bar", + plugins: helmpath.Home("/bar").Plugins(), + ns: "kube-system", + debug: false, + tlsca: "/a/ca.crt", + tlscert: "/a/client.crt", + tlskey: "/a/client.key", + tlsenable: true, + tlsverify: true, + }, + { + name: "with TLS envvars set", + args: []string{}, + envars: map[string]string{"HELM_HOME": "/bar", "HELM_TLS_CA_CERT": "/e/ca.crt", "HELM_TLS_CERT": "/e/client.crt", "HELM_TLS_KEY": "/e/client.key", "HELM_TLS_VERIFY": "true", "HELM_TLS_ENABLE": "true"}, + home: "/bar", + plugins: helmpath.Home("/bar").Plugins(), + ns: "kube-system", + tlsca: "/e/ca.crt", + tlscert: "/e/client.crt", + tlskey: "/e/client.key", + tlsenable: true, + tlsverify: true, + }, + { + name: "with TLS flags and envvars set", + args: []string{"--tls-ca-cert", "/a/ca.crt", "--tls-cert=/a/client.crt", "--tls-key", "/a/client.key", "--tls-verify"}, + envars: map[string]string{"HELM_HOME": "/bar", "HELM_TLS_CA_CERT": "/e/ca.crt", "HELM_TLS_CERT": "/e/client.crt", "HELM_TLS_KEY": "/e/client.key", "HELM_TLS_VERIFY": "true", "HELM_TLS_ENABLE": "true"}, + home: "/bar", + plugins: helmpath.Home("/bar").Plugins(), + ns: "kube-system", + tlsca: "/a/ca.crt", + tlscert: "/a/client.crt", + tlskey: "/a/client.key", + tlsenable: true, + tlsverify: true, }, } @@ -111,7 +172,25 @@ func TestEnvSettings(t *testing.T) { if settings.KubeContext != tt.kcontext { t.Errorf("expected kube-context %q, got %q", tt.kcontext, settings.KubeContext) } + if settings.TLSCaCertFile != tt.tlsca { + t.Errorf("expected tls-ca-cert %q, got %q", tt.tlsca, settings.TLSCaCertFile) + } + if settings.TLSCertFile != tt.tlscert { + t.Errorf("expected tls-cert %q, got %q", tt.tlscert, settings.TLSCertFile) + } + if settings.TLSKeyFile != tt.tlskey { + t.Errorf("expected tls-key %q, got %q", tt.tlskey, settings.TLSKeyFile) + } + if settings.TLSEnable != tt.tlsenable { + t.Errorf("expected tls %t, got %t", tt.tlsenable, settings.TLSEnable) + } + if settings.TLSVerify != tt.tlsverify { + t.Errorf("expected tls-verify %t, got %t", tt.tlsverify, settings.TLSVerify) + } + for k := range tt.envars { + os.Unsetenv(k) + } cleanup() }) }