diff --git a/cmd/helm/get.go b/cmd/helm/get.go index fc5871f46..477f730d5 100644 --- a/cmd/helm/get.go +++ b/cmd/helm/get.go @@ -64,7 +64,7 @@ func newGetCmd(client helm.Interface, out io.Writer) *cobra.Command { } get.release = args[0] if get.client == nil { - get.client = helm.NewClient(helm.Host(settings.TillerHost)) + get.client = newClient() } return get.run() }, @@ -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(newGetValuesCmd(nil, out)) - cmd.AddCommand(newGetManifestCmd(nil, out)) - cmd.AddCommand(newGetHooksCmd(nil, out)) + cmd.AddCommand(addFlagsTLS(newGetValuesCmd(nil, out))) + cmd.AddCommand(addFlagsTLS(newGetManifestCmd(nil, out))) + cmd.AddCommand(addFlagsTLS(newGetHooksCmd(nil, out))) return cmd } diff --git a/cmd/helm/helm.go b/cmd/helm/helm.go index 6412057b8..b262e577b 100644 --- a/cmd/helm/helm.go +++ b/cmd/helm/helm.go @@ -45,6 +45,10 @@ var ( 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 ) @@ -263,6 +267,16 @@ func newClient() helm.Interface { options := []helm.Option{helm.Host(settings.TillerHost)} if tlsVerify || tlsEnable { + if tlsCaCertFile == "" { + tlsCaCertFile = os.ExpandEnv(tlsCaCertDefault) + } + if tlsCertFile == "" { + tlsCertFile = os.ExpandEnv(tlsCertDefault) + } + if tlsKeyFile == "" { + tlsKeyFile = os.ExpandEnv(tlsKeyDefault) + } + 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 @@ -281,12 +295,6 @@ func newClient() helm.Interface { // 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 { - // defaults - var ( - tlsCaCertDefault = "$HELM_HOME/ca.pem" - tlsCertDefault = "$HELM_HOME/cert.pem" - tlsKeyDefault = "$HELM_HOME/key.pem" - ) // add flags cmd.Flags().StringVar(&tlsCaCertFile, "tls-ca-cert", tlsCaCertDefault, "path to TLS CA certificate file") diff --git a/cmd/helm/history.go b/cmd/helm/history.go index 08f1656f5..27c47ad3e 100644 --- a/cmd/helm/history.go +++ b/cmd/helm/history.go @@ -66,7 +66,7 @@ func newHistoryCmd(c helm.Interface, w io.Writer) *cobra.Command { case len(args) == 0: return errReleaseRequired case his.helmc == nil: - his.helmc = helm.NewClient(helm.Host(settings.TillerHost)) + his.helmc = newClient() } his.rls = args[0] return his.run() diff --git a/cmd/helm/list.go b/cmd/helm/list.go index f6cdaacfe..7c312a365 100644 --- a/cmd/helm/list.go +++ b/cmd/helm/list.go @@ -93,7 +93,7 @@ func newListCmd(client helm.Interface, out io.Writer) *cobra.Command { list.filter = strings.Join(args, " ") } if list.client == nil { - list.client = helm.NewClient(helm.Host(settings.TillerHost)) + list.client = newClient() } return list.run() }, diff --git a/cmd/helm/status.go b/cmd/helm/status.go index 36269c4b1..e5e9aa44c 100644 --- a/cmd/helm/status.go +++ b/cmd/helm/status.go @@ -67,7 +67,7 @@ func newStatusCmd(client helm.Interface, out io.Writer) *cobra.Command { } status.release = args[0] if status.client == nil { - status.client = helm.NewClient(helm.Host(settings.TillerHost)) + status.client = newClient() } return status.run() }, diff --git a/cmd/tiller/tiller.go b/cmd/tiller/tiller.go index fadf8dd3a..f9ec0d4fc 100644 --- a/cmd/tiller/tiller.go +++ b/cmd/tiller/tiller.go @@ -232,7 +232,11 @@ func tlsOptions() tlsutil.Options { opts := tlsutil.Options{CertFile: *certFile, KeyFile: *keyFile} if *tlsVerify { opts.CaCertFile = *caCertFile - opts.ClientAuth = tls.VerifyClientCertIfGiven + + // We want to force the client to not only provide a cert, but to + // provide a cert that we can validate. + // http://www.bite-code.com/2015/06/25/tls-mutual-auth-in-golang/ + opts.ClientAuth = tls.RequireAndVerifyClientCert } return opts } diff --git a/docs/helm/helm_get_hooks.md b/docs/helm/helm_get_hooks.md index 3db1b8d55..a2fb36acd 100644 --- a/docs/helm/helm_get_hooks.md +++ b/docs/helm/helm_get_hooks.md @@ -18,7 +18,12 @@ helm get hooks [flags] RELEASE_NAME ### Options ``` - --revision int32 get the named release with revision + --revision int32 get the named release with revision + --tls enable TLS for request + --tls-ca-cert string path to TLS CA certificate file (default "$HELM_HOME/ca.pem") + --tls-cert string path to TLS certificate file (default "$HELM_HOME/cert.pem") + --tls-key string path to TLS key file (default "$HELM_HOME/key.pem") + --tls-verify enable TLS for request and verify remote ``` ### Options inherited from parent commands @@ -35,4 +40,4 @@ helm get hooks [flags] RELEASE_NAME ### SEE ALSO * [helm get](helm_get.md) - download a named release -###### Auto generated by spf13/cobra on 7-Nov-2017 +###### Auto generated by spf13/cobra on 15-Nov-2017 diff --git a/docs/helm/helm_get_manifest.md b/docs/helm/helm_get_manifest.md index 11597716d..1cf712d9b 100644 --- a/docs/helm/helm_get_manifest.md +++ b/docs/helm/helm_get_manifest.md @@ -20,7 +20,12 @@ helm get manifest [flags] RELEASE_NAME ### Options ``` - --revision int32 get the named release with revision + --revision int32 get the named release with revision + --tls enable TLS for request + --tls-ca-cert string path to TLS CA certificate file (default "$HELM_HOME/ca.pem") + --tls-cert string path to TLS certificate file (default "$HELM_HOME/cert.pem") + --tls-key string path to TLS key file (default "$HELM_HOME/key.pem") + --tls-verify enable TLS for request and verify remote ``` ### Options inherited from parent commands @@ -37,4 +42,4 @@ helm get manifest [flags] RELEASE_NAME ### SEE ALSO * [helm get](helm_get.md) - download a named release -###### Auto generated by spf13/cobra on 7-Nov-2017 +###### Auto generated by spf13/cobra on 15-Nov-2017 diff --git a/docs/helm/helm_get_values.md b/docs/helm/helm_get_values.md index fe23e6819..1e57a2303 100644 --- a/docs/helm/helm_get_values.md +++ b/docs/helm/helm_get_values.md @@ -16,8 +16,13 @@ helm get values [flags] RELEASE_NAME ### Options ``` - -a, --all dump all (computed) values - --revision int32 get the named release with revision + -a, --all dump all (computed) values + --revision int32 get the named release with revision + --tls enable TLS for request + --tls-ca-cert string path to TLS CA certificate file (default "$HELM_HOME/ca.pem") + --tls-cert string path to TLS certificate file (default "$HELM_HOME/cert.pem") + --tls-key string path to TLS key file (default "$HELM_HOME/key.pem") + --tls-verify enable TLS for request and verify remote ``` ### Options inherited from parent commands @@ -34,4 +39,4 @@ helm get values [flags] RELEASE_NAME ### SEE ALSO * [helm get](helm_get.md) - download a named release -###### Auto generated by spf13/cobra on 7-Nov-2017 +###### Auto generated by spf13/cobra on 15-Nov-2017 diff --git a/docs/index.md b/docs/index.md index b787463a6..a8e4ac483 100644 --- a/docs/index.md +++ b/docs/index.md @@ -6,6 +6,8 @@ - [Frequently Asked Questions](install_faq.md) - [Using Helm](using_helm.md) - Learn the Helm tools - [Plugins](plugins.md) + - [Service Accounts for Tiller](service_accounts.md) - Apply RBACs to Tiller + - [TLS/SSL for Helm and Tiller](tiller_ssl.md) - Use Helm-to-Tiller encryption - [Developing Charts](charts.md) - An introduction to chart development - [Chart Lifecycle Hooks](charts_hooks.md) - [Chart Tips and Tricks](charts_tips_and_tricks.md) diff --git a/docs/tiller_ssl.md b/docs/tiller_ssl.md new file mode 100644 index 000000000..59d653e26 --- /dev/null +++ b/docs/tiller_ssl.md @@ -0,0 +1,291 @@ +# Using SSL Between Helm and Tiller + +This document explains how to create strong SSL/TLS connections between Helm and +Tiller. The emphasis here is on creating an internal CA, and using both the +cryptographic and identity functions of SSL. + +> Support for TLS-based auth was introduced in Helm 2.3.0 + +Configuring SSL is considered an advanced topic, and knowledge of Helm and Tiller +is assumed. + +## Overview + +The Tiller authentication model uses client-side SSL certificates. Tiller itself +verifies these certificates using a certificate authority. Likewise, the client +also verifies Tiller's identity by certificate authority. + +There are numerous possible configurations for setting up certificates and authorities, +but the method we cover here will work for most situations. + +> As of Helm 2.7.2, Tiller _requires_ that the client certificate be validated +> by its CA. In prior versions, Tiller used a weaker validation strategy that +> allowed self-signed certificates. + +In this guide, we will show how to: + +- Create a private CA that is used to issue certificates for Tiller clients and + servers. +- Create a certificate for Tiller +- Create a certificate for the Helm client +- Create a Tiller instance that uses the certificate +- Configure the Helm client to use the CA and client-side certificate + +By the end of this guide, you should have a Tiller instance running that will +only accept connections from clients who can be authenticated by SSL certificate. + +## Generating Certificate Authorities and Certificates + +One way to generate SSL CAs is via the `openssl` command line tool. There are many +guides and best practices documents available online. This explanation is focused +on getting ready within a small amount of time. For production configurations, +we urge readers to read [the official documentation](https://www.openssl.org) and +consult other resources. + +### Generate a Certificate Authority + +The simplest way to generate a certificate authority is to run two commands: + +```console +$ openssl genrsa -out ./ca.key.pem 4096 +$ openssl req -key ca.key.pem -new -x509 -days 7300 -sha256 -out ca.cert.pem -extensions v3_ca +Enter pass phrase for ca.key.pem: +You are about to be asked to enter information that will be incorporated +into your certificate request. +What you are about to enter is what is called a Distinguished Name or a DN. +There are quite a few fields but you can leave some blank +For some fields there will be a default value, +If you enter '.', the field will be left blank. +----- +Country Name (2 letter code) [AU]:US +State or Province Name (full name) [Some-State]:CO +Locality Name (eg, city) []:Boulder +Organization Name (eg, company) [Internet Widgits Pty Ltd]:tiller +Organizational Unit Name (eg, section) []: +Common Name (e.g. server FQDN or YOUR name) []:tiller +Email Address []:tiller@example.com +``` + +Note that the data input above is _sample data_. You should customize to your own +specifications. + +The above will generate both a secret key and a CA. Note that these two files are +very important. The key in particular should be handled with particular care. + +Often, you will want to generate an intermediate signing key. For the sake of brevity, +we will be signing keys with our root CA. + +### Generating Certificates + +We will be generating two certificates, each representing a type of certificate: + +- One certificate is for Tiller. You will want one of these _per tiller host_ that + you run. +- One certificate is for the user. You will want one of these _per helm user_. + +Since the commands to generate these are the same, we'll be creating both at the +same time. The names will indicate their target. + +First, the Tiller key: + +```console +$ openssl genrsa -out ./tiller.key.pem 4096 +Generating RSA private key, 4096 bit long modulus +..........................................................................................................................................................................................................................................................................................................................++ +............................................................................++ +e is 65537 (0x10001) +Enter pass phrase for ./tiller.key.pem: +Verifying - Enter pass phrase for ./tiller.key.pem: +``` + +Next, generate the Helm client's key: + +```console +$ openssl genrsa -out ./helm.key.pem 4096 +Generating RSA private key, 4096 bit long modulus +.....++ +......................................................................................................................................................................................++ +e is 65537 (0x10001) +Enter pass phrase for ./helm.key.pem: +Verifying - Enter pass phrase for ./helm.key.pem: +``` + +Again, for production use you will generate one client certificate for each user. + +Next we need to create certificates from these keys. For each certificate, this is +a two-step process of creating a CSR, and then creating the certificate. + +```console +$ openssl req -key tiller.key.pem -new -sha256 -out tiller.csr.pem +Enter pass phrase for tiller.key.pem: +You are about to be asked to enter information that will be incorporated +into your certificate request. +What you are about to enter is what is called a Distinguished Name or a DN. +There are quite a few fields but you can leave some blank +For some fields there will be a default value, +If you enter '.', the field will be left blank. +----- +Country Name (2 letter code) [AU]:US +State or Province Name (full name) [Some-State]:CO +Locality Name (eg, city) []:Boulder +Organization Name (eg, company) [Internet Widgits Pty Ltd]:Tiller Server +Organizational Unit Name (eg, section) []: +Common Name (e.g. server FQDN or YOUR name) []:tiller-server +Email Address []: + +Please enter the following 'extra' attributes +to be sent with your certificate request +A challenge password []: +An optional company name []: +``` + +And we repeat this step for the Helm client certificate: + +```console +$ openssl req -key helm.key.pem -new -sha256 -out helm.csr.pem +# Answer the questions with your client user's info +``` + +(In rare cases, we've had to add the `-nodes` flag when generating the request.) + +Now we sign each of these CSRs with the CA certificate we created: + +```console +$ openssl x509 -req -CA ca.cert.pem -CAkey ca.key.pem -CAcreateserial -in tiller.csr.pem -out tiller.cert.pem +Signature ok +subject=/C=US/ST=CO/L=Boulder/O=Tiller Server/CN=tiller-server +Getting CA Private Key +Enter pass phrase for ca.key.pem: +``` + +And again for the client certificate: + +```console +$ openssl x509 -req -CA ca.cert.pem -CAkey ca.key.pem -CAcreateserial -in helm.csr.pem -out helm.cert.pem +``` + +At this point, the important files for us are these: + +``` +# The CA. Make sure the key is kept secret. +ca.cert.pem +ca.key.pem +# The Helm client files +helm.cert.pem +helm.key.pem +# The Tiller server files. +tiller.cert.pem +tiller.key.pem +``` + +Now we're ready to move on to the next steps. + +## Creating a Custom Tiller Installation + +Helm includes full support for creating a deployment configured for SSL. By specifying +a few flags, the `helm init` command can create a new Tiller installation complete +with all of our SSL configuration. + +To take a look at what this will generate, run this command: + +```console +$ helm init --dry-run --debug --tiller-tls --tiller-tls-cert ./tiller.cert.pem --tiller-tls-key ./tiller.key.pem --tiller-tls-verify --tls-ca-cert ca.cert.pem +``` + +The output will show you a Deployment, a Secret, and a Service. Your SSL information +will be preloaded into the Secret, which the Deployment will mount to pods as they +start up. + +If you want to customize the manifest, you can save that output to a file and then +use `kubectl create` to load it into your cluster. + +> We strongly recommend enabling RBAC on your cluster and adding [service accounts](service_accounts.md) +> with RBACS. + +Otherwise, you can remove the `--dry-run` and `--debug` flags. We also recommend +putting Tiller in a non-system namespace (`--tiller-namespace=something`) and enable +a service account (`--service-account=somename`). But for this example we will stay +with the basics: + +```console +$ helm init --tiller-tls --tiller-tls-cert ./tiller.cert.pem --tiller-tls-key ./tiller.key.pem --tiller-tls-verify --tls-ca-cert ca.cert.pem +``` + +In a minute or two it should be ready. We can check Tiller like this: + +```console +$ kubectl -n kube-system get deployment +NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE +... other stuff +tiller-deploy 1 1 1 1 2m +``` + +If there is a problem, you may want to use `kubectl get pods -n kube-system` to +find out what went wrong. With the SSL/TLS support, the most common problems all +have to do with improperly generated TLS certificates or accidentally swapping the +cert and the key. + +At this point, you should get a _failure_ when you run basic Helm commands: + +```console +$ helm ls +Error: transport is closing +``` + +This is because your Helm client does not have the correct certificate to authenticate +to Tiller. + +## Configuring the Helm Client + +The Tiller server is now running with TLS protection. It's time to configure the +Helm client to also perform TLS operations. + +For a quick test, we can specify our configuration manually. We'll run a normal +Helm command (`helm ls`), but with SSL/TLS enabled. + +```console +helm ls --tls --tls-ca-cert ca.cert.pem --tls-cert helm.cert.pem --tls-key helm.key.pem +``` + +This configuration sends our client-side certificate to establish identity, uses +the client key for encryption, and uses the CA certificate to validate the remote +Tiller's identity. + +Typing a line that that is cumbersome, though. The shortcut is to move the key, +cert, and CA into `$HELM_HOME`: + +```console +$ cp ca.cert.pem $(helm home)/ca.pem +$ cp helm.cert.pem $(helm home)/cert.pem +$ cp helm.key.pem $(helm home)/key.pem +``` + +With this, you can simply run `helm ls --tls` to enable TLS. + +### Troubleshooting + +*Running a command, I get `Error: transport is closing`* + +This is almost always due to a configuration error in which the client is missing +a certificate (`--tls-cert`) or the certificate is bad. + +*I'm using a certificate, but get `Error: remote error: tls: bad certificate`* + +This means that Tiller's CA cannot verify your certificate. In the examples above, +we used a single CA to generate both the client and server certificates. In these +examples, the CA has _signed_ the client's certificate. We then load that CA +up to Tiller. So when the client certificate is sent to the server, Tiller +checks the client certificate against the CA. + +*If I use `--tls-verify` on the client, I get `Error: x509: certificate is valid for tiller-server, not localhost`* + +If you plan to use `--tls-verify` on the client, you will need to make sure that +the host name that Helm connects to matches the host name on the certificate. In +some cases this is awkward, since Helm will connect over localhost, or the FQDN is +not available for public resolution. + +## References + +https://github.com/denji/golang-tls +https://www.openssl.org/docs/ +https://jamielinux.com/docs/openssl-certificate-authority/sign-server-and-client-certificates.html diff --git a/pkg/tlsutil/tls.go b/pkg/tlsutil/tls.go index 422bddacb..df698fd4e 100644 --- a/pkg/tlsutil/tls.go +++ b/pkg/tlsutil/tls.go @@ -65,7 +65,7 @@ func CertPoolFromFile(filename string) (*x509.CertPool, error) { func CertFromFilePair(certFile, keyFile string) (*tls.Certificate, error) { cert, err := tls.LoadX509KeyPair(certFile, keyFile) if err != nil { - return nil, fmt.Errorf("can't load key pair from cert %s and key %s", certFile, keyFile) + return nil, fmt.Errorf("can't load key pair from cert %s and key %s: %s", certFile, keyFile, err) } return &cert, err }