feat: helm support for TLS

pull/2144/head
fibonacci1729 8 years ago
parent 1c1bfb273c
commit ad614b91a5

@ -45,6 +45,14 @@ const (
tillerNamespaceEnvVar = "TILLER_NAMESPACE"
)
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
)
var (
helmHome string
tillerHost string

@ -70,6 +70,7 @@ type initCmd struct {
dryRun bool
out io.Writer
home helmpath.Home
opts installer.Options
kubeClient internalclientset.Interface
}
@ -99,25 +100,73 @@ func newInitCmd(out io.Writer) *cobra.Command {
f.BoolVarP(&i.clientOnly, "client-only", "c", false, "if set does not install tiller")
f.BoolVar(&i.dryRun, "dry-run", false, "do not install local or remote")
// 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")
return cmd
}
// 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
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.TLSCertFile = tlsCertFile; i.opts.TLSCertFile == "" || missing(i.opts.TLSCertFile) {
return errors.New("missing required TLS certificate file")
}
if i.opts.VerifyTLS {
if i.opts.TLSCaCertFile = tlsCaCertFile; i.opts.TLSCaCertFile == "" || missing(i.opts.TLSCaCertFile) {
return errors.New("missing required TLS CA file")
}
}
}
return nil
}
// runInit initializes local config and installs tiller to Kubernetes Cluster
func (i *initCmd) run() error {
if err := i.tlsOptions(); err != nil {
return err
}
i.opts.Namespace = i.namespace
i.opts.UseCanary = i.canary
i.opts.ImageSpec = i.image
if flagDebug {
dm, err := installer.DeploymentManifest(i.namespace, i.image, i.canary)
if err != nil {
var mfs string
var err error
// write deployment manifest
if mfs, err = installer.DeploymentManifest(&i.opts); err != nil {
return err
}
fm := fmt.Sprintf("apiVersion: extensions/v1beta1\nkind: Deployment\n%s", dm)
fmt.Fprintln(i.out, fm)
fmt.Fprintln(i.out, fmt.Sprintf("apiVersion: extensions/v1beta1\nkind: Deployment\n%s", mfs))
sm, err := installer.ServiceManifest(i.namespace)
if err != nil {
// write service manifest
if mfs, err = installer.ServiceManifest(i.namespace); err != nil {
return err
}
fm = fmt.Sprintf("apiVersion: v1\nkind: Service\n%s", sm)
fmt.Fprintln(i.out, fm)
fmt.Fprintln(i.out, fmt.Sprintf("apiVersion: v1\nkind: Service\n%s", mfs))
// write secret manifest
if i.opts.EnableTLS {
if mfs, err = installer.SecretManifest(&i.opts); err != nil {
return err
}
fmt.Fprintln(i.out, fmt.Sprintf("apiVersion: v1\nkind: Secret\n%s", mfs))
}
}
if i.dryRun {
@ -143,13 +192,12 @@ func (i *initCmd) run() error {
}
i.kubeClient = c
}
opts := &installer.Options{Namespace: i.namespace, ImageSpec: i.image, UseCanary: i.canary}
if err := installer.Install(i.kubeClient, opts); err != nil {
if err := installer.Install(i.kubeClient, &i.opts); err != nil {
if !kerrors.IsAlreadyExists(err) {
return fmt.Errorf("error installing: %s", err)
}
if i.upgrade {
if err := installer.Upgrade(i.kubeClient, opts); err != nil {
if err := installer.Upgrade(i.kubeClient, &i.opts); err != nil {
return fmt.Errorf("error when upgrading: %s", err)
}
fmt.Fprintln(i.out, "\nTiller (the helm server side component) has been upgraded to the current version.")

@ -17,7 +17,7 @@ limitations under the License.
package installer // import "k8s.io/helm/cmd/helm/installer"
import (
"fmt"
"io/ioutil"
"github.com/ghodss/yaml"
@ -28,22 +28,23 @@ import (
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion"
extensionsclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion"
"k8s.io/kubernetes/pkg/util/intstr"
"k8s.io/helm/pkg/version"
)
const defaultImage = "gcr.io/kubernetes-helm/tiller"
// Install uses kubernetes client to install tiller.
//
// Returns an error if the command failed.
func Install(client internalclientset.Interface, opts *Options) error {
if err := createDeployment(client.Extensions(), opts.Namespace, opts.ImageSpec, opts.UseCanary); err != nil {
if err := createDeployment(client.Extensions(), opts); err != nil {
return err
}
if err := createService(client.Core(), opts.Namespace); err != nil {
return err
}
if opts.tls() {
if err := createSecret(client.Core(), opts); err != nil {
return err
}
}
return nil
}
@ -55,7 +56,7 @@ func Upgrade(client internalclientset.Interface, opts *Options) error {
if err != nil {
return err
}
obj.Spec.Template.Spec.Containers[0].Image = selectImage(opts.ImageSpec, opts.UseCanary)
obj.Spec.Template.Spec.Containers[0].Image = opts.selectImage()
if _, err := client.Extensions().Deployments(opts.Namespace).Update(obj); err != nil {
return err
}
@ -73,15 +74,15 @@ func Upgrade(client internalclientset.Interface, opts *Options) error {
}
// createDeployment creates the Tiller deployment reource
func createDeployment(client extensionsclient.DeploymentsGetter, namespace, image string, canary bool) error {
obj := deployment(namespace, image, canary)
func createDeployment(client extensionsclient.DeploymentsGetter, opts *Options) error {
obj := deployment(opts)
_, err := client.Deployments(obj.Namespace).Create(obj)
return err
}
// deployment gets the deployment object that installs Tiller.
func deployment(namespace, image string, canary bool) *extensions.Deployment {
return generateDeployment(namespace, selectImage(image, canary))
func deployment(opts *Options) *extensions.Deployment {
return generateDeployment(opts)
}
// createService creates the Tiller service resource
@ -96,21 +97,10 @@ func service(namespace string) *api.Service {
return generateService(namespace)
}
func selectImage(image string, canary bool) string {
switch {
case canary:
image = defaultImage + ":canary"
case image == "":
image = fmt.Sprintf("%s:%s", defaultImage, version.Version)
}
return image
}
// DeploymentManifest gets the manifest (as a string) that describes the Tiller Deployment
// resource.
func DeploymentManifest(namespace, image string, canary bool) (string, error) {
obj := deployment(namespace, image, canary)
func DeploymentManifest(opts *Options) (string, error) {
obj := deployment(opts)
buf, err := yaml.Marshal(obj)
return string(buf), err
}
@ -129,11 +119,11 @@ func generateLabels(labels map[string]string) map[string]string {
return labels
}
func generateDeployment(namespace, image string) *extensions.Deployment {
func generateDeployment(opts *Options) *extensions.Deployment {
labels := generateLabels(map[string]string{"name": "tiller"})
d := &extensions.Deployment{
ObjectMeta: api.ObjectMeta{
Namespace: namespace,
Namespace: opts.Namespace,
Name: "tiller-deploy",
Labels: labels,
},
@ -147,13 +137,13 @@ func generateDeployment(namespace, image string) *extensions.Deployment {
Containers: []api.Container{
{
Name: "tiller",
Image: image,
Image: opts.selectImage(),
ImagePullPolicy: "IfNotPresent",
Ports: []api.ContainerPort{
{ContainerPort: 44134, Name: "tiller"},
},
Env: []api.EnvVar{
{Name: "TILLER_NAMESPACE", Value: namespace},
{Name: "TILLER_NAMESPACE", Value: opts.Namespace},
},
LivenessProbe: &api.Probe{
Handler: api.Handler{
@ -181,6 +171,37 @@ func generateDeployment(namespace, image string) *extensions.Deployment {
},
},
}
if opts.tls() {
const certsDir = "/etc/certs"
var tlsVerify, tlsEnable = "", "1"
if opts.VerifyTLS {
tlsVerify = "1"
}
// Mount secret to "/etc/certs"
d.Spec.Template.Spec.Containers[0].VolumeMounts = append(d.Spec.Template.Spec.Containers[0].VolumeMounts, api.VolumeMount{
Name: "tiller-certs",
ReadOnly: true,
MountPath: certsDir,
})
// Add environment variable required for enabling TLS
d.Spec.Template.Spec.Containers[0].Env = append(d.Spec.Template.Spec.Containers[0].Env, []api.EnvVar{
{Name: "TILLER_TLS_VERIFY", Value: tlsVerify},
{Name: "TILLER_TLS_ENABLE", Value: tlsEnable},
{Name: "TILLER_TLS_CERTS", Value: certsDir},
}...)
// Add secret volume to deployment
d.Spec.Template.Spec.Volumes = append(d.Spec.Template.Spec.Volumes, api.Volume{
Name: "tiller-certs",
VolumeSource: api.VolumeSource{
Secret: &api.SecretVolumeSource{
SecretName: "tiller-secret",
},
},
})
}
return d
}
@ -206,3 +227,54 @@ func generateService(namespace string) *api.Service {
}
return s
}
// SecretManifest gets the manifest (as a string) that describes the Tiller Secret resource.
func SecretManifest(opts *Options) (string, error) {
o, err := generateSecret(opts)
if err != nil {
return "", err
}
buf, err := yaml.Marshal(o)
return string(buf), err
}
// createSecret creates the Tiller secret resource.
func createSecret(client internalversion.SecretsGetter, opts *Options) error {
o, err := generateSecret(opts)
if err != nil {
return err
}
_, err = client.Secrets(o.Namespace).Create(o)
return err
}
// generateSecret builds the secret object that hold Tiller secrets.
func generateSecret(opts *Options) (*api.Secret, error) {
const secretName = "tiller-secret"
labels := generateLabels(map[string]string{"name": "tiller"})
secret := &api.Secret{
Type: api.SecretTypeOpaque,
Data: make(map[string][]byte),
ObjectMeta: api.ObjectMeta{
Name: secretName,
Labels: labels,
Namespace: opts.Namespace,
},
}
var err error
if secret.Data["tls.key"], err = read(opts.TLSKeyFile); err != nil {
return nil, err
}
if secret.Data["tls.crt"], err = read(opts.TLSCertFile); err != nil {
return nil, err
}
if opts.VerifyTLS {
if secret.Data["ca.crt"], err = read(opts.TLSCaCertFile); err != nil {
return nil, err
}
}
return secret, nil
}
func read(path string) (b []byte, err error) { return ioutil.ReadFile(path) }

@ -45,7 +45,7 @@ func TestDeploymentManifest(t *testing.T) {
}
for _, tt := range tests {
o, err := DeploymentManifest(api.NamespaceDefault, tt.image, tt.canary)
o, err := DeploymentManifest(&Options{Namespace: api.NamespaceDefault, ImageSpec: tt.image, UseCanary: tt.canary})
if err != nil {
t.Fatalf("%s: error %q", tt.name, err)
}
@ -146,7 +146,11 @@ func TestInstall_canary(t *testing.T) {
func TestUpgrade(t *testing.T) {
image := "gcr.io/kubernetes-helm/tiller:v2.0.0"
existingDeployment := deployment(api.NamespaceDefault, "imageToReplace", false)
existingDeployment := deployment(&Options{
Namespace: api.NamespaceDefault,
ImageSpec: "imageToReplace",
UseCanary: false,
})
existingService := service(api.NamespaceDefault)
fc := &fake.Clientset{}
@ -178,7 +182,11 @@ func TestUpgrade(t *testing.T) {
func TestUpgrade_serviceNotFound(t *testing.T) {
image := "gcr.io/kubernetes-helm/tiller:v2.0.0"
existingDeployment := deployment(api.NamespaceDefault, "imageToReplace", false)
existingDeployment := deployment(&Options{
Namespace: api.NamespaceDefault,
ImageSpec: "imageToReplace",
UseCanary: false,
})
fc := &fake.Clientset{}
fc.AddReactor("get", "deployments", func(action testcore.Action) (bool, runtime.Object, error) {

@ -16,6 +16,13 @@ limitations under the License.
package installer // import "k8s.io/helm/cmd/helm/installer"
import (
"fmt"
"k8s.io/helm/pkg/version"
)
const defaultImage = "gcr.io/kubernetes-helm/tiller"
// Options control how to install tiller into a cluster, upgrade, and uninstall tiller from a cluster.
type Options struct {
// EnableTLS instructs tiller to serve with TLS enabled.
@ -43,7 +50,7 @@ type Options struct {
// key tiller should use.
//
// Required and valid if and only if EnableTLS or VerifyTLS is set.
TLSKey string
TLSKeyFile string
// TLSCertFile identifies the file containing the pem encoded TLS
// certificate tiller should use.
@ -57,3 +64,16 @@ type Options struct {
// Required and valid if and only if VerifyTLS is set.
TLSCaCertFile string
}
func (opts *Options) selectImage() string {
switch {
case opts.UseCanary:
return defaultImage + ":canary"
case opts.ImageSpec == "":
return fmt.Sprintf("%s:%s", defaultImage, version.Version)
default:
return opts.ImageSpec
}
}
func (opts *Options) tls() bool { return opts.EnableTLS || opts.VerifyTLS }

@ -55,7 +55,11 @@ func (f *fakeReaperFactory) Reaper(mapping *meta.RESTMapping) (kubectl.Reaper, e
func TestUninstall(t *testing.T) {
existingService := service(api.NamespaceDefault)
existingDeployment := deployment(api.NamespaceDefault, "image", false)
existingDeployment := deployment(&Options{
Namespace: api.NamespaceDefault,
ImageSpec: "image",
UseCanary: false,
})
fc := &fake.Clientset{}
fc.AddReactor("get", "services", func(action testcore.Action) (bool, runtime.Object, error) {
@ -92,7 +96,7 @@ func TestUninstall(t *testing.T) {
}
func TestUninstall_serviceNotFound(t *testing.T) {
existingDeployment := deployment(api.NamespaceDefault, "imageToReplace", false)
existingDeployment := deployment(&Options{Namespace: api.NamespaceDefault, ImageSpec: "imageToReplace", UseCanary: false})
fc := &fake.Clientset{}
fc.AddReactor("get", "services", func(action testcore.Action) (bool, runtime.Object, error) {

Loading…
Cancel
Save