k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/controlplane/apiserver/config.go (about)

     1  /*
     2  Copyright 2023 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 apiserver
    18  
    19  import (
    20  	"context"
    21  	"crypto/tls"
    22  	"fmt"
    23  	"net/http"
    24  	"time"
    25  
    26  	oteltrace "go.opentelemetry.io/otel/trace"
    27  
    28  	"k8s.io/apimachinery/pkg/runtime"
    29  	utilnet "k8s.io/apimachinery/pkg/util/net"
    30  	"k8s.io/apimachinery/pkg/util/sets"
    31  	"k8s.io/apimachinery/pkg/util/wait"
    32  	"k8s.io/apiserver/pkg/admission"
    33  	"k8s.io/apiserver/pkg/authorization/authorizer"
    34  	"k8s.io/apiserver/pkg/endpoints/discovery/aggregated"
    35  	openapinamer "k8s.io/apiserver/pkg/endpoints/openapi"
    36  	genericfeatures "k8s.io/apiserver/pkg/features"
    37  	peerreconcilers "k8s.io/apiserver/pkg/reconcilers"
    38  	genericapiserver "k8s.io/apiserver/pkg/server"
    39  	"k8s.io/apiserver/pkg/server/egressselector"
    40  	"k8s.io/apiserver/pkg/server/filters"
    41  	serverstorage "k8s.io/apiserver/pkg/server/storage"
    42  	utilfeature "k8s.io/apiserver/pkg/util/feature"
    43  	"k8s.io/apiserver/pkg/util/openapi"
    44  	utilpeerproxy "k8s.io/apiserver/pkg/util/peerproxy"
    45  	"k8s.io/client-go/dynamic"
    46  	clientgoinformers "k8s.io/client-go/informers"
    47  	clientgoclientset "k8s.io/client-go/kubernetes"
    48  	"k8s.io/client-go/util/keyutil"
    49  	"k8s.io/component-base/version"
    50  	aggregatorapiserver "k8s.io/kube-aggregator/pkg/apiserver"
    51  	openapicommon "k8s.io/kube-openapi/pkg/common"
    52  
    53  	"k8s.io/kubernetes/pkg/api/legacyscheme"
    54  	controlplaneadmission "k8s.io/kubernetes/pkg/controlplane/apiserver/admission"
    55  	"k8s.io/kubernetes/pkg/controlplane/apiserver/options"
    56  	"k8s.io/kubernetes/pkg/controlplane/controller/clusterauthenticationtrust"
    57  	"k8s.io/kubernetes/pkg/features"
    58  	"k8s.io/kubernetes/pkg/kubeapiserver"
    59  	"k8s.io/kubernetes/pkg/kubeapiserver/authorizer/modes"
    60  	rbacrest "k8s.io/kubernetes/pkg/registry/rbac/rest"
    61  	"k8s.io/kubernetes/pkg/serviceaccount"
    62  )
    63  
    64  // Config defines configuration for the master
    65  type Config struct {
    66  	Generic *genericapiserver.Config
    67  	Extra
    68  }
    69  
    70  type Extra struct {
    71  	ClusterAuthenticationInfo clusterauthenticationtrust.ClusterAuthenticationInfo
    72  
    73  	APIResourceConfigSource serverstorage.APIResourceConfigSource
    74  	StorageFactory          serverstorage.StorageFactory
    75  	EventTTL                time.Duration
    76  
    77  	EnableLogsSupport bool
    78  	ProxyTransport    *http.Transport
    79  
    80  	// PeerProxy, if not nil, sets proxy transport between kube-apiserver peers for requests
    81  	// that can not be served locally
    82  	PeerProxy utilpeerproxy.Interface
    83  	// PeerEndpointReconcileInterval defines how often the endpoint leases are reconciled in etcd.
    84  	PeerEndpointReconcileInterval time.Duration
    85  	// PeerEndpointLeaseReconciler updates the peer endpoint leases
    86  	PeerEndpointLeaseReconciler peerreconcilers.PeerEndpointLeaseReconciler
    87  	// PeerAdvertiseAddress is the IP for this kube-apiserver which is used by peer apiservers to route a request
    88  	// to this apiserver. This happens in cases where the peer is not able to serve the request due to
    89  	// version skew. If unset, AdvertiseAddress/BindAddress will be used.
    90  	PeerAdvertiseAddress peerreconcilers.PeerAdvertiseAddress
    91  
    92  	ServiceAccountIssuer        serviceaccount.TokenGenerator
    93  	ServiceAccountMaxExpiration time.Duration
    94  	ExtendExpiration            bool
    95  
    96  	// ServiceAccountIssuerDiscovery
    97  	ServiceAccountIssuerURL  string
    98  	ServiceAccountJWKSURI    string
    99  	ServiceAccountPublicKeys []interface{}
   100  
   101  	SystemNamespaces []string
   102  
   103  	VersionedInformers clientgoinformers.SharedInformerFactory
   104  }
   105  
   106  // BuildGenericConfig takes the generic controlplane apiserver options and produces
   107  // the genericapiserver.Config associated with it. The genericapiserver.Config is
   108  // often shared between multiple delegated apiservers.
   109  func BuildGenericConfig(
   110  	s options.CompletedOptions,
   111  	schemes []*runtime.Scheme,
   112  	resourceConfig *serverstorage.ResourceConfig,
   113  	getOpenAPIDefinitions func(ref openapicommon.ReferenceCallback) map[string]openapicommon.OpenAPIDefinition,
   114  ) (
   115  	genericConfig *genericapiserver.Config,
   116  	versionedInformers clientgoinformers.SharedInformerFactory,
   117  	storageFactory *serverstorage.DefaultStorageFactory,
   118  	lastErr error,
   119  ) {
   120  	genericConfig = genericapiserver.NewConfig(legacyscheme.Codecs)
   121  	genericConfig.MergedResourceConfig = resourceConfig
   122  
   123  	if lastErr = s.GenericServerRunOptions.ApplyTo(genericConfig); lastErr != nil {
   124  		return
   125  	}
   126  
   127  	if lastErr = s.SecureServing.ApplyTo(&genericConfig.SecureServing, &genericConfig.LoopbackClientConfig); lastErr != nil {
   128  		return
   129  	}
   130  
   131  	// Use protobufs for self-communication.
   132  	// Since not every generic apiserver has to support protobufs, we
   133  	// cannot default to it in generic apiserver and need to explicitly
   134  	// set it in kube-apiserver.
   135  	genericConfig.LoopbackClientConfig.ContentConfig.ContentType = "application/vnd.kubernetes.protobuf"
   136  	// Disable compression for self-communication, since we are going to be
   137  	// on a fast local network
   138  	genericConfig.LoopbackClientConfig.DisableCompression = true
   139  
   140  	kubeClientConfig := genericConfig.LoopbackClientConfig
   141  	clientgoExternalClient, err := clientgoclientset.NewForConfig(kubeClientConfig)
   142  	if err != nil {
   143  		lastErr = fmt.Errorf("failed to create real external clientset: %w", err)
   144  		return
   145  	}
   146  	versionedInformers = clientgoinformers.NewSharedInformerFactory(clientgoExternalClient, 10*time.Minute)
   147  
   148  	if lastErr = s.Features.ApplyTo(genericConfig, clientgoExternalClient, versionedInformers); lastErr != nil {
   149  		return
   150  	}
   151  	if lastErr = s.APIEnablement.ApplyTo(genericConfig, resourceConfig, legacyscheme.Scheme); lastErr != nil {
   152  		return
   153  	}
   154  	if lastErr = s.EgressSelector.ApplyTo(genericConfig); lastErr != nil {
   155  		return
   156  	}
   157  	if utilfeature.DefaultFeatureGate.Enabled(genericfeatures.APIServerTracing) {
   158  		if lastErr = s.Traces.ApplyTo(genericConfig.EgressSelector, genericConfig); lastErr != nil {
   159  			return
   160  		}
   161  	}
   162  	// wrap the definitions to revert any changes from disabled features
   163  	getOpenAPIDefinitions = openapi.GetOpenAPIDefinitionsWithoutDisabledFeatures(getOpenAPIDefinitions)
   164  	namer := openapinamer.NewDefinitionNamer(schemes...)
   165  	genericConfig.OpenAPIConfig = genericapiserver.DefaultOpenAPIConfig(getOpenAPIDefinitions, namer)
   166  	genericConfig.OpenAPIConfig.Info.Title = "Kubernetes"
   167  	genericConfig.OpenAPIV3Config = genericapiserver.DefaultOpenAPIV3Config(getOpenAPIDefinitions, namer)
   168  	genericConfig.OpenAPIV3Config.Info.Title = "Kubernetes"
   169  
   170  	genericConfig.LongRunningFunc = filters.BasicLongRunningRequestCheck(
   171  		sets.NewString("watch", "proxy"),
   172  		sets.NewString("attach", "exec", "proxy", "log", "portforward"),
   173  	)
   174  
   175  	kubeVersion := version.Get()
   176  	genericConfig.Version = &kubeVersion
   177  
   178  	if genericConfig.EgressSelector != nil {
   179  		s.Etcd.StorageConfig.Transport.EgressLookup = genericConfig.EgressSelector.Lookup
   180  	}
   181  	if utilfeature.DefaultFeatureGate.Enabled(genericfeatures.APIServerTracing) {
   182  		s.Etcd.StorageConfig.Transport.TracerProvider = genericConfig.TracerProvider
   183  	} else {
   184  		s.Etcd.StorageConfig.Transport.TracerProvider = oteltrace.NewNoopTracerProvider()
   185  	}
   186  
   187  	storageFactoryConfig := kubeapiserver.NewStorageFactoryConfig()
   188  	storageFactoryConfig.APIResourceConfig = genericConfig.MergedResourceConfig
   189  	storageFactory, lastErr = storageFactoryConfig.Complete(s.Etcd).New()
   190  	if lastErr != nil {
   191  		return
   192  	}
   193  	// storageFactory.StorageConfig is copied from etcdOptions.StorageConfig,
   194  	// the StorageObjectCountTracker is still nil. Here we copy from genericConfig.
   195  	storageFactory.StorageConfig.StorageObjectCountTracker = genericConfig.StorageObjectCountTracker
   196  	if lastErr = s.Etcd.ApplyWithStorageFactoryTo(storageFactory, genericConfig); lastErr != nil {
   197  		return
   198  	}
   199  
   200  	ctx := wait.ContextForChannel(genericConfig.DrainedNotify())
   201  
   202  	// Authentication.ApplyTo requires already applied OpenAPIConfig and EgressSelector if present
   203  	if lastErr = s.Authentication.ApplyTo(ctx, &genericConfig.Authentication, genericConfig.SecureServing, genericConfig.EgressSelector, genericConfig.OpenAPIConfig, genericConfig.OpenAPIV3Config, clientgoExternalClient, versionedInformers, genericConfig.APIServerID); lastErr != nil {
   204  		return
   205  	}
   206  
   207  	var enablesRBAC bool
   208  	genericConfig.Authorization.Authorizer, genericConfig.RuleResolver, enablesRBAC, err = BuildAuthorizer(
   209  		ctx,
   210  		s,
   211  		genericConfig.EgressSelector,
   212  		genericConfig.APIServerID,
   213  		versionedInformers,
   214  	)
   215  	if err != nil {
   216  		lastErr = fmt.Errorf("invalid authorization config: %w", err)
   217  		return
   218  	}
   219  	if s.Authorization != nil && !enablesRBAC {
   220  		genericConfig.DisabledPostStartHooks.Insert(rbacrest.PostStartHookName)
   221  	}
   222  
   223  	lastErr = s.Audit.ApplyTo(genericConfig)
   224  	if lastErr != nil {
   225  		return
   226  	}
   227  
   228  	if utilfeature.DefaultFeatureGate.Enabled(genericfeatures.AggregatedDiscoveryEndpoint) {
   229  		genericConfig.AggregatedDiscoveryGroupManager = aggregated.NewResourceManager("apis")
   230  	}
   231  
   232  	return
   233  }
   234  
   235  // BuildAuthorizer constructs the authorizer. If authorization is not set in s, it returns nil, nil, false, nil
   236  func BuildAuthorizer(ctx context.Context, s options.CompletedOptions, egressSelector *egressselector.EgressSelector, apiserverID string, versionedInformers clientgoinformers.SharedInformerFactory) (authorizer.Authorizer, authorizer.RuleResolver, bool, error) {
   237  	authorizationConfig, err := s.Authorization.ToAuthorizationConfig(versionedInformers)
   238  	if err != nil {
   239  		return nil, nil, false, err
   240  	}
   241  	if authorizationConfig == nil {
   242  		return nil, nil, false, nil
   243  	}
   244  
   245  	if egressSelector != nil {
   246  		egressDialer, err := egressSelector.Lookup(egressselector.ControlPlane.AsNetworkContext())
   247  		if err != nil {
   248  			return nil, nil, false, err
   249  		}
   250  		authorizationConfig.CustomDial = egressDialer
   251  	}
   252  
   253  	enablesRBAC := false
   254  	for _, a := range authorizationConfig.AuthorizationConfiguration.Authorizers {
   255  		if string(a.Type) == modes.ModeRBAC {
   256  			enablesRBAC = true
   257  			break
   258  		}
   259  	}
   260  
   261  	authorizer, ruleResolver, err := authorizationConfig.New(ctx, apiserverID)
   262  
   263  	return authorizer, ruleResolver, enablesRBAC, err
   264  }
   265  
   266  // CreateConfig takes the generic controlplane apiserver options and
   267  // creates a config for the generic Kube APIs out of it.
   268  func CreateConfig(
   269  	opts options.CompletedOptions,
   270  	genericConfig *genericapiserver.Config,
   271  	versionedInformers clientgoinformers.SharedInformerFactory,
   272  	storageFactory *serverstorage.DefaultStorageFactory,
   273  	serviceResolver aggregatorapiserver.ServiceResolver,
   274  	additionalInitializers []admission.PluginInitializer,
   275  ) (
   276  	*Config,
   277  	[]admission.PluginInitializer,
   278  	error,
   279  ) {
   280  	proxyTransport := CreateProxyTransport()
   281  
   282  	opts.Metrics.Apply()
   283  	serviceaccount.RegisterMetrics()
   284  
   285  	config := &Config{
   286  		Generic: genericConfig,
   287  		Extra: Extra{
   288  			APIResourceConfigSource: storageFactory.APIResourceConfigSource,
   289  			StorageFactory:          storageFactory,
   290  			EventTTL:                opts.EventTTL,
   291  			EnableLogsSupport:       opts.EnableLogsHandler,
   292  			ProxyTransport:          proxyTransport,
   293  			SystemNamespaces:        opts.SystemNamespaces,
   294  
   295  			ServiceAccountIssuer:        opts.ServiceAccountIssuer,
   296  			ServiceAccountMaxExpiration: opts.ServiceAccountTokenMaxExpiration,
   297  			ExtendExpiration:            opts.Authentication.ServiceAccounts.ExtendExpiration,
   298  
   299  			VersionedInformers: versionedInformers,
   300  		},
   301  	}
   302  
   303  	if utilfeature.DefaultFeatureGate.Enabled(features.UnknownVersionInteroperabilityProxy) {
   304  		var err error
   305  		config.PeerEndpointLeaseReconciler, err = CreatePeerEndpointLeaseReconciler(*genericConfig, storageFactory)
   306  		if err != nil {
   307  			return nil, nil, err
   308  		}
   309  		// build peer proxy config only if peer ca file exists
   310  		if opts.PeerCAFile != "" {
   311  			config.PeerProxy, err = BuildPeerProxy(versionedInformers, genericConfig.StorageVersionManager, opts.ProxyClientCertFile,
   312  				opts.ProxyClientKeyFile, opts.PeerCAFile, opts.PeerAdvertiseAddress, genericConfig.APIServerID, config.Extra.PeerEndpointLeaseReconciler, config.Generic.Serializer)
   313  			if err != nil {
   314  				return nil, nil, err
   315  			}
   316  		}
   317  	}
   318  
   319  	clientCAProvider, err := opts.Authentication.ClientCert.GetClientCAContentProvider()
   320  	if err != nil {
   321  		return nil, nil, err
   322  	}
   323  	config.ClusterAuthenticationInfo.ClientCA = clientCAProvider
   324  
   325  	requestHeaderConfig, err := opts.Authentication.RequestHeader.ToAuthenticationRequestHeaderConfig()
   326  	if err != nil {
   327  		return nil, nil, err
   328  	}
   329  	if requestHeaderConfig != nil {
   330  		config.ClusterAuthenticationInfo.RequestHeaderCA = requestHeaderConfig.CAContentProvider
   331  		config.ClusterAuthenticationInfo.RequestHeaderAllowedNames = requestHeaderConfig.AllowedClientNames
   332  		config.ClusterAuthenticationInfo.RequestHeaderExtraHeaderPrefixes = requestHeaderConfig.ExtraHeaderPrefixes
   333  		config.ClusterAuthenticationInfo.RequestHeaderGroupHeaders = requestHeaderConfig.GroupHeaders
   334  		config.ClusterAuthenticationInfo.RequestHeaderUsernameHeaders = requestHeaderConfig.UsernameHeaders
   335  	}
   336  
   337  	// setup admission
   338  	genericAdmissionConfig := controlplaneadmission.Config{
   339  		ExternalInformers:    versionedInformers,
   340  		LoopbackClientConfig: genericConfig.LoopbackClientConfig,
   341  	}
   342  	genericInitializers, err := genericAdmissionConfig.New(proxyTransport, genericConfig.EgressSelector, serviceResolver, genericConfig.TracerProvider)
   343  	if err != nil {
   344  		return nil, nil, fmt.Errorf("failed to create admission plugin initializer: %w", err)
   345  	}
   346  	clientgoExternalClient, err := clientgoclientset.NewForConfig(genericConfig.LoopbackClientConfig)
   347  	if err != nil {
   348  		return nil, nil, fmt.Errorf("failed to create real client-go external client: %w", err)
   349  	}
   350  	dynamicExternalClient, err := dynamic.NewForConfig(genericConfig.LoopbackClientConfig)
   351  	if err != nil {
   352  		return nil, nil, fmt.Errorf("failed to create real dynamic external client: %w", err)
   353  	}
   354  	err = opts.Admission.ApplyTo(
   355  		genericConfig,
   356  		versionedInformers,
   357  		clientgoExternalClient,
   358  		dynamicExternalClient,
   359  		utilfeature.DefaultFeatureGate,
   360  		append(genericInitializers, additionalInitializers...)...,
   361  	)
   362  	if err != nil {
   363  		return nil, nil, fmt.Errorf("failed to apply admission: %w", err)
   364  	}
   365  
   366  	// Load and set the public keys.
   367  	var pubKeys []interface{}
   368  	for _, f := range opts.Authentication.ServiceAccounts.KeyFiles {
   369  		keys, err := keyutil.PublicKeysFromFile(f)
   370  		if err != nil {
   371  			return nil, nil, fmt.Errorf("failed to parse key file %q: %w", f, err)
   372  		}
   373  		pubKeys = append(pubKeys, keys...)
   374  	}
   375  	config.ServiceAccountIssuerURL = opts.Authentication.ServiceAccounts.Issuers[0]
   376  	config.ServiceAccountJWKSURI = opts.Authentication.ServiceAccounts.JWKSURI
   377  	config.ServiceAccountPublicKeys = pubKeys
   378  
   379  	return config, genericInitializers, nil
   380  }
   381  
   382  // CreateProxyTransport creates the dialer infrastructure to connect to the nodes.
   383  func CreateProxyTransport() *http.Transport {
   384  	var proxyDialerFn utilnet.DialFunc
   385  	// Proxying to pods and services is IP-based... don't expect to be able to verify the hostname
   386  	proxyTLSClientConfig := &tls.Config{InsecureSkipVerify: true}
   387  	proxyTransport := utilnet.SetTransportDefaults(&http.Transport{
   388  		DialContext:     proxyDialerFn,
   389  		TLSClientConfig: proxyTLSClientConfig,
   390  	})
   391  	return proxyTransport
   392  }