kubernetes/pkg/controlplane/apiserver/options/options.go
galal-hussein 95ad165f75 Add the ability to add extra admission plugins
Signed-off-by: galal-hussein <hussein.galal.ahmed.11@gmail.com>
2025-04-23 13:20:06 -03:00

309 lines
15 KiB
Go

/*
Copyright 2023 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 options contains flags and options for initializing an apiserver
package options
import (
"fmt"
"net"
"os"
"strings"
"time"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
peerreconcilers "k8s.io/apiserver/pkg/reconcilers"
genericoptions "k8s.io/apiserver/pkg/server/options"
"k8s.io/apiserver/pkg/storage/storagebackend"
"k8s.io/client-go/util/keyutil"
cliflag "k8s.io/component-base/cli/flag"
"k8s.io/component-base/logs"
logsapi "k8s.io/component-base/logs/api/v1"
"k8s.io/component-base/metrics"
"k8s.io/klog/v2"
netutil "k8s.io/utils/net"
_ "k8s.io/kubernetes/pkg/features"
kubeoptions "k8s.io/kubernetes/pkg/kubeapiserver/options"
"k8s.io/kubernetes/pkg/serviceaccount"
)
// Options define the flags and validation for a generic controlplane. If the
// structs are nil, the options are not added to the command line and not validated.
type Options struct {
GenericServerRunOptions *genericoptions.ServerRunOptions
Etcd *genericoptions.EtcdOptions
SecureServing *genericoptions.SecureServingOptionsWithLoopback
Audit *genericoptions.AuditOptions
Features *genericoptions.FeatureOptions
Admission *kubeoptions.AdmissionOptions
Authentication *kubeoptions.BuiltInAuthenticationOptions
Authorization *kubeoptions.BuiltInAuthorizationOptions
APIEnablement *genericoptions.APIEnablementOptions
EgressSelector *genericoptions.EgressSelectorOptions
Metrics *metrics.Options
Logs *logs.Options
Traces *genericoptions.TracingOptions
EnableLogsHandler bool
EventTTL time.Duration
MaxConnectionBytesPerSec int64
ProxyClientCertFile string
ProxyClientKeyFile string
// PeerCAFile is the ca bundle used by this kube-apiserver to verify peer apiservers'
// serving certs when routing a request to the peer in the case the request can not be served
// locally due to version skew.
PeerCAFile string
// PeerAdvertiseAddress is the IP for this kube-apiserver which is used by peer apiservers to route a request
// to this apiserver. This happens in cases where the peer is not able to serve the request due to
// version skew.
PeerAdvertiseAddress peerreconcilers.PeerAdvertiseAddress
EnableAggregatorRouting bool
AggregatorRejectForwardingRedirects bool
ServiceAccountSigningKeyFile string
ServiceAccountIssuer serviceaccount.TokenGenerator
ServiceAccountTokenMaxExpiration time.Duration
ShowHiddenMetricsForVersion string
SystemNamespaces []string
}
// completedServerRunOptions is a private wrapper that enforces a call of Complete() before Run can be invoked.
type completedOptions struct {
Options
}
type CompletedOptions struct {
// Embed a private pointer that cannot be instantiated outside of this package.
*completedOptions
}
// NewOptions creates a new ServerRunOptions object with default parameters
func NewOptions() *Options {
s := Options{
GenericServerRunOptions: genericoptions.NewServerRunOptions(),
Etcd: genericoptions.NewEtcdOptions(storagebackend.NewDefaultConfig(kubeoptions.DefaultEtcdPathPrefix, nil)),
SecureServing: kubeoptions.NewSecureServingOptions(),
Audit: genericoptions.NewAuditOptions(),
Features: genericoptions.NewFeatureOptions(),
Admission: kubeoptions.NewAdmissionOptions().WithPlugins(kubeoptions.AdmissionPlugins),
Authentication: kubeoptions.NewBuiltInAuthenticationOptions().WithAll(),
Authorization: kubeoptions.NewBuiltInAuthorizationOptions(),
APIEnablement: genericoptions.NewAPIEnablementOptions(),
EgressSelector: genericoptions.NewEgressSelectorOptions(),
Metrics: metrics.NewOptions(),
Logs: logs.NewOptions(),
Traces: genericoptions.NewTracingOptions(),
EnableLogsHandler: false,
EventTTL: 1 * time.Hour,
AggregatorRejectForwardingRedirects: true,
SystemNamespaces: []string{metav1.NamespaceSystem, metav1.NamespacePublic, metav1.NamespaceDefault},
}
// Overwrite the default for storage data format.
s.Etcd.DefaultStorageMediaType = "application/vnd.kubernetes.protobuf"
return &s
}
func (s *Options) AddFlags(fss *cliflag.NamedFlagSets) {
// Add the generic flags.
s.GenericServerRunOptions.AddUniversalFlags(fss.FlagSet("generic"))
s.Etcd.AddFlags(fss.FlagSet("etcd"))
s.SecureServing.AddFlags(fss.FlagSet("secure serving"))
s.Audit.AddFlags(fss.FlagSet("auditing"))
s.Features.AddFlags(fss.FlagSet("features"))
s.Authentication.AddFlags(fss.FlagSet("authentication"))
s.Authorization.AddFlags(fss.FlagSet("authorization"))
s.APIEnablement.AddFlags(fss.FlagSet("API enablement"))
s.EgressSelector.AddFlags(fss.FlagSet("egress selector"))
s.Admission.AddFlags(fss.FlagSet("admission"))
s.Metrics.AddFlags(fss.FlagSet("metrics"))
logsapi.AddFlags(s.Logs, fss.FlagSet("logs"))
s.Traces.AddFlags(fss.FlagSet("traces"))
// Note: the weird ""+ in below lines seems to be the only way to get gofmt to
// arrange these text blocks sensibly. Grrr.
fs := fss.FlagSet("misc")
fs.DurationVar(&s.EventTTL, "event-ttl", s.EventTTL,
"Amount of time to retain events.")
fs.BoolVar(&s.EnableLogsHandler, "enable-logs-handler", s.EnableLogsHandler,
"If true, install a /logs handler for the apiserver logs.")
fs.MarkDeprecated("enable-logs-handler", "This flag will be removed in v1.33") //nolint:errcheck
fs.Int64Var(&s.MaxConnectionBytesPerSec, "max-connection-bytes-per-sec", s.MaxConnectionBytesPerSec, ""+
"If non-zero, throttle each user connection to this number of bytes/sec. "+
"Currently only applies to long-running requests.")
fs.StringVar(&s.ProxyClientCertFile, "proxy-client-cert-file", s.ProxyClientCertFile, ""+
"Client certificate used to prove the identity of the aggregator or kube-apiserver "+
"when it must call out during a request. This includes proxying requests to a user "+
"api-server and calling out to webhook admission plugins. It is expected that this "+
"cert includes a signature from the CA in the --requestheader-client-ca-file flag. "+
"That CA is published in the 'extension-apiserver-authentication' configmap in "+
"the kube-system namespace. Components receiving calls from kube-aggregator should "+
"use that CA to perform their half of the mutual TLS verification.")
fs.StringVar(&s.ProxyClientKeyFile, "proxy-client-key-file", s.ProxyClientKeyFile, ""+
"Private key for the client certificate used to prove the identity of the aggregator or kube-apiserver "+
"when it must call out during a request. This includes proxying requests to a user "+
"api-server and calling out to webhook admission plugins.")
fs.StringVar(&s.PeerCAFile, "peer-ca-file", s.PeerCAFile,
"If set and the UnknownVersionInteroperabilityProxy feature gate is enabled, this file will be used to verify serving certificates of peer kube-apiservers. "+
"This flag is only used in clusters configured with multiple kube-apiservers for high availability.")
fs.StringVar(&s.PeerAdvertiseAddress.PeerAdvertiseIP, "peer-advertise-ip", s.PeerAdvertiseAddress.PeerAdvertiseIP,
"If set and the UnknownVersionInteroperabilityProxy feature gate is enabled, this IP will be used by peer kube-apiservers to proxy requests to this kube-apiserver "+
"when the request cannot be handled by the peer due to version skew between the kube-apiservers. "+
"This flag is only used in clusters configured with multiple kube-apiservers for high availability. ")
fs.StringVar(&s.PeerAdvertiseAddress.PeerAdvertisePort, "peer-advertise-port", s.PeerAdvertiseAddress.PeerAdvertisePort,
"If set and the UnknownVersionInteroperabilityProxy feature gate is enabled, this port will be used by peer kube-apiservers to proxy requests to this kube-apiserver "+
"when the request cannot be handled by the peer due to version skew between the kube-apiservers. "+
"This flag is only used in clusters configured with multiple kube-apiservers for high availability. ")
fs.BoolVar(&s.EnableAggregatorRouting, "enable-aggregator-routing", s.EnableAggregatorRouting,
"Turns on aggregator routing requests to endpoints IP rather than cluster IP.")
fs.BoolVar(&s.AggregatorRejectForwardingRedirects, "aggregator-reject-forwarding-redirect", s.AggregatorRejectForwardingRedirects,
"Aggregator reject forwarding redirect response back to client.")
fs.StringVar(&s.ServiceAccountSigningKeyFile, "service-account-signing-key-file", s.ServiceAccountSigningKeyFile, ""+
"Path to the file that contains the current private key of the service account token issuer. The issuer will sign issued ID tokens with this private key.")
}
func (o *Options) Complete(alternateDNS []string, alternateIPs []net.IP) (CompletedOptions, error) {
if o == nil {
return CompletedOptions{completedOptions: &completedOptions{}}, nil
}
completed := completedOptions{
Options: *o,
}
if err := completed.GenericServerRunOptions.Complete(); err != nil {
return CompletedOptions{}, err
}
// set defaults
if err := completed.GenericServerRunOptions.DefaultAdvertiseAddress(completed.SecureServing.SecureServingOptions); err != nil {
return CompletedOptions{}, err
}
if err := completed.SecureServing.MaybeDefaultWithSelfSignedCerts(completed.GenericServerRunOptions.AdvertiseAddress.String(), alternateDNS, alternateIPs); err != nil {
return CompletedOptions{}, fmt.Errorf("error creating self-signed certificates: %v", err)
}
if len(completed.GenericServerRunOptions.ExternalHost) == 0 {
if len(completed.GenericServerRunOptions.AdvertiseAddress) > 0 {
completed.GenericServerRunOptions.ExternalHost = completed.GenericServerRunOptions.AdvertiseAddress.String()
} else {
hostname, err := os.Hostname()
if err != nil {
return CompletedOptions{}, fmt.Errorf("error finding host name: %v", err)
}
completed.GenericServerRunOptions.ExternalHost = hostname
}
klog.Infof("external host was not specified, using %v", completed.GenericServerRunOptions.ExternalHost)
}
// put authorization options in final state
completed.Authorization.Complete()
// adjust authentication for completed authorization
completed.Authentication.ApplyAuthorization(completed.Authorization)
// verify and adjust ServiceAccountTokenMaxExpiration
if completed.Authentication.ServiceAccounts.MaxExpiration != 0 {
lowBound := time.Hour
upBound := time.Duration(1<<32) * time.Second
if completed.Authentication.ServiceAccounts.MaxExpiration < lowBound ||
completed.Authentication.ServiceAccounts.MaxExpiration > upBound {
return CompletedOptions{}, fmt.Errorf("the service-account-max-token-expiration must be between 1 hour and 2^32 seconds")
}
if completed.Authentication.ServiceAccounts.ExtendExpiration {
if completed.Authentication.ServiceAccounts.MaxExpiration < serviceaccount.WarnOnlyBoundTokenExpirationSeconds*time.Second {
klog.Warningf("service-account-extend-token-expiration is true, in order to correctly trigger safe transition logic, service-account-max-token-expiration must be set longer than %d seconds (currently %s)", serviceaccount.WarnOnlyBoundTokenExpirationSeconds, completed.Authentication.ServiceAccounts.MaxExpiration)
}
if completed.Authentication.ServiceAccounts.MaxExpiration < serviceaccount.ExpirationExtensionSeconds*time.Second {
klog.Warningf("service-account-extend-token-expiration is true, enabling tokens valid up to %d seconds, which is longer than service-account-max-token-expiration set to %s seconds", serviceaccount.ExpirationExtensionSeconds, completed.Authentication.ServiceAccounts.MaxExpiration)
}
}
}
completed.ServiceAccountTokenMaxExpiration = completed.Authentication.ServiceAccounts.MaxExpiration
if len(completed.Authentication.ServiceAccounts.Issuers) != 0 && completed.Authentication.ServiceAccounts.Issuers[0] != "" {
if completed.ServiceAccountSigningKeyFile != "" {
sk, err := keyutil.PrivateKeyFromFile(completed.ServiceAccountSigningKeyFile)
if err != nil {
return CompletedOptions{}, fmt.Errorf("failed to parse service-account-issuer-key-file: %w", err)
}
completed.ServiceAccountIssuer, err = serviceaccount.JWTTokenGenerator(completed.Authentication.ServiceAccounts.Issuers[0], sk)
if err != nil {
return CompletedOptions{}, fmt.Errorf("failed to build token generator: %w", err)
}
}
}
for key, value := range completed.APIEnablement.RuntimeConfig {
if key == "v1" || strings.HasPrefix(key, "v1/") ||
key == "api/v1" || strings.HasPrefix(key, "api/v1/") {
delete(completed.APIEnablement.RuntimeConfig, key)
completed.APIEnablement.RuntimeConfig["/v1"] = value
}
if key == "api/legacy" {
delete(completed.APIEnablement.RuntimeConfig, key)
}
}
return CompletedOptions{
completedOptions: &completed,
}, nil
}
// ServiceIPRange checks if the serviceClusterIPRange flag is nil, raising a warning if so and
// setting service ip range to the default value in kubeoptions.DefaultServiceIPCIDR
// for now until the default is removed per the deprecation timeline guidelines.
// Returns service ip range, api server service IP, and an error
func ServiceIPRange(passedServiceClusterIPRange net.IPNet) (net.IPNet, net.IP, error) {
serviceClusterIPRange := passedServiceClusterIPRange
if passedServiceClusterIPRange.IP == nil {
klog.Warningf("No CIDR for service cluster IPs specified. Default value which was %s is deprecated and will be removed in future releases. Please specify it using --service-cluster-ip-range on kube-apiserver.", kubeoptions.DefaultServiceIPCIDR.String())
serviceClusterIPRange = kubeoptions.DefaultServiceIPCIDR
}
size := min(netutil.RangeSize(&serviceClusterIPRange), 1<<16)
if size < 8 {
return net.IPNet{}, net.IP{}, fmt.Errorf("the service cluster IP range must be at least %d IP addresses", 8)
}
// Select the first valid IP from ServiceClusterIPRange to use as the GenericAPIServer service IP.
apiServerServiceIP, err := netutil.GetIndexedIP(&serviceClusterIPRange, 1)
if err != nil {
return net.IPNet{}, net.IP{}, err
}
klog.V(4).Infof("Setting service IP to %q (read-write).", apiServerServiceIP)
return serviceClusterIPRange, apiServerServiceIP, nil
}