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 (
2019-04-07 17:07:55 +00:00
"encoding/json"
2019-01-12 04:58:27 +00:00
"fmt"
2019-04-07 17:07:55 +00:00
"io/ioutil"
2019-01-12 04:58:27 +00:00
"time"
"github.com/spf13/pflag"
"k8s.io/klog"
2019-04-07 17:07:55 +00:00
"k8s.io/api/core/v1"
2019-01-12 04:58:27 +00:00
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apiserver/pkg/authentication/authenticatorfactory"
"k8s.io/apiserver/pkg/server"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
2019-08-30 18:33:25 +00:00
openapicommon "k8s.io/kube-openapi/pkg/common"
2019-01-12 04:58:27 +00:00
)
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
}
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.
func ( s * RequestHeaderAuthenticationOptions ) ToAuthenticationRequestHeaderConfig ( ) * authenticatorfactory . RequestHeaderConfig {
if len ( s . ClientCAFile ) == 0 {
return nil
}
return & authenticatorfactory . RequestHeaderConfig {
UsernameHeaders : s . UsernameHeaders ,
GroupHeaders : s . GroupHeaders ,
ExtraHeaderPrefixes : s . ExtraHeaderPrefixes ,
ClientCA : s . ClientCAFile ,
AllowedClientNames : s . AllowedNames ,
}
}
type ClientCertAuthenticationOptions struct {
// ClientCA is the certificate bundle for all the signers that you'll recognize for incoming client certificates
ClientCA string
}
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
}
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-" } ,
} ,
2019-01-12 04:58:27 +00:00
}
}
func ( s * DelegatingAuthenticationOptions ) Validate ( ) [ ] error {
allErrors := [ ] error { }
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 " +
"tokenaccessreviews.authentication.k8s.io." + optionalKubeConfigSentence )
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-08-30 18:33:25 +00:00
func ( s * DelegatingAuthenticationOptions ) ApplyTo ( c * server . AuthenticationInfo , servingInfo * server . SecureServingInfo , openAPIConfig * openapicommon . Config ) error {
2019-01-12 04:58:27 +00:00
if s == nil {
c . Authenticator = nil
return nil
}
cfg := authenticatorfactory . DelegatingAuthenticatorConfig {
2019-08-30 18:33:25 +00:00
Anonymous : true ,
2019-01-12 04:58:27 +00:00
CacheTTL : s . CacheTTL ,
}
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 {
cfg . TokenAccessReviewClient = client . AuthenticationV1beta1 ( ) . TokenReviews ( )
}
2019-01-12 04:58:27 +00:00
// look into configmaps/external-apiserver-authentication for missing authn info
if ! s . SkipInClusterLookup {
err := s . lookupMissingConfigInCluster ( 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 err
}
}
}
2019-04-07 17:07:55 +00:00
// 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 )
}
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 )
}
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
}
c . Authenticator = authenticator
2019-08-30 18:33:25 +00:00
if openAPIConfig != nil {
openAPIConfig . SecurityDefinitions = securityDefinitions
}
2019-01-12 04:58:27 +00:00
c . SupportsBasicAuth = false
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"
authenticationRoleName = "extension-apiserver-authentication-reader"
)
func ( s * DelegatingAuthenticationOptions ) lookupMissingConfigInCluster ( client kubernetes . Interface ) error {
2019-04-07 17:07:55 +00:00
if len ( s . ClientCert . ClientCA ) > 0 && len ( s . RequestHeader . ClientCAFile ) > 0 {
return nil
}
2019-01-12 04:58:27 +00:00
if client == nil {
2019-04-07 17:07:55 +00:00
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 )
}
2019-01-12 04:58:27 +00:00
return nil
}
2019-04-07 17:07:55 +00:00
authConfigMap , err := client . CoreV1 ( ) . ConfigMaps ( authenticationConfigMapNamespace ) . Get ( authenticationConfigMapName , metav1 . GetOptions { } )
2019-01-12 04:58:27 +00:00
switch {
case errors . IsNotFound ( err ) :
// ignore, authConfigMap is nil now
case errors . IsForbidden ( err ) :
klog . Warningf ( "Unable to get configmap/%s in %s. Usually fixed by " +
2019-04-07 17:07:55 +00:00
"'kubectl create rolebinding -n %s ROLEBINDING_NAME --role=%s --serviceaccount=YOUR_NS:YOUR_SA'" ,
2019-01-12 04:58:27 +00:00
authenticationConfigMapName , authenticationConfigMapNamespace , authenticationConfigMapNamespace , authenticationRoleName )
return err
case err != nil :
return err
}
2019-04-07 17:07:55 +00:00
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 )
}
}
2019-01-12 04:58:27 +00:00
return nil
}
2019-04-07 17:07:55 +00:00
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
}
groupHeaders , err := deserializeStrings ( authConfigMap . Data [ "requestheader-group-headers" ] )
if err != nil {
return nil , err
}
extraHeaderPrefixes , err := deserializeStrings ( authConfigMap . Data [ "requestheader-extra-headers-prefix" ] )
if err != nil {
return nil , err
}
allowedNames , err := deserializeStrings ( authConfigMap . Data [ "requestheader-allowed-names" ] )
if err != nil {
return nil , err
}
return & RequestHeaderAuthenticationOptions {
UsernameHeaders : usernameHeaders ,
GroupHeaders : groupHeaders ,
ExtraHeaderPrefixes : extraHeaderPrefixes ,
ClientCAFile : f . Name ( ) ,
AllowedNames : allowedNames ,
} , nil
}
func deserializeStrings ( in string ) ( [ ] string , error ) {
if len ( in ) == 0 {
return nil , nil
}
var ret [ ] string
if err := json . Unmarshal ( [ ] byte ( in ) , & ret ) ; err != nil {
return nil , err
}
return ret , 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
return kubernetes . NewForConfig ( clientConfig )
}