diff --git a/pkg/common/const.go b/pkg/common/const.go index 5ec6eda675d..1a94e9c42b0 100644 --- a/pkg/common/const.go +++ b/pkg/common/const.go @@ -36,6 +36,15 @@ const ( OperatorRuntimeOpenshift Runtime = "OPENSHIFT" // OperatorRuntimeRancher is the Rancher runtime flag OperatorRuntimeRancher Runtime = "RANCHER" + + // TLSCRT is name of the field containing tls certificate in secret + TLSCRT = "tls.crt" + + // CACRT name of the field containing ca certificate in secret + CACRT = "ca.crt" + + // PublicCRT name of the field containing public certificate in secret + PublicCRT = "public.crt" ) // Runtimes is a map of the supported Kubernetes runtimes diff --git a/pkg/controller/minio.go b/pkg/controller/minio.go index 215fdd5fbaa..74b9e0b21f8 100644 --- a/pkg/controller/minio.go +++ b/pkg/controller/minio.go @@ -27,8 +27,11 @@ import ( "fmt" "math/big" "net" + "slices" "time" + "github.com/minio/operator/pkg/common" + "k8s.io/apimachinery/pkg/runtime/schema" "github.com/minio/operator/pkg/controller/certificates" @@ -110,23 +113,25 @@ func (c *Controller) getTLSSecret(ctx context.Context, nsName string, secretName return c.kubeClientSet.CoreV1().Secrets(nsName).Get(ctx, secretName, metav1.GetOptions{}) } -func getOperatorCACert(secretData map[string][]byte) ([]byte, error) { - for _, key := range []string{ - "tls.crt", - "ca.crt", - "public.crt", - } { +func getOperatorCertFromSecret(secretData map[string][]byte, key string) ([]byte, error) { + keys := []string{ + common.TLSCRT, + common.CACRT, + common.PublicCRT, + } + if slices.Contains(keys, key) { data, ok := secretData[key] if ok { return data, nil } } - return nil, fmt.Errorf("missing 'public.crt' in %s/%s secret", miniov2.GetNSFromFile(), OperatorCATLSSecretName) + return nil, fmt.Errorf("missing '%s' in %s/%s secret", key, miniov2.GetNSFromFile(), OperatorCATLSSecretName) } // checkOperatorCaForTenant create or updates the operator-ca-tls secret for tenant if need it func (c *Controller) checkOperatorCaForTenant(ctx context.Context, tenant *miniov2.Tenant) (operatorCATLSExists bool, err error) { - var tenantCaCert []byte + var certsData map[string][]byte + // get operator-ca-tls in minio-operator namespace operatorCaSecret, err := c.kubeClientSet.CoreV1().Secrets(miniov2.GetNSFromFile()).Get(ctx, OperatorCATLSSecretName, metav1.GetOptions{}) if err != nil { @@ -137,11 +142,24 @@ func (c *Controller) checkOperatorCaForTenant(ctx context.Context, tenant *minio return false, err } - operatorCaCert, err := getOperatorCACert(operatorCaSecret.Data) + operatorPublicCert, err := getOperatorCertFromSecret(operatorCaSecret.Data, common.PublicCRT) if err != nil { + // If no public.crt is present we error, other certs are optional return false, err } + certsData[common.PublicCRT] = operatorPublicCert + + operatorTLSCert, err := getOperatorCertFromSecret(operatorCaSecret.Data, common.TLSCRT) + if err == nil { + certsData[common.TLSCRT] = operatorTLSCert + } + + operatorCACert, err := getOperatorCertFromSecret(operatorCaSecret.Data, common.CACRT) + if err == nil { + certsData[common.CACRT] = operatorCACert + } + var tenantCaSecret *corev1.Secret createTenantCASecret := func() error { @@ -160,9 +178,7 @@ func (c *Controller) checkOperatorCaForTenant(ctx context.Context, tenant *minio }), }, }, - Data: map[string][]byte{ - "public.crt": operatorCaCert, - }, + Data: certsData, } _, err = c.kubeClientSet.CoreV1().Secrets(tenant.Namespace).Create(ctx, tenantCaSecret, metav1.CreateOptions{}) return err @@ -179,14 +195,31 @@ func (c *Controller) checkOperatorCaForTenant(ctx context.Context, tenant *minio } } - tenantCaCert = tenantCaSecret.Data["public.crt"] - if !bytes.Equal(tenantCaCert, operatorCaCert) { - tenantCaSecret.Data["public.crt"] = operatorCaCert + update := false + + if publicCert, ok := tenantCaSecret.Data[common.PublicCRT]; ok && !bytes.Equal(publicCert, operatorPublicCert) { + tenantCaSecret.Data[common.PublicCRT] = operatorPublicCert + update = true + } + + if tlsCert, ok := tenantCaSecret.Data[common.TLSCRT]; ok && !bytes.Equal(tlsCert, operatorTLSCert) { + tenantCaSecret.Data[common.TLSCRT] = tlsCert + update = true + } + + if caCert, ok := tenantCaSecret.Data[common.CACRT]; ok && !bytes.Equal(caCert, operatorCACert) { + tenantCaSecret.Data[common.CACRT] = caCert + update = true + } + + if update { _, err = c.kubeClientSet.CoreV1().Secrets(tenant.Namespace).Update(ctx, tenantCaSecret, metav1.UpdateOptions{}) if err != nil { return false, err } - return false, fmt.Errorf("'public.crt' in '%s/%s' secret changed, updating '%s/%s' secret", miniov2.GetNSFromFile(), OperatorCATLSSecretName, tenant.Namespace, OperatorCATLSSecretName) + // Reload certificates + c.createTransport() + return false, fmt.Errorf("'%s/%s' secret changed, updating '%s/%s' secret", miniov2.GetNSFromFile(), OperatorCATLSSecretName, tenant.Namespace, OperatorCATLSSecretName) } return true, nil diff --git a/pkg/controller/operator.go b/pkg/controller/operator.go index f9bf83979a5..c472efa41ad 100644 --- a/pkg/controller/operator.go +++ b/pkg/controller/operator.go @@ -19,6 +19,7 @@ package controller import ( "context" "crypto/tls" + "crypto/x509" "net" "net/http" "time" @@ -89,10 +90,49 @@ func (c *Controller) fetchUserCredentials(ctx context.Context, tenant *miniov2.T return userCredentials } +// getTransport returns a *http.Transport with the collection of the trusted CA certificates +// returns a cached transport if already available func (c *Controller) getTransport() *http.Transport { if c.transport != nil { return c.transport } + c.transport = c.createTransport() + return c.transport +} + +// createTransport returns a *http.Transport with the collection of the trusted CA certificates +func (c *Controller) createTransport() *http.Transport { + rootCAs := c.fetchTransportCACertificates() + dialer := &net.Dialer{ + Timeout: 15 * time.Second, + KeepAlive: 15 * time.Second, + } + c.transport = &http.Transport{ + Proxy: http.ProxyFromEnvironment, + DialContext: dialer.DialContext, + MaxIdleConnsPerHost: 1024, + IdleConnTimeout: 15 * time.Second, + ResponseHeaderTimeout: 15 * time.Minute, + TLSHandshakeTimeout: 15 * time.Second, + ExpectContinueTimeout: 15 * time.Second, + // Go net/http automatically unzip if content-type is + // gzip disable this feature, as we are always interested + // in raw stream. + DisableCompression: true, + TLSClientConfig: &tls.Config{ + // Can't use SSLv3 because of POODLE and BEAST + // Can't use TLSv1.0 because of POODLE and BEAST using CBC cipher + // Can't use TLSv1.1 because of RC4 cipher usage + MinVersion: tls.VersionTLS12, + RootCAs: rootCAs, + }, + } + + return c.transport +} + +// fetchTransportCACertificates retrieves a *x509.CertPool with all CA that operator will trust +func (c *Controller) fetchTransportCACertificates() (pool *x509.CertPool) { rootCAs := miniov2.MustGetSystemCertPool() // Default kubernetes CA certificate rootCAs.AppendCertsFromPEM(miniov2.GetPodCAFromFile()) @@ -140,32 +180,7 @@ func (c *Controller) getTransport() *http.Transport { } } } - dialer := &net.Dialer{ - Timeout: 15 * time.Second, - KeepAlive: 15 * time.Second, - } - c.transport = &http.Transport{ - Proxy: http.ProxyFromEnvironment, - DialContext: dialer.DialContext, - MaxIdleConnsPerHost: 1024, - IdleConnTimeout: 15 * time.Second, - ResponseHeaderTimeout: 15 * time.Minute, - TLSHandshakeTimeout: 15 * time.Second, - ExpectContinueTimeout: 15 * time.Second, - // Go net/http automatically unzip if content-type is - // gzip disable this feature, as we are always interested - // in raw stream. - DisableCompression: true, - TLSClientConfig: &tls.Config{ - // Can't use SSLv3 because of POODLE and BEAST - // Can't use TLSv1.0 because of POODLE and BEAST using CBC cipher - // Can't use TLSv1.1 because of RC4 cipher usage - MinVersion: tls.VersionTLS12, - RootCAs: rootCAs, - }, - } - - return c.transport + return rootCAs } func (c *Controller) createUsers(ctx context.Context, tenant *miniov2.Tenant, tenantConfiguration map[string][]byte) (err error) {