123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584 |
- /*
- 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"
- "crypto/ecdsa"
- cryptorand "crypto/rand"
- "crypto/rsa"
- "crypto/x509"
- "crypto/x509/pkix"
- "encoding/pem"
- "fmt"
- "io/ioutil"
- "math"
- "math/big"
- "net"
- "os"
- "path/filepath"
- "time"
- "github.com/pkg/errors"
- "k8s.io/apimachinery/pkg/util/validation"
- certutil "k8s.io/client-go/util/cert"
- "k8s.io/client-go/util/keyutil"
- 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"
- )
- const (
- // PrivateKeyBlockType is a possible value for pem.Block.Type.
- PrivateKeyBlockType = "PRIVATE KEY"
- // PublicKeyBlockType is a possible value for pem.Block.Type.
- PublicKeyBlockType = "PUBLIC KEY"
- // CertificateBlockType is a possible value for pem.Block.Type.
- CertificateBlockType = "CERTIFICATE"
- // RSAPrivateKeyBlockType is a possible value for pem.Block.Type.
- RSAPrivateKeyBlockType = "RSA PRIVATE KEY"
- rsaKeySize = 2048
- )
- // NewCertificateAuthority creates new certificate and private key for the certificate authority
- func NewCertificateAuthority(config *certutil.Config) (*x509.Certificate, crypto.Signer, error) {
- key, err := NewPrivateKey()
- if err != nil {
- return nil, nil, errors.Wrap(err, "unable to create private key while generating CA certificate")
- }
- cert, err := certutil.NewSelfSignedCACert(*config, key)
- if err != nil {
- return nil, nil, errors.Wrap(err, "unable to create self-signed CA 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 crypto.Signer, config *certutil.Config) (*x509.Certificate, crypto.Signer, error) {
- key, err := NewPrivateKey()
- if err != nil {
- return nil, nil, errors.Wrap(err, "unable to create private key")
- }
- cert, err := 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, crypto.Signer, error) {
- key, err := 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 crypto.Signer) 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, 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 crypto.Signer) error {
- if key == nil {
- return errors.New("private key cannot be nil when writing to file")
- }
- privateKeyPath := pathForKey(pkiPath, name)
- encoded, err := keyutil.MarshalPrivateKeyToPEM(key)
- if err != nil {
- return errors.Wrapf(err, "unable to marshal private key to PEM")
- }
- if err := keyutil.WriteKey(privateKeyPath, encoded); 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 crypto.PublicKey) error {
- if key == nil {
- return errors.New("public key cannot be nil when writing to file")
- }
- publicKeyBytes, err := EncodePublicKeyPEM(key)
- if err != nil {
- return err
- }
- publicKeyPath := pathForPublicKey(pkiPath, name)
- if err := keyutil.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, crypto.Signer, 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) (crypto.Signer, error) {
- privateKeyPath := pathForKey(pkiPath, name)
- // Parse the private key from a file
- privKey, err := keyutil.PrivateKeyFromFile(privateKeyPath)
- if err != nil {
- return nil, errors.Wrapf(err, "couldn't load the private key file %s", privateKeyPath)
- }
- // Allow RSA and ECDSA formats only
- var key crypto.Signer
- switch k := privKey.(type) {
- case *rsa.PrivateKey:
- key = k
- case *ecdsa.PrivateKey:
- key = k
- default:
- return nil, errors.Errorf("the private key file %s is neither in RSA nor ECDSA format", privateKeyPath)
- }
- return key, nil
- }
- // TryLoadCSRAndKeyFromDisk tries to load the CSR and key from the disk
- func TryLoadCSRAndKeyFromDisk(pkiPath, name string) (*x509.CertificateRequest, crypto.Signer, 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 := keyutil.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 := keyutil.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) {
- return getAltNames(cfg, kubeadmconstants.EtcdServerCertName)
- }
- // 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) {
- return getAltNames(cfg, kubeadmconstants.EtcdPeerCertName)
- }
- // getAltNames builds an AltNames object with the cfg and certName.
- func getAltNames(cfg *kubeadmapi.InitConfiguration, certName string) (*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 {
- if certName == kubeadmconstants.EtcdServerCertName {
- appendSANsToAltNames(altNames, cfg.Etcd.Local.ServerCertSANs, kubeadmconstants.EtcdServerCertName)
- } else if certName == kubeadmconstants.EtcdPeerCertName {
- 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
- // RFC-1123 compliant wildcard 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 if len(validation.IsWildcardDNS1123Subdomain(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, errors.New("data doesn't contain a valid certificate request")
- }
- if block.Type != certutil.CertificateRequestBlockType {
- var block *pem.Block
- return nil, errors.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, errors.Wrapf(err, "error reading certificate request file %s", file)
- }
- 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)
- }
- // EncodeCertPEM returns PEM-endcoded certificate data
- func EncodeCertPEM(cert *x509.Certificate) []byte {
- block := pem.Block{
- Type: CertificateBlockType,
- Bytes: cert.Raw,
- }
- return pem.EncodeToMemory(&block)
- }
- // EncodePublicKeyPEM returns PEM-encoded public data
- func EncodePublicKeyPEM(key crypto.PublicKey) ([]byte, error) {
- der, err := x509.MarshalPKIXPublicKey(key)
- if err != nil {
- return []byte{}, err
- }
- block := pem.Block{
- Type: PublicKeyBlockType,
- Bytes: der,
- }
- return pem.EncodeToMemory(&block), nil
- }
- // NewPrivateKey creates an RSA private key
- func NewPrivateKey() (crypto.Signer, error) {
- return rsa.GenerateKey(cryptorand.Reader, rsaKeySize)
- }
- // NewSignedCert creates a signed certificate using the given CA certificate and key
- func NewSignedCert(cfg *certutil.Config, key crypto.Signer, caCert *x509.Certificate, caKey crypto.Signer) (*x509.Certificate, error) {
- serial, err := cryptorand.Int(cryptorand.Reader, new(big.Int).SetInt64(math.MaxInt64))
- if err != nil {
- return nil, err
- }
- if len(cfg.CommonName) == 0 {
- return nil, errors.New("must specify a CommonName")
- }
- if len(cfg.Usages) == 0 {
- return nil, errors.New("must specify at least one ExtKeyUsage")
- }
- certTmpl := x509.Certificate{
- Subject: pkix.Name{
- CommonName: cfg.CommonName,
- Organization: cfg.Organization,
- },
- DNSNames: cfg.AltNames.DNSNames,
- IPAddresses: cfg.AltNames.IPs,
- SerialNumber: serial,
- NotBefore: caCert.NotBefore,
- NotAfter: time.Now().Add(kubeadmconstants.CertificateValidity).UTC(),
- KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
- ExtKeyUsage: cfg.Usages,
- }
- certDERBytes, err := x509.CreateCertificate(cryptorand.Reader, &certTmpl, caCert, key.Public(), caKey)
- if err != nil {
- return nil, err
- }
- return x509.ParseCertificate(certDERBytes)
- }
|