k8s.io/apiserver@v0.31.1/pkg/util/webhook/authentication.go (about)

     1  /*
     2  Copyright 2017 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package webhook
    18  
    19  import (
    20  	"fmt"
    21  	"net"
    22  	"net/http"
    23  	"os"
    24  	"strconv"
    25  	"strings"
    26  	"time"
    27  
    28  	"go.opentelemetry.io/otel/trace"
    29  
    30  	corev1 "k8s.io/api/core/v1"
    31  	utilnet "k8s.io/apimachinery/pkg/util/net"
    32  	"k8s.io/apiserver/pkg/features"
    33  	egressselector "k8s.io/apiserver/pkg/server/egressselector"
    34  	"k8s.io/apiserver/pkg/util/feature"
    35  	"k8s.io/client-go/rest"
    36  	"k8s.io/client-go/tools/clientcmd"
    37  	clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
    38  	tracing "k8s.io/component-base/tracing"
    39  )
    40  
    41  // AuthenticationInfoResolverWrapper can be used to inject Dial function to the
    42  // rest.Config generated by the resolver.
    43  type AuthenticationInfoResolverWrapper func(AuthenticationInfoResolver) AuthenticationInfoResolver
    44  
    45  // NewDefaultAuthenticationInfoResolverWrapper builds a default authn resolver wrapper
    46  func NewDefaultAuthenticationInfoResolverWrapper(
    47  	proxyTransport *http.Transport,
    48  	egressSelector *egressselector.EgressSelector,
    49  	kubeapiserverClientConfig *rest.Config,
    50  	tp trace.TracerProvider) AuthenticationInfoResolverWrapper {
    51  
    52  	webhookAuthResolverWrapper := func(delegate AuthenticationInfoResolver) AuthenticationInfoResolver {
    53  		return &AuthenticationInfoResolverDelegator{
    54  			ClientConfigForFunc: func(hostPort string) (*rest.Config, error) {
    55  				if hostPort == "kubernetes.default.svc:443" {
    56  					return kubeapiserverClientConfig, nil
    57  				}
    58  				ret, err := delegate.ClientConfigFor(hostPort)
    59  				if err != nil {
    60  					return nil, err
    61  				}
    62  				if feature.DefaultFeatureGate.Enabled(features.APIServerTracing) {
    63  					ret.Wrap(tracing.WrapperFor(tp))
    64  				}
    65  
    66  				if egressSelector != nil {
    67  					networkContext := egressselector.ControlPlane.AsNetworkContext()
    68  					var egressDialer utilnet.DialFunc
    69  					egressDialer, err = egressSelector.Lookup(networkContext)
    70  
    71  					if err != nil {
    72  						return nil, err
    73  					}
    74  
    75  					ret.Dial = egressDialer
    76  				}
    77  				return ret, nil
    78  			},
    79  			ClientConfigForServiceFunc: func(serviceName, serviceNamespace string, servicePort int) (*rest.Config, error) {
    80  				if serviceName == "kubernetes" && serviceNamespace == corev1.NamespaceDefault && servicePort == 443 {
    81  					return kubeapiserverClientConfig, nil
    82  				}
    83  				ret, err := delegate.ClientConfigForService(serviceName, serviceNamespace, servicePort)
    84  				if err != nil {
    85  					return nil, err
    86  				}
    87  				if feature.DefaultFeatureGate.Enabled(features.APIServerTracing) {
    88  					ret.Wrap(tracing.WrapperFor(tp))
    89  				}
    90  
    91  				if egressSelector != nil {
    92  					networkContext := egressselector.Cluster.AsNetworkContext()
    93  					var egressDialer utilnet.DialFunc
    94  					egressDialer, err = egressSelector.Lookup(networkContext)
    95  					if err != nil {
    96  						return nil, err
    97  					}
    98  
    99  					ret.Dial = egressDialer
   100  				} else if proxyTransport != nil && proxyTransport.DialContext != nil {
   101  					ret.Dial = proxyTransport.DialContext
   102  				}
   103  				return ret, nil
   104  			},
   105  		}
   106  	}
   107  	return webhookAuthResolverWrapper
   108  }
   109  
   110  // AuthenticationInfoResolver builds rest.Config base on the server or service
   111  // name and service namespace.
   112  type AuthenticationInfoResolver interface {
   113  	// ClientConfigFor builds rest.Config based on the hostPort.
   114  	ClientConfigFor(hostPort string) (*rest.Config, error)
   115  	// ClientConfigForService builds rest.Config based on the serviceName and
   116  	// serviceNamespace.
   117  	ClientConfigForService(serviceName, serviceNamespace string, servicePort int) (*rest.Config, error)
   118  }
   119  
   120  // AuthenticationInfoResolverDelegator implements AuthenticationInfoResolver.
   121  type AuthenticationInfoResolverDelegator struct {
   122  	ClientConfigForFunc        func(hostPort string) (*rest.Config, error)
   123  	ClientConfigForServiceFunc func(serviceName, serviceNamespace string, servicePort int) (*rest.Config, error)
   124  }
   125  
   126  // ClientConfigFor returns client config for given hostPort.
   127  func (a *AuthenticationInfoResolverDelegator) ClientConfigFor(hostPort string) (*rest.Config, error) {
   128  	return a.ClientConfigForFunc(hostPort)
   129  }
   130  
   131  // ClientConfigForService returns client config for given service.
   132  func (a *AuthenticationInfoResolverDelegator) ClientConfigForService(serviceName, serviceNamespace string, servicePort int) (*rest.Config, error) {
   133  	return a.ClientConfigForServiceFunc(serviceName, serviceNamespace, servicePort)
   134  }
   135  
   136  type defaultAuthenticationInfoResolver struct {
   137  	kubeconfig clientcmdapi.Config
   138  }
   139  
   140  // NewDefaultAuthenticationInfoResolver generates an AuthenticationInfoResolver
   141  // that builds rest.Config based on the kubeconfig file. kubeconfigFile is the
   142  // path to the kubeconfig.
   143  func NewDefaultAuthenticationInfoResolver(kubeconfigFile string) (AuthenticationInfoResolver, error) {
   144  	if len(kubeconfigFile) == 0 {
   145  		return &defaultAuthenticationInfoResolver{}, nil
   146  	}
   147  
   148  	loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
   149  	loadingRules.ExplicitPath = kubeconfigFile
   150  	loader := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, &clientcmd.ConfigOverrides{})
   151  	clientConfig, err := loader.RawConfig()
   152  	if err != nil {
   153  		return nil, err
   154  	}
   155  
   156  	return &defaultAuthenticationInfoResolver{kubeconfig: clientConfig}, nil
   157  }
   158  
   159  func (c *defaultAuthenticationInfoResolver) ClientConfigFor(hostPort string) (*rest.Config, error) {
   160  	return c.clientConfig(hostPort)
   161  }
   162  
   163  func (c *defaultAuthenticationInfoResolver) ClientConfigForService(serviceName, serviceNamespace string, servicePort int) (*rest.Config, error) {
   164  	return c.clientConfig(net.JoinHostPort(serviceName+"."+serviceNamespace+".svc", strconv.Itoa(servicePort)))
   165  }
   166  
   167  func (c *defaultAuthenticationInfoResolver) clientConfig(target string) (*rest.Config, error) {
   168  	// exact match
   169  	if authConfig, ok := c.kubeconfig.AuthInfos[target]; ok {
   170  		return restConfigFromKubeconfig(authConfig)
   171  	}
   172  
   173  	// star prefixed match
   174  	serverSteps := strings.Split(target, ".")
   175  	for i := 1; i < len(serverSteps); i++ {
   176  		nickName := "*." + strings.Join(serverSteps[i:], ".")
   177  		if authConfig, ok := c.kubeconfig.AuthInfos[nickName]; ok {
   178  			return restConfigFromKubeconfig(authConfig)
   179  		}
   180  	}
   181  
   182  	// If target included the default https port (443), search again without the port
   183  	if target, port, err := net.SplitHostPort(target); err == nil && port == "443" {
   184  		// exact match without port
   185  		if authConfig, ok := c.kubeconfig.AuthInfos[target]; ok {
   186  			return restConfigFromKubeconfig(authConfig)
   187  		}
   188  
   189  		// star prefixed match without port
   190  		serverSteps := strings.Split(target, ".")
   191  		for i := 1; i < len(serverSteps); i++ {
   192  			nickName := "*." + strings.Join(serverSteps[i:], ".")
   193  			if authConfig, ok := c.kubeconfig.AuthInfos[nickName]; ok {
   194  				return restConfigFromKubeconfig(authConfig)
   195  			}
   196  		}
   197  	}
   198  
   199  	// if we're trying to hit the kube-apiserver and there wasn't an explicit config, use the in-cluster config
   200  	if target == "kubernetes.default.svc:443" {
   201  		// if we can find an in-cluster-config use that.  If we can't, fall through.
   202  		inClusterConfig, err := rest.InClusterConfig()
   203  		if err == nil {
   204  			return setGlobalDefaults(inClusterConfig), nil
   205  		}
   206  	}
   207  
   208  	// star (default) match
   209  	if authConfig, ok := c.kubeconfig.AuthInfos["*"]; ok {
   210  		return restConfigFromKubeconfig(authConfig)
   211  	}
   212  
   213  	// use the current context from the kubeconfig if possible
   214  	if len(c.kubeconfig.CurrentContext) > 0 {
   215  		if currContext, ok := c.kubeconfig.Contexts[c.kubeconfig.CurrentContext]; ok {
   216  			if len(currContext.AuthInfo) > 0 {
   217  				if currAuth, ok := c.kubeconfig.AuthInfos[currContext.AuthInfo]; ok {
   218  					return restConfigFromKubeconfig(currAuth)
   219  				}
   220  			}
   221  		}
   222  	}
   223  
   224  	// anonymous
   225  	return setGlobalDefaults(&rest.Config{}), nil
   226  }
   227  
   228  func restConfigFromKubeconfig(configAuthInfo *clientcmdapi.AuthInfo) (*rest.Config, error) {
   229  	config := &rest.Config{}
   230  
   231  	// blindly overwrite existing values based on precedence
   232  	if len(configAuthInfo.Token) > 0 {
   233  		config.BearerToken = configAuthInfo.Token
   234  		config.BearerTokenFile = configAuthInfo.TokenFile
   235  	} else if len(configAuthInfo.TokenFile) > 0 {
   236  		tokenBytes, err := os.ReadFile(configAuthInfo.TokenFile)
   237  		if err != nil {
   238  			return nil, err
   239  		}
   240  		config.BearerToken = string(tokenBytes)
   241  		config.BearerTokenFile = configAuthInfo.TokenFile
   242  	}
   243  	if len(configAuthInfo.Impersonate) > 0 {
   244  		config.Impersonate = rest.ImpersonationConfig{
   245  			UserName: configAuthInfo.Impersonate,
   246  			UID:      configAuthInfo.ImpersonateUID,
   247  			Groups:   configAuthInfo.ImpersonateGroups,
   248  			Extra:    configAuthInfo.ImpersonateUserExtra,
   249  		}
   250  	}
   251  	if len(configAuthInfo.ClientCertificate) > 0 || len(configAuthInfo.ClientCertificateData) > 0 {
   252  		config.CertFile = configAuthInfo.ClientCertificate
   253  		config.CertData = configAuthInfo.ClientCertificateData
   254  		config.KeyFile = configAuthInfo.ClientKey
   255  		config.KeyData = configAuthInfo.ClientKeyData
   256  	}
   257  	if len(configAuthInfo.Username) > 0 || len(configAuthInfo.Password) > 0 {
   258  		config.Username = configAuthInfo.Username
   259  		config.Password = configAuthInfo.Password
   260  	}
   261  	if configAuthInfo.Exec != nil {
   262  		config.ExecProvider = configAuthInfo.Exec.DeepCopy()
   263  	}
   264  	if configAuthInfo.AuthProvider != nil {
   265  		return nil, fmt.Errorf("auth provider not supported")
   266  	}
   267  
   268  	return setGlobalDefaults(config), nil
   269  }
   270  
   271  func setGlobalDefaults(config *rest.Config) *rest.Config {
   272  	config.UserAgent = "kube-apiserver-admission"
   273  	config.Timeout = 30 * time.Second
   274  
   275  	return config
   276  }