feat: add ca to check the validation of the server certificate

Signed-off-by: jwcesign <jwcesign@gmail.com>
This commit is contained in:
jwcesign 2023-10-26 13:35:49 +08:00
parent 3267155766
commit 473b2beb61
4 changed files with 84 additions and 23 deletions

View File

@ -62,8 +62,9 @@ func TestProxyREST_Connect(t *testing.T) {
return &clusterapis.Cluster{ return &clusterapis.Cluster{
ObjectMeta: metav1.ObjectMeta{Name: name}, ObjectMeta: metav1.ObjectMeta{Name: name},
Spec: clusterapis.ClusterSpec{ Spec: clusterapis.ClusterSpec{
APIEndpoint: s.URL, APIEndpoint: s.URL,
ImpersonatorSecretRef: &clusterapis.LocalSecretReference{Namespace: "ns", Name: "secret"}, ImpersonatorSecretRef: &clusterapis.LocalSecretReference{Namespace: "ns", Name: "secret"},
InsecureSkipTLSVerification: true,
}, },
}, nil }, nil
}, },
@ -109,8 +110,9 @@ func TestProxyREST_Connect(t *testing.T) {
return &clusterapis.Cluster{ return &clusterapis.Cluster{
ObjectMeta: metav1.ObjectMeta{Name: name}, ObjectMeta: metav1.ObjectMeta{Name: name},
Spec: clusterapis.ClusterSpec{ Spec: clusterapis.ClusterSpec{
APIEndpoint: s.URL, APIEndpoint: s.URL,
ImpersonatorSecretRef: &clusterapis.LocalSecretReference{Namespace: "ns", Name: "secret"}, ImpersonatorSecretRef: &clusterapis.LocalSecretReference{Namespace: "ns", Name: "secret"},
InsecureSkipTLSVerification: true,
}, },
}, nil }, nil
}, },
@ -134,8 +136,9 @@ func TestProxyREST_Connect(t *testing.T) {
return &clusterapis.Cluster{ return &clusterapis.Cluster{
ObjectMeta: metav1.ObjectMeta{Name: name}, ObjectMeta: metav1.ObjectMeta{Name: name},
Spec: clusterapis.ClusterSpec{ Spec: clusterapis.ClusterSpec{
APIEndpoint: s.URL, APIEndpoint: s.URL,
ImpersonatorSecretRef: &clusterapis.LocalSecretReference{Namespace: "ns", Name: "secret"}, ImpersonatorSecretRef: &clusterapis.LocalSecretReference{Namespace: "ns", Name: "secret"},
InsecureSkipTLSVerification: true,
}, },
}, nil }, nil
}, },

View File

