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-08-30 18:33:25 +00:00
"errors"
2019-01-12 04:58:27 +00:00
"fmt"
"net/url"
"strings"
"time"
"github.com/spf13/pflag"
"k8s.io/klog"
2019-08-30 18:33:25 +00:00
"k8s.io/apimachinery/pkg/util/sets"
2019-01-12 04:58:27 +00:00
"k8s.io/apiserver/pkg/authentication/authenticator"
genericapiserver "k8s.io/apiserver/pkg/server"
2019-04-07 17:07:55 +00:00
genericoptions "k8s.io/apiserver/pkg/server/options"
2019-08-30 18:33:25 +00:00
utilfeature "k8s.io/apiserver/pkg/util/feature"
cliflag "k8s.io/component-base/cli/flag"
"k8s.io/kubernetes/pkg/features"
2019-01-12 04:58:27 +00:00
kubeauthenticator "k8s.io/kubernetes/pkg/kubeapiserver/authenticator"
2019-08-30 18:33:25 +00:00
authzmodes "k8s.io/kubernetes/pkg/kubeapiserver/authorizer/modes"
2019-01-12 04:58:27 +00:00
)
type BuiltInAuthenticationOptions struct {
APIAudiences [ ] string
2019-08-30 18:33:25 +00:00
Anonymous * AnonymousAuthenticationOptions
BootstrapToken * BootstrapTokenAuthenticationOptions
2019-04-07 17:07:55 +00:00
ClientCert * genericoptions . ClientCertAuthenticationOptions
2019-08-30 18:33:25 +00:00
OIDC * OIDCAuthenticationOptions
2019-01-12 04:58:27 +00:00
PasswordFile * PasswordFileAuthenticationOptions
2019-04-07 17:07:55 +00:00
RequestHeader * genericoptions . RequestHeaderAuthenticationOptions
2019-01-12 04:58:27 +00:00
ServiceAccounts * ServiceAccountAuthenticationOptions
TokenFile * TokenFileAuthenticationOptions
WebHook * WebHookAuthenticationOptions
TokenSuccessCacheTTL time . Duration
TokenFailureCacheTTL time . Duration
}
2019-08-30 18:33:25 +00:00
type AnonymousAuthenticationOptions struct {
Allow bool
}
type BootstrapTokenAuthenticationOptions struct {
Enable bool
}
type OIDCAuthenticationOptions struct {
CAFile string
ClientID string
IssuerURL string
UsernameClaim string
UsernamePrefix string
GroupsClaim string
GroupsPrefix string
SigningAlgs [ ] string
RequiredClaims map [ string ] string
}
2019-01-12 04:58:27 +00:00
type PasswordFileAuthenticationOptions struct {
BasicAuthFile string
}
type ServiceAccountAuthenticationOptions struct {
KeyFiles [ ] string
Lookup bool
Issuer string
MaxExpiration time . Duration
}
type TokenFileAuthenticationOptions struct {
TokenFile string
}
type WebHookAuthenticationOptions struct {
ConfigFile string
CacheTTL time . Duration
}
func NewBuiltInAuthenticationOptions ( ) * BuiltInAuthenticationOptions {
return & BuiltInAuthenticationOptions {
TokenSuccessCacheTTL : 10 * time . Second ,
TokenFailureCacheTTL : 0 * time . Second ,
}
}
func ( s * BuiltInAuthenticationOptions ) WithAll ( ) * BuiltInAuthenticationOptions {
return s .
2019-08-30 18:33:25 +00:00
WithAnonymous ( ) .
WithBootstrapToken ( ) .
2019-04-07 17:07:55 +00:00
WithClientCert ( ) .
2019-08-30 18:33:25 +00:00
WithOIDC ( ) .
2019-01-12 04:58:27 +00:00
WithPasswordFile ( ) .
2019-04-07 17:07:55 +00:00
WithRequestHeader ( ) .
2019-01-12 04:58:27 +00:00
WithServiceAccounts ( ) .
WithTokenFile ( ) .
WithWebHook ( )
}
2019-08-30 18:33:25 +00:00
func ( s * BuiltInAuthenticationOptions ) WithAnonymous ( ) * BuiltInAuthenticationOptions {
s . Anonymous = & AnonymousAuthenticationOptions { Allow : true }
return s
}
func ( s * BuiltInAuthenticationOptions ) WithBootstrapToken ( ) * BuiltInAuthenticationOptions {
s . BootstrapToken = & BootstrapTokenAuthenticationOptions { }
return s
}
2019-04-07 17:07:55 +00:00
func ( s * BuiltInAuthenticationOptions ) WithClientCert ( ) * BuiltInAuthenticationOptions {
s . ClientCert = & genericoptions . ClientCertAuthenticationOptions { }
return s
}
2019-08-30 18:33:25 +00:00
func ( s * BuiltInAuthenticationOptions ) WithOIDC ( ) * BuiltInAuthenticationOptions {
s . OIDC = & OIDCAuthenticationOptions { }
return s
}
2019-01-12 04:58:27 +00:00
func ( s * BuiltInAuthenticationOptions ) WithPasswordFile ( ) * BuiltInAuthenticationOptions {
s . PasswordFile = & PasswordFileAuthenticationOptions { }
return s
}
2019-04-07 17:07:55 +00:00
func ( s * BuiltInAuthenticationOptions ) WithRequestHeader ( ) * BuiltInAuthenticationOptions {
s . RequestHeader = & genericoptions . RequestHeaderAuthenticationOptions { }
return s
}
2019-01-12 04:58:27 +00:00
func ( s * BuiltInAuthenticationOptions ) WithServiceAccounts ( ) * BuiltInAuthenticationOptions {
s . ServiceAccounts = & ServiceAccountAuthenticationOptions { Lookup : true }
return s
}
func ( s * BuiltInAuthenticationOptions ) WithTokenFile ( ) * BuiltInAuthenticationOptions {
s . TokenFile = & TokenFileAuthenticationOptions { }
return s
}
func ( s * BuiltInAuthenticationOptions ) WithWebHook ( ) * BuiltInAuthenticationOptions {
s . WebHook = & WebHookAuthenticationOptions {
CacheTTL : 2 * time . Minute ,
}
return s
}
// Validate checks invalid config combination
func ( s * BuiltInAuthenticationOptions ) Validate ( ) [ ] error {
allErrors := [ ] error { }
2019-08-30 18:33:25 +00:00
if s . OIDC != nil && ( len ( s . OIDC . IssuerURL ) > 0 ) != ( len ( s . OIDC . ClientID ) > 0 ) {
allErrors = append ( allErrors , fmt . Errorf ( "oidc-issuer-url and oidc-client-id should be specified together" ) )
}
2019-01-12 04:58:27 +00:00
if s . ServiceAccounts != nil && len ( s . ServiceAccounts . Issuer ) > 0 && strings . Contains ( s . ServiceAccounts . Issuer , ":" ) {
if _ , err := url . Parse ( s . ServiceAccounts . Issuer ) ; err != nil {
allErrors = append ( allErrors , fmt . Errorf ( "service-account-issuer contained a ':' but was not a valid URL: %v" , err ) )
}
}
2019-08-30 18:33:25 +00:00
if s . ServiceAccounts != nil && utilfeature . DefaultFeatureGate . Enabled ( features . BoundServiceAccountTokenVolume ) {
if ! utilfeature . DefaultFeatureGate . Enabled ( features . TokenRequest ) || ! utilfeature . DefaultFeatureGate . Enabled ( features . TokenRequestProjection ) {
allErrors = append ( allErrors , errors . New ( "If the BoundServiceAccountTokenVolume feature is enabled," +
" the TokenRequest and TokenRequestProjection features must also be enabled" ) )
}
if len ( s . ServiceAccounts . Issuer ) == 0 {
allErrors = append ( allErrors , errors . New ( "service-account-issuer is a required flag when BoundServiceAccountTokenVolume is enabled" ) )
}
if len ( s . ServiceAccounts . KeyFiles ) == 0 {
allErrors = append ( allErrors , errors . New ( "service-account-key-file is a required flag when BoundServiceAccountTokenVolume is enabled" ) )
}
}
2019-01-12 04:58:27 +00:00
return allErrors
}
func ( s * BuiltInAuthenticationOptions ) AddFlags ( fs * pflag . FlagSet ) {
fs . StringSliceVar ( & s . APIAudiences , "api-audiences" , s . APIAudiences , "" +
"Identifiers of the API. The service account token authenticator will validate that " +
"tokens used against the API are bound to at least one of these audiences. If the " +
"--service-account-issuer flag is configured and this flag is not, this field " +
"defaults to a single element list containing the issuer URL ." )
2019-08-30 18:33:25 +00:00
if s . Anonymous != nil {
fs . BoolVar ( & s . Anonymous . Allow , "anonymous-auth" , s . Anonymous . Allow , "" +
"Enables anonymous requests to the secure port of the API server. " +
"Requests that are not rejected by another authentication method are treated as anonymous requests. " +
"Anonymous requests have a username of system:anonymous, and a group name of system:unauthenticated." )
}
if s . BootstrapToken != nil {
fs . BoolVar ( & s . BootstrapToken . Enable , "enable-bootstrap-token-auth" , s . BootstrapToken . Enable , "" +
"Enable to allow secrets of type 'bootstrap.kubernetes.io/token' in the 'kube-system' " +
"namespace to be used for TLS bootstrapping authentication." )
}
2019-04-07 17:07:55 +00:00
if s . ClientCert != nil {
s . ClientCert . AddFlags ( fs )
}
2019-08-30 18:33:25 +00:00
if s . OIDC != nil {
fs . StringVar ( & s . OIDC . IssuerURL , "oidc-issuer-url" , s . OIDC . IssuerURL , "" +
"The URL of the OpenID issuer, only HTTPS scheme will be accepted. " +
"If set, it will be used to verify the OIDC JSON Web Token (JWT)." )
fs . StringVar ( & s . OIDC . ClientID , "oidc-client-id" , s . OIDC . ClientID ,
"The client ID for the OpenID Connect client, must be set if oidc-issuer-url is set." )
fs . StringVar ( & s . OIDC . CAFile , "oidc-ca-file" , s . OIDC . CAFile , "" +
"If set, the OpenID server's certificate will be verified by one of the authorities " +
"in the oidc-ca-file, otherwise the host's root CA set will be used." )
fs . StringVar ( & s . OIDC . UsernameClaim , "oidc-username-claim" , "sub" , "" +
"The OpenID claim to use as the user name. Note that claims other than the default ('sub') " +
"is not guaranteed to be unique and immutable. This flag is experimental, please see " +
"the authentication documentation for further details." )
fs . StringVar ( & s . OIDC . UsernamePrefix , "oidc-username-prefix" , "" , "" +
"If provided, all usernames will be prefixed with this value. If not provided, " +
"username claims other than 'email' are prefixed by the issuer URL to avoid " +
"clashes. To skip any prefixing, provide the value '-'." )
fs . StringVar ( & s . OIDC . GroupsClaim , "oidc-groups-claim" , "" , "" +
"If provided, the name of a custom OpenID Connect claim for specifying user groups. " +
"The claim value is expected to be a string or array of strings. This flag is experimental, " +
"please see the authentication documentation for further details." )
fs . StringVar ( & s . OIDC . GroupsPrefix , "oidc-groups-prefix" , "" , "" +
"If provided, all groups will be prefixed with this value to prevent conflicts with " +
"other authentication strategies." )
fs . StringSliceVar ( & s . OIDC . SigningAlgs , "oidc-signing-algs" , [ ] string { "RS256" } , "" +
"Comma-separated list of allowed JOSE asymmetric signing algorithms. JWTs with a " +
"'alg' header value not in this list will be rejected. " +
"Values are defined by RFC 7518 https://tools.ietf.org/html/rfc7518#section-3.1." )
fs . Var ( cliflag . NewMapStringStringNoSplit ( & s . OIDC . RequiredClaims ) , "oidc-required-claim" , "" +
"A key=value pair that describes a required claim in the ID Token. " +
"If set, the claim is verified to be present in the ID Token with a matching value. " +
"Repeat this flag to specify multiple claims." )
}
2019-01-12 04:58:27 +00:00
if s . PasswordFile != nil {
fs . StringVar ( & s . PasswordFile . BasicAuthFile , "basic-auth-file" , s . PasswordFile . BasicAuthFile , "" +
"If set, the file that will be used to admit requests to the secure port of the API server " +
"via http basic authentication." )
}
2019-04-07 17:07:55 +00:00
if s . RequestHeader != nil {
s . RequestHeader . AddFlags ( fs )
}
2019-01-12 04:58:27 +00:00
if s . ServiceAccounts != nil {
fs . StringArrayVar ( & s . ServiceAccounts . KeyFiles , "service-account-key-file" , s . ServiceAccounts . KeyFiles , "" +
"File containing PEM-encoded x509 RSA or ECDSA private or public keys, used to verify " +
"ServiceAccount tokens. The specified file can contain multiple keys, and the flag can " +
"be specified multiple times with different files. If unspecified, " +
"--tls-private-key-file is used. Must be specified when " +
"--service-account-signing-key is provided" )
fs . BoolVar ( & s . ServiceAccounts . Lookup , "service-account-lookup" , s . ServiceAccounts . Lookup ,
"If true, validate ServiceAccount tokens exist in etcd as part of authentication." )
fs . StringVar ( & s . ServiceAccounts . Issuer , "service-account-issuer" , s . ServiceAccounts . Issuer , "" +
"Identifier of the service account token issuer. The issuer will assert this identifier " +
"in \"iss\" claim of issued tokens. This value is a string or URI." )
// Deprecated in 1.13
fs . StringSliceVar ( & s . APIAudiences , "service-account-api-audiences" , s . APIAudiences , "" +
"Identifiers of the API. The service account token authenticator will validate that " +
"tokens used against the API are bound to at least one of these audiences." )
fs . MarkDeprecated ( "service-account-api-audiences" , "Use --api-audiences" )
fs . DurationVar ( & s . ServiceAccounts . MaxExpiration , "service-account-max-token-expiration" , s . ServiceAccounts . MaxExpiration , "" +
"The maximum validity duration of a token created by the service account token issuer. If an otherwise valid " +
"TokenRequest with a validity duration larger than this value is requested, a token will be issued with a validity duration of this value." )
}
if s . TokenFile != nil {
fs . StringVar ( & s . TokenFile . TokenFile , "token-auth-file" , s . TokenFile . TokenFile , "" +
"If set, the file that will be used to secure the secure port of the API server " +
"via token authentication." )
}
if s . WebHook != nil {
fs . StringVar ( & s . WebHook . ConfigFile , "authentication-token-webhook-config-file" , s . WebHook . ConfigFile , "" +
"File with webhook configuration for token authentication in kubeconfig format. " +
"The API server will query the remote service to determine authentication for bearer tokens." )
fs . DurationVar ( & s . WebHook . CacheTTL , "authentication-token-webhook-cache-ttl" , s . WebHook . CacheTTL ,
"The duration to cache responses from the webhook token authenticator." )
}
}
func ( s * BuiltInAuthenticationOptions ) ToAuthenticationConfig ( ) kubeauthenticator . Config {
ret := kubeauthenticator . Config {
TokenSuccessCacheTTL : s . TokenSuccessCacheTTL ,
TokenFailureCacheTTL : s . TokenFailureCacheTTL ,
}
2019-08-30 18:33:25 +00:00
if s . Anonymous != nil {
ret . Anonymous = s . Anonymous . Allow
}
if s . BootstrapToken != nil {
ret . BootstrapToken = s . BootstrapToken . Enable
}
2019-04-07 17:07:55 +00:00
if s . ClientCert != nil {
ret . ClientCAFile = s . ClientCert . ClientCA
}
2019-08-30 18:33:25 +00:00
if s . OIDC != nil {
ret . OIDCCAFile = s . OIDC . CAFile
ret . OIDCClientID = s . OIDC . ClientID
ret . OIDCGroupsClaim = s . OIDC . GroupsClaim
ret . OIDCGroupsPrefix = s . OIDC . GroupsPrefix
ret . OIDCIssuerURL = s . OIDC . IssuerURL
ret . OIDCUsernameClaim = s . OIDC . UsernameClaim
ret . OIDCUsernamePrefix = s . OIDC . UsernamePrefix
ret . OIDCSigningAlgs = s . OIDC . SigningAlgs
ret . OIDCRequiredClaims = s . OIDC . RequiredClaims
}
2019-01-12 04:58:27 +00:00
if s . PasswordFile != nil {
ret . BasicAuthFile = s . PasswordFile . BasicAuthFile
}
2019-04-07 17:07:55 +00:00
if s . RequestHeader != nil {
ret . RequestHeaderConfig = s . RequestHeader . ToAuthenticationRequestHeaderConfig ( )
}
2019-01-12 04:58:27 +00:00
2019-04-07 17:07:55 +00:00
ret . APIAudiences = s . APIAudiences
2019-01-12 04:58:27 +00:00
if s . ServiceAccounts != nil {
if s . ServiceAccounts . Issuer != "" && len ( s . APIAudiences ) == 0 {
ret . APIAudiences = authenticator . Audiences { s . ServiceAccounts . Issuer }
}
ret . ServiceAccountKeyFiles = s . ServiceAccounts . KeyFiles
ret . ServiceAccountIssuer = s . ServiceAccounts . Issuer
ret . ServiceAccountLookup = s . ServiceAccounts . Lookup
}
if s . TokenFile != nil {
ret . TokenAuthFile = s . TokenFile . TokenFile
}
if s . WebHook != nil {
ret . WebhookTokenAuthnConfigFile = s . WebHook . ConfigFile
ret . WebhookTokenAuthnCacheTTL = s . WebHook . CacheTTL
if len ( s . WebHook . ConfigFile ) > 0 && s . WebHook . CacheTTL > 0 {
if s . TokenSuccessCacheTTL > 0 && s . WebHook . CacheTTL < s . TokenSuccessCacheTTL {
klog . Warningf ( "the webhook cache ttl of %s is shorter than the overall cache ttl of %s for successful token authentication attempts." , s . WebHook . CacheTTL , s . TokenSuccessCacheTTL )
}
if s . TokenFailureCacheTTL > 0 && s . WebHook . CacheTTL < s . TokenFailureCacheTTL {
klog . Warningf ( "the webhook cache ttl of %s is shorter than the overall cache ttl of %s for failed token authentication attempts." , s . WebHook . CacheTTL , s . TokenFailureCacheTTL )
}
}
}
return ret
}
func ( o * BuiltInAuthenticationOptions ) ApplyTo ( c * genericapiserver . Config ) error {
if o == nil {
return nil
}
2019-04-07 17:07:55 +00:00
var err error
if o . ClientCert != nil {
if err = c . Authentication . ApplyClientCert ( o . ClientCert . ClientCA , c . SecureServing ) ; err != nil {
return fmt . Errorf ( "unable to load client CA file: %v" , err )
}
}
if o . RequestHeader != nil {
if err = c . Authentication . ApplyClientCert ( o . RequestHeader . ClientCAFile , c . SecureServing ) ; err != nil {
return fmt . Errorf ( "unable to load client CA file: %v" , err )
}
}
2019-01-12 04:58:27 +00:00
c . Authentication . SupportsBasicAuth = o . PasswordFile != nil && len ( o . PasswordFile . BasicAuthFile ) > 0
c . Authentication . APIAudiences = o . APIAudiences
if o . ServiceAccounts != nil && o . ServiceAccounts . Issuer != "" && len ( o . APIAudiences ) == 0 {
c . Authentication . APIAudiences = authenticator . Audiences { o . ServiceAccounts . Issuer }
}
return nil
}
// ApplyAuthorization will conditionally modify the authentication options based on the authorization options
func ( o * BuiltInAuthenticationOptions ) ApplyAuthorization ( authorization * BuiltInAuthorizationOptions ) {
2019-08-30 18:33:25 +00:00
if o == nil || authorization == nil || o . Anonymous == nil {
2019-01-12 04:58:27 +00:00
return
}
2019-08-30 18:33:25 +00:00
// authorization ModeAlwaysAllow cannot be combined with AnonymousAuth.
// in such a case the AnonymousAuth is stomped to false and you get a message
if o . Anonymous . Allow && sets . NewString ( authorization . Modes ... ) . Has ( authzmodes . ModeAlwaysAllow ) {
klog . Warningf ( "AnonymousAuth is not allowed with the AlwaysAllow authorizer. Resetting AnonymousAuth to false. You should use a different authorizer" )
o . Anonymous . Allow = false
}
2019-01-12 04:58:27 +00:00
}