// Copyright 2019 Yunion // // 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 certs import ( "crypto" "crypto/x509" "fmt" "yunion.io/x/pkg/errors" certutil "yunion.io/x/onecloud/pkg/util/tls/cert" pkiutil "yunion.io/x/onecloud/pkg/util/tls/pki" ) type certKeyLocation struct { pkiDir string caBaseName string baseName string uxName string } // validateSignedCert tries to load a x509 certificate and private key from pkiDir and validates // that the cert is signed by a given CA func validateSignedCert(l certKeyLocation) error { // Try to load CA caCert, err := pkiutil.TryLoadCertFromDisk(l.pkiDir, l.caBaseName) if err != nil { return errors.Wrapf(err, "failure loading certificate authority for %s", l.uxName) } return validateSignedCertWithCA(l, caCert) } // validateSignedCertWithCA tries to load a certificate and validate it with the given caCert func validateSignedCertWithCA(l certKeyLocation, caCert *x509.Certificate) error { // Try to load key and signed certificate signedCert, _, err := pkiutil.TryLoadCertAndKeyFromDisk(l.pkiDir, l.baseName) if err != nil { return errors.Wrapf(err, "failure loading certificate for %s", l.uxName) } // Check if the cert is signed by the CA if err := signedCert.CheckSignatureFrom(caCert); err != nil { return errors.Wrapf(err, "certificate %s is not signed by corresponding CA", l.uxName) } return nil } // validatePrivatePublicKey tries to load a private key from pkiDir func validatePrivatePublicKey(l certKeyLocation) error { // Try to load key _, _, err := pkiutil.TryLoadPrivatePublicKeyFromDisk(l.pkiDir, l.baseName) if err != nil { return errors.Wrapf(err, "failure loading key for %s", l.uxName) } return nil } // writeCertificateAuthorithyFilesIfNotExist write a new certificate Authority to the given path. // If there already is a certificate file at the given path; tries to load it and check if the values in the // existing and the expected certificate equals. If they do; will just skip writing the file as it's up-to-date, // otherwise this function returns an error. func writeCertificateAuthorithyFilesIfNotExist(pkiDir string, baseName string, caCert *x509.Certificate, caKey crypto.Signer) error { // If cert or key exists, we should try to load them if pkiutil.CertOrKeyExist(pkiDir, baseName) { // Try to load .crt and .key from the PKI directory caCert, _, err := pkiutil.TryLoadCertAndKeyFromDisk(pkiDir, baseName) if err != nil { return errors.Wrapf(err, "failure loading %s certificate", baseName) } // Check if the existing cert is a CA if !caCert.IsCA { return errors.Errorf("certificate %s is not a CA", baseName) } // kubeadm doesn't validate the existing certificate Authority more than this; // Basically, if we find a certificate file with the same path; and it is a CA // kubeadm thinks those files are equal and doesn't bother writing a new file fmt.Printf("[certs] Using the existing %q certificate and key\n", baseName) } else { // Write .crt and .key files to disk fmt.Printf("[certs] Generating %q certificate and key\n", baseName) if err := pkiutil.WriteCertAndKey(pkiDir, baseName, caCert, caKey); err != nil { return errors.Wrapf(err, "failure while saving %s certificate and key", baseName) } } return nil } // writeCertificateFilesIfNotExist write a new certificate to the given path. // If there already is a certificate file at the given path; kubeadm tries to load it and check if the values in the // existing and the expected certificate equals. If they do; kubeadm will just skip writing the file as it's up-to-date, // otherwise this function returns an error. func writeCertificateFilesIfNotExist(pkiDir string, baseName string, signingCert *x509.Certificate, cert *x509.Certificate, key crypto.Signer, cfg *certutil.Config) error { // Checks if the signed certificate exists in the PKI directory if pkiutil.CertOrKeyExist(pkiDir, baseName) { // Try to load signed certificate .crt and .key from the PKI directory signedCert, _, err := pkiutil.TryLoadCertAndKeyFromDisk(pkiDir, baseName) if err != nil { return errors.Wrapf(err, "failure loading %s certificate", baseName) } // Check if the existing cert is signed by the given CA if err := signedCert.CheckSignatureFrom(signingCert); err != nil { return errors.Errorf("certificate %s is not signed by corresponding CA", baseName) } // Check if the certificate has the correct attributes if err := validateCertificateWithConfig(signedCert, baseName, cfg); err != nil { return err } fmt.Printf("[certs] Using the existing %q certificate and key\n", baseName) } else { // Write .crt and .key files to disk fmt.Printf("[certs] Generating %q certificate and key\n", baseName) if err := pkiutil.WriteCertAndKey(pkiDir, baseName, cert, key); err != nil { return errors.Wrapf(err, "failure while saving %s certificate and key", baseName) } if pkiutil.HasServerAuth(cert) { fmt.Printf("[certs] %s serving cert is signed for DNS names %v and IPs %v\n", baseName, cert.DNSNames, cert.IPAddresses) } } return nil } // validateCertificateWithConfig makes sure that a given certificate is valid at // least for the SANs defined in the configuration. func validateCertificateWithConfig(cert *x509.Certificate, baseName string, cfg *certutil.Config) error { for _, dnsName := range cfg.AltNames.DNSNames { if err := cert.VerifyHostname(dnsName); err != nil { return errors.Wrapf(err, "certificate %s is invalid", baseName) } } for _, ipAddress := range cfg.AltNames.IPs { if err := cert.VerifyHostname(ipAddress.String()); err != nil { return errors.Wrapf(err, "certificate %s is invalid", baseName) } } return nil }