github.com/michaelhenkel/operator-sdk@v0.8.1/pkg/tls/tls.go (about)

     1  // Copyright 2018 The Operator-SDK Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package tlsutil
    16  
    17  import (
    18  	"crypto/rsa"
    19  	"crypto/x509"
    20  	"errors"
    21  	"fmt"
    22  	"io/ioutil"
    23  	"strings"
    24  
    25  	"k8s.io/api/core/v1"
    26  	apiErrors "k8s.io/apimachinery/pkg/api/errors"
    27  	"k8s.io/apimachinery/pkg/api/meta"
    28  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    29  	"k8s.io/apimachinery/pkg/runtime"
    30  	"k8s.io/client-go/kubernetes"
    31  )
    32  
    33  // CertType defines the type of the cert.
    34  type CertType int
    35  
    36  const (
    37  	// ClientAndServingCert defines both client and serving cert.
    38  	ClientAndServingCert CertType = iota
    39  	// ServingCert defines a serving cert.
    40  	ServingCert
    41  	// ClientCert defines a client cert.
    42  	ClientCert
    43  )
    44  
    45  // CertConfig configures how to generate the Cert.
    46  type CertConfig struct {
    47  	// CertName is the name of the cert.
    48  	CertName string
    49  	// Optional CertType. Serving, client or both; defaults to both.
    50  	CertType CertType
    51  	// Optional CommonName is the common name of the cert; defaults to "".
    52  	CommonName string
    53  	// Optional Organization is Organization of the cert; defaults to "".
    54  	Organization []string
    55  	// Optional CA Key, if user wants to provide custom CA key via a file path.
    56  	CAKey string
    57  	// Optional CA Certificate, if user wants to provide custom CA cert via file path.
    58  	CACert string
    59  	// TODO: consider to add passed in SAN fields.
    60  }
    61  
    62  // CertGenerator is an operator specific TLS tool that generates TLS assets for the deploying a user's application.
    63  type CertGenerator interface {
    64  	// GenerateCert generates a secret containing TLS encryption key and cert, a Secret
    65  	// containing the CA key, and a ConfigMap containing the CA Certificate given the Custom
    66  	// Resource(CR) "cr", the Kubernetes Service "Service", and the CertConfig "config".
    67  	//
    68  	// GenerateCert creates and manages TLS key and cert and CA with the following:
    69  	// CA creation and management:
    70  	// - If CA is not given:
    71  	//  - A unique CA is generated for the CR.
    72  	//  - CA's key is packaged into a Secret as shown below.
    73  	//  - CA's cert is packaged in a ConfigMap as shown below.
    74  	//  - The CA Secret and ConfigMap are created on the k8s cluster in the CR's namespace before
    75  	//    returned to the user. The CertGenerator manages the CA Secret and ConfigMap to ensure it's
    76  	//    unqiue per CR.
    77  	// - If CA is given:
    78  	//  - CA's key is packaged into a Secret as shown below.
    79  	//  - CA's cert is packaged in a ConfigMap as shown below.
    80  	//  - The CA Secret and ConfigMap are returned but not created in the K8s cluster in the CR's
    81  	//    namespace. The CertGenerator doesn't manage the CA because the user controls the lifecycle
    82  	//    of the CA.
    83  	//
    84  	// TLS Key and Cert Creation and Management:
    85  	// - A unique TLS cert and key pair is generated per CR + CertConfig.CertName.
    86  	// - The CA is used to generate and sign the TLS cert.
    87  	// - The signing process uses the passed in "service" to set the Subject Alternative Names(SAN)
    88  	//   for the certificate. We assume that the deployed applications are typically communicated
    89  	//   with via a Kubernetes Service. The SAN is set to the FQDN of the service
    90  	//   `<service-name>.<service-namespace>.svc.cluster.local`.
    91  	// - Once TLS key and cert are created, they are packaged into a secret as shown below.
    92  	// - Finally, the secret are created on the k8s cluster in the CR's namespace before returned to
    93  	//   the user. The CertGenerator manages this secret to ensure that it is unique per CR +
    94  	//   CertConfig.CertName.
    95  	//
    96  	// TLS encryption key and cert Secret format:
    97  	// kind: Secret
    98  	// apiVersion: v1
    99  	// metadata:
   100  	//  name: <cr-kind>-<cr-name>-<CertConfig.CertName>
   101  	//  namespace: <cr-namespace>
   102  	// data:
   103  	//  tls.crt: ...
   104  	//  tls.key: ...
   105  	//
   106  	// CA Certificate ConfigMap format:
   107  	// kind: ConfigMap
   108  	//   apiVersion: v1
   109  	//   metadata:
   110  	//     name: <cr-kind>-<cr-name>-ca
   111  	//     namespace: <cr-namespace>
   112  	//   data:
   113  	//     ca.crt: ...
   114  	//
   115  	// CA Key Secret format:
   116  	//  kind: Secret
   117  	//  apiVersion: v1
   118  	//  metadata:
   119  	//   name: <cr-kind>-<cr-name>-ca
   120  	//   namespace: <cr-namespace>
   121  	//  data:
   122  	//   ca.key: ..
   123  	GenerateCert(cr runtime.Object, service *v1.Service, config *CertConfig) (*v1.Secret, *v1.ConfigMap, *v1.Secret, error)
   124  }
   125  
   126  const (
   127  	// TLSPrivateCAKeyKey is the key for the private CA key field.
   128  	TLSPrivateCAKeyKey = "ca.key"
   129  	// TLSCertKey is the key for tls CA certificates.
   130  	TLSCACertKey = "ca.crt"
   131  )
   132  
   133  // NewSDKCertGenerator constructs a new CertGenerator given the kubeClient.
   134  func NewSDKCertGenerator(kubeClient kubernetes.Interface) CertGenerator {
   135  	return &SDKCertGenerator{KubeClient: kubeClient}
   136  }
   137  
   138  type SDKCertGenerator struct {
   139  	KubeClient kubernetes.Interface
   140  }
   141  
   142  // GenerateCert returns a secret containing the TLS encryption key and cert,
   143  // a ConfigMap containing the CA Certificate and a Secret containing the CA key or it
   144  // returns a error incase something goes wrong.
   145  func (scg *SDKCertGenerator) GenerateCert(cr runtime.Object, service *v1.Service, config *CertConfig) (*v1.Secret, *v1.ConfigMap, *v1.Secret, error) {
   146  	if err := verifyConfig(config); err != nil {
   147  		return nil, nil, nil, err
   148  	}
   149  
   150  	k, n, ns, err := toKindNameNamespace(cr)
   151  	if err != nil {
   152  		return nil, nil, nil, err
   153  	}
   154  	appSecretName := ToAppSecretName(k, n, config.CertName)
   155  	appSecret, err := getAppSecretInCluster(scg.KubeClient, appSecretName, ns)
   156  	if err != nil {
   157  		return nil, nil, nil, err
   158  	}
   159  	caSecretAndConfigMapName := ToCASecretAndConfigMapName(k, n)
   160  
   161  	var (
   162  		caSecret    *v1.Secret
   163  		caConfigMap *v1.ConfigMap
   164  	)
   165  
   166  	caSecret, caConfigMap, err = getCASecretAndConfigMapInCluster(scg.KubeClient, caSecretAndConfigMapName, ns)
   167  	if err != nil {
   168  		return nil, nil, nil, err
   169  	}
   170  
   171  	if config.CAKey != "" && config.CACert != "" {
   172  		// custom CA provided by the user.
   173  		customCAKeyData, err := ioutil.ReadFile(config.CAKey)
   174  		if err != nil {
   175  			return nil, nil, nil, fmt.Errorf("error reading CA Key from the given file name: %v", err)
   176  		}
   177  
   178  		customCACertData, err := ioutil.ReadFile(config.CACert)
   179  		if err != nil {
   180  			return nil, nil, nil, fmt.Errorf("error reading CA Cert from the given file name: %v", err)
   181  		}
   182  
   183  		customCAKey, err := parsePEMEncodedPrivateKey(customCAKeyData)
   184  		if err != nil {
   185  			return nil, nil, nil, fmt.Errorf("error parsing CA Key from the given file name: %v", err)
   186  		}
   187  
   188  		customCACert, err := parsePEMEncodedCert(customCACertData)
   189  		if err != nil {
   190  			return nil, nil, nil, fmt.Errorf("error parsing CA Cert from the given file name: %v", err)
   191  		}
   192  		caSecret, caConfigMap = toCASecretAndConfigmap(customCAKey, customCACert, caSecretAndConfigMapName)
   193  	} else if config.CAKey != "" || config.CACert != "" {
   194  		// if only one of the custom CA Key or Cert is provided
   195  		return nil, nil, nil, ErrCAKeyAndCACertReq
   196  	}
   197  
   198  	hasAppSecret := appSecret != nil
   199  	hasCASecretAndConfigMap := caSecret != nil && caConfigMap != nil
   200  
   201  	switch {
   202  	case hasAppSecret && hasCASecretAndConfigMap:
   203  		return appSecret, caConfigMap, caSecret, nil
   204  
   205  	case hasAppSecret && !hasCASecretAndConfigMap:
   206  		return nil, nil, nil, ErrCANotFound
   207  
   208  	case !hasAppSecret && hasCASecretAndConfigMap:
   209  		// Note: if a custom CA is passed in my the user it takes preference over an already
   210  		// generated CA secret and CA configmap that might exist in the cluster
   211  		caKey, err := parsePEMEncodedPrivateKey(caSecret.Data[TLSPrivateCAKeyKey])
   212  		if err != nil {
   213  			return nil, nil, nil, err
   214  		}
   215  		caCert, err := parsePEMEncodedCert([]byte(caConfigMap.Data[TLSCACertKey]))
   216  		if err != nil {
   217  			return nil, nil, nil, err
   218  		}
   219  		key, err := newPrivateKey()
   220  		if err != nil {
   221  			return nil, nil, nil, err
   222  		}
   223  		cert, err := newSignedCertificate(config, service, key, caCert, caKey)
   224  		if err != nil {
   225  			return nil, nil, nil, err
   226  		}
   227  		appSecret, err := scg.KubeClient.CoreV1().Secrets(ns).Create(toTLSSecret(key, cert, appSecretName))
   228  		if err != nil {
   229  			return nil, nil, nil, err
   230  		}
   231  		return appSecret, caConfigMap, caSecret, nil
   232  
   233  	case !hasAppSecret && !hasCASecretAndConfigMap:
   234  		// If no custom CAKey and CACert are provided we have to generate them
   235  		caKey, err := newPrivateKey()
   236  		if err != nil {
   237  			return nil, nil, nil, err
   238  		}
   239  		caCert, err := newSelfSignedCACertificate(caKey)
   240  		if err != nil {
   241  			return nil, nil, nil, err
   242  		}
   243  
   244  		caSecret, caConfigMap := toCASecretAndConfigmap(caKey, caCert, caSecretAndConfigMapName)
   245  		caSecret, err = scg.KubeClient.CoreV1().Secrets(ns).Create(caSecret)
   246  		if err != nil {
   247  			return nil, nil, nil, err
   248  		}
   249  		caConfigMap, err = scg.KubeClient.CoreV1().ConfigMaps(ns).Create(caConfigMap)
   250  		if err != nil {
   251  			return nil, nil, nil, err
   252  		}
   253  		key, err := newPrivateKey()
   254  		if err != nil {
   255  			return nil, nil, nil, err
   256  		}
   257  		cert, err := newSignedCertificate(config, service, key, caCert, caKey)
   258  		if err != nil {
   259  			return nil, nil, nil, err
   260  		}
   261  		appSecret, err := scg.KubeClient.CoreV1().Secrets(ns).Create(toTLSSecret(key, cert, appSecretName))
   262  		if err != nil {
   263  			return nil, nil, nil, err
   264  		}
   265  		return appSecret, caConfigMap, caSecret, nil
   266  	default:
   267  		return nil, nil, nil, ErrInternal
   268  	}
   269  }
   270  
   271  func verifyConfig(config *CertConfig) error {
   272  	if config == nil {
   273  		return errors.New("nil CertConfig not allowed")
   274  	}
   275  	if config.CertName == "" {
   276  		return errors.New("empty CertConfig.CertName not allowed")
   277  	}
   278  	return nil
   279  }
   280  
   281  func ToAppSecretName(kind, name, certName string) string {
   282  	return strings.ToLower(kind) + "-" + name + "-" + certName
   283  }
   284  
   285  func ToCASecretAndConfigMapName(kind, name string) string {
   286  	return strings.ToLower(kind) + "-" + name + "-ca"
   287  }
   288  
   289  func getAppSecretInCluster(kubeClient kubernetes.Interface, name, namespace string) (*v1.Secret, error) {
   290  	se, err := kubeClient.CoreV1().Secrets(namespace).Get(name, metav1.GetOptions{})
   291  	if err != nil && !apiErrors.IsNotFound(err) {
   292  		return nil, err
   293  	}
   294  	if apiErrors.IsNotFound(err) {
   295  		return nil, nil
   296  	}
   297  	return se, nil
   298  }
   299  
   300  // getCASecretAndConfigMapInCluster gets CA secret and configmap of the given name and namespace.
   301  // it only returns both if they are found and nil if both are not found. In the case if only one of them is found,
   302  // then we error out because we expect either both CA secret and configmap exit or not.
   303  //
   304  // NOTE: both the CA secret and configmap have the same name with template `<cr-kind>-<cr-name>-ca` which is what the
   305  // input parameter `name` refers to.
   306  func getCASecretAndConfigMapInCluster(kubeClient kubernetes.Interface, name, namespace string) (*v1.Secret, *v1.ConfigMap, error) {
   307  	hasConfigMap := true
   308  	cm, err := kubeClient.CoreV1().ConfigMaps(namespace).Get(name, metav1.GetOptions{})
   309  	if err != nil && !apiErrors.IsNotFound(err) {
   310  		return nil, nil, err
   311  	}
   312  	if apiErrors.IsNotFound(err) {
   313  		hasConfigMap = false
   314  	}
   315  
   316  	hasSecret := true
   317  	se, err := kubeClient.CoreV1().Secrets(namespace).Get(name, metav1.GetOptions{})
   318  	if err != nil && !apiErrors.IsNotFound(err) {
   319  		return nil, nil, err
   320  	}
   321  	if apiErrors.IsNotFound(err) {
   322  		hasSecret = false
   323  	}
   324  
   325  	if hasConfigMap != hasSecret {
   326  		// TODO: this case can happen if creating CA configmap succeeds and creating CA secret failed. We need to handle this case properly.
   327  		return nil, nil, fmt.Errorf("expect either both ca configmap and secret both exist or not exist, but got hasCAConfigmap==%v and hasCASecret==%v", hasConfigMap, hasSecret)
   328  	}
   329  	if hasConfigMap == false {
   330  		return nil, nil, nil
   331  	}
   332  	return se, cm, nil
   333  }
   334  
   335  func toKindNameNamespace(cr runtime.Object) (string, string, string, error) {
   336  	a := meta.NewAccessor()
   337  	k, err := a.Kind(cr)
   338  	if err != nil {
   339  		return "", "", "", err
   340  	}
   341  	n, err := a.Name(cr)
   342  	if err != nil {
   343  		return "", "", "", err
   344  	}
   345  	ns, err := a.Namespace(cr)
   346  	if err != nil {
   347  		return "", "", "", err
   348  	}
   349  	return k, n, ns, nil
   350  }
   351  
   352  // toTLSSecret returns a client/server "kubernetes.io/tls" secret.
   353  // TODO: add owner ref.
   354  func toTLSSecret(key *rsa.PrivateKey, cert *x509.Certificate, name string) *v1.Secret {
   355  	return &v1.Secret{
   356  		ObjectMeta: metav1.ObjectMeta{
   357  			Name: name,
   358  		},
   359  		Data: map[string][]byte{
   360  			v1.TLSPrivateKeyKey: encodePrivateKeyPEM(key),
   361  			v1.TLSCertKey:       encodeCertificatePEM(cert),
   362  		},
   363  		Type: v1.SecretTypeTLS,
   364  	}
   365  }
   366  
   367  // TODO: add owner ref.
   368  func toCASecretAndConfigmap(key *rsa.PrivateKey, cert *x509.Certificate, name string) (*v1.Secret, *v1.ConfigMap) {
   369  	return &v1.Secret{
   370  			ObjectMeta: metav1.ObjectMeta{
   371  				Name: name,
   372  			},
   373  			Data: map[string][]byte{
   374  				TLSPrivateCAKeyKey: encodePrivateKeyPEM(key),
   375  			},
   376  		}, &v1.ConfigMap{
   377  			ObjectMeta: metav1.ObjectMeta{
   378  				Name: name,
   379  			},
   380  			Data: map[string]string{
   381  				TLSCACertKey: string(encodeCertificatePEM(cert)),
   382  			},
   383  		}
   384  }