github.com/jlmeeker/kismatic@v1.10.1-0.20180612190640-57f9005a1f1a/pkg/install/pki.go (about)

     1  package install
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"io/ioutil"
     7  	"os"
     8  	"path/filepath"
     9  	"sort"
    10  	"time"
    11  
    12  	"github.com/apprenda/kismatic/pkg/tls"
    13  	"github.com/apprenda/kismatic/pkg/util"
    14  	"github.com/cloudflare/cfssl/csr"
    15  )
    16  
    17  const (
    18  	adminUser                                  = "admin"
    19  	adminGroup                                 = "system:masters"
    20  	adminCertFilename                          = "admin"
    21  	adminCertFilenameKETPre133                 = "admin"
    22  	serviceAccountCertFilename                 = "service-account"
    23  	serviceAccountCertCommonName               = "kube-service-account"
    24  	schedulerCertFilenamePrefix                = "kube-scheduler"
    25  	schedulerUser                              = "system:kube-scheduler"
    26  	controllerManagerCertFilenamePrefix        = "kube-controller-manager"
    27  	controllerManagerUser                      = "system:kube-controller-manager"
    28  	kubeletUserPrefix                          = "system:node"
    29  	kubeletGroup                               = "system:nodes"
    30  	kubeAPIServerKubeletClientClientFilename   = "apiserver-kubelet-client"
    31  	kubeAPIServerKubeletClientClientCommonName = "kube-apiserver-kubelet-client"
    32  	contivProxyServerCertFilename              = "contiv-proxy-server"
    33  	proxyClientCACommonName                    = "proxyClientCA"
    34  	proxyClientCertFilename                    = "proxy-client"
    35  	proxyClientCertCommonName                  = "aggregator"
    36  )
    37  
    38  // The PKI provides a way for generating certificates for the cluster described by the Plan
    39  type PKI interface {
    40  	CertificateAuthorityExists() (bool, error)
    41  	GenerateClusterCA(p *Plan) (*tls.CA, error)
    42  	GetClusterCA() (*tls.CA, error)
    43  	GenerateProxyClientCA(p *Plan) (*tls.CA, error)
    44  	GetProxyClientCA() (*tls.CA, error)
    45  	GenerateClusterCertificates(p *Plan, clusterCA *tls.CA, proxyClientCA *tls.CA) error
    46  	NodeCertificateExists(node Node) (bool, error)
    47  	GenerateNodeCertificate(plan *Plan, node Node, ca *tls.CA) error
    48  	GenerateCertificate(name string, validityPeriod string, commonName string, subjectAlternateNames []string, organizations []string, ca *tls.CA, overwrite bool) (bool, error)
    49  }
    50  
    51  // LocalPKI is a file-based PKI
    52  type LocalPKI struct {
    53  	CACsr                   string
    54  	GeneratedCertsDirectory string
    55  	Log                     io.Writer
    56  }
    57  
    58  type certificateSpec struct {
    59  	description           string
    60  	filename              string
    61  	commonName            string
    62  	subjectAlternateNames []string
    63  	organizations         []string
    64  	ca                    *tls.CA
    65  }
    66  
    67  func (s certificateSpec) equal(other certificateSpec) bool {
    68  	prelimEqual := s.description == other.description &&
    69  		s.filename == other.filename &&
    70  		s.commonName == other.commonName &&
    71  		len(s.subjectAlternateNames) == len(other.subjectAlternateNames) &&
    72  		len(s.organizations) == len(other.organizations)
    73  	if !prelimEqual {
    74  		return false
    75  	}
    76  	// Compare subject alt. names
    77  	thisSAN := make([]string, len(s.subjectAlternateNames))
    78  	otherSAN := make([]string, len(other.subjectAlternateNames))
    79  	// Clone and sort
    80  	copy(thisSAN, s.subjectAlternateNames)
    81  	copy(otherSAN, other.subjectAlternateNames)
    82  	sort.Strings(thisSAN)
    83  	sort.Strings(otherSAN)
    84  
    85  	for _, x := range thisSAN {
    86  		for _, y := range otherSAN {
    87  			if x != y {
    88  				return false
    89  			}
    90  		}
    91  	}
    92  	// Compare organizations
    93  	thisOrgs := make([]string, len(s.organizations))
    94  	otherOrgs := make([]string, len(other.organizations))
    95  	// clone and sort
    96  	copy(thisOrgs, s.organizations)
    97  	copy(otherOrgs, other.organizations)
    98  	sort.Strings(thisOrgs)
    99  	sort.Strings(otherOrgs)
   100  
   101  	for _, x := range thisOrgs {
   102  		for _, y := range otherOrgs {
   103  			if x != y {
   104  				return false
   105  			}
   106  		}
   107  	}
   108  	return true
   109  }
   110  
   111  // CertificateAuthorityExists returns true if the CA for the cluster exists
   112  func (lp *LocalPKI) CertificateAuthorityExists() (bool, error) {
   113  	return tls.CertKeyPairExists("ca", lp.GeneratedCertsDirectory)
   114  }
   115  
   116  // GenerateClusterCA creates a Certificate Authority for the cluster
   117  func (lp *LocalPKI) GenerateClusterCA(p *Plan) (*tls.CA, error) {
   118  	exists, err := tls.CertKeyPairExists("ca", lp.GeneratedCertsDirectory)
   119  	if err != nil {
   120  		return nil, fmt.Errorf("error verifying CA certificate/key: %v", err)
   121  	}
   122  	if exists {
   123  		return lp.GetClusterCA()
   124  	}
   125  
   126  	// CA keypair doesn't exist, generate one
   127  	util.PrettyPrintOk(lp.Log, "Generating cluster Certificate Authority")
   128  	key, cert, err := tls.NewCACert(lp.CACsr, p.Cluster.Name, p.Cluster.Certificates.CAExpiry)
   129  	if err != nil {
   130  		return nil, fmt.Errorf("failed to create CA Cert: %v", err)
   131  	}
   132  	if err = tls.WriteCert(key, cert, "ca", lp.GeneratedCertsDirectory); err != nil {
   133  		return nil, fmt.Errorf("error writing CA files: %v", err)
   134  	}
   135  	return &tls.CA{
   136  		Cert: cert,
   137  		Key:  key,
   138  	}, nil
   139  }
   140  
   141  // GetClusterCA returns the cluster CA
   142  func (lp *LocalPKI) GetClusterCA() (*tls.CA, error) {
   143  	key, cert, err := tls.ReadCACert("ca", lp.GeneratedCertsDirectory)
   144  	if err != nil {
   145  		return nil, fmt.Errorf("error reading CA certificate/key: %v", err)
   146  	}
   147  	return &tls.CA{
   148  		Cert: cert,
   149  		Key:  key,
   150  	}, nil
   151  }
   152  
   153  // GenerateProxyClientCA creates a Certificate Authority for the cluster
   154  func (lp *LocalPKI) GenerateProxyClientCA(p *Plan) (*tls.CA, error) {
   155  	exists, err := tls.CertKeyPairExists("proxy-client-ca", lp.GeneratedCertsDirectory)
   156  	if err != nil {
   157  		return nil, fmt.Errorf("error verifying proxy-client CA certificate/key: %v", err)
   158  	}
   159  	if exists {
   160  		return lp.GetProxyClientCA()
   161  	}
   162  
   163  	// CA keypair doesn't exist, generate one
   164  	util.PrettyPrintOk(lp.Log, "Generating proxy-client Certificate Authority")
   165  	key, cert, err := tls.NewCACert(lp.CACsr, proxyClientCACommonName, p.Cluster.Certificates.CAExpiry)
   166  	if err != nil {
   167  		return nil, fmt.Errorf("failed to create proxy-client CA Cert: %v", err)
   168  	}
   169  	if err = tls.WriteCert(key, cert, "proxy-client-ca", lp.GeneratedCertsDirectory); err != nil {
   170  		return nil, fmt.Errorf("error writing proxy-client CA files: %v", err)
   171  	}
   172  	return &tls.CA{
   173  		Cert: cert,
   174  		Key:  key,
   175  	}, nil
   176  }
   177  
   178  // GetProxyClientCA returns the cluster CA
   179  func (lp *LocalPKI) GetProxyClientCA() (*tls.CA, error) {
   180  	key, cert, err := tls.ReadCACert("proxy-client-ca", lp.GeneratedCertsDirectory)
   181  	if err != nil {
   182  		return nil, fmt.Errorf("error reading proxy-client CA certificate/key: %v", err)
   183  	}
   184  	return &tls.CA{
   185  		Cert: cert,
   186  		Key:  key,
   187  	}, nil
   188  }
   189  
   190  // GenerateClusterCertificates creates all certificates required for the cluster
   191  // described in the plan file.
   192  func (lp *LocalPKI) GenerateClusterCertificates(p *Plan, clusterCA *tls.CA, proxyClientCA *tls.CA) error {
   193  	if lp.Log == nil {
   194  		lp.Log = ioutil.Discard
   195  	}
   196  
   197  	manifest, err := p.certSpecs(clusterCA, proxyClientCA)
   198  	if err != nil {
   199  		return err
   200  	}
   201  
   202  	for _, s := range manifest {
   203  		exists, err := tls.CertKeyPairExists(s.filename, lp.GeneratedCertsDirectory)
   204  		if err != nil {
   205  			return err
   206  		}
   207  
   208  		// Pre-existing admin certificates from KET < 1.3.3 are not valid
   209  		// due to changes required for RBAC. Rename it if necessary.
   210  		if exists && s.filename == adminCertFilenameKETPre133 {
   211  			ok, err := renamePre133AdminCert(s.filename, lp.GeneratedCertsDirectory)
   212  			if err != nil {
   213  				return err
   214  			}
   215  			// We renamed it, so it doesn't exist anymore
   216  			if ok {
   217  				util.PrettyPrintWarn(lp.Log, "Existing admin certificate is invalid. Backing up and regenerating.")
   218  				exists = false
   219  			}
   220  		}
   221  
   222  		if exists {
   223  			warnings, err := tls.CertValid(s.commonName, s.subjectAlternateNames, s.organizations, s.filename, lp.GeneratedCertsDirectory)
   224  			if err != nil {
   225  				return err
   226  			}
   227  			if len(warnings) > 0 {
   228  				util.PrettyPrintErr(lp.Log, "Found certificate for %s, but it is not valid", s.description)
   229  				util.PrintValidationErrors(lp.Log, warnings)
   230  				return fmt.Errorf("invalid certificate found for %q", s.description)
   231  			}
   232  			// This cert is valid, move onto the next certificate
   233  			util.PrettyPrintOk(lp.Log, "Found valid certificate for %s", s.description)
   234  			continue
   235  		}
   236  
   237  		// Cert doesn't exist. Generate it
   238  		if err := generateCert(lp.GeneratedCertsDirectory, s, p.Cluster.Certificates.Expiry); err != nil {
   239  			return err
   240  		}
   241  		util.PrettyPrintOk(lp.Log, "Generated certificate for %s", s.description)
   242  	}
   243  	return nil
   244  }
   245  
   246  // Validates that the certificate was generated by us. If so, renames it
   247  // to make a backup and returns true. Otherwise returns false.
   248  func renamePre133AdminCert(filename, dir string) (bool, error) {
   249  	cert, err := tls.ReadCert(filename, dir)
   250  
   251  	if err != nil {
   252  		return false, fmt.Errorf("error reading admin certificate: %v", err)
   253  	}
   254  	// Ensure it was generated by us
   255  	if len(cert.Subject.Organization) == 1 && cert.Subject.Organization[0] == "Apprenda" &&
   256  		len(cert.Subject.OrganizationalUnit) == 1 && cert.Subject.OrganizationalUnit[0] == "Kismatic" &&
   257  		len(cert.Subject.Country) == 1 && cert.Subject.Country[0] == "US" &&
   258  		len(cert.Subject.Province) == 1 && cert.Subject.Province[0] == "NY" &&
   259  		len(cert.Subject.Locality) == 1 && cert.Subject.Locality[0] == "Troy" {
   260  
   261  		certFile := filepath.Join(dir, filename+".pem")
   262  		if err = os.Rename(certFile, certFile+".bak"); err != nil {
   263  			return false, fmt.Errorf("error backing up existing admin certificate: %v", err)
   264  		}
   265  		return true, nil
   266  	}
   267  	return false, nil
   268  }
   269  
   270  // ValidateClusterCertificates validates any certificates that already exist
   271  // in the expected directory.
   272  func (lp *LocalPKI) ValidateClusterCertificates(p *Plan) (warns []error, errs []error) {
   273  	if lp.Log == nil {
   274  		lp.Log = ioutil.Discard
   275  	}
   276  	manifest, err := p.certSpecs(nil, nil)
   277  	if err != nil {
   278  		return nil, []error{err}
   279  	}
   280  	for _, s := range manifest {
   281  		exists, err := tls.CertKeyPairExists(s.filename, lp.GeneratedCertsDirectory)
   282  		if err != nil {
   283  			errs = append(errs, err)
   284  			continue
   285  		}
   286  		if !exists {
   287  			continue // nothing to validate... move on
   288  		}
   289  		warn, err := tls.CertValid(s.commonName, s.subjectAlternateNames, s.organizations, s.filename, lp.GeneratedCertsDirectory)
   290  		if err != nil {
   291  			errs = append(errs, err)
   292  		}
   293  		if len(warn) > 0 {
   294  			warns = append(warns, warn...)
   295  		}
   296  	}
   297  	return warns, errs
   298  }
   299  
   300  // NodeCertificateExists returns true if the node's key and certificate exist
   301  func (lp *LocalPKI) NodeCertificateExists(node Node) (bool, error) {
   302  	return tls.CertKeyPairExists(node.Host, lp.GeneratedCertsDirectory)
   303  }
   304  
   305  // GenerateNodeCertificate creates a private key and certificate for the given node
   306  func (lp *LocalPKI) GenerateNodeCertificate(plan *Plan, node Node, ca *tls.CA) error {
   307  	m, err := node.certSpecs(*plan, ca)
   308  	if err != nil {
   309  		return err
   310  	}
   311  	for _, s := range m {
   312  		exists, err := tls.CertKeyPairExists(s.filename, lp.GeneratedCertsDirectory)
   313  		if err != nil {
   314  			return err
   315  		}
   316  		if exists {
   317  			warn, err := tls.CertValid(s.commonName, s.subjectAlternateNames, s.organizations, s.filename, lp.GeneratedCertsDirectory)
   318  			if err != nil {
   319  				return err
   320  			}
   321  			if len(warn) > 0 {
   322  				util.PrettyPrintErr(lp.Log, "Found certificate for %s, but it is not valid", s.description)
   323  				util.PrintValidationErrors(lp.Log, warn)
   324  				return fmt.Errorf("invalid certificate found for %q", s.description)
   325  			}
   326  			// This cert is valid, move on
   327  			util.PrettyPrintOk(lp.Log, "Found valid certificate for %s", s.description)
   328  			continue
   329  		}
   330  		// Cert doesn't exist. Generate it
   331  		if err := generateCert(lp.GeneratedCertsDirectory, s, plan.Cluster.Certificates.Expiry); err != nil {
   332  			return err
   333  		}
   334  		util.PrettyPrintOk(lp.Log, "Generated certificate for %s", s.description)
   335  	}
   336  	return nil
   337  }
   338  
   339  // GenerateCertificate creates a private key and certificate for the given name, CN, subjectAlternateNames and organizations
   340  // If cert exists, will not fail
   341  // Pass overwrite to replace an existing cert
   342  func (lp *LocalPKI) GenerateCertificate(name string, validityPeriod string, commonName string, subjectAlternateNames []string, organizations []string, ca *tls.CA, overwrite bool) (bool, error) {
   343  	if name == "" {
   344  		return false, fmt.Errorf("name cannot be empty")
   345  	}
   346  	if validityPeriod == "" {
   347  		return false, fmt.Errorf("validityPeriod cannot be empty")
   348  	}
   349  	if ca == nil {
   350  		return false, fmt.Errorf("ca cannot be nil")
   351  	}
   352  	exists, err := tls.CertKeyPairExists(name, lp.GeneratedCertsDirectory)
   353  	if err != nil {
   354  		return false, fmt.Errorf("could not determine if certificate for %s exists: %v", name, err)
   355  	}
   356  	// if exists and overwrite == false return
   357  	// otherwise cert will be created and replaced if exists
   358  	if exists && !overwrite {
   359  		return true, nil
   360  	}
   361  
   362  	spec := certificateSpec{
   363  		description:           name,
   364  		filename:              name,
   365  		commonName:            commonName,
   366  		subjectAlternateNames: subjectAlternateNames,
   367  		organizations:         organizations,
   368  		ca:                    ca,
   369  	}
   370  
   371  	if err := generateCert(lp.GeneratedCertsDirectory, spec, validityPeriod); err != nil {
   372  		return exists, fmt.Errorf("could not generate certificate %s: %v", name, err)
   373  	}
   374  
   375  	return exists, nil
   376  }
   377  
   378  func generateCert(certDir string, spec certificateSpec, expiryStr string) error {
   379  	expiry, err := time.ParseDuration(expiryStr)
   380  	if err != nil {
   381  		return fmt.Errorf("%q is not a valid duration for certificate expiry", expiryStr)
   382  	}
   383  	req := csr.CertificateRequest{
   384  		CN: spec.commonName,
   385  		KeyRequest: &csr.BasicKeyRequest{
   386  			A: "rsa",
   387  			S: 2048,
   388  		},
   389  	}
   390  
   391  	if len(spec.subjectAlternateNames) > 0 {
   392  		req.Hosts = spec.subjectAlternateNames
   393  	}
   394  
   395  	for _, org := range spec.organizations {
   396  		name := csr.Name{O: org}
   397  		req.Names = append(req.Names, name)
   398  	}
   399  
   400  	key, cert, err := tls.NewCert(spec.ca, req, expiry)
   401  	if err != nil {
   402  		return fmt.Errorf("error generating certs for %q: %v", spec.description, err)
   403  	}
   404  	if err = tls.WriteCert(key, cert, spec.filename, certDir); err != nil {
   405  		return fmt.Errorf("error writing cert for %q: %v", spec.description, err)
   406  	}
   407  	return nil
   408  }
   409  
   410  func clusterCertsSubjectAlternateNames(plan Plan) ([]string, error) {
   411  	kubeServiceIP, err := getKubernetesServiceIP(&plan)
   412  	if err != nil {
   413  		return nil, fmt.Errorf("Error getting kubernetes service IP: %v", err)
   414  	}
   415  	defaultCertHosts := []string{
   416  		"kubernetes",
   417  		"kubernetes.default",
   418  		"kubernetes.default.svc",
   419  		"kubernetes.default.svc.cluster.local",
   420  		"127.0.0.1",
   421  		kubeServiceIP,
   422  	}
   423  	return defaultCertHosts, nil
   424  }
   425  
   426  func contains(x string, xs []string) bool {
   427  	for _, s := range xs {
   428  		if x == s {
   429  			return true
   430  		}
   431  	}
   432  	return false
   433  }
   434  
   435  func containsAny(x []string, xs []string) bool {
   436  	for _, s := range x {
   437  		if contains(s, xs) {
   438  			return true
   439  		}
   440  	}
   441  	return false
   442  }
   443  
   444  func certSpecInManifest(spec certificateSpec, manifest []certificateSpec) bool {
   445  	for _, s := range manifest {
   446  		if s.equal(spec) {
   447  			return true
   448  		}
   449  	}
   450  	return false
   451  }