2023-05-29 21:03:29 +00:00
/ *
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 server
import (
"context"
"crypto/sha256"
"encoding/base32"
"fmt"
"net"
"net/http"
"os"
goruntime "runtime"
"runtime/debug"
"sort"
"strconv"
"strings"
"sync/atomic"
"time"
"github.com/google/uuid"
2023-06-01 16:58:10 +00:00
"golang.org/x/crypto/cryptobyte"
2024-08-19 08:01:33 +00:00
jsonpatch "gopkg.in/evanphx/json-patch.v4"
2023-05-29 21:03:29 +00:00
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/runtime/serializer"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/util/sets"
2024-08-19 08:01:33 +00:00
"k8s.io/apimachinery/pkg/util/version"
2023-05-29 21:03:29 +00:00
utilwaitgroup "k8s.io/apimachinery/pkg/util/waitgroup"
"k8s.io/apiserver/pkg/admission"
"k8s.io/apiserver/pkg/audit"
"k8s.io/apiserver/pkg/authentication/authenticator"
"k8s.io/apiserver/pkg/authentication/authenticatorfactory"
authenticatorunion "k8s.io/apiserver/pkg/authentication/request/union"
"k8s.io/apiserver/pkg/authentication/user"
"k8s.io/apiserver/pkg/authorization/authorizer"
"k8s.io/apiserver/pkg/endpoints/discovery"
discoveryendpoint "k8s.io/apiserver/pkg/endpoints/discovery/aggregated"
"k8s.io/apiserver/pkg/endpoints/filterlatency"
genericapifilters "k8s.io/apiserver/pkg/endpoints/filters"
apiopenapi "k8s.io/apiserver/pkg/endpoints/openapi"
apirequest "k8s.io/apiserver/pkg/endpoints/request"
genericfeatures "k8s.io/apiserver/pkg/features"
genericregistry "k8s.io/apiserver/pkg/registry/generic"
"k8s.io/apiserver/pkg/server/dynamiccertificates"
"k8s.io/apiserver/pkg/server/egressselector"
genericfilters "k8s.io/apiserver/pkg/server/filters"
"k8s.io/apiserver/pkg/server/healthz"
"k8s.io/apiserver/pkg/server/routes"
2024-08-19 08:01:33 +00:00
"k8s.io/apiserver/pkg/server/routine"
2023-05-29 21:03:29 +00:00
serverstore "k8s.io/apiserver/pkg/server/storage"
2023-08-17 05:15:28 +00:00
storagevalue "k8s.io/apiserver/pkg/storage/value"
2023-05-29 21:03:29 +00:00
"k8s.io/apiserver/pkg/storageversion"
utilfeature "k8s.io/apiserver/pkg/util/feature"
utilflowcontrol "k8s.io/apiserver/pkg/util/flowcontrol"
flowcontrolrequest "k8s.io/apiserver/pkg/util/flowcontrol/request"
2024-08-19 08:01:33 +00:00
utilversion "k8s.io/apiserver/pkg/util/version"
2023-05-29 21:03:29 +00:00
"k8s.io/client-go/informers"
restclient "k8s.io/client-go/rest"
2024-08-19 08:01:33 +00:00
"k8s.io/component-base/featuregate"
2023-05-29 21:03:29 +00:00
"k8s.io/component-base/logs"
"k8s.io/component-base/metrics/features"
"k8s.io/component-base/metrics/prometheus/slis"
"k8s.io/component-base/tracing"
"k8s.io/klog/v2"
openapicommon "k8s.io/kube-openapi/pkg/common"
2023-12-20 12:23:59 +00:00
"k8s.io/kube-openapi/pkg/spec3"
2023-05-29 21:03:29 +00:00
"k8s.io/kube-openapi/pkg/validation/spec"
"k8s.io/utils/clock"
utilsnet "k8s.io/utils/net"
// install apis
_ "k8s.io/apiserver/pkg/apis/apiserver/install"
)
2023-08-17 05:15:28 +00:00
// hostnameFunc is a function to set the hostnameFunc of this apiserver.
// To be used for testing purpose only, to simulate scenarios where multiple apiservers
// exist. In such cases we want to ensure unique apiserver IDs which are a hash of hostnameFunc.
var (
hostnameFunc = os . Hostname
)
2023-05-29 21:03:29 +00:00
const (
// DefaultLegacyAPIPrefix is where the legacy APIs will be located.
DefaultLegacyAPIPrefix = "/api"
// APIGroupPrefix is where non-legacy API group will be located.
APIGroupPrefix = "/apis"
)
// Config is a structure used to configure a GenericAPIServer.
// Its members are sorted roughly in order of importance for composers.
type Config struct {
// SecureServing is required to serve https
SecureServing * SecureServingInfo
// Authentication is the configuration for authentication
Authentication AuthenticationInfo
// Authorization is the configuration for authorization
Authorization AuthorizationInfo
// LoopbackClientConfig is a config for a privileged loopback connection to the API server
// This is required for proper functioning of the PostStartHooks on a GenericAPIServer
// TODO: move into SecureServing(WithLoopback) as soon as insecure serving is gone
LoopbackClientConfig * restclient . Config
// EgressSelector provides a lookup mechanism for dialing outbound connections.
// It does so based on a EgressSelectorConfiguration which was read at startup.
EgressSelector * egressselector . EgressSelector
// RuleResolver is required to get the list of rules that apply to a given user
// in a given namespace
RuleResolver authorizer . RuleResolver
// AdmissionControl performs deep inspection of a given request (including content)
// to set values and determine whether its allowed
AdmissionControl admission . Interface
CorsAllowedOriginList [ ] string
HSTSDirectives [ ] string
// FlowControl, if not nil, gives priority and fairness to request handling
FlowControl utilflowcontrol . Interface
EnableIndex bool
EnableProfiling bool
2023-06-01 16:58:10 +00:00
DebugSocketPath string
2023-05-29 21:03:29 +00:00
EnableDiscovery bool
// Requires generic profiling enabled
EnableContentionProfiling bool
EnableMetrics bool
DisabledPostStartHooks sets . String
// done values in this values for this map are ignored.
PostStartHooks map [ string ] PostStartHookConfigEntry
2024-08-19 08:01:33 +00:00
// EffectiveVersion determines which apis and features are available
// based on when the api/feature lifecyle.
EffectiveVersion utilversion . EffectiveVersion
// FeatureGate is a way to plumb feature gate through if you have them.
FeatureGate featuregate . FeatureGate
2023-05-29 21:03:29 +00:00
// AuditBackend is where audit events are sent to.
AuditBackend audit . Backend
// AuditPolicyRuleEvaluator makes the decision of whether and how to audit log a request.
AuditPolicyRuleEvaluator audit . PolicyRuleEvaluator
// ExternalAddress is the host name to use for external (public internet) facing URLs (e.g. Swagger)
// Will default to a value based on secure serving info and available ipv4 IPs.
ExternalAddress string
// TracerProvider can provide a tracer, which records spans for distributed tracing.
TracerProvider tracing . TracerProvider
//===========================================================================
// Fields you probably don't care about changing
//===========================================================================
// BuildHandlerChainFunc allows you to build custom handler chains by decorating the apiHandler.
BuildHandlerChainFunc func ( apiHandler http . Handler , c * Config ) ( secure http . Handler )
2023-06-01 16:58:10 +00:00
// NonLongRunningRequestWaitGroup allows you to wait for all chain
// handlers associated with non long-running requests
// to complete while the server is shuting down.
NonLongRunningRequestWaitGroup * utilwaitgroup . SafeWaitGroup
// WatchRequestWaitGroup allows us to wait for all chain
// handlers associated with active watch requests to
// complete while the server is shuting down.
WatchRequestWaitGroup * utilwaitgroup . RateLimitedSafeWaitGroup
2023-05-29 21:03:29 +00:00
// DiscoveryAddresses is used to build the IPs pass to discovery. If nil, the ExternalAddress is
// always reported
DiscoveryAddresses discovery . Addresses
// The default set of healthz checks. There might be more added via AddHealthChecks dynamically.
HealthzChecks [ ] healthz . HealthChecker
// The default set of livez checks. There might be more added via AddHealthChecks dynamically.
LivezChecks [ ] healthz . HealthChecker
// The default set of readyz-only checks. There might be more added via AddReadyzChecks dynamically.
ReadyzChecks [ ] healthz . HealthChecker
// LegacyAPIGroupPrefixes is used to set up URL parsing for authorization and for validating requests
// to InstallLegacyAPIGroup. New API servers don't generally have legacy groups at all.
LegacyAPIGroupPrefixes sets . String
// RequestInfoResolver is used to assign attributes (used by admission and authorization) based on a request URL.
// Use-cases that are like kubelets may need to customize this.
RequestInfoResolver apirequest . RequestInfoResolver
// Serializer is required and provides the interface for serializing and converting objects to and from the wire
// The default (api.Codecs) usually works fine.
Serializer runtime . NegotiatedSerializer
// OpenAPIConfig will be used in generating OpenAPI spec. This is nil by default. Use DefaultOpenAPIConfig for "working" defaults.
OpenAPIConfig * openapicommon . Config
// OpenAPIV3Config will be used in generating OpenAPI V3 spec. This is nil by default. Use DefaultOpenAPIV3Config for "working" defaults.
2023-12-20 12:23:59 +00:00
OpenAPIV3Config * openapicommon . OpenAPIV3Config
2023-05-29 21:03:29 +00:00
// SkipOpenAPIInstallation avoids installing the OpenAPI handler if set to true.
SkipOpenAPIInstallation bool
2023-08-17 05:15:28 +00:00
// ResourceTransformers are used to transform resources from and to etcd, e.g. encryption.
ResourceTransformers storagevalue . ResourceTransformers
2023-05-29 21:03:29 +00:00
// RESTOptionsGetter is used to construct RESTStorage types via the generic registry.
RESTOptionsGetter genericregistry . RESTOptionsGetter
// If specified, all requests except those which match the LongRunningFunc predicate will timeout
// after this duration.
RequestTimeout time . Duration
// If specified, long running requests such as watch will be allocated a random timeout between this value, and
// twice this value. Note that it is up to the request handlers to ignore or honor this timeout. In seconds.
MinRequestTimeout int
2024-08-19 08:01:33 +00:00
// StorageInitializationTimeout defines the maximum amount of time to wait for storage initialization
// before declaring apiserver ready.
StorageInitializationTimeout time . Duration
2023-05-29 21:03:29 +00:00
// This represents the maximum amount of time it should take for apiserver to complete its startup
// sequence and become healthy. From apiserver's start time to when this amount of time has
// elapsed, /livez will assume that unfinished post-start hooks will complete successfully and
// therefore return true.
LivezGracePeriod time . Duration
// ShutdownDelayDuration allows to block shutdown for some time, e.g. until endpoints pointing to this API server
// have converged on all node. During this time, the API server keeps serving, /healthz will return 200,
// but /readyz will return failure.
ShutdownDelayDuration time . Duration
// The limit on the total size increase all "copy" operations in a json
// patch may cause.
// This affects all places that applies json patch in the binary.
JSONPatchMaxCopyBytes int64
// The limit on the request size that would be accepted and decoded in a write request
// 0 means no limit.
MaxRequestBodyBytes int64
// MaxRequestsInFlight is the maximum number of parallel non-long-running requests. Every further
// request has to wait. Applies only to non-mutating requests.
MaxRequestsInFlight int
// MaxMutatingRequestsInFlight is the maximum number of parallel mutating requests. Every further
// request has to wait.
MaxMutatingRequestsInFlight int
// Predicate which is true for paths of long-running http requests
LongRunningFunc apirequest . LongRunningRequestCheck
// GoawayChance is the probability that send a GOAWAY to HTTP/2 clients. When client received
// GOAWAY, the in-flight requests will not be affected and new requests will use
// a new TCP connection to triggering re-balancing to another server behind the load balance.
// Default to 0, means never send GOAWAY. Max is 0.02 to prevent break the apiserver.
GoawayChance float64
// MergedResourceConfig indicates which groupVersion enabled and its resources enabled/disabled.
// This is composed of genericapiserver defaultAPIResourceConfig and those parsed from flags.
// If not specify any in flags, then genericapiserver will only enable defaultAPIResourceConfig.
MergedResourceConfig * serverstore . ResourceConfig
// lifecycleSignals provides access to the various signals
// that happen during lifecycle of the apiserver.
// it's intentionally marked private as it should never be overridden.
lifecycleSignals lifecycleSignals
// StorageObjectCountTracker is used to keep track of the total number of objects
// in the storage per resource, so we can estimate width of incoming requests.
StorageObjectCountTracker flowcontrolrequest . StorageObjectCountTracker
// ShutdownSendRetryAfter dictates when to initiate shutdown of the HTTP
// Server during the graceful termination of the apiserver. If true, we wait
// for non longrunning requests in flight to be drained and then initiate a
// shutdown of the HTTP Server. If false, we initiate a shutdown of the HTTP
// Server as soon as ShutdownDelayDuration has elapsed.
// If enabled, after ShutdownDelayDuration elapses, any incoming request is
// rejected with a 429 status code and a 'Retry-After' response.
ShutdownSendRetryAfter bool
//===========================================================================
// values below here are targets for removal
//===========================================================================
// PublicAddress is the IP address where members of the cluster (kubelet,
// kube-proxy, services, etc.) can reach the GenericAPIServer.
// If nil or 0.0.0.0, the host's default interface will be used.
PublicAddress net . IP
// EquivalentResourceRegistry provides information about resources equivalent to a given resource,
// and the kind associated with a given resource. As resources are installed, they are registered here.
EquivalentResourceRegistry runtime . EquivalentResourceRegistry
// APIServerID is the ID of this API server
APIServerID string
// StorageVersionManager holds the storage versions of the API resources installed by this server.
StorageVersionManager storageversion . Manager
// AggregatedDiscoveryGroupManager serves /apis in an aggregated form.
AggregatedDiscoveryGroupManager discoveryendpoint . ResourceManager
2023-06-01 16:58:10 +00:00
// ShutdownWatchTerminationGracePeriod, if set to a positive value,
// is the maximum duration the apiserver will wait for all active
// watch request(s) to drain.
// Once this grace period elapses, the apiserver will no longer
// wait for any active watch request(s) in flight to drain, it will
// proceed to the next step in the graceful server shutdown process.
// If set to a positive value, the apiserver will keep track of the
// number of active watch request(s) in flight and during shutdown
// it will wait, at most, for the specified duration and allow these
// active watch requests to drain with some rate limiting in effect.
// The default is zero, which implies the apiserver will not keep
// track of active watch request(s) in flight and will not wait
// for them to drain, this maintains backward compatibility.
// This grace period is orthogonal to other grace periods, and
// it is not overridden by any other grace period.
ShutdownWatchTerminationGracePeriod time . Duration
2023-05-29 21:03:29 +00:00
}
type RecommendedConfig struct {
Config
// SharedInformerFactory provides shared informers for Kubernetes resources. This value is set by
// RecommendedOptions.CoreAPI.ApplyTo called by RecommendedOptions.ApplyTo. It uses an in-cluster client config
// by default, or the kubeconfig given with kubeconfig command line flag.
SharedInformerFactory informers . SharedInformerFactory
// ClientConfig holds the kubernetes client configuration.
// This value is set by RecommendedOptions.CoreAPI.ApplyTo called by RecommendedOptions.ApplyTo.
// By default in-cluster client config is used.
ClientConfig * restclient . Config
}
type SecureServingInfo struct {
// Listener is the secure server network listener.
Listener net . Listener
// Cert is the main server cert which is used if SNI does not match. Cert must be non-nil and is
// allowed to be in SNICerts.
Cert dynamiccertificates . CertKeyContentProvider
// SNICerts are the TLS certificates used for SNI.
SNICerts [ ] dynamiccertificates . SNICertKeyContentProvider
// ClientCA is the certificate bundle for all the signers that you'll recognize for incoming client certificates
ClientCA dynamiccertificates . CAContentProvider
// MinTLSVersion optionally overrides the minimum TLS version supported.
// Values are from tls package constants (https://golang.org/pkg/crypto/tls/#pkg-constants).
MinTLSVersion uint16
// CipherSuites optionally overrides the list of allowed cipher suites for the server.
// Values are from tls package constants (https://golang.org/pkg/crypto/tls/#pkg-constants).
CipherSuites [ ] uint16
// HTTP2MaxStreamsPerConnection is the limit that the api server imposes on each client.
// A value of zero means to use the default provided by golang's HTTP/2 support.
HTTP2MaxStreamsPerConnection int
// DisableHTTP2 indicates that http2 should not be enabled.
DisableHTTP2 bool
}
type AuthenticationInfo struct {
// APIAudiences is a list of identifier that the API identifies as. This is
// used by some authenticators to validate audience bound credentials.
APIAudiences authenticator . Audiences
// Authenticator determines which subject is making the request
Authenticator authenticator . Request
2023-06-01 16:58:10 +00:00
RequestHeaderConfig * authenticatorfactory . RequestHeaderConfig
2023-05-29 21:03:29 +00:00
}
type AuthorizationInfo struct {
// Authorizer determines whether the subject is allowed to make the request based only
// on the RequestURI
Authorizer authorizer . Authorizer
}
func init ( ) {
utilruntime . Must ( features . AddFeatureGates ( utilfeature . DefaultMutableFeatureGate ) )
}
// NewConfig returns a Config struct with the default values
func NewConfig ( codecs serializer . CodecFactory ) * Config {
defaultHealthChecks := [ ] healthz . HealthChecker { healthz . PingHealthz , healthz . LogHealthz }
var id string
if utilfeature . DefaultFeatureGate . Enabled ( genericfeatures . APIServerIdentity ) {
2023-08-17 05:15:28 +00:00
hostname , err := hostnameFunc ( )
2023-05-29 21:03:29 +00:00
if err != nil {
klog . Fatalf ( "error getting hostname for apiserver identity: %v" , err )
}
2023-06-01 16:58:10 +00:00
// Since the hash needs to be unique across each kube-apiserver and aggregated apiservers,
// the hash used for the identity should include both the hostname and the identity value.
// TODO: receive the identity value as a parameter once the apiserver identity lease controller
// post start hook is moved to generic apiserver.
b := cryptobyte . NewBuilder ( nil )
b . AddUint16LengthPrefixed ( func ( b * cryptobyte . Builder ) {
b . AddBytes ( [ ] byte ( hostname ) )
} )
b . AddUint16LengthPrefixed ( func ( b * cryptobyte . Builder ) {
b . AddBytes ( [ ] byte ( "kube-apiserver" ) )
} )
hashData , err := b . Bytes ( )
if err != nil {
klog . Fatalf ( "error building hash data for apiserver identity: %v" , err )
}
hash := sha256 . Sum256 ( hashData )
id = "apiserver-" + strings . ToLower ( base32 . StdEncoding . WithPadding ( base32 . NoPadding ) . EncodeToString ( hash [ : 16 ] ) )
2023-05-29 21:03:29 +00:00
}
lifecycleSignals := newLifecycleSignals ( )
return & Config {
2023-06-01 16:58:10 +00:00
Serializer : codecs ,
BuildHandlerChainFunc : DefaultBuildHandlerChain ,
NonLongRunningRequestWaitGroup : new ( utilwaitgroup . SafeWaitGroup ) ,
WatchRequestWaitGroup : & utilwaitgroup . RateLimitedSafeWaitGroup { } ,
LegacyAPIGroupPrefixes : sets . NewString ( DefaultLegacyAPIPrefix ) ,
DisabledPostStartHooks : sets . NewString ( ) ,
PostStartHooks : map [ string ] PostStartHookConfigEntry { } ,
HealthzChecks : append ( [ ] healthz . HealthChecker { } , defaultHealthChecks ... ) ,
ReadyzChecks : append ( [ ] healthz . HealthChecker { } , defaultHealthChecks ... ) ,
LivezChecks : append ( [ ] healthz . HealthChecker { } , defaultHealthChecks ... ) ,
EnableIndex : true ,
EnableDiscovery : true ,
EnableProfiling : true ,
DebugSocketPath : "" ,
EnableMetrics : true ,
MaxRequestsInFlight : 400 ,
MaxMutatingRequestsInFlight : 200 ,
RequestTimeout : time . Duration ( 60 ) * time . Second ,
MinRequestTimeout : 1800 ,
2024-08-19 08:01:33 +00:00
StorageInitializationTimeout : time . Minute ,
2023-06-01 16:58:10 +00:00
LivezGracePeriod : time . Duration ( 0 ) ,
ShutdownDelayDuration : time . Duration ( 0 ) ,
2023-05-29 21:03:29 +00:00
// 1.5MB is the default client request size in bytes
// the etcd server should accept. See
// https://github.com/etcd-io/etcd/blob/release-3.4/embed/config.go#L56.
// A request body might be encoded in json, and is converted to
// proto when persisted in etcd, so we allow 2x as the largest size
// increase the "copy" operations in a json patch may cause.
JSONPatchMaxCopyBytes : int64 ( 3 * 1024 * 1024 ) ,
// 1.5MB is the recommended client request size in byte
// the etcd server should accept. See
// https://github.com/etcd-io/etcd/blob/release-3.4/embed/config.go#L56.
// A request body might be encoded in json, and is converted to
// proto when persisted in etcd, so we allow 2x as the largest request
// body size to be accepted and decoded in a write request.
// If this constant is changed, DefaultMaxRequestSizeBytes in k8s.io/apiserver/pkg/cel/limits.go
// should be changed to reflect the new value, if the two haven't
// been wired together already somehow.
MaxRequestBodyBytes : int64 ( 3 * 1024 * 1024 ) ,
// Default to treating watch as a long-running operation
// Generic API servers have no inherent long-running subresources
2023-06-01 16:58:10 +00:00
LongRunningFunc : genericfilters . BasicLongRunningRequestCheck ( sets . NewString ( "watch" ) , sets . NewString ( ) ) ,
lifecycleSignals : lifecycleSignals ,
StorageObjectCountTracker : flowcontrolrequest . NewStorageObjectCountTracker ( ) ,
ShutdownWatchTerminationGracePeriod : time . Duration ( 0 ) ,
2023-05-29 21:03:29 +00:00
APIServerID : id ,
StorageVersionManager : storageversion . NewDefaultManager ( ) ,
TracerProvider : tracing . NewNoopTracerProvider ( ) ,
}
}
// NewRecommendedConfig returns a RecommendedConfig struct with the default values
func NewRecommendedConfig ( codecs serializer . CodecFactory ) * RecommendedConfig {
return & RecommendedConfig {
Config : * NewConfig ( codecs ) ,
}
}
// DefaultOpenAPIConfig provides the default OpenAPIConfig used to build the OpenAPI V2 spec
func DefaultOpenAPIConfig ( getDefinitions openapicommon . GetOpenAPIDefinitions , defNamer * apiopenapi . DefinitionNamer ) * openapicommon . Config {
return & openapicommon . Config {
ProtocolList : [ ] string { "https" } ,
IgnorePrefixes : [ ] string { } ,
Info : & spec . Info {
InfoProps : spec . InfoProps {
Title : "Generic API Server" ,
} ,
} ,
DefaultResponse : & spec . Response {
ResponseProps : spec . ResponseProps {
Description : "Default Response." ,
} ,
} ,
GetOperationIDAndTags : apiopenapi . GetOperationIDAndTags ,
GetDefinitionName : defNamer . GetDefinitionName ,
GetDefinitions : getDefinitions ,
}
}
// DefaultOpenAPIV3Config provides the default OpenAPIV3Config used to build the OpenAPI V3 spec
2023-12-20 12:23:59 +00:00
func DefaultOpenAPIV3Config ( getDefinitions openapicommon . GetOpenAPIDefinitions , defNamer * apiopenapi . DefinitionNamer ) * openapicommon . OpenAPIV3Config {
defaultConfig := & openapicommon . OpenAPIV3Config {
IgnorePrefixes : [ ] string { } ,
Info : & spec . Info {
InfoProps : spec . InfoProps {
Title : "Generic API Server" ,
} ,
} ,
DefaultResponse : & spec3 . Response {
ResponseProps : spec3 . ResponseProps {
Description : "Default Response." ,
} ,
} ,
GetOperationIDAndTags : apiopenapi . GetOperationIDAndTags ,
GetDefinitionName : defNamer . GetDefinitionName ,
GetDefinitions : getDefinitions ,
}
2023-05-29 21:03:29 +00:00
defaultConfig . Definitions = getDefinitions ( func ( name string ) spec . Ref {
defName , _ := defaultConfig . GetDefinitionName ( name )
return spec . MustCreateRef ( "#/components/schemas/" + openapicommon . EscapeJsonPointer ( defName ) )
} )
return defaultConfig
}
func ( c * AuthenticationInfo ) ApplyClientCert ( clientCA dynamiccertificates . CAContentProvider , servingInfo * SecureServingInfo ) error {
if servingInfo == nil {
return nil
}
if clientCA == nil {
return nil
}
if servingInfo . ClientCA == nil {
servingInfo . ClientCA = clientCA
return nil
}
servingInfo . ClientCA = dynamiccertificates . NewUnionCAContentProvider ( servingInfo . ClientCA , clientCA )
return nil
}
type completedConfig struct {
* Config
//===========================================================================
// values below here are filled in during completion
//===========================================================================
// SharedInformerFactory provides shared informers for resources
SharedInformerFactory informers . SharedInformerFactory
}
type CompletedConfig struct {
// Embed a private pointer that cannot be instantiated outside of this package.
* completedConfig
}
// AddHealthChecks adds a health check to our config to be exposed by the health endpoints
// of our configured apiserver. We should prefer this to adding healthChecks directly to
// the config unless we explicitly want to add a healthcheck only to a specific health endpoint.
func ( c * Config ) AddHealthChecks ( healthChecks ... healthz . HealthChecker ) {
c . HealthzChecks = append ( c . HealthzChecks , healthChecks ... )
c . LivezChecks = append ( c . LivezChecks , healthChecks ... )
c . ReadyzChecks = append ( c . ReadyzChecks , healthChecks ... )
}
// AddReadyzChecks adds a health check to our config to be exposed by the readyz endpoint
// of our configured apiserver.
func ( c * Config ) AddReadyzChecks ( healthChecks ... healthz . HealthChecker ) {
c . ReadyzChecks = append ( c . ReadyzChecks , healthChecks ... )
}
// AddPostStartHook allows you to add a PostStartHook that will later be added to the server itself in a New call.
// Name conflicts will cause an error.
func ( c * Config ) AddPostStartHook ( name string , hook PostStartHookFunc ) error {
if len ( name ) == 0 {
return fmt . Errorf ( "missing name" )
}
if hook == nil {
return fmt . Errorf ( "hook func may not be nil: %q" , name )
}
if c . DisabledPostStartHooks . Has ( name ) {
klog . V ( 1 ) . Infof ( "skipping %q because it was explicitly disabled" , name )
return nil
}
if postStartHook , exists := c . PostStartHooks [ name ] ; exists {
// this is programmer error, but it can be hard to debug
return fmt . Errorf ( "unable to add %q because it was already registered by: %s" , name , postStartHook . originatingStack )
}
c . PostStartHooks [ name ] = PostStartHookConfigEntry { hook : hook , originatingStack : string ( debug . Stack ( ) ) }
return nil
}
// AddPostStartHookOrDie allows you to add a PostStartHook, but dies on failure.
func ( c * Config ) AddPostStartHookOrDie ( name string , hook PostStartHookFunc ) {
if err := c . AddPostStartHook ( name , hook ) ; err != nil {
klog . Fatalf ( "Error registering PostStartHook %q: %v" , name , err )
}
}
2024-08-19 08:01:33 +00:00
func completeOpenAPI ( config * openapicommon . Config , version * version . Version ) {
2023-05-29 21:03:29 +00:00
if config == nil {
return
}
if config . SecurityDefinitions != nil {
// Setup OpenAPI security: all APIs will have the same authentication for now.
config . DefaultSecurity = [ ] map [ string ] [ ] string { }
keys := [ ] string { }
for k := range * config . SecurityDefinitions {
keys = append ( keys , k )
}
sort . Strings ( keys )
for _ , k := range keys {
config . DefaultSecurity = append ( config . DefaultSecurity , map [ string ] [ ] string { k : { } } )
}
if config . CommonResponses == nil {
config . CommonResponses = map [ int ] spec . Response { }
}
if _ , exists := config . CommonResponses [ http . StatusUnauthorized ] ; ! exists {
config . CommonResponses [ http . StatusUnauthorized ] = spec . Response {
ResponseProps : spec . ResponseProps {
Description : "Unauthorized" ,
} ,
}
}
}
// make sure we populate info, and info.version, if not manually set
if config . Info == nil {
config . Info = & spec . Info { }
}
if config . Info . Version == "" {
if version != nil {
config . Info . Version = strings . Split ( version . String ( ) , "-" ) [ 0 ]
} else {
config . Info . Version = "unversioned"
}
}
}
2024-08-19 08:01:33 +00:00
func completeOpenAPIV3 ( config * openapicommon . OpenAPIV3Config , version * version . Version ) {
2023-12-20 12:23:59 +00:00
if config == nil {
return
}
if config . SecuritySchemes != nil {
// Setup OpenAPI security: all APIs will have the same authentication for now.
config . DefaultSecurity = [ ] map [ string ] [ ] string { }
keys := [ ] string { }
for k := range config . SecuritySchemes {
keys = append ( keys , k )
}
sort . Strings ( keys )
for _ , k := range keys {
config . DefaultSecurity = append ( config . DefaultSecurity , map [ string ] [ ] string { k : { } } )
}
if config . CommonResponses == nil {
config . CommonResponses = map [ int ] * spec3 . Response { }
}
if _ , exists := config . CommonResponses [ http . StatusUnauthorized ] ; ! exists {
config . CommonResponses [ http . StatusUnauthorized ] = & spec3 . Response {
ResponseProps : spec3 . ResponseProps {
Description : "Unauthorized" ,
} ,
}
}
}
// make sure we populate info, and info.version, if not manually set
if config . Info == nil {
config . Info = & spec . Info { }
}
if config . Info . Version == "" {
if version != nil {
config . Info . Version = strings . Split ( version . String ( ) , "-" ) [ 0 ]
} else {
config . Info . Version = "unversioned"
}
}
}
2023-05-29 21:03:29 +00:00
// DrainedNotify returns a lifecycle signal of genericapiserver already drained while shutting down.
func ( c * Config ) DrainedNotify ( ) <- chan struct { } {
return c . lifecycleSignals . InFlightRequestsDrained . Signaled ( )
}
2024-05-15 06:54:18 +00:00
// ShutdownInitiated returns a lifecycle signal of apiserver shutdown having been initiated.
func ( c * Config ) ShutdownInitiatedNotify ( ) <- chan struct { } {
return c . lifecycleSignals . ShutdownInitiated . Signaled ( )
}
2023-05-29 21:03:29 +00:00
// Complete fills in any fields not set that are required to have valid data and can be derived
// from other fields. If you're going to `ApplyOptions`, do that first. It's mutating the receiver.
func ( c * Config ) Complete ( informers informers . SharedInformerFactory ) CompletedConfig {
2024-08-19 08:01:33 +00:00
if c . FeatureGate == nil {
c . FeatureGate = utilfeature . DefaultFeatureGate
}
2023-05-29 21:03:29 +00:00
if len ( c . ExternalAddress ) == 0 && c . PublicAddress != nil {
c . ExternalAddress = c . PublicAddress . String ( )
}
// if there is no port, and we listen on one securely, use that one
if _ , _ , err := net . SplitHostPort ( c . ExternalAddress ) ; err != nil {
if c . SecureServing == nil {
klog . Fatalf ( "cannot derive external address port without listening on a secure port." )
}
_ , port , err := c . SecureServing . HostPort ( )
if err != nil {
klog . Fatalf ( "cannot derive external address from the secure port: %v" , err )
}
c . ExternalAddress = net . JoinHostPort ( c . ExternalAddress , strconv . Itoa ( port ) )
}
2024-08-19 08:01:33 +00:00
completeOpenAPI ( c . OpenAPIConfig , c . EffectiveVersion . EmulationVersion ( ) )
completeOpenAPIV3 ( c . OpenAPIV3Config , c . EffectiveVersion . EmulationVersion ( ) )
2023-05-29 21:03:29 +00:00
if c . DiscoveryAddresses == nil {
c . DiscoveryAddresses = discovery . DefaultAddresses { DefaultAddress : c . ExternalAddress }
}
AuthorizeClientBearerToken ( c . LoopbackClientConfig , & c . Authentication , & c . Authorization )
if c . RequestInfoResolver == nil {
c . RequestInfoResolver = NewRequestInfoResolver ( c )
}
if c . EquivalentResourceRegistry == nil {
if c . RESTOptionsGetter == nil {
c . EquivalentResourceRegistry = runtime . NewEquivalentResourceRegistry ( )
} else {
c . EquivalentResourceRegistry = runtime . NewEquivalentResourceRegistryWithIdentity ( func ( groupResource schema . GroupResource ) string {
// use the storage prefix as the key if possible
2024-08-19 08:01:33 +00:00
if opts , err := c . RESTOptionsGetter . GetRESTOptions ( groupResource , nil ) ; err == nil {
2023-05-29 21:03:29 +00:00
return opts . ResourcePrefix
}
// otherwise return "" to use the default key (parent GV name)
return ""
} )
}
}
return CompletedConfig { & completedConfig { c , informers } }
}
// Complete fills in any fields not set that are required to have valid data and can be derived
// from other fields. If you're going to `ApplyOptions`, do that first. It's mutating the receiver.
func ( c * RecommendedConfig ) Complete ( ) CompletedConfig {
return c . Config . Complete ( c . SharedInformerFactory )
}
2023-12-20 12:23:59 +00:00
var allowedMediaTypes = [ ] string {
runtime . ContentTypeJSON ,
runtime . ContentTypeYAML ,
runtime . ContentTypeProtobuf ,
}
2023-05-29 21:03:29 +00:00
// New creates a new server which logically combines the handling chain with the passed server.
// name is used to differentiate for logging. The handler chain in particular can be difficult as it starts delegating.
// delegationTarget may not be nil.
func ( c completedConfig ) New ( name string , delegationTarget DelegationTarget ) ( * GenericAPIServer , error ) {
if c . Serializer == nil {
return nil , fmt . Errorf ( "Genericapiserver.New() called with config.Serializer == nil" )
}
2023-12-20 12:23:59 +00:00
for _ , info := range c . Serializer . SupportedMediaTypes ( ) {
var ok bool
for _ , mt := range allowedMediaTypes {
if info . MediaType == mt {
ok = true
break
}
}
if ! ok {
return nil , fmt . Errorf ( "refusing to create new apiserver %q with support for media type %q (allowed media types are: %s)" , name , info . MediaType , strings . Join ( allowedMediaTypes , ", " ) )
}
}
2023-05-29 21:03:29 +00:00
if c . LoopbackClientConfig == nil {
return nil , fmt . Errorf ( "Genericapiserver.New() called with config.LoopbackClientConfig == nil" )
}
if c . EquivalentResourceRegistry == nil {
return nil , fmt . Errorf ( "Genericapiserver.New() called with config.EquivalentResourceRegistry == nil" )
}
handlerChainBuilder := func ( handler http . Handler ) http . Handler {
return c . BuildHandlerChainFunc ( handler , c . Config )
}
2023-06-01 16:58:10 +00:00
var debugSocket * routes . DebugSocket
if c . DebugSocketPath != "" {
debugSocket = routes . NewDebugSocket ( c . DebugSocketPath )
}
2023-05-29 21:03:29 +00:00
apiServerHandler := NewAPIServerHandler ( name , c . Serializer , handlerChainBuilder , delegationTarget . UnprotectedHandler ( ) )
s := & GenericAPIServer {
2023-06-01 16:58:10 +00:00
discoveryAddresses : c . DiscoveryAddresses ,
LoopbackClientConfig : c . LoopbackClientConfig ,
legacyAPIGroupPrefixes : c . LegacyAPIGroupPrefixes ,
admissionControl : c . AdmissionControl ,
Serializer : c . Serializer ,
AuditBackend : c . AuditBackend ,
Authorizer : c . Authorization . Authorizer ,
delegationTarget : delegationTarget ,
EquivalentResourceRegistry : c . EquivalentResourceRegistry ,
NonLongRunningRequestWaitGroup : c . NonLongRunningRequestWaitGroup ,
WatchRequestWaitGroup : c . WatchRequestWaitGroup ,
Handler : apiServerHandler ,
UnprotectedDebugSocket : debugSocket ,
2023-05-29 21:03:29 +00:00
listedPathProvider : apiServerHandler ,
2023-06-01 16:58:10 +00:00
minRequestTimeout : time . Duration ( c . MinRequestTimeout ) * time . Second ,
ShutdownTimeout : c . RequestTimeout ,
ShutdownDelayDuration : c . ShutdownDelayDuration ,
ShutdownWatchTerminationGracePeriod : c . ShutdownWatchTerminationGracePeriod ,
SecureServingInfo : c . SecureServing ,
ExternalAddress : c . ExternalAddress ,
2023-05-29 21:03:29 +00:00
openAPIConfig : c . OpenAPIConfig ,
openAPIV3Config : c . OpenAPIV3Config ,
skipOpenAPIInstallation : c . SkipOpenAPIInstallation ,
postStartHooks : map [ string ] postStartHookEntry { } ,
preShutdownHooks : map [ string ] preShutdownHookEntry { } ,
disabledPostStartHooks : c . DisabledPostStartHooks ,
2024-05-15 06:54:18 +00:00
healthzRegistry : healthCheckRegistry { path : "/healthz" , checks : c . HealthzChecks } ,
livezRegistry : healthCheckRegistry { path : "/livez" , checks : c . LivezChecks , clock : clock . RealClock { } } ,
readyzRegistry : healthCheckRegistry { path : "/readyz" , checks : c . ReadyzChecks } ,
2023-05-29 21:03:29 +00:00
livezGracePeriod : c . LivezGracePeriod ,
DiscoveryGroupManager : discovery . NewRootAPIsHandler ( c . DiscoveryAddresses , c . Serializer ) ,
maxRequestBodyBytes : c . MaxRequestBodyBytes ,
lifecycleSignals : c . lifecycleSignals ,
ShutdownSendRetryAfter : c . ShutdownSendRetryAfter ,
APIServerID : c . APIServerID ,
2024-08-19 08:01:33 +00:00
StorageReadinessHook : NewStorageReadinessHook ( c . StorageInitializationTimeout ) ,
2023-05-29 21:03:29 +00:00
StorageVersionManager : c . StorageVersionManager ,
2024-08-19 08:01:33 +00:00
EffectiveVersion : c . EffectiveVersion ,
FeatureGate : c . FeatureGate ,
2023-05-29 21:03:29 +00:00
muxAndDiscoveryCompleteSignals : map [ string ] <- chan struct { } { } ,
}
2024-08-19 08:01:33 +00:00
if c . FeatureGate . Enabled ( genericfeatures . AggregatedDiscoveryEndpoint ) {
2023-05-29 21:03:29 +00:00
manager := c . AggregatedDiscoveryGroupManager
if manager == nil {
2023-06-01 16:58:10 +00:00
manager = discoveryendpoint . NewResourceManager ( "apis" )
2023-05-29 21:03:29 +00:00
}
s . AggregatedDiscoveryGroupManager = manager
2023-06-01 16:58:10 +00:00
s . AggregatedLegacyDiscoveryGroupManager = discoveryendpoint . NewResourceManager ( "api" )
2023-05-29 21:03:29 +00:00
}
for {
if c . JSONPatchMaxCopyBytes <= 0 {
break
}
existing := atomic . LoadInt64 ( & jsonpatch . AccumulatedCopySizeLimit )
if existing > 0 && existing < c . JSONPatchMaxCopyBytes {
break
}
if atomic . CompareAndSwapInt64 ( & jsonpatch . AccumulatedCopySizeLimit , existing , c . JSONPatchMaxCopyBytes ) {
break
}
}
// first add poststarthooks from delegated targets
for k , v := range delegationTarget . PostStartHooks ( ) {
s . postStartHooks [ k ] = v
}
for k , v := range delegationTarget . PreShutdownHooks ( ) {
s . preShutdownHooks [ k ] = v
}
// add poststarthooks that were preconfigured. Using the add method will give us an error if the same name has already been registered.
for name , preconfiguredPostStartHook := range c . PostStartHooks {
if err := s . AddPostStartHook ( name , preconfiguredPostStartHook . hook ) ; err != nil {
return nil , err
}
}
// register mux signals from the delegated server
for k , v := range delegationTarget . MuxAndDiscoveryCompleteSignals ( ) {
if err := s . RegisterMuxAndDiscoveryCompleteSignal ( k , v ) ; err != nil {
return nil , err
}
}
genericApiServerHookName := "generic-apiserver-start-informers"
if c . SharedInformerFactory != nil {
if ! s . isPostStartHookRegistered ( genericApiServerHookName ) {
err := s . AddPostStartHook ( genericApiServerHookName , func ( context PostStartHookContext ) error {
c . SharedInformerFactory . Start ( context . StopCh )
return nil
} )
if err != nil {
return nil , err
}
}
// TODO: Once we get rid of /healthz consider changing this to post-start-hook.
err := s . AddReadyzChecks ( healthz . NewInformerSyncHealthz ( c . SharedInformerFactory ) )
if err != nil {
return nil , err
}
}
const priorityAndFairnessConfigConsumerHookName = "priority-and-fairness-config-consumer"
if s . isPostStartHookRegistered ( priorityAndFairnessConfigConsumerHookName ) {
} else if c . FlowControl != nil {
err := s . AddPostStartHook ( priorityAndFairnessConfigConsumerHookName , func ( context PostStartHookContext ) error {
go c . FlowControl . Run ( context . StopCh )
return nil
} )
if err != nil {
return nil , err
}
// TODO(yue9944882): plumb pre-shutdown-hook for request-management system?
} else {
klog . V ( 3 ) . Infof ( "Not requested to run hook %s" , priorityAndFairnessConfigConsumerHookName )
}
// Add PostStartHooks for maintaining the watermarks for the Priority-and-Fairness and the Max-in-Flight filters.
if c . FlowControl != nil {
const priorityAndFairnessFilterHookName = "priority-and-fairness-filter"
if ! s . isPostStartHookRegistered ( priorityAndFairnessFilterHookName ) {
err := s . AddPostStartHook ( priorityAndFairnessFilterHookName , func ( context PostStartHookContext ) error {
genericfilters . StartPriorityAndFairnessWatermarkMaintenance ( context . StopCh )
return nil
} )
if err != nil {
return nil , err
}
}
} else {
const maxInFlightFilterHookName = "max-in-flight-filter"
if ! s . isPostStartHookRegistered ( maxInFlightFilterHookName ) {
err := s . AddPostStartHook ( maxInFlightFilterHookName , func ( context PostStartHookContext ) error {
genericfilters . StartMaxInFlightWatermarkMaintenance ( context . StopCh )
return nil
} )
if err != nil {
return nil , err
}
}
}
// Add PostStartHook for maintenaing the object count tracker.
if c . StorageObjectCountTracker != nil {
const storageObjectCountTrackerHookName = "storage-object-count-tracker-hook"
if ! s . isPostStartHookRegistered ( storageObjectCountTrackerHookName ) {
if err := s . AddPostStartHook ( storageObjectCountTrackerHookName , func ( context PostStartHookContext ) error {
go c . StorageObjectCountTracker . RunUntil ( context . StopCh )
return nil
} ) ; err != nil {
return nil , err
}
}
}
for _ , delegateCheck := range delegationTarget . HealthzChecks ( ) {
skip := false
for _ , existingCheck := range c . HealthzChecks {
if existingCheck . Name ( ) == delegateCheck . Name ( ) {
skip = true
break
}
}
if skip {
continue
}
s . AddHealthChecks ( delegateCheck )
}
s . RegisterDestroyFunc ( func ( ) {
if err := c . Config . TracerProvider . Shutdown ( context . Background ( ) ) ; err != nil {
klog . Errorf ( "failed to shut down tracer provider: %v" , err )
}
} )
s . listedPathProvider = routes . ListedPathProviders { s . listedPathProvider , delegationTarget }
installAPI ( s , c . Config )
// use the UnprotectedHandler from the delegation target to ensure that we don't attempt to double authenticator, authorize,
// or some other part of the filter chain in delegation cases.
if delegationTarget . UnprotectedHandler ( ) == nil && c . EnableIndex {
s . Handler . NonGoRestfulMux . NotFoundHandler ( routes . IndexLister {
StatusCode : http . StatusNotFound ,
PathProvider : s . listedPathProvider ,
} )
}
return s , nil
}
func BuildHandlerChainWithStorageVersionPrecondition ( apiHandler http . Handler , c * Config ) http . Handler {
// WithStorageVersionPrecondition needs the WithRequestInfo to run first
handler := genericapifilters . WithStorageVersionPrecondition ( apiHandler , c . StorageVersionManager , c . Serializer )
return DefaultBuildHandlerChain ( handler , c )
}
func DefaultBuildHandlerChain ( apiHandler http . Handler , c * Config ) http . Handler {
2023-08-17 05:15:28 +00:00
handler := apiHandler
handler = filterlatency . TrackCompleted ( handler )
2023-05-29 21:03:29 +00:00
handler = genericapifilters . WithAuthorization ( handler , c . Authorization . Authorizer , c . Serializer )
handler = filterlatency . TrackStarted ( handler , c . TracerProvider , "authorization" )
if c . FlowControl != nil {
workEstimatorCfg := flowcontrolrequest . DefaultWorkEstimatorConfig ( )
requestWorkEstimator := flowcontrolrequest . NewWorkEstimator (
2023-08-17 05:15:28 +00:00
c . StorageObjectCountTracker . Get , c . FlowControl . GetInterestedWatchCount , workEstimatorCfg , c . FlowControl . GetMaxSeats )
2023-05-29 21:03:29 +00:00
handler = filterlatency . TrackCompleted ( handler )
2023-12-20 12:23:59 +00:00
handler = genericfilters . WithPriorityAndFairness ( handler , c . LongRunningFunc , c . FlowControl , requestWorkEstimator , c . RequestTimeout / 4 )
2023-05-29 21:03:29 +00:00
handler = filterlatency . TrackStarted ( handler , c . TracerProvider , "priorityandfairness" )
} else {
handler = genericfilters . WithMaxInFlightLimit ( handler , c . MaxRequestsInFlight , c . MaxMutatingRequestsInFlight , c . LongRunningFunc )
}
handler = filterlatency . TrackCompleted ( handler )
handler = genericapifilters . WithImpersonation ( handler , c . Authorization . Authorizer , c . Serializer )
handler = filterlatency . TrackStarted ( handler , c . TracerProvider , "impersonation" )
handler = filterlatency . TrackCompleted ( handler )
handler = genericapifilters . WithAudit ( handler , c . AuditBackend , c . AuditPolicyRuleEvaluator , c . LongRunningFunc )
handler = filterlatency . TrackStarted ( handler , c . TracerProvider , "audit" )
failedHandler := genericapifilters . Unauthorized ( c . Serializer )
failedHandler = genericapifilters . WithFailedAuthenticationAudit ( failedHandler , c . AuditBackend , c . AuditPolicyRuleEvaluator )
failedHandler = filterlatency . TrackCompleted ( failedHandler )
handler = filterlatency . TrackCompleted ( handler )
2023-06-01 16:58:10 +00:00
handler = genericapifilters . WithAuthentication ( handler , c . Authentication . Authenticator , failedHandler , c . Authentication . APIAudiences , c . Authentication . RequestHeaderConfig )
2023-05-29 21:03:29 +00:00
handler = filterlatency . TrackStarted ( handler , c . TracerProvider , "authentication" )
handler = genericfilters . WithCORS ( handler , c . CorsAllowedOriginList , nil , nil , nil , "true" )
2024-05-15 06:54:18 +00:00
// WithWarningRecorder must be wrapped by the timeout handler
// to make the addition of warning headers threadsafe
handler = genericapifilters . WithWarningRecorder ( handler )
2023-05-29 21:03:29 +00:00
// WithTimeoutForNonLongRunningRequests will call the rest of the request handling in a go-routine with the
// context with deadline. The go-routine can keep running, while the timeout logic will return a timeout to the client.
handler = genericfilters . WithTimeoutForNonLongRunningRequests ( handler , c . LongRunningFunc )
handler = genericapifilters . WithRequestDeadline ( handler , c . AuditBackend , c . AuditPolicyRuleEvaluator ,
c . LongRunningFunc , c . Serializer , c . RequestTimeout )
2023-06-01 16:58:10 +00:00
handler = genericfilters . WithWaitGroup ( handler , c . LongRunningFunc , c . NonLongRunningRequestWaitGroup )
if c . ShutdownWatchTerminationGracePeriod > 0 {
handler = genericfilters . WithWatchTerminationDuringShutdown ( handler , c . lifecycleSignals , c . WatchRequestWaitGroup )
}
2023-05-29 21:03:29 +00:00
if c . SecureServing != nil && ! c . SecureServing . DisableHTTP2 && c . GoawayChance > 0 {
handler = genericfilters . WithProbabilisticGoaway ( handler , c . GoawayChance )
}
handler = genericapifilters . WithCacheControl ( handler )
handler = genericfilters . WithHSTS ( handler , c . HSTSDirectives )
if c . ShutdownSendRetryAfter {
handler = genericfilters . WithRetryAfter ( handler , c . lifecycleSignals . NotAcceptingNewRequest . Signaled ( ) )
}
handler = genericfilters . WithHTTPLogging ( handler )
2024-08-19 08:01:33 +00:00
if c . FeatureGate . Enabled ( genericfeatures . APIServerTracing ) {
2023-05-29 21:03:29 +00:00
handler = genericapifilters . WithTracing ( handler , c . TracerProvider )
}
handler = genericapifilters . WithLatencyTrackers ( handler )
2024-05-15 06:54:18 +00:00
// WithRoutine will execute future handlers in a separate goroutine and serving
// handler in current goroutine to minimize the stack memory usage. It must be
// after WithPanicRecover() to be protected from panics.
2024-08-19 08:01:33 +00:00
if c . FeatureGate . Enabled ( genericfeatures . APIServingWithRoutine ) {
handler = routine . WithRoutine ( handler , c . LongRunningFunc )
2024-05-15 06:54:18 +00:00
}
2023-05-29 21:03:29 +00:00
handler = genericapifilters . WithRequestInfo ( handler , c . RequestInfoResolver )
handler = genericapifilters . WithRequestReceivedTimestamp ( handler )
handler = genericapifilters . WithMuxAndDiscoveryComplete ( handler , c . lifecycleSignals . MuxAndDiscoveryComplete . Signaled ( ) )
handler = genericfilters . WithPanicRecovery ( handler , c . RequestInfoResolver )
handler = genericapifilters . WithAuditInit ( handler )
return handler
}
func installAPI ( s * GenericAPIServer , c * Config ) {
if c . EnableIndex {
routes . Index { } . Install ( s . listedPathProvider , s . Handler . NonGoRestfulMux )
}
if c . EnableProfiling {
routes . Profiling { } . Install ( s . Handler . NonGoRestfulMux )
if c . EnableContentionProfiling {
goruntime . SetBlockProfileRate ( 1 )
}
// so far, only logging related endpoints are considered valid to add for these debug flags.
routes . DebugFlags { } . Install ( s . Handler . NonGoRestfulMux , "v" , routes . StringFlagPutHandler ( logs . GlogSetter ) )
}
2023-06-01 16:58:10 +00:00
if s . UnprotectedDebugSocket != nil {
s . UnprotectedDebugSocket . InstallProfiling ( )
s . UnprotectedDebugSocket . InstallDebugFlag ( "v" , routes . StringFlagPutHandler ( logs . GlogSetter ) )
if c . EnableContentionProfiling {
goruntime . SetBlockProfileRate ( 1 )
}
}
2023-05-29 21:03:29 +00:00
if c . EnableMetrics {
if c . EnableProfiling {
routes . MetricsWithReset { } . Install ( s . Handler . NonGoRestfulMux )
2023-12-20 12:23:59 +00:00
slis . SLIMetricsWithReset { } . Install ( s . Handler . NonGoRestfulMux )
2023-05-29 21:03:29 +00:00
} else {
routes . DefaultMetrics { } . Install ( s . Handler . NonGoRestfulMux )
2023-12-20 12:23:59 +00:00
slis . SLIMetrics { } . Install ( s . Handler . NonGoRestfulMux )
2023-05-29 21:03:29 +00:00
}
}
2024-08-19 08:01:33 +00:00
routes . Version { Version : c . EffectiveVersion . BinaryVersion ( ) . Info ( ) } . Install ( s . Handler . GoRestfulContainer )
2023-05-29 21:03:29 +00:00
if c . EnableDiscovery {
2024-08-19 08:01:33 +00:00
if c . FeatureGate . Enabled ( genericfeatures . AggregatedDiscoveryEndpoint ) {
2023-05-29 21:03:29 +00:00
wrapped := discoveryendpoint . WrapAggregatedDiscoveryToHandler ( s . DiscoveryGroupManager , s . AggregatedDiscoveryGroupManager )
s . Handler . GoRestfulContainer . Add ( wrapped . GenerateWebService ( "/apis" , metav1 . APIGroupList { } ) )
} else {
s . Handler . GoRestfulContainer . Add ( s . DiscoveryGroupManager . WebService ( ) )
}
}
2023-12-20 12:23:59 +00:00
if c . FlowControl != nil {
2023-05-29 21:03:29 +00:00
c . FlowControl . Install ( s . Handler . NonGoRestfulMux )
}
}
func NewRequestInfoResolver ( c * Config ) * apirequest . RequestInfoFactory {
apiPrefixes := sets . NewString ( strings . Trim ( APIGroupPrefix , "/" ) ) // all possible API prefixes
legacyAPIPrefixes := sets . String { } // APIPrefixes that won't have groups (legacy)
for legacyAPIPrefix := range c . LegacyAPIGroupPrefixes {
apiPrefixes . Insert ( strings . Trim ( legacyAPIPrefix , "/" ) )
legacyAPIPrefixes . Insert ( strings . Trim ( legacyAPIPrefix , "/" ) )
}
return & apirequest . RequestInfoFactory {
APIPrefixes : apiPrefixes ,
GrouplessAPIPrefixes : legacyAPIPrefixes ,
}
}
func ( s * SecureServingInfo ) HostPort ( ) ( string , int , error ) {
if s == nil || s . Listener == nil {
return "" , 0 , fmt . Errorf ( "no listener found" )
}
addr := s . Listener . Addr ( ) . String ( )
host , portStr , err := net . SplitHostPort ( addr )
if err != nil {
return "" , 0 , fmt . Errorf ( "failed to get port from listener address %q: %v" , addr , err )
}
port , err := utilsnet . ParsePort ( portStr , true )
if err != nil {
return "" , 0 , fmt . Errorf ( "invalid non-numeric port %q" , portStr )
}
return host , port , nil
}
// AuthorizeClientBearerToken wraps the authenticator and authorizer in loopback authentication logic
// if the loopback client config is specified AND it has a bearer token. Note that if either authn or
// authz is nil, this function won't add a token authenticator or authorizer.
func AuthorizeClientBearerToken ( loopback * restclient . Config , authn * AuthenticationInfo , authz * AuthorizationInfo ) {
if loopback == nil || len ( loopback . BearerToken ) == 0 {
return
}
if authn == nil || authz == nil {
// prevent nil pointer panic
return
}
if authn . Authenticator == nil || authz . Authorizer == nil {
// authenticator or authorizer might be nil if we want to bypass authz/authn
// and we also do nothing in this case.
return
}
privilegedLoopbackToken := loopback . BearerToken
var uid = uuid . New ( ) . String ( )
tokens := make ( map [ string ] * user . DefaultInfo )
tokens [ privilegedLoopbackToken ] = & user . DefaultInfo {
Name : user . APIServerUser ,
UID : uid ,
2024-11-27 09:48:30 +00:00
Groups : [ ] string { user . AllAuthenticated , user . SystemPrivilegedGroup } ,
2023-05-29 21:03:29 +00:00
}
tokenAuthenticator := authenticatorfactory . NewFromTokens ( tokens , authn . APIAudiences )
authn . Authenticator = authenticatorunion . New ( tokenAuthenticator , authn . Authenticator )
}
2023-08-17 05:15:28 +00:00
// For testing purpose only
func SetHostnameFuncForTests ( name string ) {
hostnameFunc = func ( ) ( host string , err error ) {
host = name
err = nil
return
}
}