Files
kubesphere/vendor/sigs.k8s.io/kubefed/pkg/controller/util/cluster_util.go
zryfish 5a3eb651f3 change cluster schema (#2026)
* change cluster schema

* change cluster schema
2020-04-27 17:34:02 +08:00

222 lines
7.3 KiB
Go

/*
Copyright 2016 The Kubernetes Authors.
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 util
import (
"context"
"crypto/tls"
"crypto/x509"
"net"
"net/http"
"net/url"
"time"
"github.com/pkg/errors"
apiv1 "k8s.io/api/core/v1"
pkgruntime "k8s.io/apimachinery/pkg/runtime"
restclient "k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/transport"
"k8s.io/klog"
fedv1b1 "sigs.k8s.io/kubefed/pkg/apis/core/v1beta1"
"sigs.k8s.io/kubefed/pkg/client/generic"
)
const (
DefaultKubeFedSystemNamespace = "kube-federation-system"
KubeAPIQPS = 20.0
KubeAPIBurst = 30
TokenKey = "token"
KubeFedConfigName = "kubefed"
)
// BuildClusterConfig returns a restclient.Config that can be used to configure
// a client for the given KubeFedCluster or an error. The client is used to
// access kubernetes secrets in the kubefed namespace.
func BuildClusterConfig(fedCluster *fedv1b1.KubeFedCluster, client generic.Client, fedNamespace string) (*restclient.Config, error) {
clusterName := fedCluster.Name
apiEndpoint := fedCluster.Spec.APIEndpoint
// TODO(marun) Remove when validation ensures a non-empty value.
if apiEndpoint == "" {
return nil, errors.Errorf("The api endpoint of cluster %s is empty", clusterName)
}
secretName := fedCluster.Spec.SecretRef.Name
if secretName == "" {
return nil, errors.Errorf("Cluster %s does not have a secret name", clusterName)
}
secret := &apiv1.Secret{}
err := client.Get(context.TODO(), secret, fedNamespace, secretName)
if err != nil {
return nil, err
}
token, tokenFound := secret.Data[TokenKey]
if !tokenFound || len(token) == 0 {
return nil, errors.Errorf("The secret for cluster %s is missing a non-empty value for %q", clusterName, TokenKey)
}
clusterConfig, err := clientcmd.BuildConfigFromFlags(apiEndpoint, "")
if err != nil {
return nil, err
}
clusterConfig.CAData = fedCluster.Spec.CABundle
clusterConfig.BearerToken = string(token)
clusterConfig.QPS = KubeAPIQPS
clusterConfig.Burst = KubeAPIBurst
if len(fedCluster.Spec.DisabledTLSValidations) != 0 {
klog.V(1).Infof("Cluster %s will use a custom transport for TLS certificate validation", fedCluster.Name)
if err = CustomizeTLSTransport(fedCluster, clusterConfig); err != nil {
return nil, err
}
}
return clusterConfig, nil
}
// IsPrimaryCluster checks if the caller is working with objects for the
// primary cluster by checking if the UIDs match for both ObjectMetas passed
// in.
// TODO (font): Need to revisit this when cluster ID is available.
func IsPrimaryCluster(obj, clusterObj pkgruntime.Object) bool {
meta := MetaAccessor(obj)
clusterMeta := MetaAccessor(clusterObj)
return meta.GetUID() == clusterMeta.GetUID()
}
// CustomizeTLSTransport replaces the restclient.Config.Transport with one that
// implements the desired TLS certificate validations
func CustomizeTLSTransport(fedCluster *fedv1b1.KubeFedCluster, clientConfig *restclient.Config) error {
clientTransportConfig, err := clientConfig.TransportConfig()
if err != nil {
return errors.Errorf("Cluster %s client transport config error: %s", fedCluster.Name, err)
}
transportConfig, err := transport.TLSConfigFor(clientTransportConfig)
if err != nil {
return errors.Errorf("Cluster %s transport error: %s", fedCluster.Name, err)
}
err = CustomizeCertificateValidation(fedCluster, transportConfig)
if err != nil {
return errors.Errorf("Cluster %s custom certificate validation error: %s", fedCluster.Name, err)
}
// using the same defaults as http.DefaultTransport
clientConfig.Transport = &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
DualStack: true,
}).DialContext,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
TLSClientConfig: transportConfig,
}
clientConfig.TLSClientConfig = restclient.TLSClientConfig{}
return nil
}
// CustomizeCertificateValidation modifies an existing tls.Config to disable the
// desired TLS checks in KubeFedCluster config
func CustomizeCertificateValidation(fedCluster *fedv1b1.KubeFedCluster, tlsConfig *tls.Config) error {
// InsecureSkipVerify must be enabled to prevent early validation errors from
// returning before VerifyPeerCertificate is run
tlsConfig.InsecureSkipVerify = true
var ignoreSubjectName, ignoreValidityPeriod bool
for _, validation := range fedCluster.Spec.DisabledTLSValidations {
switch fedv1b1.TLSValidation(validation) {
case fedv1b1.TLSAll:
klog.V(1).Infof("Cluster %s will not perform TLS certificate validation", fedCluster.Name)
return nil
case fedv1b1.TLSSubjectName:
ignoreSubjectName = true
case fedv1b1.TLSValidityPeriod:
ignoreValidityPeriod = true
}
}
// Normal TLS SubjectName validation uses the conn dnsname for validation,
// but this is not available when using a VerifyPeerCertificate functions.
// As a workaround, we will fill the tls.Config.ServerName with the URL host
// specified as the KubeFedCluster API target
if !ignoreSubjectName && tlsConfig.ServerName == "" {
apiURL, err := url.Parse(fedCluster.Spec.APIEndpoint)
if err != nil {
return errors.Errorf("failed to identify a valid host from APIEndpoint for use in SubjectName validation")
}
tlsConfig.ServerName = apiURL.Hostname()
}
// VerifyPeerCertificate uses the same logic as crypto/tls Conn.verifyServerCertificate
// but uses a modified set of options to ignore specific validations
tlsConfig.VerifyPeerCertificate = func(certificates [][]byte, verifiedChains [][]*x509.Certificate) error {
opts := x509.VerifyOptions{
Roots: tlsConfig.RootCAs,
CurrentTime: time.Now(),
Intermediates: x509.NewCertPool(),
DNSName: tlsConfig.ServerName,
}
if tlsConfig.Time != nil {
opts.CurrentTime = tlsConfig.Time()
}
certs := make([]*x509.Certificate, len(certificates))
for i, asn1Data := range certificates {
cert, err := x509.ParseCertificate(asn1Data)
if err != nil {
return errors.New("tls: failed to parse certificate from server: " + err.Error())
}
certs[i] = cert
}
for i, cert := range certs {
if i == 0 {
continue
}
opts.Intermediates.AddCert(cert)
}
if ignoreSubjectName {
// set the DNSName to nil to ignore the name validation
opts.DNSName = ""
klog.V(1).Infof("Cluster %s will not perform tls certificate SubjectName validation", fedCluster.Name)
}
if ignoreValidityPeriod {
// set the CurrentTime to immediately after the certificate start time
// this will ensure that certificate passes the validity period check
opts.CurrentTime = certs[0].NotBefore.Add(time.Second)
klog.V(1).Infof("Cluster %s will not perform tls certificate ValidityPeriod validation", fedCluster.Name)
}
_, err := certs[0].Verify(opts)
return err
}
return nil
}