github.com/verrazzano/verrazzano@v1.7.0/cluster-operator/internal/certificate/certificate.go (about)

     1  // Copyright (c) 2022, 2023, Oracle and/or its affiliates.
     2  // Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
     3  
     4  package certificate
     5  
     6  import (
     7  	"bytes"
     8  	"context"
     9  	cryptorand "crypto/rand"
    10  	"crypto/rsa"
    11  	"crypto/x509"
    12  	"crypto/x509/pkix"
    13  	"encoding/pem"
    14  	"fmt"
    15  	"math/big"
    16  	"os"
    17  	"time"
    18  
    19  	"go.uber.org/zap"
    20  	v1 "k8s.io/api/core/v1"
    21  	"k8s.io/apimachinery/pkg/api/errors"
    22  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    23  	"k8s.io/client-go/kubernetes"
    24  	corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
    25  )
    26  
    27  const (
    28  	// WebhookName is the resource name for the Verrazzano cluster operator webhook
    29  	WebhookName = "verrazzano-cluster-operator-webhook"
    30  	OperatorCA  = "verrazzano-cluster-operator-ca"
    31  	OperatorTLS = "verrazzano-cluster-operator-tls"
    32  
    33  	// WebhookNamespace is the resource namespace for the Verrazzano cluster operator webhook
    34  	WebhookNamespace = "verrazzano-system"
    35  
    36  	CertKey = "tls.crt"
    37  	PrivKey = "tls.key"
    38  
    39  	certYearsValid = 3
    40  )
    41  
    42  // CreateWebhookCertificates creates the needed certificates for the validating webhook
    43  func CreateWebhookCertificates(log *zap.SugaredLogger, kubeClient kubernetes.Interface, certDir string) error {
    44  	commonName := fmt.Sprintf("%s.%s.svc", WebhookName, WebhookNamespace)
    45  	ca, caKey, err := createCACert(log, kubeClient, commonName)
    46  	if err != nil {
    47  		return err
    48  	}
    49  
    50  	serverPEM, serverKeyPEM, err := createTLSCert(log, kubeClient, commonName, ca, caKey)
    51  	if err != nil {
    52  		return err
    53  	}
    54  
    55  	log.Debugf("Creating certs dir %s", certDir)
    56  	if err := os.MkdirAll(certDir, 0666); err != nil {
    57  		log.Errorf("Mkdir error %v", err)
    58  		return err
    59  	}
    60  
    61  	if err := writeFile(log, fmt.Sprintf("%s/%s", certDir, CertKey), serverPEM); err != nil {
    62  		log.Errorf("Error writing cert file: %v", err)
    63  		return err
    64  	}
    65  
    66  	if err := writeFile(log, fmt.Sprintf("%s/%s", certDir, PrivKey), serverKeyPEM); err != nil {
    67  		log.Errorf("Error writing key file %v", err)
    68  		return err
    69  	}
    70  
    71  	return nil
    72  }
    73  
    74  func createTLSCert(log *zap.SugaredLogger, kubeClient kubernetes.Interface, commonName string, ca *x509.Certificate, caKey *rsa.PrivateKey) ([]byte, []byte, error) {
    75  	secretsClient := kubeClient.CoreV1().Secrets(WebhookNamespace)
    76  	existingSecret, err := secretsClient.Get(context.TODO(), OperatorTLS, metav1.GetOptions{})
    77  	if err == nil {
    78  		log.Infof("Secret %s exists, using...", OperatorTLS)
    79  		return existingSecret.Data[CertKey], existingSecret.Data[PrivKey], nil
    80  	}
    81  	if !errors.IsNotFound(err) {
    82  		return []byte{}, []byte{}, err
    83  	}
    84  
    85  	serialNumber, err := newSerialNumber()
    86  	if err != nil {
    87  		return []byte{}, []byte{}, err
    88  	}
    89  
    90  	// server cert config
    91  	cert := &x509.Certificate{
    92  		DNSNames:     []string{commonName},
    93  		SerialNumber: serialNumber,
    94  		Subject: pkix.Name{
    95  			CommonName: commonName,
    96  		},
    97  		NotBefore:    time.Now(),
    98  		NotAfter:     time.Now().AddDate(certYearsValid, 0, 0),
    99  		IsCA:         false,
   100  		SubjectKeyId: []byte{1, 2, 3, 4, 6},
   101  		ExtKeyUsage:  []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
   102  		KeyUsage:     x509.KeyUsageDigitalSignature,
   103  	}
   104  
   105  	// server private key
   106  	serverPrivKey, err := rsa.GenerateKey(cryptorand.Reader, 4096)
   107  	if err != nil {
   108  		return []byte{}, []byte{}, err
   109  	}
   110  
   111  	// sign the server cert
   112  	serverCertBytes, err := x509.CreateCertificate(cryptorand.Reader, cert, ca, &serverPrivKey.PublicKey, caKey)
   113  	if err != nil {
   114  		return []byte{}, []byte{}, err
   115  	}
   116  
   117  	return createTLSCertSecretIfNecesary(log, secretsClient, serverCertBytes, serverPrivKey)
   118  }
   119  
   120  func createTLSCertSecretIfNecesary(log *zap.SugaredLogger, secretsClient corev1.SecretInterface,
   121  	serverCertBytes []byte, serverPrivKey *rsa.PrivateKey) ([]byte, []byte, error) {
   122  	// PEM encode Server cert
   123  	serverPEM := new(bytes.Buffer)
   124  	_ = pem.Encode(serverPEM, &pem.Block{
   125  		Type:  "CERTIFICATE",
   126  		Bytes: serverCertBytes,
   127  	})
   128  
   129  	// PEM encode Server cert
   130  	serverKeyPEM := new(bytes.Buffer)
   131  	_ = pem.Encode(serverKeyPEM, &pem.Block{
   132  		Type:  "RSA PRIVATE KEY",
   133  		Bytes: x509.MarshalPKCS1PrivateKey(serverPrivKey),
   134  	})
   135  
   136  	serverPEMBytes := serverPEM.Bytes()
   137  
   138  	serverKeyPEMBytes := serverKeyPEM.Bytes()
   139  
   140  	var webhookCrt v1.Secret
   141  	webhookCrt.Namespace = WebhookNamespace
   142  	webhookCrt.Name = OperatorTLS
   143  	webhookCrt.Type = v1.SecretTypeTLS
   144  	webhookCrt.Data = make(map[string][]byte)
   145  	webhookCrt.Data[CertKey] = serverPEMBytes
   146  	webhookCrt.Data[PrivKey] = serverKeyPEMBytes
   147  
   148  	_, createError := secretsClient.Create(context.TODO(), &webhookCrt, metav1.CreateOptions{})
   149  	if createError != nil {
   150  		if errors.IsAlreadyExists(createError) {
   151  			log.Infof("Operator CA secret %s already exists, skipping", OperatorCA)
   152  			existingSecret, err := secretsClient.Get(context.TODO(), OperatorTLS, metav1.GetOptions{})
   153  			if err != nil {
   154  				return []byte{}, []byte{}, err
   155  			}
   156  			log.Infof("Secret %s exists, using...", OperatorTLS)
   157  			return existingSecret.Data[CertKey], existingSecret.Data[PrivKey], nil
   158  		}
   159  		return []byte{}, []byte{}, createError
   160  	}
   161  
   162  	return serverPEMBytes, serverKeyPEMBytes, nil
   163  }
   164  
   165  func createCACert(log *zap.SugaredLogger, kubeClient kubernetes.Interface, commonName string) (*x509.Certificate, *rsa.PrivateKey, error) {
   166  	secretsClient := kubeClient.CoreV1().Secrets(WebhookNamespace)
   167  	existingSecret, err := secretsClient.Get(context.TODO(), OperatorCA, metav1.GetOptions{})
   168  	if err == nil {
   169  		log.Infof("CA secret %s exists, using...", OperatorCA)
   170  		return decodeExistingSecretData(existingSecret)
   171  	}
   172  	if !errors.IsNotFound(err) {
   173  		return nil, nil, err
   174  	}
   175  
   176  	log.Infof("Creating CA secret %s", OperatorCA)
   177  	serialNumber, err := newSerialNumber()
   178  	if err != nil {
   179  		return nil, nil, err
   180  	}
   181  
   182  	// CA config
   183  	ca := &x509.Certificate{
   184  		DNSNames:     []string{commonName},
   185  		SerialNumber: serialNumber,
   186  		Subject: pkix.Name{
   187  			CommonName: commonName,
   188  		},
   189  		NotBefore:             time.Now(),
   190  		NotAfter:              time.Now().AddDate(certYearsValid, 0, 0),
   191  		IsCA:                  true,
   192  		ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
   193  		KeyUsage:              x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
   194  		BasicConstraintsValid: true,
   195  	}
   196  
   197  	// CA private key
   198  	caPrivKey, err := rsa.GenerateKey(cryptorand.Reader, 4096)
   199  	if err != nil {
   200  		return nil, nil, err
   201  	}
   202  
   203  	// Self signed CA certificate
   204  	caBytes, err := x509.CreateCertificate(cryptorand.Reader, ca, ca, &caPrivKey.PublicKey, caPrivKey)
   205  	if err != nil {
   206  		return nil, nil, err
   207  	}
   208  
   209  	return createCACertSecretIfNecessary(log, secretsClient, ca, caPrivKey, caBytes)
   210  }
   211  
   212  func createCACertSecretIfNecessary(log *zap.SugaredLogger, secretsClient corev1.SecretInterface, ca *x509.Certificate,
   213  	caPrivKey *rsa.PrivateKey, caBytes []byte) (*x509.Certificate, *rsa.PrivateKey, error) {
   214  	caPEMBytes, caKeyPEMBytes := encodeCABytes(caBytes, caPrivKey)
   215  
   216  	webhookCA := v1.Secret{}
   217  	webhookCA.Namespace = WebhookNamespace
   218  	webhookCA.Name = OperatorCA
   219  	webhookCA.Type = v1.SecretTypeTLS
   220  
   221  	webhookCA.Data = make(map[string][]byte)
   222  	webhookCA.Data[CertKey] = caPEMBytes
   223  	webhookCA.Data[PrivKey] = caKeyPEMBytes
   224  
   225  	_, createError := secretsClient.Create(context.TODO(), &webhookCA, metav1.CreateOptions{})
   226  	if createError != nil {
   227  		if errors.IsAlreadyExists(createError) {
   228  			log.Infof("Operator CA secret %s already exists, using existing secret", OperatorCA)
   229  			existingSecret, err := secretsClient.Get(context.TODO(), OperatorCA, metav1.GetOptions{})
   230  			if err != nil {
   231  				return nil, nil, err
   232  			}
   233  			return decodeExistingSecretData(existingSecret)
   234  		}
   235  		return nil, nil, createError
   236  	}
   237  	return ca, caPrivKey, nil
   238  }
   239  
   240  // encodeCABytes PEM-encode the certificate and Key data
   241  func encodeCABytes(caBytes []byte, caPrivKey *rsa.PrivateKey) ([]byte, []byte) {
   242  	// PEM encode CA cert
   243  	caPEM := new(bytes.Buffer)
   244  	_ = pem.Encode(caPEM, &pem.Block{
   245  		Type:  "CERTIFICATE",
   246  		Bytes: caBytes,
   247  	})
   248  
   249  	// PEM encode CA cert
   250  	caKeyPEM := new(bytes.Buffer)
   251  	_ = pem.Encode(caKeyPEM, &pem.Block{
   252  		Type:  "RSA PRIVATE KEY",
   253  		Bytes: x509.MarshalPKCS1PrivateKey(caPrivKey),
   254  	})
   255  
   256  	return caPEM.Bytes(), caKeyPEM.Bytes()
   257  }
   258  
   259  // decodeExistingSecretData Decode existing secret data into their X509 and RSA objects
   260  func decodeExistingSecretData(secret *v1.Secret) (*x509.Certificate, *rsa.PrivateKey, error) {
   261  	cert, err := decodeCertificate(secret.Data[CertKey])
   262  	if err != nil {
   263  		return nil, nil, err
   264  	}
   265  	key, err := decodeKey(secret.Data[PrivKey])
   266  	if err != nil {
   267  		return nil, nil, err
   268  	}
   269  	return cert, key, err
   270  }
   271  
   272  // decodeCertificate Decode certificate PEM data
   273  func decodeCertificate(certBytes []byte) (*x509.Certificate, error) {
   274  	p, _ := pem.Decode(certBytes)
   275  	if p == nil {
   276  		return nil, fmt.Errorf("Unable to decode certificate")
   277  	}
   278  	certificate, err := x509.ParseCertificate(p.Bytes)
   279  	if err != nil {
   280  		return nil, err
   281  	}
   282  	return certificate, nil
   283  }
   284  
   285  // decodeKey - Decode private Key PEM data
   286  func decodeKey(keyBytes []byte) (*rsa.PrivateKey, error) {
   287  	p, _ := pem.Decode(keyBytes)
   288  	if p == nil {
   289  		return nil, fmt.Errorf("Unable to decode certificate")
   290  	}
   291  	key, err := x509.ParsePKCS1PrivateKey(p.Bytes)
   292  	if err != nil {
   293  		return nil, err
   294  	}
   295  	return key, nil
   296  }
   297  
   298  // newSerialNumber returns a new random serial number suitable for use in a certificate.
   299  func newSerialNumber() (*big.Int, error) {
   300  	// A serial number can be up to 20 octets in size.
   301  	return cryptorand.Int(cryptorand.Reader, new(big.Int).Lsh(big.NewInt(1), 8*20))
   302  }
   303  
   304  // writeFile writes data in the file at the given path
   305  func writeFile(log *zap.SugaredLogger, filepath string, pemData []byte) error {
   306  	log.Debugf("Writing file %s", filepath)
   307  	f, err := os.Create(filepath)
   308  	if err != nil {
   309  		return err
   310  	}
   311  	defer f.Close()
   312  
   313  	_, err = f.Write(pemData)
   314  	if err != nil {
   315  		return err
   316  	}
   317  
   318  	return nil
   319  }