322 lines
9.4 KiB
Go
322 lines
9.4 KiB
Go
package cert
|
|
|
|
import (
|
|
"crypto"
|
|
"crypto/ecdsa"
|
|
"crypto/elliptic"
|
|
"crypto/rand"
|
|
"crypto/rsa"
|
|
"crypto/x509"
|
|
"crypto/x509/pkix"
|
|
"encoding/pem"
|
|
"errors"
|
|
"fmt"
|
|
"math"
|
|
"math/big"
|
|
"net"
|
|
"path/filepath"
|
|
"time"
|
|
|
|
certutil "k8s.io/client-go/util/cert"
|
|
"k8s.io/client-go/util/keyutil"
|
|
"k8s.io/klog/v2"
|
|
"k8s.io/kube-openapi/pkg/util/sets"
|
|
|
|
"github.com/karmada-io/karmada/pkg/karmadactl/cmdinit/options"
|
|
globaloptions "github.com/karmada-io/karmada/pkg/karmadactl/options"
|
|
)
|
|
|
|
const (
|
|
// certificateBlockType is a possible value for pem.Block.Type.
|
|
certificateBlockType = "CERTIFICATE"
|
|
rsaKeySize = 2048
|
|
// Duration365d Certificate validity period
|
|
Duration365d = time.Hour * 24 * 365
|
|
)
|
|
|
|
// NewPrivateKey returns a new private key.
|
|
var NewPrivateKey = GeneratePrivateKey
|
|
|
|
// GeneratePrivateKey Generate CA Private Key
|
|
func GeneratePrivateKey(keyType x509.PublicKeyAlgorithm) (crypto.Signer, error) {
|
|
if keyType == x509.ECDSA {
|
|
return ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
|
}
|
|
|
|
return rsa.GenerateKey(rand.Reader, rsaKeySize)
|
|
}
|
|
|
|
// CertsConfig is a wrapper around certutil.Config extending it with PublicKeyAlgorithm.
|
|
type CertsConfig struct {
|
|
certutil.Config
|
|
NotAfter *time.Time
|
|
PublicKeyAlgorithm x509.PublicKeyAlgorithm
|
|
}
|
|
|
|
// EncodeCertPEM returns PEM-encoded certificate data
|
|
func EncodeCertPEM(cert *x509.Certificate) []byte {
|
|
block := pem.Block{
|
|
Type: certificateBlockType,
|
|
Bytes: cert.Raw,
|
|
}
|
|
return pem.EncodeToMemory(&block)
|
|
}
|
|
|
|
// NewCertificateAuthority creates new certificate and private key for the certificate authority
|
|
func NewCertificateAuthority(config *CertsConfig) (*x509.Certificate, crypto.Signer, error) {
|
|
key, err := NewPrivateKey(config.PublicKeyAlgorithm)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("unable to create private key while generating CA certificate %v", err)
|
|
}
|
|
|
|
cert, err := certutil.NewSelfSignedCACert(config.Config, key)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("unable to create self-signed CA certificate %v", err)
|
|
}
|
|
|
|
return cert, key, nil
|
|
}
|
|
|
|
// NewCACertAndKey The public and private keys of the root certificate are returned
|
|
func NewCACertAndKey(cn string) (*x509.Certificate, *crypto.Signer, error) {
|
|
certCfg := &CertsConfig{Config: certutil.Config{
|
|
CommonName: cn,
|
|
},
|
|
}
|
|
caCert, caKey, err := NewCertificateAuthority(certCfg)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("failure while generating CA certificate and key: %v", err)
|
|
}
|
|
|
|
return caCert, &caKey, nil
|
|
}
|
|
|
|
// NewSignedCert creates a signed certificate using the given CA certificate and key
|
|
func NewSignedCert(cfg *CertsConfig, key crypto.Signer, caCert *x509.Certificate, caKey crypto.Signer, isCA bool) (*x509.Certificate, error) {
|
|
serial, err := rand.Int(rand.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")
|
|
}
|
|
|
|
keyUsage := x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature
|
|
if isCA {
|
|
keyUsage |= x509.KeyUsageCertSign
|
|
}
|
|
|
|
RemoveDuplicateAltNames(&cfg.AltNames)
|
|
|
|
notAfter := time.Now().Add(Duration365d).UTC()
|
|
if cfg.NotAfter != nil {
|
|
notAfter = *cfg.NotAfter
|
|
}
|
|
|
|
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: notAfter,
|
|
KeyUsage: keyUsage,
|
|
ExtKeyUsage: cfg.Usages,
|
|
BasicConstraintsValid: true,
|
|
IsCA: isCA,
|
|
}
|
|
certDERBytes, err := x509.CreateCertificate(rand.Reader, &certTmpl, caCert, key.Public(), caKey)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return x509.ParseCertificate(certDERBytes)
|
|
}
|
|
|
|
// RemoveDuplicateAltNames removes duplicate items in altNames.
|
|
func RemoveDuplicateAltNames(altNames *certutil.AltNames) {
|
|
if altNames == nil {
|
|
return
|
|
}
|
|
|
|
if altNames.DNSNames != nil {
|
|
altNames.DNSNames = sets.NewString(altNames.DNSNames...).List()
|
|
}
|
|
|
|
ipsKeys := make(map[string]struct{})
|
|
var ips []net.IP
|
|
for _, one := range altNames.IPs {
|
|
if _, ok := ipsKeys[one.String()]; !ok {
|
|
ipsKeys[one.String()] = struct{}{}
|
|
ips = append(ips, one)
|
|
}
|
|
}
|
|
altNames.IPs = ips
|
|
}
|
|
|
|
// NewCertAndKey creates new certificate and key by passing the certificate authority certificate and key
|
|
func NewCertAndKey(caCert *x509.Certificate, caKey crypto.Signer, config *CertsConfig) (*x509.Certificate, crypto.Signer, error) {
|
|
if len(config.Usages) == 0 {
|
|
return nil, nil, errors.New("must specify at least one ExtKeyUsage")
|
|
}
|
|
|
|
key, err := NewPrivateKey(config.PublicKeyAlgorithm)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("unable to create private key %v", err)
|
|
}
|
|
|
|
cert, err := NewSignedCert(config, key, caCert, caKey, false)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("unable to sign certificate. %v", err)
|
|
}
|
|
|
|
return cert, key, nil
|
|
}
|
|
|
|
// PathForKey returns the paths for the key given the path and basename.
|
|
func PathForKey(pkiPath, name string) string {
|
|
return filepath.Join(pkiPath, fmt.Sprintf("%s.key", name))
|
|
}
|
|
|
|
// PathForCert returns the paths for the certificate given the path and basename.
|
|
func PathForCert(pkiPath, name string) string {
|
|
return filepath.Join(pkiPath, fmt.Sprintf("%s.crt", name))
|
|
}
|
|
|
|
// 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 fmt.Errorf("unable to write certificate to file %v", err)
|
|
}
|
|
|
|
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 fmt.Errorf("unable to marshal private key to PEM %v", err)
|
|
}
|
|
if err := keyutil.WriteKey(privateKeyPath, encoded); err != nil {
|
|
return fmt.Errorf("unable to write private key to file %v", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// WriteCertAndKey Write certificate and key to file.
|
|
func WriteCertAndKey(pkiPath, pkiName string, ca *x509.Certificate, key *crypto.Signer) error {
|
|
if err := WriteKey(pkiPath, pkiName, *key); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := WriteCert(pkiPath, pkiName, ca); err != nil {
|
|
return err
|
|
}
|
|
|
|
klog.Infof("Generate %s certificate success.", pkiName)
|
|
return nil
|
|
}
|
|
|
|
// NewCertConfig create new CertConfig
|
|
func NewCertConfig(cn string, org []string, altNames certutil.AltNames, notAfter *time.Time) *CertsConfig {
|
|
return &CertsConfig{
|
|
Config: certutil.Config{
|
|
CommonName: cn,
|
|
Organization: org,
|
|
Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
|
|
AltNames: altNames,
|
|
},
|
|
NotAfter: notAfter,
|
|
}
|
|
}
|
|
|
|
// GenCerts Create CA certificate and sign etcd karmada certificate.
|
|
func GenCerts(pkiPath string, etcdServerCertCfg, etcdClientCertCfg, karmadaCertCfg, apiserverCertCfg, frontProxyClientCertCfg *CertsConfig) error {
|
|
caCert, caKey, err := NewCACertAndKey("karmada")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err = WriteCertAndKey(pkiPath, globaloptions.CaCertAndKeyName, caCert, caKey); err != nil {
|
|
return err
|
|
}
|
|
|
|
karmadaCert, karmadaKey, err := NewCertAndKey(caCert, *caKey, karmadaCertCfg)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err = WriteCertAndKey(pkiPath, options.KarmadaCertAndKeyName, karmadaCert, &karmadaKey); err != nil {
|
|
return err
|
|
}
|
|
|
|
apiserverCert, apiserverKey, err := NewCertAndKey(caCert, *caKey, apiserverCertCfg)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err = WriteCertAndKey(pkiPath, options.ApiserverCertAndKeyName, apiserverCert, &apiserverKey); err != nil {
|
|
return err
|
|
}
|
|
|
|
frontProxyCaCert, frontProxyCaKey, err := NewCACertAndKey("front-proxy-ca")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err = WriteCertAndKey(pkiPath, options.FrontProxyCaCertAndKeyName, frontProxyCaCert, frontProxyCaKey); err != nil {
|
|
return err
|
|
}
|
|
|
|
frontProxyClientCert, frontProxyClientKey, err := NewCertAndKey(frontProxyCaCert, *frontProxyCaKey, frontProxyClientCertCfg)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := WriteCertAndKey(pkiPath, options.FrontProxyClientCertAndKeyName, frontProxyClientCert, &frontProxyClientKey); err != nil {
|
|
return err
|
|
}
|
|
|
|
if etcdServerCertCfg == nil && etcdClientCertCfg == nil {
|
|
// use external etcd
|
|
return nil
|
|
}
|
|
return genEtcdCerts(pkiPath, etcdServerCertCfg, etcdClientCertCfg)
|
|
}
|
|
|
|
func genEtcdCerts(pkiPath string, etcdServerCertCfg, etcdClientCertCfg *CertsConfig) error {
|
|
etcdCaCert, etcdCaKey, err := NewCACertAndKey("etcd-ca")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err = WriteCertAndKey(pkiPath, options.EtcdCaCertAndKeyName, etcdCaCert, etcdCaKey); err != nil {
|
|
return err
|
|
}
|
|
|
|
etcdServerCert, etcdServerKey, err := NewCertAndKey(etcdCaCert, *etcdCaKey, etcdServerCertCfg)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err = WriteCertAndKey(pkiPath, options.EtcdServerCertAndKeyName, etcdServerCert, &etcdServerKey); err != nil {
|
|
return err
|
|
}
|
|
|
|
etcdClientCert, etcdClientKey, err := NewCertAndKey(etcdCaCert, *etcdCaKey, etcdClientCertCfg)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err = WriteCertAndKey(pkiPath, options.EtcdClientCertAndKeyName, etcdClientCert, &etcdClientKey); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|