github.com/percona/percona-xtradb-cluster-operator@v1.14.0/pkg/controller/pxc/tls.go (about)

     1  package pxc
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"time"
     7  
     8  	"github.com/pkg/errors"
     9  
    10  	cm "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1"
    11  	cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1"
    12  	api "github.com/percona/percona-xtradb-cluster-operator/pkg/apis/pxc/v1"
    13  	"github.com/percona/percona-xtradb-cluster-operator/pkg/pxctls"
    14  	corev1 "k8s.io/api/core/v1"
    15  	k8serr "k8s.io/apimachinery/pkg/api/errors"
    16  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    17  	"k8s.io/apimachinery/pkg/types"
    18  )
    19  
    20  func (r *ReconcilePerconaXtraDBCluster) reconcileSSL(cr *api.PerconaXtraDBCluster) error {
    21  	if cr.Spec.AllowUnsafeConfig && (cr.Spec.TLS == nil || cr.Spec.TLS.IssuerConf == nil) {
    22  		return nil
    23  	}
    24  	secretObj := corev1.Secret{}
    25  	secretInternalObj := corev1.Secret{}
    26  	errSecret := r.client.Get(context.TODO(),
    27  		types.NamespacedName{
    28  			Namespace: cr.Namespace,
    29  			Name:      cr.Spec.PXC.SSLSecretName,
    30  		},
    31  		&secretObj,
    32  	)
    33  	errInternalSecret := r.client.Get(context.TODO(),
    34  		types.NamespacedName{
    35  			Namespace: cr.Namespace,
    36  			Name:      cr.Spec.PXC.SSLInternalSecretName,
    37  		},
    38  		&secretInternalObj,
    39  	)
    40  	if errSecret == nil && errInternalSecret == nil {
    41  		return nil
    42  	} else if errSecret != nil && !k8serr.IsNotFound(errSecret) {
    43  		return fmt.Errorf("get secret: %v", errSecret)
    44  	} else if errInternalSecret != nil && !k8serr.IsNotFound(errInternalSecret) {
    45  		return fmt.Errorf("get internal secret: %v", errInternalSecret)
    46  	}
    47  	// don't create secret ssl-internal if secret ssl is not created by operator
    48  	if errSecret == nil && !metav1.IsControlledBy(&secretObj, cr) {
    49  		return nil
    50  	}
    51  	err := r.createSSLByCertManager(cr)
    52  	if err != nil {
    53  		if cr.Spec.TLS != nil && cr.Spec.TLS.IssuerConf != nil {
    54  			return fmt.Errorf("create ssl with cert manager %w", err)
    55  		}
    56  		err = r.createSSLManualy(cr)
    57  		if err != nil {
    58  			return fmt.Errorf("create ssl internally: %v", err)
    59  		}
    60  	}
    61  	return nil
    62  }
    63  
    64  func (r *ReconcilePerconaXtraDBCluster) createSSLByCertManager(cr *api.PerconaXtraDBCluster) error {
    65  	issuerName := cr.Name + "-pxc-issuer"
    66  	caIssuerName := cr.Name + "-pxc-ca-issuer"
    67  	issuerKind := "Issuer"
    68  	issuerGroup := ""
    69  	if cr.Spec.TLS != nil && cr.Spec.TLS.IssuerConf != nil {
    70  		issuerKind = cr.Spec.TLS.IssuerConf.Kind
    71  		issuerName = cr.Spec.TLS.IssuerConf.Name
    72  		issuerGroup = cr.Spec.TLS.IssuerConf.Group
    73  	} else {
    74  		if err := r.createIssuer(cr.Namespace, caIssuerName, ""); err != nil {
    75  			return err
    76  		}
    77  
    78  		caCert := &cm.Certificate{
    79  			ObjectMeta: metav1.ObjectMeta{
    80  				Name:      cr.Name + "-ca-cert",
    81  				Namespace: cr.Namespace,
    82  			},
    83  			Spec: cm.CertificateSpec{
    84  				SecretName: cr.Name + "-ca-cert",
    85  				CommonName: cr.Name + "-ca",
    86  				IsCA:       true,
    87  				IssuerRef: cmmeta.ObjectReference{
    88  					Name:  caIssuerName,
    89  					Kind:  issuerKind,
    90  					Group: issuerGroup,
    91  				},
    92  				Duration:    &metav1.Duration{Duration: time.Hour * 24 * 365},
    93  				RenewBefore: &metav1.Duration{Duration: 730 * time.Hour},
    94  			},
    95  		}
    96  
    97  		err := r.client.Create(context.TODO(), caCert)
    98  		if err != nil && !k8serr.IsAlreadyExists(err) {
    99  			return fmt.Errorf("create CA certificate: %v", err)
   100  		}
   101  
   102  		if err := r.waitForCerts(cr.Namespace, caCert.Spec.SecretName); err != nil {
   103  			return err
   104  		}
   105  
   106  		if err := r.createIssuer(cr.Namespace, issuerName, caCert.Spec.SecretName); err != nil {
   107  			return err
   108  		}
   109  	}
   110  
   111  	kubeCert := &cm.Certificate{
   112  		ObjectMeta: metav1.ObjectMeta{
   113  			Name:      cr.Name + "-ssl",
   114  			Namespace: cr.Namespace,
   115  		},
   116  		Spec: cm.CertificateSpec{
   117  			SecretName: cr.Spec.PXC.SSLSecretName,
   118  			CommonName: cr.Name + "-proxysql",
   119  			DNSNames: []string{
   120  				cr.Name + "-pxc",
   121  				cr.Name + "-proxysql",
   122  				"*." + cr.Name + "-pxc",
   123  				"*." + cr.Name + "-proxysql",
   124  			},
   125  			IssuerRef: cmmeta.ObjectReference{
   126  				Name:  issuerName,
   127  				Kind:  issuerKind,
   128  				Group: issuerGroup,
   129  			},
   130  		},
   131  	}
   132  
   133  	if cr.Spec.TLS != nil && len(cr.Spec.TLS.SANs) > 0 {
   134  		kubeCert.Spec.DNSNames = append(kubeCert.Spec.DNSNames, cr.Spec.TLS.SANs...)
   135  	}
   136  
   137  	err := r.client.Create(context.TODO(), kubeCert)
   138  	if err != nil && !k8serr.IsAlreadyExists(err) {
   139  		return fmt.Errorf("create certificate: %v", err)
   140  	}
   141  
   142  	if cr.Spec.PXC.SSLSecretName == cr.Spec.PXC.SSLInternalSecretName {
   143  		return r.waitForCerts(cr.Namespace, cr.Spec.PXC.SSLSecretName)
   144  	}
   145  
   146  	kubeCert = &cm.Certificate{
   147  		ObjectMeta: metav1.ObjectMeta{
   148  			Name:      cr.Name + "-ssl-internal",
   149  			Namespace: cr.Namespace,
   150  		},
   151  		Spec: cm.CertificateSpec{
   152  			SecretName: cr.Spec.PXC.SSLInternalSecretName,
   153  			CommonName: cr.Name + "-pxc",
   154  			DNSNames: []string{
   155  				cr.Name + "-pxc",
   156  				"*." + cr.Name + "-pxc",
   157  				cr.Name + "-haproxy-replicas." + cr.Namespace + ".svc.cluster.local",
   158  				cr.Name + "-haproxy-replicas." + cr.Namespace,
   159  				cr.Name + "-haproxy-replicas",
   160  				cr.Name + "-haproxy." + cr.Namespace + ".svc.cluster.local",
   161  				cr.Name + "-haproxy." + cr.Namespace,
   162  				cr.Name + "-haproxy",
   163  			},
   164  			IssuerRef: cmmeta.ObjectReference{
   165  				Name:  issuerName,
   166  				Kind:  issuerKind,
   167  				Group: issuerGroup,
   168  			},
   169  		},
   170  	}
   171  	if cr.Spec.TLS != nil && len(cr.Spec.TLS.SANs) > 0 {
   172  		kubeCert.Spec.DNSNames = append(kubeCert.Spec.DNSNames, cr.Spec.TLS.SANs...)
   173  	}
   174  	err = r.client.Create(context.TODO(), kubeCert)
   175  	if err != nil && !k8serr.IsAlreadyExists(err) {
   176  		return fmt.Errorf("create internal certificate: %v", err)
   177  	}
   178  
   179  	return r.waitForCerts(cr.Namespace, cr.Spec.PXC.SSLSecretName, cr.Spec.PXC.SSLInternalSecretName)
   180  }
   181  
   182  func (r *ReconcilePerconaXtraDBCluster) waitForCerts(namespace string, secretsList ...string) error {
   183  	ticker := time.NewTicker(3 * time.Second)
   184  	timeoutTimer := time.NewTimer(30 * time.Second)
   185  	defer timeoutTimer.Stop()
   186  	defer ticker.Stop()
   187  	for {
   188  		select {
   189  		case <-timeoutTimer.C:
   190  			return errors.Errorf("timeout: can't get tls certificates from certmanager, %s", secretsList)
   191  		case <-ticker.C:
   192  			sucessCount := 0
   193  			for _, secretName := range secretsList {
   194  				secret := &corev1.Secret{}
   195  				err := r.client.Get(context.TODO(), types.NamespacedName{
   196  					Name:      secretName,
   197  					Namespace: namespace,
   198  				}, secret)
   199  				if err != nil && !k8serr.IsNotFound(err) {
   200  					return err
   201  				} else if err == nil {
   202  					sucessCount++
   203  				}
   204  			}
   205  			if sucessCount == len(secretsList) {
   206  				return nil
   207  			}
   208  		}
   209  	}
   210  }
   211  
   212  func (r *ReconcilePerconaXtraDBCluster) createIssuer(namespace, issuer string, caCertSecret string) error {
   213  	spec := cm.IssuerSpec{}
   214  
   215  	if caCertSecret == "" {
   216  		spec = cm.IssuerSpec{
   217  			IssuerConfig: cm.IssuerConfig{
   218  				SelfSigned: &cm.SelfSignedIssuer{},
   219  			},
   220  		}
   221  	} else {
   222  		spec = cm.IssuerSpec{
   223  			IssuerConfig: cm.IssuerConfig{
   224  				CA: &cm.CAIssuer{SecretName: caCertSecret},
   225  			},
   226  		}
   227  	}
   228  
   229  	err := r.client.Create(context.TODO(), &cm.Issuer{
   230  		ObjectMeta: metav1.ObjectMeta{
   231  			Name:      issuer,
   232  			Namespace: namespace,
   233  		},
   234  		Spec: spec,
   235  	})
   236  	if err != nil && !k8serr.IsAlreadyExists(err) {
   237  		return fmt.Errorf("create issuer: %v", err)
   238  	}
   239  	return nil
   240  }
   241  
   242  func (r *ReconcilePerconaXtraDBCluster) createSSLManualy(cr *api.PerconaXtraDBCluster) error {
   243  	data := make(map[string][]byte)
   244  	proxyHosts := []string{
   245  		cr.Name + "-pxc",
   246  		cr.Name + "-proxysql",
   247  		"*." + cr.Name + "-pxc",
   248  		"*." + cr.Name + "-proxysql",
   249  	}
   250  	if cr.Spec.TLS != nil && len(cr.Spec.TLS.SANs) > 0 {
   251  		proxyHosts = append(proxyHosts, cr.Spec.TLS.SANs...)
   252  	}
   253  	caCert, tlsCert, key, err := pxctls.Issue(proxyHosts)
   254  	if err != nil {
   255  		return fmt.Errorf("create proxy certificate: %v", err)
   256  	}
   257  	data["ca.crt"] = caCert
   258  	data["tls.crt"] = tlsCert
   259  	data["tls.key"] = key
   260  	owner, err := OwnerRef(cr, r.scheme)
   261  	if err != nil {
   262  		return err
   263  	}
   264  	ownerReferences := []metav1.OwnerReference{owner}
   265  	secretObj := corev1.Secret{
   266  		ObjectMeta: metav1.ObjectMeta{
   267  			Name:            cr.Spec.PXC.SSLSecretName,
   268  			Namespace:       cr.Namespace,
   269  			OwnerReferences: ownerReferences,
   270  		},
   271  		Data: data,
   272  		Type: corev1.SecretTypeTLS,
   273  	}
   274  	err = r.client.Create(context.TODO(), &secretObj)
   275  	if err != nil && !k8serr.IsAlreadyExists(err) {
   276  		return fmt.Errorf("create TLS secret: %v", err)
   277  	}
   278  	pxcHosts := []string{
   279  		cr.Name + "-pxc",
   280  		"*." + cr.Name + "-pxc",
   281  		cr.Name + "-haproxy-replicas." + cr.Namespace + ".svc.cluster.local",
   282  		cr.Name + "-haproxy-replicas." + cr.Namespace,
   283  		cr.Name + "-haproxy-replicas",
   284  		cr.Name + "-haproxy." + cr.Namespace + ".svc.cluster.local",
   285  		cr.Name + "-haproxy." + cr.Namespace,
   286  		cr.Name + "-haproxy",
   287  	}
   288  	if cr.Spec.TLS != nil && len(cr.Spec.TLS.SANs) > 0 {
   289  		pxcHosts = append(pxcHosts, cr.Spec.TLS.SANs...)
   290  	}
   291  	caCert, tlsCert, key, err = pxctls.Issue(pxcHosts)
   292  	if err != nil {
   293  		return fmt.Errorf("create pxc certificate: %v", err)
   294  	}
   295  	data["ca.crt"] = caCert
   296  	data["tls.crt"] = tlsCert
   297  	data["tls.key"] = key
   298  	secretObjInternal := corev1.Secret{
   299  		ObjectMeta: metav1.ObjectMeta{
   300  			Name:            cr.Spec.PXC.SSLInternalSecretName,
   301  			Namespace:       cr.Namespace,
   302  			OwnerReferences: ownerReferences,
   303  		},
   304  		Data: data,
   305  		Type: corev1.SecretTypeTLS,
   306  	}
   307  	err = r.client.Create(context.TODO(), &secretObjInternal)
   308  	if err != nil && !k8serr.IsAlreadyExists(err) {
   309  		return fmt.Errorf("create TLS internal secret: %v", err)
   310  	}
   311  	return nil
   312  }