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 }