| /* |
| Copyright 2016 The Kubernetes Authors. |
| |
| Licensed under the Apache License, Version 2.0 (the "License"); |
| you may not use this file except in compliance with the License. |
| You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| */ |
| |
| package pkiutil |
| |
| import ( |
| "crypto" |
| cryptorand "crypto/rand" |
| "crypto/rsa" |
| "crypto/x509" |
| "crypto/x509/pkix" |
| "encoding/pem" |
| "fmt" |
| "io/ioutil" |
| "net" |
| "os" |
| "path/filepath" |
| "time" |
| |
| "github.com/pkg/errors" |
| |
| "k8s.io/apimachinery/pkg/util/validation" |
| certutil "k8s.io/client-go/util/cert" |
| kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" |
| kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" |
| kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" |
| "k8s.io/kubernetes/pkg/registry/core/service/ipallocator" |
| ) |
| |
| // NewCertificateAuthority creates new certificate and private key for the certificate authority |
| func NewCertificateAuthority(config *certutil.Config) (*x509.Certificate, *rsa.PrivateKey, error) { |
| key, err := certutil.NewPrivateKey() |
| if err != nil { |
| return nil, nil, errors.Wrap(err, "unable to create private key") |
| } |
| |
| cert, err := certutil.NewSelfSignedCACert(*config, key) |
| if err != nil { |
| return nil, nil, errors.Wrap(err, "unable to create self-signed certificate") |
| } |
| |
| return cert, key, nil |
| } |
| |
| // NewCertAndKey creates new certificate and key by passing the certificate authority certificate and key |
| func NewCertAndKey(caCert *x509.Certificate, caKey *rsa.PrivateKey, config *certutil.Config) (*x509.Certificate, *rsa.PrivateKey, error) { |
| key, err := certutil.NewPrivateKey() |
| if err != nil { |
| return nil, nil, errors.Wrap(err, "unable to create private key") |
| } |
| |
| cert, err := certutil.NewSignedCert(*config, key, caCert, caKey) |
| if err != nil { |
| return nil, nil, errors.Wrap(err, "unable to sign certificate") |
| } |
| |
| return cert, key, nil |
| } |
| |
| // NewCSRAndKey generates a new key and CSR and that could be signed to create the given certificate |
| func NewCSRAndKey(config *certutil.Config) (*x509.CertificateRequest, *rsa.PrivateKey, error) { |
| key, err := certutil.NewPrivateKey() |
| if err != nil { |
| return nil, nil, errors.Wrap(err, "unable to create private key") |
| } |
| |
| csr, err := NewCSR(*config, key) |
| if err != nil { |
| return nil, nil, errors.Wrap(err, "unable to generate CSR") |
| } |
| |
| return csr, key, nil |
| } |
| |
| // HasServerAuth returns true if the given certificate is a ServerAuth |
| func HasServerAuth(cert *x509.Certificate) bool { |
| for i := range cert.ExtKeyUsage { |
| if cert.ExtKeyUsage[i] == x509.ExtKeyUsageServerAuth { |
| return true |
| } |
| } |
| return false |
| } |
| |
| // WriteCertAndKey stores certificate and key at the specified location |
| func WriteCertAndKey(pkiPath string, name string, cert *x509.Certificate, key *rsa.PrivateKey) error { |
| if err := WriteKey(pkiPath, name, key); err != nil { |
| return errors.Wrap(err, "couldn't write key") |
| } |
| |
| return WriteCert(pkiPath, name, cert) |
| } |
| |
| // WriteCert stores the given certificate at the given location |
| func WriteCert(pkiPath, name string, cert *x509.Certificate) error { |
| if cert == nil { |
| return errors.New("certificate cannot be nil when writing to file") |
| } |
| |
| certificatePath := pathForCert(pkiPath, name) |
| if err := certutil.WriteCert(certificatePath, certutil.EncodeCertPEM(cert)); err != nil { |
| return errors.Wrapf(err, "unable to write certificate to file %s", certificatePath) |
| } |
| |
| return nil |
| } |
| |
| // WriteKey stores the given key at the given location |
| func WriteKey(pkiPath, name string, key *rsa.PrivateKey) error { |
| if key == nil { |
| return errors.New("private key cannot be nil when writing to file") |
| } |
| |
| privateKeyPath := pathForKey(pkiPath, name) |
| if err := certutil.WriteKey(privateKeyPath, certutil.EncodePrivateKeyPEM(key)); err != nil { |
| return errors.Wrapf(err, "unable to write private key to file %s", privateKeyPath) |
| } |
| |
| return nil |
| } |
| |
| // WriteCSR writes the pem-encoded CSR data to csrPath. |
| // The CSR file will be created with file mode 0644. |
| // If the CSR file already exists, it will be overwritten. |
| // The parent directory of the csrPath will be created as needed with file mode 0755. |
| func WriteCSR(csrDir, name string, csr *x509.CertificateRequest) error { |
| if csr == nil { |
| return errors.New("certificate request cannot be nil when writing to file") |
| } |
| |
| csrPath := pathForCSR(csrDir, name) |
| if err := os.MkdirAll(filepath.Dir(csrPath), os.FileMode(0755)); err != nil { |
| return errors.Wrapf(err, "failed to make directory %s", filepath.Dir(csrPath)) |
| } |
| |
| if err := ioutil.WriteFile(csrPath, EncodeCSRPEM(csr), os.FileMode(0644)); err != nil { |
| return errors.Wrapf(err, "unable to write CSR to file %s", csrPath) |
| } |
| |
| return nil |
| } |
| |
| // WritePublicKey stores the given public key at the given location |
| func WritePublicKey(pkiPath, name string, key *rsa.PublicKey) error { |
| if key == nil { |
| return errors.New("public key cannot be nil when writing to file") |
| } |
| |
| publicKeyBytes, err := certutil.EncodePublicKeyPEM(key) |
| if err != nil { |
| return err |
| } |
| publicKeyPath := pathForPublicKey(pkiPath, name) |
| if err := certutil.WriteKey(publicKeyPath, publicKeyBytes); err != nil { |
| return errors.Wrapf(err, "unable to write public key to file %s", publicKeyPath) |
| } |
| |
| return nil |
| } |
| |
| // CertOrKeyExist returns a boolean whether the cert or the key exists |
| func CertOrKeyExist(pkiPath, name string) bool { |
| certificatePath, privateKeyPath := PathsForCertAndKey(pkiPath, name) |
| |
| _, certErr := os.Stat(certificatePath) |
| _, keyErr := os.Stat(privateKeyPath) |
| if os.IsNotExist(certErr) && os.IsNotExist(keyErr) { |
| // The cert or the key did not exist |
| return false |
| } |
| |
| // Both files exist or one of them |
| return true |
| } |
| |
| // CSROrKeyExist returns true if one of the CSR or key exists |
| func CSROrKeyExist(csrDir, name string) bool { |
| csrPath := pathForCSR(csrDir, name) |
| keyPath := pathForKey(csrDir, name) |
| |
| _, csrErr := os.Stat(csrPath) |
| _, keyErr := os.Stat(keyPath) |
| |
| return !(os.IsNotExist(csrErr) && os.IsNotExist(keyErr)) |
| } |
| |
| // TryLoadCertAndKeyFromDisk tries to load a cert and a key from the disk and validates that they are valid |
| func TryLoadCertAndKeyFromDisk(pkiPath, name string) (*x509.Certificate, *rsa.PrivateKey, error) { |
| cert, err := TryLoadCertFromDisk(pkiPath, name) |
| if err != nil { |
| return nil, nil, errors.Wrap(err, "failed to load certificate") |
| } |
| |
| key, err := TryLoadKeyFromDisk(pkiPath, name) |
| if err != nil { |
| return nil, nil, errors.Wrap(err, "failed to load key") |
| } |
| |
| return cert, key, nil |
| } |
| |
| // TryLoadCertFromDisk tries to load the cert from the disk and validates that it is valid |
| func TryLoadCertFromDisk(pkiPath, name string) (*x509.Certificate, error) { |
| certificatePath := pathForCert(pkiPath, name) |
| |
| certs, err := certutil.CertsFromFile(certificatePath) |
| if err != nil { |
| return nil, errors.Wrapf(err, "couldn't load the certificate file %s", certificatePath) |
| } |
| |
| // We are only putting one certificate in the certificate pem file, so it's safe to just pick the first one |
| // TODO: Support multiple certs here in order to be able to rotate certs |
| cert := certs[0] |
| |
| // Check so that the certificate is valid now |
| now := time.Now() |
| if now.Before(cert.NotBefore) { |
| return nil, errors.New("the certificate is not valid yet") |
| } |
| if now.After(cert.NotAfter) { |
| return nil, errors.New("the certificate has expired") |
| } |
| |
| return cert, nil |
| } |
| |
| // TryLoadKeyFromDisk tries to load the key from the disk and validates that it is valid |
| func TryLoadKeyFromDisk(pkiPath, name string) (*rsa.PrivateKey, error) { |
| privateKeyPath := pathForKey(pkiPath, name) |
| |
| // Parse the private key from a file |
| privKey, err := certutil.PrivateKeyFromFile(privateKeyPath) |
| if err != nil { |
| return nil, errors.Wrapf(err, "couldn't load the private key file %s", privateKeyPath) |
| } |
| |
| // Allow RSA format only |
| var key *rsa.PrivateKey |
| switch k := privKey.(type) { |
| case *rsa.PrivateKey: |
| key = k |
| default: |
| return nil, errors.Errorf("the private key file %s isn't in RSA format", privateKeyPath) |
| } |
| |
| return key, nil |
| } |
| |
| // TryLoadCSRAndKeyFromDisk tries to load the CSR and key from the disk |
| func TryLoadCSRAndKeyFromDisk(pkiPath, name string) (*x509.CertificateRequest, *rsa.PrivateKey, error) { |
| csrPath := pathForCSR(pkiPath, name) |
| |
| csr, err := CertificateRequestFromFile(csrPath) |
| if err != nil { |
| return nil, nil, errors.Wrapf(err, "couldn't load the certificate request %s", csrPath) |
| } |
| |
| key, err := TryLoadKeyFromDisk(pkiPath, name) |
| if err != nil { |
| return nil, nil, errors.Wrap(err, "couldn't load key file") |
| } |
| |
| return csr, key, nil |
| } |
| |
| // TryLoadPrivatePublicKeyFromDisk tries to load the key from the disk and validates that it is valid |
| func TryLoadPrivatePublicKeyFromDisk(pkiPath, name string) (*rsa.PrivateKey, *rsa.PublicKey, error) { |
| privateKeyPath := pathForKey(pkiPath, name) |
| |
| // Parse the private key from a file |
| privKey, err := certutil.PrivateKeyFromFile(privateKeyPath) |
| if err != nil { |
| return nil, nil, errors.Wrapf(err, "couldn't load the private key file %s", privateKeyPath) |
| } |
| |
| publicKeyPath := pathForPublicKey(pkiPath, name) |
| |
| // Parse the public key from a file |
| pubKeys, err := certutil.PublicKeysFromFile(publicKeyPath) |
| if err != nil { |
| return nil, nil, errors.Wrapf(err, "couldn't load the public key file %s", publicKeyPath) |
| } |
| |
| // Allow RSA format only |
| k, ok := privKey.(*rsa.PrivateKey) |
| if !ok { |
| return nil, nil, errors.Errorf("the private key file %s isn't in RSA format", privateKeyPath) |
| } |
| |
| p := pubKeys[0].(*rsa.PublicKey) |
| |
| return k, p, nil |
| } |
| |
| // PathsForCertAndKey returns the paths for the certificate and key given the path and basename. |
| func PathsForCertAndKey(pkiPath, name string) (string, string) { |
| return pathForCert(pkiPath, name), pathForKey(pkiPath, name) |
| } |
| |
| func pathForCert(pkiPath, name string) string { |
| return filepath.Join(pkiPath, fmt.Sprintf("%s.crt", name)) |
| } |
| |
| func pathForKey(pkiPath, name string) string { |
| return filepath.Join(pkiPath, fmt.Sprintf("%s.key", name)) |
| } |
| |
| func pathForPublicKey(pkiPath, name string) string { |
| return filepath.Join(pkiPath, fmt.Sprintf("%s.pub", name)) |
| } |
| |
| func pathForCSR(pkiPath, name string) string { |
| return filepath.Join(pkiPath, fmt.Sprintf("%s.csr", name)) |
| } |
| |
| // GetAPIServerAltNames builds an AltNames object for to be used when generating apiserver certificate |
| func GetAPIServerAltNames(cfg *kubeadmapi.InitConfiguration) (*certutil.AltNames, error) { |
| // advertise address |
| advertiseAddress := net.ParseIP(cfg.LocalAPIEndpoint.AdvertiseAddress) |
| if advertiseAddress == nil { |
| return nil, errors.Errorf("error parsing LocalAPIEndpoint AdvertiseAddress %v: is not a valid textual representation of an IP address", |
| cfg.LocalAPIEndpoint.AdvertiseAddress) |
| } |
| |
| // internal IP address for the API server |
| _, svcSubnet, err := net.ParseCIDR(cfg.Networking.ServiceSubnet) |
| if err != nil { |
| return nil, errors.Wrapf(err, "error parsing CIDR %q", cfg.Networking.ServiceSubnet) |
| } |
| |
| internalAPIServerVirtualIP, err := ipallocator.GetIndexedIP(svcSubnet, 1) |
| if err != nil { |
| return nil, errors.Wrapf(err, "unable to get first IP address from the given CIDR (%s)", svcSubnet.String()) |
| } |
| |
| // create AltNames with defaults DNSNames/IPs |
| altNames := &certutil.AltNames{ |
| DNSNames: []string{ |
| cfg.NodeRegistration.Name, |
| "kubernetes", |
| "kubernetes.default", |
| "kubernetes.default.svc", |
| fmt.Sprintf("kubernetes.default.svc.%s", cfg.Networking.DNSDomain), |
| }, |
| IPs: []net.IP{ |
| internalAPIServerVirtualIP, |
| advertiseAddress, |
| }, |
| } |
| |
| // add cluster controlPlaneEndpoint if present (dns or ip) |
| if len(cfg.ControlPlaneEndpoint) > 0 { |
| if host, _, err := kubeadmutil.ParseHostPort(cfg.ControlPlaneEndpoint); err == nil { |
| if ip := net.ParseIP(host); ip != nil { |
| altNames.IPs = append(altNames.IPs, ip) |
| } else { |
| altNames.DNSNames = append(altNames.DNSNames, host) |
| } |
| } else { |
| return nil, errors.Wrapf(err, "error parsing cluster controlPlaneEndpoint %q", cfg.ControlPlaneEndpoint) |
| } |
| } |
| |
| appendSANsToAltNames(altNames, cfg.APIServer.CertSANs, kubeadmconstants.APIServerCertName) |
| |
| return altNames, nil |
| } |
| |
| // GetEtcdAltNames builds an AltNames object for generating the etcd server certificate. |
| // `advertise address` and localhost are included in the SAN since this is the interfaces the etcd static pod listens on. |
| // The user can override the listen address with `Etcd.ExtraArgs` and add SANs with `Etcd.ServerCertSANs`. |
| func GetEtcdAltNames(cfg *kubeadmapi.InitConfiguration) (*certutil.AltNames, error) { |
| // advertise address |
| advertiseAddress := net.ParseIP(cfg.LocalAPIEndpoint.AdvertiseAddress) |
| if advertiseAddress == nil { |
| return nil, errors.Errorf("error parsing LocalAPIEndpoint AdvertiseAddress %q: is not a valid textual representation of an IP address", cfg.LocalAPIEndpoint.AdvertiseAddress) |
| } |
| |
| // create AltNames with defaults DNSNames/IPs |
| altNames := &certutil.AltNames{ |
| DNSNames: []string{cfg.NodeRegistration.Name, "localhost"}, |
| IPs: []net.IP{advertiseAddress, net.IPv4(127, 0, 0, 1), net.IPv6loopback}, |
| } |
| |
| if cfg.Etcd.Local != nil { |
| appendSANsToAltNames(altNames, cfg.Etcd.Local.ServerCertSANs, kubeadmconstants.EtcdServerCertName) |
| } |
| |
| return altNames, nil |
| } |
| |
| // GetEtcdPeerAltNames builds an AltNames object for generating the etcd peer certificate. |
| // Hostname and `API.AdvertiseAddress` are included if the user chooses to promote the single node etcd cluster into a multi-node one (stacked etcd). |
| // The user can override the listen address with `Etcd.ExtraArgs` and add SANs with `Etcd.PeerCertSANs`. |
| func GetEtcdPeerAltNames(cfg *kubeadmapi.InitConfiguration) (*certutil.AltNames, error) { |
| // advertise address |
| advertiseAddress := net.ParseIP(cfg.LocalAPIEndpoint.AdvertiseAddress) |
| if advertiseAddress == nil { |
| return nil, errors.Errorf("error parsing LocalAPIEndpoint AdvertiseAddress %v: is not a valid textual representation of an IP address", |
| cfg.LocalAPIEndpoint.AdvertiseAddress) |
| } |
| |
| // create AltNames with defaults DNSNames/IPs |
| altNames := &certutil.AltNames{ |
| DNSNames: []string{cfg.NodeRegistration.Name, "localhost"}, |
| IPs: []net.IP{advertiseAddress, net.IPv4(127, 0, 0, 1), net.IPv6loopback}, |
| } |
| |
| if cfg.Etcd.Local != nil { |
| appendSANsToAltNames(altNames, cfg.Etcd.Local.PeerCertSANs, kubeadmconstants.EtcdPeerCertName) |
| } |
| |
| return altNames, nil |
| } |
| |
| // appendSANsToAltNames parses SANs from as list of strings and adds them to altNames for use on a specific cert |
| // altNames is passed in with a pointer, and the struct is modified |
| // valid IP address strings are parsed and added to altNames.IPs as net.IP's |
| // RFC-1123 compliant DNS strings are added to altNames.DNSNames as strings |
| // certNames is used to print user facing warnings and should be the name of the cert the altNames will be used for |
| func appendSANsToAltNames(altNames *certutil.AltNames, SANs []string, certName string) { |
| for _, altname := range SANs { |
| if ip := net.ParseIP(altname); ip != nil { |
| altNames.IPs = append(altNames.IPs, ip) |
| } else if len(validation.IsDNS1123Subdomain(altname)) == 0 { |
| altNames.DNSNames = append(altNames.DNSNames, altname) |
| } else { |
| fmt.Printf( |
| "[certificates] WARNING: '%s' was not added to the '%s' SAN, because it is not a valid IP or RFC-1123 compliant DNS entry\n", |
| altname, |
| certName, |
| ) |
| } |
| } |
| } |
| |
| // EncodeCSRPEM returns PEM-encoded CSR data |
| func EncodeCSRPEM(csr *x509.CertificateRequest) []byte { |
| block := pem.Block{ |
| Type: certutil.CertificateRequestBlockType, |
| Bytes: csr.Raw, |
| } |
| return pem.EncodeToMemory(&block) |
| } |
| |
| func parseCSRPEM(pemCSR []byte) (*x509.CertificateRequest, error) { |
| block, _ := pem.Decode(pemCSR) |
| if block == nil { |
| return nil, fmt.Errorf("data doesn't contain a valid certificate request") |
| } |
| |
| if block.Type != certutil.CertificateRequestBlockType { |
| var block *pem.Block |
| return nil, fmt.Errorf("expected block type %q, but PEM had type %v", certutil.CertificateRequestBlockType, block.Type) |
| } |
| |
| return x509.ParseCertificateRequest(block.Bytes) |
| } |
| |
| // CertificateRequestFromFile returns the CertificateRequest from a given PEM-encoded file. |
| // Returns an error if the file could not be read or if the CSR could not be parsed. |
| func CertificateRequestFromFile(file string) (*x509.CertificateRequest, error) { |
| pemBlock, err := ioutil.ReadFile(file) |
| if err != nil { |
| return nil, errors.Wrap(err, "failed to read file") |
| } |
| |
| csr, err := parseCSRPEM(pemBlock) |
| if err != nil { |
| return nil, fmt.Errorf("error reading certificate request file %s: %v", file, err) |
| } |
| return csr, nil |
| } |
| |
| // NewCSR creates a new CSR |
| func NewCSR(cfg certutil.Config, key crypto.Signer) (*x509.CertificateRequest, error) { |
| template := &x509.CertificateRequest{ |
| Subject: pkix.Name{ |
| CommonName: cfg.CommonName, |
| Organization: cfg.Organization, |
| }, |
| DNSNames: cfg.AltNames.DNSNames, |
| IPAddresses: cfg.AltNames.IPs, |
| } |
| |
| csrBytes, err := x509.CreateCertificateRequest(cryptorand.Reader, template, key) |
| |
| if err != nil { |
| return nil, errors.Wrap(err, "failed to create a CSR") |
| } |
| |
| return x509.ParseCertificateRequest(csrBytes) |
| } |