vendor: Update vendor logic

This commit is contained in:
Clayton Coleman 2020-04-08 14:34:43 -04:00
parent c6ac5cbc87
commit 4ca64b85f0
No known key found for this signature in database
GPG key ID: 3D16906B4F1C5CB3
1540 changed files with 265304 additions and 91616 deletions

View file

@ -32,11 +32,13 @@ import (
mutatingwebhook "k8s.io/apiserver/pkg/admission/plugin/webhook/mutating"
validatingwebhook "k8s.io/apiserver/pkg/admission/plugin/webhook/validating"
apiserverapi "k8s.io/apiserver/pkg/apis/apiserver"
apiserverapiv1 "k8s.io/apiserver/pkg/apis/apiserver/v1"
apiserverapiv1alpha1 "k8s.io/apiserver/pkg/apis/apiserver/v1alpha1"
"k8s.io/apiserver/pkg/server"
"k8s.io/client-go/informers"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/component-base/featuregate"
)
var configScheme = runtime.NewScheme()
@ -44,6 +46,7 @@ var configScheme = runtime.NewScheme()
func init() {
utilruntime.Must(apiserverapi.AddToScheme(configScheme))
utilruntime.Must(apiserverapiv1alpha1.AddToScheme(configScheme))
utilruntime.Must(apiserverapiv1.AddToScheme(configScheme))
}
// AdmissionOptions holds the admission options
@ -117,6 +120,7 @@ func (a *AdmissionOptions) ApplyTo(
c *server.Config,
informers informers.SharedInformerFactory,
kubeAPIServerClientConfig *rest.Config,
features featuregate.FeatureGate,
pluginInitializers ...admission.PluginInitializer,
) error {
if a == nil {
@ -139,7 +143,7 @@ func (a *AdmissionOptions) ApplyTo(
if err != nil {
return err
}
genericInitializer := initializer.New(clientset, informers, c.Authorization.Authorizer)
genericInitializer := initializer.New(clientset, informers, c.Authorization.Authorizer, features)
initializersChain := admission.PluginInitializers{}
pluginInitializers = append(pluginInitializers, genericInitializer)
initializersChain = append(initializersChain, pluginInitializers...)

View file

@ -43,11 +43,14 @@ func NewAPIEnablementOptions() *APIEnablementOptions {
// AddFlags adds flags for a specific APIServer to the specified FlagSet
func (s *APIEnablementOptions) AddFlags(fs *pflag.FlagSet) {
fs.Var(&s.RuntimeConfig, "runtime-config", ""+
"A set of key=value pairs that describe runtime configuration that may be passed "+
"to apiserver. <group>/<version> (or <version> for the core group) key can be used to "+
"turn on/off specific api versions. api/all is special key to control all api versions, "+
"be careful setting it false, unless you know what you do. api/legacy is deprecated, "+
"we will remove it in the future, so stop using it.")
"A set of key=value pairs that enable or disable built-in APIs. Supported options are:\n"+
"v1=true|false for the core API group\n"+
"<group>/<version>=true|false for a specific API group and version (e.g. apps/v1=true)\n"+
"api/all=true|false controls all API versions\n"+
"api/ga=true|false controls all API versions of the form v[0-9]+\n"+
"api/beta=true|false controls all API versions of the form v[0-9]+beta[0-9]+\n"+
"api/alpha=true|false controls all API versions of the form v[0-9]+alpha[0-9]+\n"+
"api/legacy is deprecated, and will be removed in a future version")
}
// Validate validates RuntimeConfig with a list of registries.
@ -61,9 +64,9 @@ func (s *APIEnablementOptions) Validate(registries ...GroupRegisty) []error {
}
errors := []error{}
if s.RuntimeConfig["api/all"] == "false" && len(s.RuntimeConfig) == 1 {
if s.RuntimeConfig[resourceconfig.APIAll] == "false" && len(s.RuntimeConfig) == 1 {
// Do not allow only set api/all=false, in such case apiserver startup has no meaning.
return append(errors, fmt.Errorf("invalid key with only api/all=false"))
return append(errors, fmt.Errorf("invalid key with only %v=false", resourceconfig.APIAll))
}
groups, err := resourceconfig.ParseGroups(s.RuntimeConfig)

View file

@ -19,20 +19,22 @@ package options
import (
"encoding/json"
"fmt"
"io/ioutil"
"strings"
"time"
"github.com/spf13/pflag"
"k8s.io/klog"
"k8s.io/apiserver/pkg/server/dynamiccertificates"
"github.com/spf13/pflag"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apiserver/pkg/authentication/authenticatorfactory"
"k8s.io/apiserver/pkg/authentication/request/headerrequest"
"k8s.io/apiserver/pkg/server"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/klog"
openapicommon "k8s.io/kube-openapi/pkg/common"
)
@ -47,6 +49,35 @@ type RequestHeaderAuthenticationOptions struct {
AllowedNames []string
}
func (s *RequestHeaderAuthenticationOptions) Validate() []error {
allErrors := []error{}
if err := checkForWhiteSpaceOnly("requestheader-username-headers", s.UsernameHeaders...); err != nil {
allErrors = append(allErrors, err)
}
if err := checkForWhiteSpaceOnly("requestheader-group-headers", s.GroupHeaders...); err != nil {
allErrors = append(allErrors, err)
}
if err := checkForWhiteSpaceOnly("requestheader-extra-headers-prefix", s.ExtraHeaderPrefixes...); err != nil {
allErrors = append(allErrors, err)
}
if err := checkForWhiteSpaceOnly("requestheader-allowed-names", s.AllowedNames...); err != nil {
allErrors = append(allErrors, err)
}
return allErrors
}
func checkForWhiteSpaceOnly(flag string, headerNames ...string) error {
for _, headerName := range headerNames {
if len(strings.TrimSpace(headerName)) == 0 {
return fmt.Errorf("empty value in %q", flag)
}
}
return nil
}
func (s *RequestHeaderAuthenticationOptions) AddFlags(fs *pflag.FlagSet) {
if s == nil {
return
@ -74,23 +105,48 @@ func (s *RequestHeaderAuthenticationOptions) AddFlags(fs *pflag.FlagSet) {
// ToAuthenticationRequestHeaderConfig returns a RequestHeaderConfig config object for these options
// if necessary, nil otherwise.
func (s *RequestHeaderAuthenticationOptions) ToAuthenticationRequestHeaderConfig() *authenticatorfactory.RequestHeaderConfig {
func (s *RequestHeaderAuthenticationOptions) ToAuthenticationRequestHeaderConfig() (*authenticatorfactory.RequestHeaderConfig, error) {
if len(s.ClientCAFile) == 0 {
return nil
return nil, nil
}
caBundleProvider, err := dynamiccertificates.NewDynamicCAContentFromFile("request-header", s.ClientCAFile)
if err != nil {
return nil, err
}
return &authenticatorfactory.RequestHeaderConfig{
UsernameHeaders: s.UsernameHeaders,
GroupHeaders: s.GroupHeaders,
ExtraHeaderPrefixes: s.ExtraHeaderPrefixes,
ClientCA: s.ClientCAFile,
AllowedClientNames: s.AllowedNames,
}
UsernameHeaders: headerrequest.StaticStringSlice(s.UsernameHeaders),
GroupHeaders: headerrequest.StaticStringSlice(s.GroupHeaders),
ExtraHeaderPrefixes: headerrequest.StaticStringSlice(s.ExtraHeaderPrefixes),
CAContentProvider: caBundleProvider,
AllowedClientNames: headerrequest.StaticStringSlice(s.AllowedNames),
}, nil
}
// ClientCertAuthenticationOptions provides different options for client cert auth. You should use `GetClientVerifyOptionFn` to
// get the verify options for your authenticator.
type ClientCertAuthenticationOptions struct {
// ClientCA is the certificate bundle for all the signers that you'll recognize for incoming client certificates
ClientCA string
// CAContentProvider are the options for verifying incoming connections using mTLS and directly assigning to users.
// Generally this is the CA bundle file used to authenticate client certificates
// If non-nil, this takes priority over the ClientCA file.
CAContentProvider dynamiccertificates.CAContentProvider
}
// GetClientVerifyOptionFn provides verify options for your authenticator while respecting the preferred order of verifiers.
func (s *ClientCertAuthenticationOptions) GetClientCAContentProvider() (dynamiccertificates.CAContentProvider, error) {
if s.CAContentProvider != nil {
return s.CAContentProvider, nil
}
if len(s.ClientCA) == 0 {
return nil, nil
}
return dynamiccertificates.NewDynamicCAContentFromFile("client-ca-bundle", s.ClientCA)
}
func (s *ClientCertAuthenticationOptions) AddFlags(fs *pflag.FlagSet) {
@ -140,6 +196,8 @@ func NewDelegatingAuthenticationOptions() *DelegatingAuthenticationOptions {
func (s *DelegatingAuthenticationOptions) Validate() []error {
allErrors := []error{}
allErrors = append(allErrors, s.RequestHeader.Validate()...)
return allErrors
}
@ -170,9 +228,9 @@ func (s *DelegatingAuthenticationOptions) AddFlags(fs *pflag.FlagSet) {
"Note that this can result in authentication that treats all requests as anonymous.")
}
func (s *DelegatingAuthenticationOptions) ApplyTo(c *server.AuthenticationInfo, servingInfo *server.SecureServingInfo, openAPIConfig *openapicommon.Config) error {
func (s *DelegatingAuthenticationOptions) ApplyTo(authenticationInfo *server.AuthenticationInfo, servingInfo *server.SecureServingInfo, openAPIConfig *openapicommon.Config) error {
if s == nil {
c.Authenticator = nil
authenticationInfo.Authenticator = nil
return nil
}
@ -188,32 +246,67 @@ func (s *DelegatingAuthenticationOptions) ApplyTo(c *server.AuthenticationInfo,
// configure token review
if client != nil {
cfg.TokenAccessReviewClient = client.AuthenticationV1beta1().TokenReviews()
cfg.TokenAccessReviewClient = client.AuthenticationV1().TokenReviews()
}
// look into configmaps/external-apiserver-authentication for missing authn info
if !s.SkipInClusterLookup {
err := s.lookupMissingConfigInCluster(client)
// get the clientCA information
clientCAFileSpecified := len(s.ClientCert.ClientCA) > 0
var clientCAProvider dynamiccertificates.CAContentProvider
if clientCAFileSpecified {
clientCAProvider, err = s.ClientCert.GetClientCAContentProvider()
if err != nil {
if s.TolerateInClusterLookupFailure {
klog.Warningf("Error looking up in-cluster authentication configuration: %v", err)
klog.Warningf("Continuing without authentication configuration. This may treat all requests as anonymous.")
klog.Warningf("To require authentication configuration lookup to succeed, set --authentication-tolerate-lookup-failure=false")
} else {
return err
return fmt.Errorf("unable to load client CA file %q: %v", s.ClientCert.ClientCA, err)
}
cfg.ClientCertificateCAContentProvider = clientCAProvider
if err = authenticationInfo.ApplyClientCert(cfg.ClientCertificateCAContentProvider, servingInfo); err != nil {
return fmt.Errorf("unable to assign client CA file: %v", err)
}
} else if !s.SkipInClusterLookup {
if client == nil {
klog.Warningf("No authentication-kubeconfig provided in order to lookup client-ca-file in configmap/%s in %s, so client certificate authentication won't work.", authenticationConfigMapName, authenticationConfigMapNamespace)
} else {
clientCAProvider, err = dynamiccertificates.NewDynamicCAFromConfigMapController("client-ca", authenticationConfigMapNamespace, authenticationConfigMapName, "client-ca-file", client)
if err != nil {
return fmt.Errorf("unable to load configmap based client CA file: %v", err)
}
cfg.ClientCertificateCAContentProvider = clientCAProvider
if err = authenticationInfo.ApplyClientCert(cfg.ClientCertificateCAContentProvider, servingInfo); err != nil {
return fmt.Errorf("unable to assign configmap based client CA file: %v", err)
}
}
}
// configure AuthenticationInfo config
cfg.ClientCAFile = s.ClientCert.ClientCA
if err = c.ApplyClientCert(s.ClientCert.ClientCA, servingInfo); err != nil {
return fmt.Errorf("unable to load client CA file: %v", err)
}
requestHeaderCAFileSpecified := len(s.RequestHeader.ClientCAFile) > 0
var requestHeaderConfig *authenticatorfactory.RequestHeaderConfig
if requestHeaderCAFileSpecified {
requestHeaderConfig, err = s.RequestHeader.ToAuthenticationRequestHeaderConfig()
if err != nil {
return fmt.Errorf("unable to create request header authentication config: %v", err)
}
cfg.RequestHeaderConfig = s.RequestHeader.ToAuthenticationRequestHeaderConfig()
if err = c.ApplyClientCert(s.RequestHeader.ClientCAFile, servingInfo); err != nil {
return fmt.Errorf("unable to load client CA file: %v", err)
} else if !s.SkipInClusterLookup {
if client == nil {
klog.Warningf("No authentication-kubeconfig provided in order to lookup requestheader-client-ca-file in configmap/%s in %s, so request-header client certificate authentication won't work.", authenticationConfigMapName, authenticationConfigMapNamespace)
} else {
requestHeaderConfig, err = s.createRequestHeaderConfig(client)
if err != nil {
if s.TolerateInClusterLookupFailure {
klog.Warningf("Error looking up in-cluster authentication configuration: %v", err)
klog.Warningf("Continuing without authentication configuration. This may treat all requests as anonymous.")
klog.Warningf("To require authentication configuration lookup to succeed, set --authentication-tolerate-lookup-failure=false")
} else {
return fmt.Errorf("unable to load configmap based request-header-client-ca-file: %v", err)
}
}
}
}
if requestHeaderConfig != nil {
cfg.RequestHeaderConfig = requestHeaderConfig
if err = authenticationInfo.ApplyClientCert(cfg.RequestHeaderConfig.CAContentProvider, servingInfo); err != nil {
return fmt.Errorf("unable to load request-header-client-ca-file: %v", err)
}
}
// create authenticator
@ -221,11 +314,11 @@ func (s *DelegatingAuthenticationOptions) ApplyTo(c *server.AuthenticationInfo,
if err != nil {
return err
}
c.Authenticator = authenticator
authenticationInfo.Authenticator = authenticator
if openAPIConfig != nil {
openAPIConfig.SecurityDefinitions = securityDefinitions
}
c.SupportsBasicAuth = false
authenticationInfo.SupportsBasicAuth = false
return nil
}
@ -240,97 +333,26 @@ const (
authenticationRoleName = "extension-apiserver-authentication-reader"
)
func (s *DelegatingAuthenticationOptions) lookupMissingConfigInCluster(client kubernetes.Interface) error {
if len(s.ClientCert.ClientCA) > 0 && len(s.RequestHeader.ClientCAFile) > 0 {
return nil
}
if client == nil {
if len(s.ClientCert.ClientCA) == 0 {
klog.Warningf("No authentication-kubeconfig provided in order to lookup client-ca-file in configmap/%s in %s, so client certificate authentication won't work.", authenticationConfigMapName, authenticationConfigMapNamespace)
}
if len(s.RequestHeader.ClientCAFile) == 0 {
klog.Warningf("No authentication-kubeconfig provided in order to lookup requestheader-client-ca-file in configmap/%s in %s, so request-header client certificate authentication won't work.", authenticationConfigMapName, authenticationConfigMapNamespace)
}
return nil
func (s *DelegatingAuthenticationOptions) createRequestHeaderConfig(client kubernetes.Interface) (*authenticatorfactory.RequestHeaderConfig, error) {
requestHeaderCAProvider, err := dynamiccertificates.NewDynamicCAFromConfigMapController("client-ca", authenticationConfigMapNamespace, authenticationConfigMapName, "requestheader-client-ca-file", client)
if err != nil {
return nil, fmt.Errorf("unable to create request header authentication config: %v", err)
}
authConfigMap, err := client.CoreV1().ConfigMaps(authenticationConfigMapNamespace).Get(authenticationConfigMapName, metav1.GetOptions{})
switch {
case errors.IsNotFound(err):
// ignore, authConfigMap is nil now
return nil, nil
case errors.IsForbidden(err):
klog.Warningf("Unable to get configmap/%s in %s. Usually fixed by "+
"'kubectl create rolebinding -n %s ROLEBINDING_NAME --role=%s --serviceaccount=YOUR_NS:YOUR_SA'",
authenticationConfigMapName, authenticationConfigMapNamespace, authenticationConfigMapNamespace, authenticationRoleName)
return err
return nil, err
case err != nil:
return err
}
if len(s.ClientCert.ClientCA) == 0 {
if authConfigMap != nil {
opt, err := inClusterClientCA(authConfigMap)
if err != nil {
return err
}
if opt != nil {
s.ClientCert = *opt
}
}
if len(s.ClientCert.ClientCA) == 0 {
klog.Warningf("Cluster doesn't provide client-ca-file in configmap/%s in %s, so client certificate authentication won't work.", authenticationConfigMapName, authenticationConfigMapNamespace)
}
}
if len(s.RequestHeader.ClientCAFile) == 0 {
if authConfigMap != nil {
opt, err := inClusterRequestHeader(authConfigMap)
if err != nil {
return err
}
if opt != nil {
s.RequestHeader = *opt
}
}
if len(s.RequestHeader.ClientCAFile) == 0 {
klog.Warningf("Cluster doesn't provide requestheader-client-ca-file in configmap/%s in %s, so request-header client certificate authentication won't work.", authenticationConfigMapName, authenticationConfigMapNamespace)
}
}
return nil
}
func inClusterClientCA(authConfigMap *v1.ConfigMap) (*ClientCertAuthenticationOptions, error) {
clientCA, ok := authConfigMap.Data["client-ca-file"]
if !ok {
// not having a client-ca is fine, return nil
return nil, nil
}
f, err := ioutil.TempFile("", "client-ca-file")
if err != nil {
return nil, err
}
if err := ioutil.WriteFile(f.Name(), []byte(clientCA), 0600); err != nil {
return nil, err
}
return &ClientCertAuthenticationOptions{ClientCA: f.Name()}, nil
}
func inClusterRequestHeader(authConfigMap *v1.ConfigMap) (*RequestHeaderAuthenticationOptions, error) {
requestHeaderCA, ok := authConfigMap.Data["requestheader-client-ca-file"]
if !ok {
// not having a requestheader-client-ca is fine, return nil
return nil, nil
}
f, err := ioutil.TempFile("", "requestheader-client-ca-file")
if err != nil {
return nil, err
}
if err := ioutil.WriteFile(f.Name(), []byte(requestHeaderCA), 0600); err != nil {
return nil, err
}
usernameHeaders, err := deserializeStrings(authConfigMap.Data["requestheader-username-headers"])
if err != nil {
return nil, err
@ -348,12 +370,12 @@ func inClusterRequestHeader(authConfigMap *v1.ConfigMap) (*RequestHeaderAuthenti
return nil, err
}
return &RequestHeaderAuthenticationOptions{
UsernameHeaders: usernameHeaders,
GroupHeaders: groupHeaders,
ExtraHeaderPrefixes: extraHeaderPrefixes,
ClientCAFile: f.Name(),
AllowedNames: allowedNames,
return &authenticatorfactory.RequestHeaderConfig{
CAContentProvider: requestHeaderCAProvider,
UsernameHeaders: headerrequest.StaticStringSlice(usernameHeaders),
GroupHeaders: headerrequest.StaticStringSlice(groupHeaders),
ExtraHeaderPrefixes: headerrequest.StaticStringSlice(extraHeaderPrefixes),
AllowedClientNames: headerrequest.StaticStringSlice(allowedNames),
}, nil
}

View file

@ -146,7 +146,7 @@ func (s *DelegatingAuthorizationOptions) toAuthorizer(client kubernetes.Interfac
klog.Warningf("No authorization-kubeconfig provided, so SubjectAccessReview of authorization tokens won't work.")
} else {
cfg := authorizerfactory.DelegatingAuthorizerConfig{
SubjectAccessReviewClient: client.AuthorizationV1beta1().SubjectAccessReviews(),
SubjectAccessReviewClient: client.AuthorizationV1().SubjectAccessReviews(),
AllowCacheTTL: s.AllowCacheTTL,
DenyCacheTTL: s.DenyCacheTTL,
}

View file

@ -54,8 +54,8 @@ func (s *DeprecatedInsecureServingOptions) Validate() []error {
errors := []error{}
if s.BindPort < 0 || s.BindPort > 65335 {
errors = append(errors, fmt.Errorf("insecure port %v must be between 0 and 65335, inclusive. 0 for turning off insecure (HTTP) port", s.BindPort))
if s.BindPort < 0 || s.BindPort > 65535 {
errors = append(errors, fmt.Errorf("insecure port %v must be between 0 and 65535, inclusive. 0 for turning off insecure (HTTP) port", s.BindPort))
}
return errors

View file

@ -0,0 +1,92 @@
/*
Copyright 2019 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
import (
"fmt"
"github.com/spf13/pflag"
"k8s.io/utils/path"
"k8s.io/apiserver/pkg/server"
"k8s.io/apiserver/pkg/server/egressselector"
)
// EgressSelectorOptions holds the api server egress selector options.
// See https://github.com/kubernetes/enhancements/blob/master/keps/sig-api-machinery/20190226-network-proxy.md
type EgressSelectorOptions struct {
// ConfigFile is the file path with api-server egress selector configuration.
ConfigFile string
}
// NewEgressSelectorOptions creates a new instance of EgressSelectorOptions
//
// The option is to point to a configuration file for egress/konnectivity.
// This determines which types of requests use egress/konnectivity and how they use it.
// If empty the API Server will attempt to connect directly using the network.
func NewEgressSelectorOptions() *EgressSelectorOptions {
return &EgressSelectorOptions{}
}
// AddFlags adds flags related to admission for a specific APIServer to the specified FlagSet
func (o *EgressSelectorOptions) AddFlags(fs *pflag.FlagSet) {
if o == nil {
return
}
fs.StringVar(&o.ConfigFile, "egress-selector-config-file", o.ConfigFile,
"File with apiserver egress selector configuration.")
}
// ApplyTo adds the egress selector settings to the server configuration.
// In case egress selector settings were not provided by a cluster-admin
// they will be prepared from the recommended/default/no-op values.
func (o *EgressSelectorOptions) ApplyTo(c *server.Config) error {
if o == nil {
return nil
}
npConfig, err := egressselector.ReadEgressSelectorConfiguration(o.ConfigFile)
if err != nil {
return fmt.Errorf("failed to read egress selector config: %v", err)
}
errs := egressselector.ValidateEgressSelectorConfiguration(npConfig)
if len(errs) > 0 {
return fmt.Errorf("failed to validate egress selector configuration: %v", errs.ToAggregate())
}
cs, err := egressselector.NewEgressSelector(npConfig)
if err != nil {
return fmt.Errorf("failed to setup egress selector with config %#v: %v", npConfig, err)
}
c.EgressSelector = cs
return nil
}
// Validate verifies flags passed to EgressSelectorOptions.
func (o *EgressSelectorOptions) Validate() []error {
if o == nil || o.ConfigFile == "" {
return nil
}
errs := []error{}
if exists, err := path.Exists(path.CheckFollowSymlink, o.ConfigFile); exists == false || err != nil {
errs = append(errs, fmt.Errorf("egress-selector-config-file %s does not exist", o.ConfigFile))
}
return errs
}

View file

@ -0,0 +1,9 @@
# See the OWNERS docs at https://go.k8s.io/owners
approvers:
- sig-auth-encryption-at-rest-approvers
reviewers:
- sig-auth-encryption-at-rest-reviewers
labels:
- sig/auth

View file

@ -0,0 +1,428 @@
/*
Copyright 2017 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 encryptionconfig
import (
"crypto/aes"
"crypto/cipher"
"encoding/base64"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"sync"
"time"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/runtime/serializer"
apiserverconfig "k8s.io/apiserver/pkg/apis/config"
apiserverconfigv1 "k8s.io/apiserver/pkg/apis/config/v1"
"k8s.io/apiserver/pkg/server/healthz"
"k8s.io/apiserver/pkg/storage/value"
aestransformer "k8s.io/apiserver/pkg/storage/value/encrypt/aes"
"k8s.io/apiserver/pkg/storage/value/encrypt/envelope"
"k8s.io/apiserver/pkg/storage/value/encrypt/identity"
"k8s.io/apiserver/pkg/storage/value/encrypt/secretbox"
)
const (
aesCBCTransformerPrefixV1 = "k8s:enc:aescbc:v1:"
aesGCMTransformerPrefixV1 = "k8s:enc:aesgcm:v1:"
secretboxTransformerPrefixV1 = "k8s:enc:secretbox:v1:"
kmsTransformerPrefixV1 = "k8s:enc:kms:v1:"
kmsPluginConnectionTimeout = 3 * time.Second
kmsPluginHealthzTTL = 3 * time.Second
)
type kmsPluginHealthzResponse struct {
err error
received time.Time
}
type kmsPluginProbe struct {
name string
envelope.Service
lastResponse *kmsPluginHealthzResponse
l *sync.Mutex
}
func (h *kmsPluginProbe) toHealthzCheck(idx int) healthz.HealthChecker {
return healthz.NamedCheck(fmt.Sprintf("kms-provider-%d", idx), func(r *http.Request) error {
return h.Check()
})
}
// GetKMSPluginHealthzCheckers extracts KMSPluginProbes from the EncryptionConfig.
func GetKMSPluginHealthzCheckers(filepath string) ([]healthz.HealthChecker, error) {
f, err := os.Open(filepath)
if err != nil {
return nil, fmt.Errorf("error opening encryption provider configuration file %q: %v", filepath, err)
}
defer f.Close()
var result []healthz.HealthChecker
probes, err := getKMSPluginProbes(f)
if err != nil {
return nil, err
}
for i, p := range probes {
probe := p
result = append(result, probe.toHealthzCheck(i))
}
return result, nil
}
func getKMSPluginProbes(reader io.Reader) ([]*kmsPluginProbe, error) {
var result []*kmsPluginProbe
configFileContents, err := ioutil.ReadAll(reader)
if err != nil {
return result, fmt.Errorf("could not read content of encryption provider configuration: %v", err)
}
config, err := loadConfig(configFileContents)
if err != nil {
return result, fmt.Errorf("error while parsing encrypiton provider configuration: %v", err)
}
for _, r := range config.Resources {
for _, p := range r.Providers {
if p.KMS != nil {
timeout := kmsPluginConnectionTimeout
if p.KMS.Timeout != nil {
if p.KMS.Timeout.Duration <= 0 {
return nil, fmt.Errorf("could not configure KMS-Plugin's probe %q, timeout should be a positive value", p.KMS.Name)
}
timeout = p.KMS.Timeout.Duration
}
s, err := envelope.NewGRPCService(p.KMS.Endpoint, timeout)
if err != nil {
return nil, fmt.Errorf("could not configure KMS-Plugin's probe %q, error: %v", p.KMS.Name, err)
}
result = append(result, &kmsPluginProbe{
name: p.KMS.Name,
Service: s,
l: &sync.Mutex{},
lastResponse: &kmsPluginHealthzResponse{},
})
}
}
}
return result, nil
}
// Check encrypts and decrypts test data against KMS-Plugin's gRPC endpoint.
func (h *kmsPluginProbe) Check() error {
h.l.Lock()
defer h.l.Unlock()
if (time.Since(h.lastResponse.received)) < kmsPluginHealthzTTL {
return h.lastResponse.err
}
p, err := h.Service.Encrypt([]byte("ping"))
if err != nil {
h.lastResponse = &kmsPluginHealthzResponse{err: err, received: time.Now()}
return fmt.Errorf("failed to perform encrypt section of the healthz check for KMS Provider %s, error: %v", h.name, err)
}
if _, err := h.Service.Decrypt(p); err != nil {
h.lastResponse = &kmsPluginHealthzResponse{err: err, received: time.Now()}
return fmt.Errorf("failed to perform decrypt section of the healthz check for KMS Provider %s, error: %v", h.name, err)
}
h.lastResponse = &kmsPluginHealthzResponse{err: nil, received: time.Now()}
return nil
}
// GetTransformerOverrides returns the transformer overrides by reading and parsing the encryption provider configuration file
func GetTransformerOverrides(filepath string) (map[schema.GroupResource]value.Transformer, error) {
f, err := os.Open(filepath)
if err != nil {
return nil, fmt.Errorf("error opening encryption provider configuration file %q: %v", filepath, err)
}
defer f.Close()
result, err := ParseEncryptionConfiguration(f)
if err != nil {
return nil, fmt.Errorf("error while parsing encryption provider configuration file %q: %v", filepath, err)
}
return result, nil
}
// ParseEncryptionConfiguration parses configuration data and returns the transformer overrides
func ParseEncryptionConfiguration(f io.Reader) (map[schema.GroupResource]value.Transformer, error) {
configFileContents, err := ioutil.ReadAll(f)
if err != nil {
return nil, fmt.Errorf("could not read contents: %v", err)
}
config, err := loadConfig(configFileContents)
if err != nil {
return nil, fmt.Errorf("error while parsing file: %v", err)
}
resourceToPrefixTransformer := map[schema.GroupResource][]value.PrefixTransformer{}
// For each entry in the configuration
for _, resourceConfig := range config.Resources {
transformers, err := GetPrefixTransformers(&resourceConfig)
if err != nil {
return nil, err
}
// For each resource, create a list of providers to use
for _, resource := range resourceConfig.Resources {
gr := schema.ParseGroupResource(resource)
resourceToPrefixTransformer[gr] = append(
resourceToPrefixTransformer[gr], transformers...)
}
}
result := map[schema.GroupResource]value.Transformer{}
for gr, transList := range resourceToPrefixTransformer {
result[gr] = value.NewMutableTransformer(value.NewPrefixTransformers(fmt.Errorf("no matching prefix found"), transList...))
}
return result, nil
}
// loadConfig decodes data as a EncryptionConfiguration object.
func loadConfig(data []byte) (*apiserverconfig.EncryptionConfiguration, error) {
scheme := runtime.NewScheme()
codecs := serializer.NewCodecFactory(scheme)
apiserverconfig.AddToScheme(scheme)
apiserverconfigv1.AddToScheme(scheme)
configObj, gvk, err := codecs.UniversalDecoder().Decode(data, nil, nil)
if err != nil {
return nil, err
}
config, ok := configObj.(*apiserverconfig.EncryptionConfiguration)
if !ok {
return nil, fmt.Errorf("got unexpected config type: %v", gvk)
}
return config, nil
}
// The factory to create kms service. This is to make writing test easier.
var envelopeServiceFactory = envelope.NewGRPCService
// GetPrefixTransformers constructs and returns the appropriate prefix transformers for the passed resource using its configuration.
func GetPrefixTransformers(config *apiserverconfig.ResourceConfiguration) ([]value.PrefixTransformer, error) {
var result []value.PrefixTransformer
for _, provider := range config.Providers {
found := false
var transformer value.PrefixTransformer
var err error
if provider.AESGCM != nil {
transformer, err = GetAESPrefixTransformer(provider.AESGCM, aestransformer.NewGCMTransformer, aesGCMTransformerPrefixV1)
if err != nil {
return result, err
}
found = true
}
if provider.AESCBC != nil {
if found == true {
return result, fmt.Errorf("more than one provider specified in a single element, should split into different list elements")
}
transformer, err = GetAESPrefixTransformer(provider.AESCBC, aestransformer.NewCBCTransformer, aesCBCTransformerPrefixV1)
found = true
}
if provider.Secretbox != nil {
if found == true {
return result, fmt.Errorf("more than one provider specified in a single element, should split into different list elements")
}
transformer, err = GetSecretboxPrefixTransformer(provider.Secretbox)
found = true
}
if provider.Identity != nil {
if found == true {
return result, fmt.Errorf("more than one provider specified in a single element, should split into different list elements")
}
transformer = value.PrefixTransformer{
Transformer: identity.NewEncryptCheckTransformer(),
Prefix: []byte{},
}
found = true
}
if provider.KMS != nil {
if found == true {
return nil, fmt.Errorf("more than one provider specified in a single element, should split into different list elements")
}
// Ensure the endpoint is provided.
if len(provider.KMS.Endpoint) == 0 {
return nil, fmt.Errorf("remote KMS provider can't use empty string as endpoint")
}
timeout := kmsPluginConnectionTimeout
if provider.KMS.Timeout != nil {
if provider.KMS.Timeout.Duration <= 0 {
return nil, fmt.Errorf("could not configure KMS plugin %q, timeout should be a positive value", provider.KMS.Name)
}
timeout = provider.KMS.Timeout.Duration
}
// Get gRPC client service with endpoint.
envelopeService, err := envelopeServiceFactory(provider.KMS.Endpoint, timeout)
if err != nil {
return nil, fmt.Errorf("could not configure KMS plugin %q, error: %v", provider.KMS.Name, err)
}
transformer, err = getEnvelopePrefixTransformer(provider.KMS, envelopeService, kmsTransformerPrefixV1)
found = true
}
if err != nil {
return result, err
}
result = append(result, transformer)
if found == false {
return result, fmt.Errorf("invalid provider configuration: at least one provider must be specified")
}
}
return result, nil
}
// BlockTransformerFunc takes an AES cipher block and returns a value transformer.
type BlockTransformerFunc func(cipher.Block) value.Transformer
// GetAESPrefixTransformer returns a prefix transformer from the provided configuration.
// Returns an AES transformer based on the provided prefix and block transformer.
func GetAESPrefixTransformer(config *apiserverconfig.AESConfiguration, fn BlockTransformerFunc, prefix string) (value.PrefixTransformer, error) {
var result value.PrefixTransformer
if len(config.Keys) == 0 {
return result, fmt.Errorf("aes provider has no valid keys")
}
for _, key := range config.Keys {
if key.Name == "" {
return result, fmt.Errorf("key with invalid name provided")
}
if key.Secret == "" {
return result, fmt.Errorf("key %v has no provided secret", key.Name)
}
}
keyTransformers := []value.PrefixTransformer{}
for _, keyData := range config.Keys {
key, err := base64.StdEncoding.DecodeString(keyData.Secret)
if err != nil {
return result, fmt.Errorf("could not obtain secret for named key %s: %s", keyData.Name, err)
}
block, err := aes.NewCipher(key)
if err != nil {
return result, fmt.Errorf("error while creating cipher for named key %s: %s", keyData.Name, err)
}
// Create a new PrefixTransformer for this key
keyTransformers = append(keyTransformers,
value.PrefixTransformer{
Transformer: fn(block),
Prefix: []byte(keyData.Name + ":"),
})
}
// Create a prefixTransformer which can choose between these keys
keyTransformer := value.NewPrefixTransformers(
fmt.Errorf("no matching key was found for the provided AES transformer"), keyTransformers...)
// Create a PrefixTransformer which shall later be put in a list with other providers
result = value.PrefixTransformer{
Transformer: keyTransformer,
Prefix: []byte(prefix),
}
return result, nil
}
// GetSecretboxPrefixTransformer returns a prefix transformer from the provided configuration
func GetSecretboxPrefixTransformer(config *apiserverconfig.SecretboxConfiguration) (value.PrefixTransformer, error) {
var result value.PrefixTransformer
if len(config.Keys) == 0 {
return result, fmt.Errorf("secretbox provider has no valid keys")
}
for _, key := range config.Keys {
if key.Name == "" {
return result, fmt.Errorf("key with invalid name provided")
}
if key.Secret == "" {
return result, fmt.Errorf("key %v has no provided secret", key.Name)
}
}
keyTransformers := []value.PrefixTransformer{}
for _, keyData := range config.Keys {
key, err := base64.StdEncoding.DecodeString(keyData.Secret)
if err != nil {
return result, fmt.Errorf("could not obtain secret for named key %s: %s", keyData.Name, err)
}
if len(key) != 32 {
return result, fmt.Errorf("expected key size 32 for secretbox provider, got %v", len(key))
}
keyArray := [32]byte{}
copy(keyArray[:], key)
// Create a new PrefixTransformer for this key
keyTransformers = append(keyTransformers,
value.PrefixTransformer{
Transformer: secretbox.NewSecretboxTransformer(keyArray),
Prefix: []byte(keyData.Name + ":"),
})
}
// Create a prefixTransformer which can choose between these keys
keyTransformer := value.NewPrefixTransformers(
fmt.Errorf("no matching key was found for the provided Secretbox transformer"), keyTransformers...)
// Create a PrefixTransformer which shall later be put in a list with other providers
result = value.PrefixTransformer{
Transformer: keyTransformer,
Prefix: []byte(secretboxTransformerPrefixV1),
}
return result, nil
}
// getEnvelopePrefixTransformer returns a prefix transformer from the provided config.
// envelopeService is used as the root of trust.
func getEnvelopePrefixTransformer(config *apiserverconfig.KMSConfiguration, envelopeService envelope.Service, prefix string) (value.PrefixTransformer, error) {
envelopeTransformer, err := envelope.NewEnvelopeTransformer(envelopeService, int(config.CacheSize), aestransformer.NewCBCTransformer)
if err != nil {
return value.PrefixTransformer{}, err
}
return value.PrefixTransformer{
Transformer: envelopeTransformer,
Prefix: []byte(prefix + config.Name + ":"),
}, nil
}

View file

@ -31,6 +31,7 @@ import (
genericregistry "k8s.io/apiserver/pkg/registry/generic/registry"
"k8s.io/apiserver/pkg/server"
"k8s.io/apiserver/pkg/server/healthz"
"k8s.io/apiserver/pkg/server/options/encryptionconfig"
serverstorage "k8s.io/apiserver/pkg/server/storage"
"k8s.io/apiserver/pkg/storage/storagebackend"
storagefactory "k8s.io/apiserver/pkg/storage/storagebackend/factory"
@ -160,7 +161,7 @@ func (s *EtcdOptions) AddFlags(fs *pflag.FlagSet) {
fs.StringVar(&s.StorageConfig.Transport.CertFile, "etcd-certfile", s.StorageConfig.Transport.CertFile,
"SSL certification file used to secure etcd communication.")
fs.StringVar(&s.StorageConfig.Transport.CAFile, "etcd-cafile", s.StorageConfig.Transport.CAFile,
fs.StringVar(&s.StorageConfig.Transport.TrustedCAFile, "etcd-cafile", s.StorageConfig.Transport.TrustedCAFile,
"SSL Certificate Authority file used to secure etcd communication.")
fs.StringVar(&s.EncryptionProviderConfigFilepath, "experimental-encryption-provider-config", s.EncryptionProviderConfigFilepath,
@ -201,9 +202,18 @@ func (s *EtcdOptions) addEtcdHealthEndpoint(c *server.Config) error {
if err != nil {
return err
}
c.HealthzChecks = append(c.HealthzChecks, healthz.NamedCheck("etcd", func(r *http.Request) error {
c.AddHealthChecks(healthz.NamedCheck("etcd", func(r *http.Request) error {
return healthCheck()
}))
if s.EncryptionProviderConfigFilepath != "" {
kmsPluginHealthzChecks, err := encryptionconfig.GetKMSPluginHealthzCheckers(s.EncryptionProviderConfigFilepath)
if err != nil {
return err
}
c.AddHealthChecks(kmsPluginHealthzChecks...)
}
return nil
}

View file

@ -18,11 +18,13 @@ package options
import (
"github.com/spf13/pflag"
"k8s.io/apiserver/pkg/util/feature"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apiserver/pkg/admission"
"k8s.io/apiserver/pkg/server"
"k8s.io/apiserver/pkg/storage/storagebackend"
"k8s.io/component-base/featuregate"
)
// RecommendedOptions contains the recommended options for running an API server.
@ -37,6 +39,8 @@ type RecommendedOptions struct {
Features *FeatureOptions
CoreAPI *CoreAPIOptions
// FeatureGate is a way to plumb feature gate through if you have them.
FeatureGate featuregate.FeatureGate
// ExtraAdmissionInitializers is called once after all ApplyTo from the options above, to pass the returned
// admission plugin initializers to Admission.ApplyTo.
ExtraAdmissionInitializers func(c *server.RecommendedConfig) ([]admission.PluginInitializer, error)
@ -44,6 +48,8 @@ type RecommendedOptions struct {
// ProcessInfo is used to identify events created by the server.
ProcessInfo *ProcessInfo
Webhook *WebhookOptions
// API Server Egress Selector is used to control outbound traffic from the API Server
EgressSelector *EgressSelectorOptions
}
func NewRecommendedOptions(prefix string, codec runtime.Codec, processInfo *ProcessInfo) *RecommendedOptions {
@ -56,17 +62,22 @@ func NewRecommendedOptions(prefix string, codec runtime.Codec, processInfo *Proc
sso.HTTP2MaxStreamsPerConnection = 1000
return &RecommendedOptions{
Etcd: NewEtcdOptions(storagebackend.NewDefaultConfig(prefix, codec)),
SecureServing: sso.WithLoopback(),
Authentication: NewDelegatingAuthenticationOptions(),
Authorization: NewDelegatingAuthorizationOptions(),
Audit: NewAuditOptions(),
Features: NewFeatureOptions(),
CoreAPI: NewCoreAPIOptions(),
Etcd: NewEtcdOptions(storagebackend.NewDefaultConfig(prefix, codec)),
SecureServing: sso.WithLoopback(),
Authentication: NewDelegatingAuthenticationOptions(),
Authorization: NewDelegatingAuthorizationOptions(),
Audit: NewAuditOptions(),
Features: NewFeatureOptions(),
CoreAPI: NewCoreAPIOptions(),
// Wired a global by default that sadly people will abuse to have different meanings in different repos.
// Please consider creating your own FeatureGate so you can have a consistent meaning for what a variable contains
// across different repos. Future you will thank you.
FeatureGate: feature.DefaultFeatureGate,
ExtraAdmissionInitializers: func(c *server.RecommendedConfig) ([]admission.PluginInitializer, error) { return nil, nil },
Admission: NewAdmissionOptions(),
ProcessInfo: processInfo,
Webhook: NewWebhookOptions(),
EgressSelector: NewEgressSelectorOptions(),
}
}
@ -79,6 +90,7 @@ func (o *RecommendedOptions) AddFlags(fs *pflag.FlagSet) {
o.Features.AddFlags(fs)
o.CoreAPI.AddFlags(fs)
o.Admission.AddFlags(fs)
o.EgressSelector.AddFlags(fs)
}
// ApplyTo adds RecommendedOptions to the server configuration.
@ -107,7 +119,10 @@ func (o *RecommendedOptions) ApplyTo(config *server.RecommendedConfig) error {
}
if initializers, err := o.ExtraAdmissionInitializers(config); err != nil {
return err
} else if err := o.Admission.ApplyTo(&config.Config, config.SharedInformerFactory, config.ClientConfig, initializers...); err != nil {
} else if err := o.Admission.ApplyTo(&config.Config, config.SharedInformerFactory, config.ClientConfig, o.FeatureGate, initializers...); err != nil {
return err
}
if err := o.EgressSelector.ApplyTo(&config.Config); err != nil {
return err
}
@ -124,6 +139,7 @@ func (o *RecommendedOptions) Validate() []error {
errors = append(errors, o.Features.Validate()...)
errors = append(errors, o.CoreAPI.Validate()...)
errors = append(errors, o.Admission.Validate()...)
errors = append(errors, o.EgressSelector.Validate()...)
return errors
}

View file

@ -41,7 +41,9 @@ type ServerRunOptions struct {
MaxRequestsInFlight int
MaxMutatingRequestsInFlight int
RequestTimeout time.Duration
LivezGracePeriod time.Duration
MinRequestTimeout int
ShutdownDelayDuration time.Duration
// We intentionally did not add a flag for this option. Users of the
// apiserver library can wire it to a flag.
JSONPatchMaxCopyBytes int64
@ -49,9 +51,9 @@ type ServerRunOptions struct {
// decoded in a write request. 0 means no limit.
// We intentionally did not add a flag for this option. Users of the
// apiserver library can wire it to a flag.
MaxRequestBodyBytes int64
TargetRAMMB int
EnableInfightQuotaHandler bool
MaxRequestBodyBytes int64
TargetRAMMB int
EnableInflightQuotaHandler bool
}
func NewServerRunOptions() *ServerRunOptions {
@ -60,7 +62,9 @@ func NewServerRunOptions() *ServerRunOptions {
MaxRequestsInFlight: defaults.MaxRequestsInFlight,
MaxMutatingRequestsInFlight: defaults.MaxMutatingRequestsInFlight,
RequestTimeout: defaults.RequestTimeout,
LivezGracePeriod: defaults.LivezGracePeriod,
MinRequestTimeout: defaults.MinRequestTimeout,
ShutdownDelayDuration: defaults.ShutdownDelayDuration,
JSONPatchMaxCopyBytes: defaults.JSONPatchMaxCopyBytes,
MaxRequestBodyBytes: defaults.MaxRequestBodyBytes,
}
@ -72,8 +76,10 @@ func (s *ServerRunOptions) ApplyTo(c *server.Config) error {
c.ExternalAddress = s.ExternalHost
c.MaxRequestsInFlight = s.MaxRequestsInFlight
c.MaxMutatingRequestsInFlight = s.MaxMutatingRequestsInFlight
c.LivezGracePeriod = s.LivezGracePeriod
c.RequestTimeout = s.RequestTimeout
c.MinRequestTimeout = s.MinRequestTimeout
c.ShutdownDelayDuration = s.ShutdownDelayDuration
c.JSONPatchMaxCopyBytes = s.JSONPatchMaxCopyBytes
c.MaxRequestBodyBytes = s.MaxRequestBodyBytes
c.PublicAddress = s.AdvertiseAddress
@ -106,10 +112,14 @@ func (s *ServerRunOptions) Validate() []error {
errors = append(errors, fmt.Errorf("--target-ram-mb can not be negative value"))
}
if s.EnableInfightQuotaHandler {
if !utilfeature.DefaultFeatureGate.Enabled(features.RequestManagement) {
if s.LivezGracePeriod < 0 {
errors = append(errors, fmt.Errorf("--livez-grace-period can not be a negative value"))
}
if s.EnableInflightQuotaHandler {
if !utilfeature.DefaultFeatureGate.Enabled(features.APIPriorityAndFairness) {
errors = append(errors, fmt.Errorf("--enable-inflight-quota-handler can not be set if feature "+
"gate RequestManagement is disabled"))
"gate APIPriorityAndFairness is disabled"))
}
if s.MaxMutatingRequestsInFlight != 0 {
errors = append(errors, fmt.Errorf("--max-mutating-requests-inflight=%v "+
@ -136,6 +146,10 @@ func (s *ServerRunOptions) Validate() []error {
errors = append(errors, fmt.Errorf("--min-request-timeout can not be negative value"))
}
if s.ShutdownDelayDuration < 0 {
errors = append(errors, fmt.Errorf("--shutdown-delay-duration can not be negative value"))
}
if s.JSONPatchMaxCopyBytes < 0 {
errors = append(errors, fmt.Errorf("--json-patch-max-copy-bytes can not be negative value"))
}
@ -185,14 +199,24 @@ func (s *ServerRunOptions) AddUniversalFlags(fs *pflag.FlagSet) {
"it out. This is the default request timeout for requests but may be overridden by flags such as "+
"--min-request-timeout for specific types of requests.")
fs.DurationVar(&s.LivezGracePeriod, "livez-grace-period", s.LivezGracePeriod, ""+
"This option represents the maximum amount of time it should take for apiserver to complete its startup sequence "+
"and become live. 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.")
fs.IntVar(&s.MinRequestTimeout, "min-request-timeout", s.MinRequestTimeout, ""+
"An optional field indicating the minimum number of seconds a handler must keep "+
"a request open before timing it out. Currently only honored by the watch request "+
"handler, which picks a randomized value above this number as the connection timeout, "+
"to spread out load.")
fs.BoolVar(&s.EnableInfightQuotaHandler, "enable-inflight-quota-handler", s.EnableInfightQuotaHandler, ""+
fs.BoolVar(&s.EnableInflightQuotaHandler, "enable-inflight-quota-handler", s.EnableInflightQuotaHandler, ""+
"If true, replace the max-in-flight handler with an enhanced one that queues and dispatches with priority and fairness")
fs.DurationVar(&s.ShutdownDelayDuration, "shutdown-delay-duration", s.ShutdownDelayDuration, ""+
"Time to delay the termination. During that time the server keeps serving requests normally and /healthz "+
"returns success, but /readyz immediately returns failure. Graceful termination starts after this delay "+
"has elapsed. This can be used to allow load balancer to stop sending traffic to this server.")
utilfeature.DefaultMutableFeatureGate.AddFlag(fs)
}

View file

@ -17,7 +17,6 @@ limitations under the License.
package options
import (
"crypto/tls"
"fmt"
"net"
"path"
@ -29,6 +28,7 @@ import (
utilnet "k8s.io/apimachinery/pkg/util/net"
"k8s.io/apiserver/pkg/server"
"k8s.io/apiserver/pkg/server/dynamiccertificates"
certutil "k8s.io/client-go/util/cert"
"k8s.io/client-go/util/keyutil"
cliflag "k8s.io/component-base/cli/flag"
@ -88,7 +88,7 @@ type GeneratableKeyCert struct {
PairName string
// GeneratedCert holds an in-memory generated certificate if CertFile/KeyFile aren't explicitly set, and CertDirectory/PairName are not set.
GeneratedCert *tls.Certificate
GeneratedCert dynamiccertificates.CertKeyContentProvider
// FixtureDirectory is a directory that contains test fixture used to avoid regeneration of certs during tests.
// The format is:
@ -109,10 +109,10 @@ func NewSecureServingOptions() *SecureServingOptions {
}
func (s *SecureServingOptions) DefaultExternalAddress() (net.IP, error) {
if !s.ExternalAddress.IsUnspecified() {
if s.ExternalAddress != nil && !s.ExternalAddress.IsUnspecified() {
return s.ExternalAddress, nil
}
return utilnet.ChooseBindAddress(s.BindAddress)
return utilnet.ResolveBindAddress(s.BindAddress)
}
func (s *SecureServingOptions) Validate() []error {
@ -225,11 +225,11 @@ func (s *SecureServingOptions) ApplyTo(config **server.SecureServingInfo) error
serverCertFile, serverKeyFile := s.ServerCert.CertKey.CertFile, s.ServerCert.CertKey.KeyFile
// load main cert
if len(serverCertFile) != 0 || len(serverKeyFile) != 0 {
tlsCert, err := tls.LoadX509KeyPair(serverCertFile, serverKeyFile)
var err error
c.Cert, err = dynamiccertificates.NewDynamicServingContentFromFiles("serving-cert", serverCertFile, serverKeyFile)
if err != nil {
return fmt.Errorf("unable to load server certificate: %v", err)
return err
}
c.Cert = &tlsCert
} else if s.ServerCert.GeneratedCert != nil {
c.Cert = s.ServerCert.GeneratedCert
}
@ -249,21 +249,15 @@ func (s *SecureServingOptions) ApplyTo(config **server.SecureServingInfo) error
}
// load SNI certs
namedTLSCerts := make([]server.NamedTLSCert, 0, len(s.SNICertKeys))
namedTLSCerts := make([]dynamiccertificates.SNICertKeyContentProvider, 0, len(s.SNICertKeys))
for _, nck := range s.SNICertKeys {
tlsCert, err := tls.LoadX509KeyPair(nck.CertFile, nck.KeyFile)
namedTLSCerts = append(namedTLSCerts, server.NamedTLSCert{
TLSCert: tlsCert,
Names: nck.Names,
})
tlsCert, err := dynamiccertificates.NewDynamicSNIContentFromFiles("sni-serving-cert", nck.CertFile, nck.KeyFile, nck.Names...)
namedTLSCerts = append(namedTLSCerts, tlsCert)
if err != nil {
return fmt.Errorf("failed to load SNI cert and key: %v", err)
}
}
c.SNICerts, err = server.GetNamedCertificateMap(namedTLSCerts)
if err != nil {
return err
}
c.SNICerts = namedTLSCerts
return nil
}
@ -311,11 +305,10 @@ func (s *SecureServingOptions) MaybeDefaultWithSelfSignedCerts(publicAddress str
}
klog.Infof("Generated self-signed cert (%s, %s)", keyCert.CertFile, keyCert.KeyFile)
} else {
tlsCert, err := tls.X509KeyPair(cert, key)
s.ServerCert.GeneratedCert, err = dynamiccertificates.NewStaticCertKeyContent("Generated self signed cert", cert, key)
if err != nil {
return fmt.Errorf("unable to generate self signed cert: %v", err)
return err
}
s.ServerCert.GeneratedCert = &tlsCert
klog.Infof("Generated self-signed cert in-memory")
}
}

View file

@ -17,12 +17,12 @@ limitations under the License.
package options
import (
"crypto/tls"
"fmt"
"github.com/pborman/uuid"
"github.com/google/uuid"
"k8s.io/apiserver/pkg/server"
"k8s.io/apiserver/pkg/server/dynamiccertificates"
"k8s.io/client-go/rest"
certutil "k8s.io/client-go/util/cert"
)
@ -55,12 +55,12 @@ func (s *SecureServingOptionsWithLoopback) ApplyTo(secureServingInfo **server.Se
if err != nil {
return fmt.Errorf("failed to generate self-signed certificate for loopback connection: %v", err)
}
tlsCert, err := tls.X509KeyPair(certPem, keyPem)
certProvider, err := dynamiccertificates.NewStaticSNICertKeyContent("self-signed loopback", certPem, keyPem, server.LoopbackClientServerNameOverride)
if err != nil {
return fmt.Errorf("failed to generate self-signed certificate for loopback connection: %v", err)
}
secureLoopbackClientConfig, err := (*secureServingInfo).NewLoopbackClientConfig(uuid.NewRandom().String(), certPem)
secureLoopbackClientConfig, err := (*secureServingInfo).NewLoopbackClientConfig(uuid.New().String(), certPem)
switch {
// if we failed and there's no fallback loopback client config, we need to fail
case err != nil && *loopbackClientConfig == nil:
@ -71,7 +71,8 @@ func (s *SecureServingOptionsWithLoopback) ApplyTo(secureServingInfo **server.Se
default:
*loopbackClientConfig = secureLoopbackClientConfig
(*secureServingInfo).SNICerts[server.LoopbackClientServerNameOverride] = &tlsCert
// Write to the front of SNICerts so that this overrides any other certs with the same name
(*secureServingInfo).SNICerts = append([]dynamiccertificates.SNICertKeyContentProvider{certProvider}, (*secureServingInfo).SNICerts...)
}
return nil