pkg/helpers/pki.go (305 lines of code) (raw):

// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. package helpers import ( "bytes" "crypto/rand" "crypto/rsa" "crypto/x509" "crypto/x509/pkix" "encoding/pem" "errors" "fmt" "math/big" "net" "time" "golang.org/x/sync/errgroup" log "github.com/sirupsen/logrus" ) const ( // ValidityDuration specifies the duration an TLS certificate is valid ValidityDuration = time.Hour * 24 * 365 * 30 ) // PkiParams is used when we create Pki type PkiParams struct { ExtraFQDNs []string ExtraIPs []net.IP ClusterDomain string CaPair *PkiKeyCertPair MasterCount int PkiKeySize int } // PkiKeyCertPairParams is the params when we create the pki key cert pair. type PkiKeyCertPairParams struct { CommonName string PkiKeySize int } // PkiKeyCertPair represents an PKI public and private cert pair type PkiKeyCertPair struct { CertificatePem string PrivateKeyPem string } // CreatePkiKeyCertPair generates a pair of PKI certificate and private key func CreatePkiKeyCertPair(params PkiKeyCertPairParams) (*PkiKeyCertPair, error) { certPram := certParams{ commonName: params.CommonName, caCertificate: nil, caPrivateKey: nil, isEtcd: false, isServer: false, extraFQDNs: nil, extraIPs: nil, organization: nil, keySize: params.PkiKeySize, } caCertificate, caPrivateKey, err := createCertificate(certPram) if err != nil { return nil, err } caPair := &PkiKeyCertPair{CertificatePem: string(certificateToPem(caCertificate.Raw)), PrivateKeyPem: string(privateKeyToPem(caPrivateKey))} return caPair, nil } // CreatePki creates PKI certificates func CreatePki(pkiParams PkiParams) (*PkiKeyCertPair, *PkiKeyCertPair, *PkiKeyCertPair, *PkiKeyCertPair, *PkiKeyCertPair, []*PkiKeyCertPair, error) { start := time.Now() defer func(s time.Time) { log.Debugf("pki: PKI asset creation took %s", time.Since(s)) }(start) pkiParams.ExtraFQDNs = append(pkiParams.ExtraFQDNs, "kubernetes") pkiParams.ExtraFQDNs = append(pkiParams.ExtraFQDNs, "kubernetes.default") pkiParams.ExtraFQDNs = append(pkiParams.ExtraFQDNs, "kubernetes.default.svc") pkiParams.ExtraFQDNs = append(pkiParams.ExtraFQDNs, fmt.Sprintf("kubernetes.default.svc.%s", pkiParams.ClusterDomain)) pkiParams.ExtraFQDNs = append(pkiParams.ExtraFQDNs, "kubernetes.kube-system") pkiParams.ExtraFQDNs = append(pkiParams.ExtraFQDNs, "kubernetes.kube-system.svc") pkiParams.ExtraFQDNs = append(pkiParams.ExtraFQDNs, fmt.Sprintf("kubernetes.kube-system.svc.%s", pkiParams.ClusterDomain)) var ( caCertificate *x509.Certificate caPrivateKey *rsa.PrivateKey apiServerCertificate *x509.Certificate apiServerPrivateKey *rsa.PrivateKey clientCertificate *x509.Certificate clientPrivateKey *rsa.PrivateKey kubeConfigCertificate *x509.Certificate kubeConfigPrivateKey *rsa.PrivateKey etcdServerCertificate *x509.Certificate etcdServerPrivateKey *rsa.PrivateKey etcdClientCertificate *x509.Certificate etcdClientPrivateKey *rsa.PrivateKey etcdPeerCertPairs []*PkiKeyCertPair ) var group errgroup.Group var err error caCertificate, err = pemToCertificate(pkiParams.CaPair.CertificatePem) if err != nil { return nil, nil, nil, nil, nil, nil, err } caPrivateKey, err = pemToKey(pkiParams.CaPair.PrivateKeyPem) if err != nil { return nil, nil, nil, nil, nil, nil, err } group.Go(func() (err error) { certPram := certParams{ commonName: "apiserver", caCertificate: caCertificate, caPrivateKey: caPrivateKey, isEtcd: false, isServer: true, extraFQDNs: pkiParams.ExtraFQDNs, extraIPs: pkiParams.ExtraIPs, organization: nil, keySize: pkiParams.PkiKeySize, } apiServerCertificate, apiServerPrivateKey, err = createCertificate(certPram) return err }) group.Go(func() (err error) { organization := make([]string, 1) organization[0] = "system:masters" certPram := certParams{ commonName: "client", caCertificate: caCertificate, caPrivateKey: caPrivateKey, isEtcd: false, isServer: false, extraFQDNs: nil, extraIPs: nil, organization: organization, keySize: pkiParams.PkiKeySize, } clientCertificate, clientPrivateKey, err = createCertificate(certPram) return err }) group.Go(func() (err error) { organization := make([]string, 1) organization[0] = "system:masters" certPram := certParams{ commonName: "client", caCertificate: caCertificate, caPrivateKey: caPrivateKey, isEtcd: false, isServer: false, extraFQDNs: nil, extraIPs: nil, organization: organization, keySize: pkiParams.PkiKeySize, } kubeConfigCertificate, kubeConfigPrivateKey, err = createCertificate(certPram) return err }) group.Go(func() (err error) { certPram := certParams{ commonName: "etcdserver", caCertificate: caCertificate, caPrivateKey: caPrivateKey, isEtcd: true, isServer: true, extraFQDNs: nil, extraIPs: pkiParams.ExtraIPs, organization: nil, keySize: pkiParams.PkiKeySize, } etcdServerCertificate, etcdServerPrivateKey, err = createCertificate(certPram) return err }) group.Go(func() (err error) { certPram := certParams{ commonName: "etcdclient", caCertificate: caCertificate, caPrivateKey: caPrivateKey, isEtcd: true, isServer: false, extraFQDNs: nil, extraIPs: pkiParams.ExtraIPs, organization: nil, keySize: pkiParams.PkiKeySize, } etcdClientCertificate, etcdClientPrivateKey, err = createCertificate(certPram) return err }) etcdPeerCertPairs = make([]*PkiKeyCertPair, pkiParams.MasterCount) for i := 0; i < pkiParams.MasterCount; i++ { i := i group.Go(func() (err error) { certPram := certParams{ commonName: "etcdpeer", caCertificate: caCertificate, caPrivateKey: caPrivateKey, isEtcd: true, isServer: false, extraFQDNs: nil, extraIPs: pkiParams.ExtraIPs, organization: nil, keySize: pkiParams.PkiKeySize, } etcdPeerCertificate, etcdPeerPrivateKey, err := createCertificate(certPram) etcdPeerCertPairs[i] = &PkiKeyCertPair{CertificatePem: string(certificateToPem(etcdPeerCertificate.Raw)), PrivateKeyPem: string(privateKeyToPem(etcdPeerPrivateKey))} return err }) } if err := group.Wait(); err != nil { return nil, nil, nil, nil, nil, nil, err } return &PkiKeyCertPair{CertificatePem: string(certificateToPem(apiServerCertificate.Raw)), PrivateKeyPem: string(privateKeyToPem(apiServerPrivateKey))}, &PkiKeyCertPair{CertificatePem: string(certificateToPem(clientCertificate.Raw)), PrivateKeyPem: string(privateKeyToPem(clientPrivateKey))}, &PkiKeyCertPair{CertificatePem: string(certificateToPem(kubeConfigCertificate.Raw)), PrivateKeyPem: string(privateKeyToPem(kubeConfigPrivateKey))}, &PkiKeyCertPair{CertificatePem: string(certificateToPem(etcdServerCertificate.Raw)), PrivateKeyPem: string(privateKeyToPem(etcdServerPrivateKey))}, &PkiKeyCertPair{CertificatePem: string(certificateToPem(etcdClientCertificate.Raw)), PrivateKeyPem: string(privateKeyToPem(etcdClientPrivateKey))}, etcdPeerCertPairs, nil } type certParams struct { commonName string caCertificate *x509.Certificate caPrivateKey *rsa.PrivateKey isEtcd bool isServer bool extraFQDNs []string extraIPs []net.IP organization []string keySize int } func createCertificate(options certParams) (*x509.Certificate, *rsa.PrivateKey, error) { var err error isCA := (options.caCertificate == nil) now := time.Now() template := x509.Certificate{ Subject: pkix.Name{CommonName: options.commonName}, NotBefore: now, NotAfter: now.Add(ValidityDuration), KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, BasicConstraintsValid: true, } if options.organization != nil { template.Subject.Organization = options.organization } if isCA { template.KeyUsage |= x509.KeyUsageCertSign template.IsCA = isCA } else if options.isEtcd { if options.commonName == "etcdServer" { template.IPAddresses = options.extraIPs template.ExtKeyUsage = append(template.ExtKeyUsage, x509.ExtKeyUsageServerAuth) } else if options.commonName == "etcdClient" { template.IPAddresses = options.extraIPs template.ExtKeyUsage = append(template.ExtKeyUsage, x509.ExtKeyUsageClientAuth) } else { template.IPAddresses = options.extraIPs template.ExtKeyUsage = append(template.ExtKeyUsage, x509.ExtKeyUsageServerAuth) template.ExtKeyUsage = append(template.ExtKeyUsage, x509.ExtKeyUsageClientAuth) } } else if options.isServer { template.DNSNames = options.extraFQDNs template.IPAddresses = options.extraIPs template.ExtKeyUsage = append(template.ExtKeyUsage, x509.ExtKeyUsageServerAuth) } else { template.ExtKeyUsage = append(template.ExtKeyUsage, x509.ExtKeyUsageClientAuth) } snMax := new(big.Int).Lsh(big.NewInt(1), 128) template.SerialNumber, err = rand.Int(rand.Reader, snMax) if err != nil { return nil, nil, err } privateKey, _ := rsa.GenerateKey(rand.Reader, options.keySize) var privateKeyToUse *rsa.PrivateKey var certificateToUse *x509.Certificate if !isCA { privateKeyToUse = options.caPrivateKey certificateToUse = options.caCertificate } else { privateKeyToUse = privateKey certificateToUse = &template } certDerBytes, err := x509.CreateCertificate(rand.Reader, &template, certificateToUse, &privateKey.PublicKey, privateKeyToUse) if err != nil { return nil, nil, err } certificate, err := x509.ParseCertificate(certDerBytes) if err != nil { return nil, nil, err } return certificate, privateKey, nil } func certificateToPem(derBytes []byte) []byte { pemBlock := &pem.Block{ Type: "CERTIFICATE", Bytes: derBytes, } pemBuffer := bytes.Buffer{} _ = pem.Encode(&pemBuffer, pemBlock) return pemBuffer.Bytes() } func privateKeyToPem(privateKey *rsa.PrivateKey) []byte { pemBlock := &pem.Block{ Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(privateKey), } pemBuffer := bytes.Buffer{} _ = pem.Encode(&pemBuffer, pemBlock) return pemBuffer.Bytes() } func pemToCertificate(raw string) (*x509.Certificate, error) { cpb, _ := pem.Decode([]byte(raw)) if cpb == nil { return nil, errors.New("The raw pem is not a valid PEM formatted block") } return x509.ParseCertificate(cpb.Bytes) } func pemToKey(raw string) (*rsa.PrivateKey, error) { kpb, _ := pem.Decode([]byte(raw)) if kpb == nil { return nil, errors.New("The raw pem is not a valid PEM formatted block") } return x509.ParsePKCS1PrivateKey(kpb.Bytes) }