@ -6,6 +6,7 @@ import (
"net/http" "net/http"
"net/url" "net/url"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apiserver/pkg/registry/generic" "k8s.io/apiserver/pkg/registry/generic"
@ -58,7 +59,7 @@ func NewStorage(scheme *runtime.Scheme, kubeClient kubernetes.Interface, secretL
statusStore.UpdateStrategy = statusStrategy statusStore.UpdateStrategy = statusStrategy
statusStore.ResetFieldsStrategy = statusStrategy statusStore.ResetFieldsStrategy = statusStrategy
clusterRest := &REST{store} clusterRest := &REST{secretLister, store}
return &ClusterStorage{ return &ClusterStorage{
Cluster: clusterRest, Cluster: clusterRest,
Status: &StatusREST{&statusStore}, Status: &StatusREST{&statusStore},
@ -72,6 +73,7 @@ func NewStorage(scheme *runtime.Scheme, kubeClient kubernetes.Interface, secretL
// REST implements a RESTStorage for Cluster. // REST implements a RESTStorage for Cluster.
type REST struct { type REST struct {
secretLister listcorev1.SecretLister
*genericregistry.Store *genericregistry.Store
} }
@ -85,7 +87,15 @@ func (r *REST) ResourceLocation(ctx context.Context, name string) (*url.URL, htt
return nil, nil, err return nil, nil, err
} }
return proxy.Location(cluster) secretGetter := func(ctx context.Context, namespace, name string) (*corev1.Secret, error) {
return r.secretLister.Secrets(namespace).Get(name)
}
tlsConfig, err := proxy.GetTlsConfigForCluster(ctx, cluster, secretGetter)
if err != nil {
return nil, nil, err
}
return proxy.Location(cluster, tlsConfig)
} }
func (r *REST) getCluster(ctx context.Context, name string) (*clusterapis.Cluster, error) { func (r *REST) getCluster(ctx context.Context, name string) (*clusterapis.Cluster, error) {

View File

@ -3,6 +3,7 @@ package proxy
import ( import (
"context" "context"
"crypto/tls" "crypto/tls"
"crypto/x509"
"errors" "errors"
"fmt" "fmt"
"net/http" "net/http"
@ -25,9 +26,16 @@ import (
clusterapis "github.com/karmada-io/karmada/pkg/apis/cluster" clusterapis "github.com/karmada-io/karmada/pkg/apis/cluster"
) )
type SecretGetterFunc func(context.Context, string, string) (*corev1.Secret, error)
// ConnectCluster returns a handler for proxy cluster. // ConnectCluster returns a handler for proxy cluster.
func ConnectCluster(ctx context.Context, cluster *clusterapis.Cluster, proxyPath string, secretGetter func(context.Context, string, string) (*corev1.Secret, error), responder registryrest.Responder) (http.Handler, error) { func ConnectCluster(ctx context.Context, cluster *clusterapis.Cluster, proxyPath string, secretGetter SecretGetterFunc, responder registryrest.Responder) (http.Handler, error) {
location, proxyTransport, err := Location(cluster) tlsConfig, err := GetTlsConfigForCluster(ctx, cluster, secretGetter)
if err != nil {
return nil, err
}
location, proxyTransport, err := Location(cluster, tlsConfig)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -37,20 +45,20 @@ func ConnectCluster(ctx context.Context, cluster *clusterapis.Cluster, proxyPath
return nil, fmt.Errorf("the impersonatorSecretRef of cluster %s is nil", cluster.Name) return nil, fmt.Errorf("the impersonatorSecretRef of cluster %s is nil", cluster.Name)
} }
secret, err := secretGetter(ctx, cluster.Spec.ImpersonatorSecretRef.Namespace, cluster.Spec.ImpersonatorSecretRef.Name) impersonateTokenSecret, err := secretGetter(ctx, cluster.Spec.ImpersonatorSecretRef.Namespace, cluster.Spec.ImpersonatorSecretRef.Name)
if err != nil { if err != nil {
return nil, err return nil, err
} }
impersonateToken, err := getImpersonateToken(cluster.Name, impersonateTokenSecret)
impersonateToken, err := getImpersonateToken(cluster.Name, secret)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to get impresonateToken for cluster %s: %v", cluster.Name, err) return nil, fmt.Errorf("failed to get impresonateToken for cluster %s: %v", cluster.Name, err)
} }
return newProxyHandler(location, proxyTransport, cluster, impersonateToken, responder) return newProxyHandler(location, proxyTransport, cluster, impersonateToken, responder, tlsConfig)
} }
func newProxyHandler(location *url.URL, proxyTransport http.RoundTripper, cluster *clusterapis.Cluster, impersonateToken string, responder registryrest.Responder) (http.Handler, error) { func newProxyHandler(location *url.URL, proxyTransport http.RoundTripper, cluster *clusterapis.Cluster, impersonateToken string,
responder registryrest.Responder, tlsConfig *tls.Config) (http.Handler, error) {
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
requester, exist := request.UserFrom(req.Context()) requester, exist := request.UserFrom(req.Context())
if !exist { if !exist {
@ -76,7 +84,7 @@ func newProxyHandler(location *url.URL, proxyTransport http.RoundTripper, cluste
location.RawQuery = req.URL.RawQuery location.RawQuery = req.URL.RawQuery
upgradeDialer := NewUpgradeDialerWithConfig(UpgradeDialerWithConfig{ upgradeDialer := NewUpgradeDialerWithConfig(UpgradeDialerWithConfig{
TLS: &tls.Config{InsecureSkipVerify: true}, //nolint:gosec TLS: tlsConfig,
Proxier: http.ProxyURL(proxyURL), Proxier: http.ProxyURL(proxyURL),
PingPeriod: time.Second * 5, PingPeriod: time.Second * 5,
Header: ParseProxyHeaders(cluster.Spec.ProxyHeader), Header: ParseProxyHeaders(cluster.Spec.ProxyHeader),
@ -94,14 +102,46 @@ func NewThrottledUpgradeAwareProxyHandler(location *url.URL, transport http.Roun
return proxy.NewUpgradeAwareHandler(location, transport, wrapTransport, upgradeRequired, proxy.NewErrorResponder(responder)) return proxy.NewUpgradeAwareHandler(location, transport, wrapTransport, upgradeRequired, proxy.NewErrorResponder(responder))
} }
func GetTlsConfigForCluster(ctx context.Context, cluster *clusterapis.Cluster, secretGetter SecretGetterFunc) (*tls.Config, error) {
// The secret is optional for a pull-mode cluster, if no secret just returns a config with root CA unset.
if cluster.Spec.SecretRef == nil {
return &tls.Config{
MinVersion: tls.VersionTLS13,
// Ignore false positive warning: "TLS InsecureSkipVerify may be true. (gosec)"
// Whether to skip server certificate verification depends on the
// configuration(.spec.insecureSkipTLSVerification, defaults to false) in a Cluster object.
InsecureSkipVerify: cluster.Spec.InsecureSkipTLSVerification, //nolint:gosec
}, nil
}
caSecret, err := secretGetter(ctx, cluster.Spec.SecretRef.Namespace, cluster.Spec.SecretRef.Name)
if err != nil {
return nil, err
}
caBundle, err := getClusterCABundle(cluster.Name, caSecret)
if err != nil {
return nil, fmt.Errorf("failed to get CA bundle for cluster %s: %v", cluster.Name, err)
}
caCertPool := x509.NewCertPool()
caCertPool.AppendCertsFromPEM([]byte(caBundle))
return &tls.Config{
RootCAs: caCertPool,
MinVersion: tls.VersionTLS13,
// Ignore false positive warning: "TLS InsecureSkipVerify may be true. (gosec)"
// Whether to skip server certificate verification depends on the
// configuration(.spec.insecureSkipTLSVerification, defaults to false) in a Cluster object.
InsecureSkipVerify: cluster.Spec.InsecureSkipTLSVerification, //nolint:gosec
}, nil
}
// Location returns a URL to which one can send traffic for the specified cluster. // Location returns a URL to which one can send traffic for the specified cluster.
func Location(cluster *clusterapis.Cluster) (*url.URL, http.RoundTripper, error) { func Location(cluster *clusterapis.Cluster, tlsConfig *tls.Config) (*url.URL, http.RoundTripper, error) {
location, err := constructLocation(cluster) location, err := constructLocation(cluster)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
proxyTransport, err := createProxyTransport(cluster) proxyTransport, err := createProxyTransport(cluster, tlsConfig)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -122,12 +162,11 @@ func constructLocation(cluster *clusterapis.Cluster) (*url.URL, error) {
return uri, nil return uri, nil
} }
func createProxyTransport(cluster *clusterapis.Cluster) (*http.Transport, error) { func createProxyTransport(cluster *clusterapis.Cluster, tlsConfig *tls.Config) (*http.Transport, error) {
var proxyDialerFn utilnet.DialFunc var proxyDialerFn utilnet.DialFunc
proxyTLSClientConfig := &tls.Config{InsecureSkipVerify: true} // #nosec
trans := utilnet.SetTransportDefaults(&http.Transport{ trans := utilnet.SetTransportDefaults(&http.Transport{
DialContext: proxyDialerFn, DialContext: proxyDialerFn,
TLSClientConfig: proxyTLSClientConfig, TLSClientConfig: tlsConfig,
}) })
if proxyURL := cluster.Spec.ProxyURL; proxyURL != "" { if proxyURL := cluster.Spec.ProxyURL; proxyURL != "" {
@ -162,6 +201,14 @@ func getImpersonateToken(clusterName string, secret *corev1.Secret) (string, err
return string(token), nil return string(token), nil
} }
func getClusterCABundle(clusterName string, secret *corev1.Secret) (string, error) {
caBundle, found := secret.Data[clusterapis.SecretCADataKey]
if !found {
return "", fmt.Errorf("the CA bundle of cluster %s is empty", clusterName)
}
return string(caBundle), nil
}
func skipGroup(group string) bool { func skipGroup(group string) bool {
switch group { switch group {
case user.AllAuthenticated, user.AllUnauthenticated: case user.AllAuthenticated, user.AllUnauthenticated:

View File

@ -167,8 +167,9 @@ func TestConnectCluster(t *testing.T) {
cluster: &clusterapis.Cluster{ cluster: &clusterapis.Cluster{
ObjectMeta: metav1.ObjectMeta{Namespace: "ns", Name: "cluster"}, ObjectMeta: metav1.ObjectMeta{Namespace: "ns", Name: "cluster"},
Spec: clusterapis.ClusterSpec{ Spec: clusterapis.ClusterSpec{
APIEndpoint: s.URL, APIEndpoint: s.URL,
ImpersonatorSecretRef: &clusterapis.LocalSecretReference{Namespace: "ns", Name: "secret"}, ImpersonatorSecretRef: &clusterapis.LocalSecretReference{Namespace: "ns", Name: "secret"},
InsecureSkipTLSVerification: true,
}, },
}, },
secretGetter: func(_ context.Context, ns string, name string) (*corev1.Secret, error) { secretGetter: func(_ context.Context, ns string, name string) (*corev1.Secret, error) {