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 }