2019-01-12 04:58:27 +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 options
import (
"fmt"
2019-12-12 01:27:03 +00:00
"strings"
2019-01-12 04:58:27 +00:00
"time"
2019-12-12 01:27:03 +00:00
"k8s.io/apiserver/pkg/server/dynamiccertificates"
2019-01-12 04:58:27 +00:00
"github.com/spf13/pflag"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2020-12-01 01:06:26 +00:00
"k8s.io/apimachinery/pkg/util/wait"
2019-01-12 04:58:27 +00:00
"k8s.io/apiserver/pkg/authentication/authenticatorfactory"
2019-12-12 01:27:03 +00:00
"k8s.io/apiserver/pkg/authentication/request/headerrequest"
2019-01-12 04:58:27 +00:00
"k8s.io/apiserver/pkg/server"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
2020-08-10 17:43:49 +00:00
"k8s.io/klog/v2"
2019-08-30 18:33:25 +00:00
openapicommon "k8s.io/kube-openapi/pkg/common"
2019-01-12 04:58:27 +00:00
)
2020-12-01 01:06:26 +00:00
// DefaultAuthWebhookRetryBackoff is the default backoff parameters for
// both authentication and authorization webhook used by the apiserver.
func DefaultAuthWebhookRetryBackoff ( ) * wait . Backoff {
return & wait . Backoff {
Duration : 500 * time . Millisecond ,
Factor : 1.5 ,
Jitter : 0.2 ,
Steps : 5 ,
}
}
2019-04-07 17:07:55 +00:00
type RequestHeaderAuthenticationOptions struct {
// ClientCAFile is the root certificate bundle to verify client certificates on incoming requests
// before trusting usernames in headers.
ClientCAFile string
UsernameHeaders [ ] string
GroupHeaders [ ] string
ExtraHeaderPrefixes [ ] string
AllowedNames [ ] string
}
2019-12-12 01:27:03 +00:00
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
}
2019-04-07 17:07:55 +00:00
func ( s * RequestHeaderAuthenticationOptions ) AddFlags ( fs * pflag . FlagSet ) {
if s == nil {
return
}
fs . StringSliceVar ( & s . UsernameHeaders , "requestheader-username-headers" , s . UsernameHeaders , "" +
"List of request headers to inspect for usernames. X-Remote-User is common." )
fs . StringSliceVar ( & s . GroupHeaders , "requestheader-group-headers" , s . GroupHeaders , "" +
"List of request headers to inspect for groups. X-Remote-Group is suggested." )
fs . StringSliceVar ( & s . ExtraHeaderPrefixes , "requestheader-extra-headers-prefix" , s . ExtraHeaderPrefixes , "" +
"List of request header prefixes to inspect. X-Remote-Extra- is suggested." )
fs . StringVar ( & s . ClientCAFile , "requestheader-client-ca-file" , s . ClientCAFile , "" +
"Root certificate bundle to use to verify client certificates on incoming requests " +
"before trusting usernames in headers specified by --requestheader-username-headers. " +
"WARNING: generally do not depend on authorization being already done for incoming requests." )
fs . StringSliceVar ( & s . AllowedNames , "requestheader-allowed-names" , s . AllowedNames , "" +
"List of client certificate common names to allow to provide usernames in headers " +
"specified by --requestheader-username-headers. If empty, any client certificate validated " +
"by the authorities in --requestheader-client-ca-file is allowed." )
}
// ToAuthenticationRequestHeaderConfig returns a RequestHeaderConfig config object for these options
// if necessary, nil otherwise.
2019-12-12 01:27:03 +00:00
func ( s * RequestHeaderAuthenticationOptions ) ToAuthenticationRequestHeaderConfig ( ) ( * authenticatorfactory . RequestHeaderConfig , error ) {
2019-04-07 17:07:55 +00:00
if len ( s . ClientCAFile ) == 0 {
2019-12-12 01:27:03 +00:00
return nil , nil
2019-04-07 17:07:55 +00:00
}
2019-12-12 01:27:03 +00:00
caBundleProvider , err := dynamiccertificates . NewDynamicCAContentFromFile ( "request-header" , s . ClientCAFile )
if err != nil {
return nil , err
2019-04-07 17:07:55 +00:00
}
2019-12-12 01:27:03 +00:00
return & authenticatorfactory . RequestHeaderConfig {
UsernameHeaders : headerrequest . StaticStringSlice ( s . UsernameHeaders ) ,
GroupHeaders : headerrequest . StaticStringSlice ( s . GroupHeaders ) ,
ExtraHeaderPrefixes : headerrequest . StaticStringSlice ( s . ExtraHeaderPrefixes ) ,
CAContentProvider : caBundleProvider ,
AllowedClientNames : headerrequest . StaticStringSlice ( s . AllowedNames ) ,
} , nil
2019-04-07 17:07:55 +00:00
}
2019-12-12 01:27:03 +00:00
// ClientCertAuthenticationOptions provides different options for client cert auth. You should use `GetClientVerifyOptionFn` to
// get the verify options for your authenticator.
2019-04-07 17:07:55 +00:00
type ClientCertAuthenticationOptions struct {
// ClientCA is the certificate bundle for all the signers that you'll recognize for incoming client certificates
ClientCA string
2019-12-12 01:27:03 +00:00
// 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 )
2019-04-07 17:07:55 +00:00
}
func ( s * ClientCertAuthenticationOptions ) AddFlags ( fs * pflag . FlagSet ) {
fs . StringVar ( & s . ClientCA , "client-ca-file" , s . ClientCA , "" +
"If set, any request presenting a client certificate signed by one of " +
"the authorities in the client-ca-file is authenticated with an identity " +
"corresponding to the CommonName of the client certificate." )
}
2019-01-12 04:58:27 +00:00
// DelegatingAuthenticationOptions provides an easy way for composing API servers to delegate their authentication to
// the root kube API server. The API federator will act as
// a front proxy and direction connections will be able to delegate to the core kube API server
type DelegatingAuthenticationOptions struct {
// RemoteKubeConfigFile is the file to use to connect to a "normal" kube API server which hosts the
// TokenAccessReview.authentication.k8s.io endpoint for checking tokens.
RemoteKubeConfigFile string
// RemoteKubeConfigFileOptional is specifying whether not specifying the kubeconfig or
// a missing in-cluster config will be fatal.
RemoteKubeConfigFileOptional bool
// CacheTTL is the length of time that a token authentication answer will be cached.
CacheTTL time . Duration
2019-04-07 17:07:55 +00:00
ClientCert ClientCertAuthenticationOptions
RequestHeader RequestHeaderAuthenticationOptions
// SkipInClusterLookup indicates missing authentication configuration should not be retrieved from the cluster configmap
2019-01-12 04:58:27 +00:00
SkipInClusterLookup bool
// TolerateInClusterLookupFailure indicates failures to look up authentication configuration from the cluster configmap should not be fatal.
// Setting this can result in an authenticator that will reject all requests.
TolerateInClusterLookupFailure bool
2020-12-01 01:06:26 +00:00
// WebhookRetryBackoff specifies the backoff parameters for the authentication webhook retry logic.
// This allows us to configure the sleep time at each iteration and the maximum number of retries allowed
// before we fail the webhook call in order to limit the fan out that ensues when the system is degraded.
WebhookRetryBackoff * wait . Backoff
// ClientTimeout specifies a time limit for requests made by the authorization webhook client.
// The default value is set to 10 seconds.
ClientTimeout time . Duration
2019-01-12 04:58:27 +00:00
}
func NewDelegatingAuthenticationOptions ( ) * DelegatingAuthenticationOptions {
return & DelegatingAuthenticationOptions {
// very low for responsiveness, but high enough to handle storms
CacheTTL : 10 * time . Second ,
2019-04-07 17:07:55 +00:00
ClientCert : ClientCertAuthenticationOptions { } ,
RequestHeader : RequestHeaderAuthenticationOptions {
UsernameHeaders : [ ] string { "x-remote-user" } ,
GroupHeaders : [ ] string { "x-remote-group" } ,
ExtraHeaderPrefixes : [ ] string { "x-remote-extra-" } ,
} ,
2020-12-01 01:06:26 +00:00
WebhookRetryBackoff : DefaultAuthWebhookRetryBackoff ( ) ,
ClientTimeout : 10 * time . Second ,
2019-01-12 04:58:27 +00:00
}
}
2020-12-01 01:06:26 +00:00
// WithCustomRetryBackoff sets the custom backoff parameters for the authentication webhook retry logic.
func ( s * DelegatingAuthenticationOptions ) WithCustomRetryBackoff ( backoff wait . Backoff ) {
s . WebhookRetryBackoff = & backoff
}
// WithClientTimeout sets the given timeout for the authentication webhook client.
func ( s * DelegatingAuthenticationOptions ) WithClientTimeout ( timeout time . Duration ) {
s . ClientTimeout = timeout
}
2019-01-12 04:58:27 +00:00
func ( s * DelegatingAuthenticationOptions ) Validate ( ) [ ] error {
2021-02-22 20:08:19 +00:00
if s == nil {
return nil
}
2019-01-12 04:58:27 +00:00
allErrors := [ ] error { }
2019-12-12 01:27:03 +00:00
allErrors = append ( allErrors , s . RequestHeader . Validate ( ) ... )
2020-12-01 01:06:26 +00:00
if s . WebhookRetryBackoff != nil && s . WebhookRetryBackoff . Steps <= 0 {
allErrors = append ( allErrors , fmt . Errorf ( "number of webhook retry attempts must be greater than 1, but is: %d" , s . WebhookRetryBackoff . Steps ) )
}
2019-01-12 04:58:27 +00:00
return allErrors
}
func ( s * DelegatingAuthenticationOptions ) AddFlags ( fs * pflag . FlagSet ) {
if s == nil {
return
}
var optionalKubeConfigSentence string
if s . RemoteKubeConfigFileOptional {
optionalKubeConfigSentence = " This is optional. If empty, all token requests are considered to be anonymous and no client CA is looked up in the cluster."
}
fs . StringVar ( & s . RemoteKubeConfigFile , "authentication-kubeconfig" , s . RemoteKubeConfigFile , "" +
"kubeconfig file pointing at the 'core' kubernetes server with enough rights to create " +
2020-03-26 21:07:15 +00:00
"tokenreviews.authentication.k8s.io." + optionalKubeConfigSentence )
2019-01-12 04:58:27 +00:00
fs . DurationVar ( & s . CacheTTL , "authentication-token-webhook-cache-ttl" , s . CacheTTL ,
"The duration to cache responses from the webhook token authenticator." )
2019-04-07 17:07:55 +00:00
s . ClientCert . AddFlags ( fs )
s . RequestHeader . AddFlags ( fs )
2019-01-12 04:58:27 +00:00
fs . BoolVar ( & s . SkipInClusterLookup , "authentication-skip-lookup" , s . SkipInClusterLookup , "" +
"If false, the authentication-kubeconfig will be used to lookup missing authentication " +
"configuration from the cluster." )
fs . BoolVar ( & s . TolerateInClusterLookupFailure , "authentication-tolerate-lookup-failure" , s . TolerateInClusterLookupFailure , "" +
"If true, failures to look up missing authentication configuration from the cluster are not considered fatal. " +
"Note that this can result in authentication that treats all requests as anonymous." )
}
2019-12-12 01:27:03 +00:00
func ( s * DelegatingAuthenticationOptions ) ApplyTo ( authenticationInfo * server . AuthenticationInfo , servingInfo * server . SecureServingInfo , openAPIConfig * openapicommon . Config ) error {
2019-01-12 04:58:27 +00:00
if s == nil {
2019-12-12 01:27:03 +00:00
authenticationInfo . Authenticator = nil
2019-01-12 04:58:27 +00:00
return nil
}
cfg := authenticatorfactory . DelegatingAuthenticatorConfig {
2020-12-01 01:06:26 +00:00
Anonymous : true ,
CacheTTL : s . CacheTTL ,
WebhookRetryBackoff : s . WebhookRetryBackoff ,
2019-01-12 04:58:27 +00:00
}
client , err := s . getClient ( )
if err != nil {
return fmt . Errorf ( "failed to get delegated authentication kubeconfig: %v" , err )
}
2019-08-30 18:33:25 +00:00
// configure token review
if client != nil {
2019-12-12 01:27:03 +00:00
cfg . TokenAccessReviewClient = client . AuthenticationV1 ( ) . TokenReviews ( )
2019-08-30 18:33:25 +00:00
}
2019-12-12 01:27:03 +00:00
// get the clientCA information
2021-03-18 22:40:29 +00:00
clientCASpecified := s . ClientCert != ClientCertAuthenticationOptions { }
2019-12-12 01:27:03 +00:00
var clientCAProvider dynamiccertificates . CAContentProvider
2021-03-18 22:40:29 +00:00
if clientCASpecified {
2019-12-12 01:27:03 +00:00
clientCAProvider , err = s . ClientCert . GetClientCAContentProvider ( )
2019-01-12 04:58:27 +00:00
if err != nil {
2021-03-18 22:40:29 +00:00
return fmt . Errorf ( "unable to load client CA provider: %v" , err )
2019-12-12 01:27:03 +00:00
}
cfg . ClientCertificateCAContentProvider = clientCAProvider
if err = authenticationInfo . ApplyClientCert ( cfg . ClientCertificateCAContentProvider , servingInfo ) ; err != nil {
2021-03-18 22:40:29 +00:00
return fmt . Errorf ( "unable to assign client CA provider: %v" , err )
2019-12-12 01:27:03 +00:00
}
} 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 )
2019-01-12 04:58:27 +00:00
}
2019-12-12 01:27:03 +00:00
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 )
}
2019-01-12 04:58:27 +00:00
}
}
2019-12-12 01:27:03 +00:00
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 )
}
2019-04-07 17:07:55 +00:00
2019-12-12 01:27:03 +00:00
} 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 )
2021-03-18 22:40:29 +00:00
klog . Warning ( "Continuing without authentication configuration. This may treat all requests as anonymous." )
klog . Warning ( "To require authentication configuration lookup to succeed, set --authentication-tolerate-lookup-failure=false" )
2019-12-12 01:27:03 +00:00
} 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 )
}
2019-04-07 17:07:55 +00:00
}
2019-01-12 04:58:27 +00:00
// create authenticator
2019-08-30 18:33:25 +00:00
authenticator , securityDefinitions , err := cfg . New ( )
2019-01-12 04:58:27 +00:00
if err != nil {
return err
}
2019-12-12 01:27:03 +00:00
authenticationInfo . Authenticator = authenticator
2019-08-30 18:33:25 +00:00
if openAPIConfig != nil {
openAPIConfig . SecurityDefinitions = securityDefinitions
}
2019-01-12 04:58:27 +00:00
return nil
}
const (
authenticationConfigMapNamespace = metav1 . NamespaceSystem
// authenticationConfigMapName is the name of ConfigMap in the kube-system namespace holding the root certificate
// bundle to use to verify client certificates on incoming requests before trusting usernames in headers specified
// by --requestheader-username-headers. This is created in the cluster by the kube-apiserver.
// "WARNING: generally do not depend on authorization being already done for incoming requests.")
authenticationConfigMapName = "extension-apiserver-authentication"
)
2019-12-12 01:27:03 +00:00
func ( s * DelegatingAuthenticationOptions ) createRequestHeaderConfig ( client kubernetes . Interface ) ( * authenticatorfactory . RequestHeaderConfig , error ) {
2020-08-10 17:43:49 +00:00
dynamicRequestHeaderProvider , err := newDynamicRequestHeaderController ( client )
2019-12-12 01:27:03 +00:00
if err != nil {
return nil , fmt . Errorf ( "unable to create request header authentication config: %v" , err )
2019-01-12 04:58:27 +00:00
}
2020-08-10 17:43:49 +00:00
// look up authentication configuration in the cluster and in case of an err defer to authentication-tolerate-lookup-failure flag
if err := dynamicRequestHeaderProvider . RunOnce ( ) ; err != nil {
2019-04-07 17:07:55 +00:00
return nil , err
}
2019-12-12 01:27:03 +00:00
return & authenticatorfactory . RequestHeaderConfig {
2020-08-10 17:43:49 +00:00
CAContentProvider : dynamicRequestHeaderProvider ,
UsernameHeaders : headerrequest . StringSliceProvider ( headerrequest . StringSliceProviderFunc ( dynamicRequestHeaderProvider . UsernameHeaders ) ) ,
GroupHeaders : headerrequest . StringSliceProvider ( headerrequest . StringSliceProviderFunc ( dynamicRequestHeaderProvider . GroupHeaders ) ) ,
ExtraHeaderPrefixes : headerrequest . StringSliceProvider ( headerrequest . StringSliceProviderFunc ( dynamicRequestHeaderProvider . ExtraHeaderPrefixes ) ) ,
AllowedClientNames : headerrequest . StringSliceProvider ( headerrequest . StringSliceProviderFunc ( dynamicRequestHeaderProvider . AllowedClientNames ) ) ,
2019-04-07 17:07:55 +00:00
} , nil
}
2019-01-12 04:58:27 +00:00
// getClient returns a Kubernetes clientset. If s.RemoteKubeConfigFileOptional is true, nil will be returned
// if no kubeconfig is specified by the user and the in-cluster config is not found.
func ( s * DelegatingAuthenticationOptions ) getClient ( ) ( kubernetes . Interface , error ) {
var clientConfig * rest . Config
var err error
if len ( s . RemoteKubeConfigFile ) > 0 {
loadingRules := & clientcmd . ClientConfigLoadingRules { ExplicitPath : s . RemoteKubeConfigFile }
loader := clientcmd . NewNonInteractiveDeferredLoadingClientConfig ( loadingRules , & clientcmd . ConfigOverrides { } )
clientConfig , err = loader . ClientConfig ( )
} else {
// without the remote kubeconfig file, try to use the in-cluster config. Most addon API servers will
// use this path. If it is optional, ignore errors.
clientConfig , err = rest . InClusterConfig ( )
if err != nil && s . RemoteKubeConfigFileOptional {
if err != rest . ErrNotInCluster {
klog . Warningf ( "failed to read in-cluster kubeconfig for delegated authentication: %v" , err )
}
return nil , nil
}
}
if err != nil {
return nil , fmt . Errorf ( "failed to get delegated authentication kubeconfig: %v" , err )
}
// set high qps/burst limits since this will effectively limit API server responsiveness
clientConfig . QPS = 200
clientConfig . Burst = 400
2020-12-01 01:06:26 +00:00
clientConfig . Timeout = s . ClientTimeout
2019-01-12 04:58:27 +00:00
return kubernetes . NewForConfig ( clientConfig )
}