github.com/verrazzano/verrazzano@v1.7.0/application-operator/internal/certificates/certificates.go (about)

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