authentication.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419
  1. /*
  2. Copyright 2016 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package options
  14. import (
  15. "errors"
  16. "fmt"
  17. "net/url"
  18. "strings"
  19. "time"
  20. "github.com/spf13/pflag"
  21. "k8s.io/klog"
  22. "k8s.io/apimachinery/pkg/util/sets"
  23. "k8s.io/apiserver/pkg/authentication/authenticator"
  24. genericapiserver "k8s.io/apiserver/pkg/server"
  25. genericoptions "k8s.io/apiserver/pkg/server/options"
  26. utilfeature "k8s.io/apiserver/pkg/util/feature"
  27. cliflag "k8s.io/component-base/cli/flag"
  28. "k8s.io/kubernetes/pkg/features"
  29. kubeauthenticator "k8s.io/kubernetes/pkg/kubeapiserver/authenticator"
  30. authzmodes "k8s.io/kubernetes/pkg/kubeapiserver/authorizer/modes"
  31. )
  32. type BuiltInAuthenticationOptions struct {
  33. APIAudiences []string
  34. Anonymous *AnonymousAuthenticationOptions
  35. BootstrapToken *BootstrapTokenAuthenticationOptions
  36. ClientCert *genericoptions.ClientCertAuthenticationOptions
  37. OIDC *OIDCAuthenticationOptions
  38. PasswordFile *PasswordFileAuthenticationOptions
  39. RequestHeader *genericoptions.RequestHeaderAuthenticationOptions
  40. ServiceAccounts *ServiceAccountAuthenticationOptions
  41. TokenFile *TokenFileAuthenticationOptions
  42. WebHook *WebHookAuthenticationOptions
  43. TokenSuccessCacheTTL time.Duration
  44. TokenFailureCacheTTL time.Duration
  45. }
  46. type AnonymousAuthenticationOptions struct {
  47. Allow bool
  48. }
  49. type BootstrapTokenAuthenticationOptions struct {
  50. Enable bool
  51. }
  52. type OIDCAuthenticationOptions struct {
  53. CAFile string
  54. ClientID string
  55. IssuerURL string
  56. UsernameClaim string
  57. UsernamePrefix string
  58. GroupsClaim string
  59. GroupsPrefix string
  60. SigningAlgs []string
  61. RequiredClaims map[string]string
  62. }
  63. type PasswordFileAuthenticationOptions struct {
  64. BasicAuthFile string
  65. }
  66. type ServiceAccountAuthenticationOptions struct {
  67. KeyFiles []string
  68. Lookup bool
  69. Issuer string
  70. MaxExpiration time.Duration
  71. }
  72. type TokenFileAuthenticationOptions struct {
  73. TokenFile string
  74. }
  75. type WebHookAuthenticationOptions struct {
  76. ConfigFile string
  77. CacheTTL time.Duration
  78. }
  79. func NewBuiltInAuthenticationOptions() *BuiltInAuthenticationOptions {
  80. return &BuiltInAuthenticationOptions{
  81. TokenSuccessCacheTTL: 10 * time.Second,
  82. TokenFailureCacheTTL: 0 * time.Second,
  83. }
  84. }
  85. func (s *BuiltInAuthenticationOptions) WithAll() *BuiltInAuthenticationOptions {
  86. return s.
  87. WithAnonymous().
  88. WithBootstrapToken().
  89. WithClientCert().
  90. WithOIDC().
  91. WithPasswordFile().
  92. WithRequestHeader().
  93. WithServiceAccounts().
  94. WithTokenFile().
  95. WithWebHook()
  96. }
  97. func (s *BuiltInAuthenticationOptions) WithAnonymous() *BuiltInAuthenticationOptions {
  98. s.Anonymous = &AnonymousAuthenticationOptions{Allow: true}
  99. return s
  100. }
  101. func (s *BuiltInAuthenticationOptions) WithBootstrapToken() *BuiltInAuthenticationOptions {
  102. s.BootstrapToken = &BootstrapTokenAuthenticationOptions{}
  103. return s
  104. }
  105. func (s *BuiltInAuthenticationOptions) WithClientCert() *BuiltInAuthenticationOptions {
  106. s.ClientCert = &genericoptions.ClientCertAuthenticationOptions{}
  107. return s
  108. }
  109. func (s *BuiltInAuthenticationOptions) WithOIDC() *BuiltInAuthenticationOptions {
  110. s.OIDC = &OIDCAuthenticationOptions{}
  111. return s
  112. }
  113. func (s *BuiltInAuthenticationOptions) WithPasswordFile() *BuiltInAuthenticationOptions {
  114. s.PasswordFile = &PasswordFileAuthenticationOptions{}
  115. return s
  116. }
  117. func (s *BuiltInAuthenticationOptions) WithRequestHeader() *BuiltInAuthenticationOptions {
  118. s.RequestHeader = &genericoptions.RequestHeaderAuthenticationOptions{}
  119. return s
  120. }
  121. func (s *BuiltInAuthenticationOptions) WithServiceAccounts() *BuiltInAuthenticationOptions {
  122. s.ServiceAccounts = &ServiceAccountAuthenticationOptions{Lookup: true}
  123. return s
  124. }
  125. func (s *BuiltInAuthenticationOptions) WithTokenFile() *BuiltInAuthenticationOptions {
  126. s.TokenFile = &TokenFileAuthenticationOptions{}
  127. return s
  128. }
  129. func (s *BuiltInAuthenticationOptions) WithWebHook() *BuiltInAuthenticationOptions {
  130. s.WebHook = &WebHookAuthenticationOptions{
  131. CacheTTL: 2 * time.Minute,
  132. }
  133. return s
  134. }
  135. // Validate checks invalid config combination
  136. func (s *BuiltInAuthenticationOptions) Validate() []error {
  137. allErrors := []error{}
  138. if s.OIDC != nil && (len(s.OIDC.IssuerURL) > 0) != (len(s.OIDC.ClientID) > 0) {
  139. allErrors = append(allErrors, fmt.Errorf("oidc-issuer-url and oidc-client-id should be specified together"))
  140. }
  141. if s.ServiceAccounts != nil && len(s.ServiceAccounts.Issuer) > 0 && strings.Contains(s.ServiceAccounts.Issuer, ":") {
  142. if _, err := url.Parse(s.ServiceAccounts.Issuer); err != nil {
  143. allErrors = append(allErrors, fmt.Errorf("service-account-issuer contained a ':' but was not a valid URL: %v", err))
  144. }
  145. }
  146. if s.ServiceAccounts != nil && utilfeature.DefaultFeatureGate.Enabled(features.BoundServiceAccountTokenVolume) {
  147. if !utilfeature.DefaultFeatureGate.Enabled(features.TokenRequest) || !utilfeature.DefaultFeatureGate.Enabled(features.TokenRequestProjection) {
  148. allErrors = append(allErrors, errors.New("If the BoundServiceAccountTokenVolume feature is enabled,"+
  149. " the TokenRequest and TokenRequestProjection features must also be enabled"))
  150. }
  151. if len(s.ServiceAccounts.Issuer) == 0 {
  152. allErrors = append(allErrors, errors.New("service-account-issuer is a required flag when BoundServiceAccountTokenVolume is enabled"))
  153. }
  154. if len(s.ServiceAccounts.KeyFiles) == 0 {
  155. allErrors = append(allErrors, errors.New("service-account-key-file is a required flag when BoundServiceAccountTokenVolume is enabled"))
  156. }
  157. }
  158. return allErrors
  159. }
  160. func (s *BuiltInAuthenticationOptions) AddFlags(fs *pflag.FlagSet) {
  161. fs.StringSliceVar(&s.APIAudiences, "api-audiences", s.APIAudiences, ""+
  162. "Identifiers of the API. The service account token authenticator will validate that "+
  163. "tokens used against the API are bound to at least one of these audiences. If the "+
  164. "--service-account-issuer flag is configured and this flag is not, this field "+
  165. "defaults to a single element list containing the issuer URL .")
  166. if s.Anonymous != nil {
  167. fs.BoolVar(&s.Anonymous.Allow, "anonymous-auth", s.Anonymous.Allow, ""+
  168. "Enables anonymous requests to the secure port of the API server. "+
  169. "Requests that are not rejected by another authentication method are treated as anonymous requests. "+
  170. "Anonymous requests have a username of system:anonymous, and a group name of system:unauthenticated.")
  171. }
  172. if s.BootstrapToken != nil {
  173. fs.BoolVar(&s.BootstrapToken.Enable, "enable-bootstrap-token-auth", s.BootstrapToken.Enable, ""+
  174. "Enable to allow secrets of type 'bootstrap.kubernetes.io/token' in the 'kube-system' "+
  175. "namespace to be used for TLS bootstrapping authentication.")
  176. }
  177. if s.ClientCert != nil {
  178. s.ClientCert.AddFlags(fs)
  179. }
  180. if s.OIDC != nil {
  181. fs.StringVar(&s.OIDC.IssuerURL, "oidc-issuer-url", s.OIDC.IssuerURL, ""+
  182. "The URL of the OpenID issuer, only HTTPS scheme will be accepted. "+
  183. "If set, it will be used to verify the OIDC JSON Web Token (JWT).")
  184. fs.StringVar(&s.OIDC.ClientID, "oidc-client-id", s.OIDC.ClientID,
  185. "The client ID for the OpenID Connect client, must be set if oidc-issuer-url is set.")
  186. fs.StringVar(&s.OIDC.CAFile, "oidc-ca-file", s.OIDC.CAFile, ""+
  187. "If set, the OpenID server's certificate will be verified by one of the authorities "+
  188. "in the oidc-ca-file, otherwise the host's root CA set will be used.")
  189. fs.StringVar(&s.OIDC.UsernameClaim, "oidc-username-claim", "sub", ""+
  190. "The OpenID claim to use as the user name. Note that claims other than the default ('sub') "+
  191. "is not guaranteed to be unique and immutable. This flag is experimental, please see "+
  192. "the authentication documentation for further details.")
  193. fs.StringVar(&s.OIDC.UsernamePrefix, "oidc-username-prefix", "", ""+
  194. "If provided, all usernames will be prefixed with this value. If not provided, "+
  195. "username claims other than 'email' are prefixed by the issuer URL to avoid "+
  196. "clashes. To skip any prefixing, provide the value '-'.")
  197. fs.StringVar(&s.OIDC.GroupsClaim, "oidc-groups-claim", "", ""+
  198. "If provided, the name of a custom OpenID Connect claim for specifying user groups. "+
  199. "The claim value is expected to be a string or array of strings. This flag is experimental, "+
  200. "please see the authentication documentation for further details.")
  201. fs.StringVar(&s.OIDC.GroupsPrefix, "oidc-groups-prefix", "", ""+
  202. "If provided, all groups will be prefixed with this value to prevent conflicts with "+
  203. "other authentication strategies.")
  204. fs.StringSliceVar(&s.OIDC.SigningAlgs, "oidc-signing-algs", []string{"RS256"}, ""+
  205. "Comma-separated list of allowed JOSE asymmetric signing algorithms. JWTs with a "+
  206. "'alg' header value not in this list will be rejected. "+
  207. "Values are defined by RFC 7518 https://tools.ietf.org/html/rfc7518#section-3.1.")
  208. fs.Var(cliflag.NewMapStringStringNoSplit(&s.OIDC.RequiredClaims), "oidc-required-claim", ""+
  209. "A key=value pair that describes a required claim in the ID Token. "+
  210. "If set, the claim is verified to be present in the ID Token with a matching value. "+
  211. "Repeat this flag to specify multiple claims.")
  212. }
  213. if s.PasswordFile != nil {
  214. fs.StringVar(&s.PasswordFile.BasicAuthFile, "basic-auth-file", s.PasswordFile.BasicAuthFile, ""+
  215. "If set, the file that will be used to admit requests to the secure port of the API server "+
  216. "via http basic authentication.")
  217. }
  218. if s.RequestHeader != nil {
  219. s.RequestHeader.AddFlags(fs)
  220. }
  221. if s.ServiceAccounts != nil {
  222. fs.StringArrayVar(&s.ServiceAccounts.KeyFiles, "service-account-key-file", s.ServiceAccounts.KeyFiles, ""+
  223. "File containing PEM-encoded x509 RSA or ECDSA private or public keys, used to verify "+
  224. "ServiceAccount tokens. The specified file can contain multiple keys, and the flag can "+
  225. "be specified multiple times with different files. If unspecified, "+
  226. "--tls-private-key-file is used. Must be specified when "+
  227. "--service-account-signing-key is provided")
  228. fs.BoolVar(&s.ServiceAccounts.Lookup, "service-account-lookup", s.ServiceAccounts.Lookup,
  229. "If true, validate ServiceAccount tokens exist in etcd as part of authentication.")
  230. fs.StringVar(&s.ServiceAccounts.Issuer, "service-account-issuer", s.ServiceAccounts.Issuer, ""+
  231. "Identifier of the service account token issuer. The issuer will assert this identifier "+
  232. "in \"iss\" claim of issued tokens. This value is a string or URI.")
  233. // Deprecated in 1.13
  234. fs.StringSliceVar(&s.APIAudiences, "service-account-api-audiences", s.APIAudiences, ""+
  235. "Identifiers of the API. The service account token authenticator will validate that "+
  236. "tokens used against the API are bound to at least one of these audiences.")
  237. fs.MarkDeprecated("service-account-api-audiences", "Use --api-audiences")
  238. fs.DurationVar(&s.ServiceAccounts.MaxExpiration, "service-account-max-token-expiration", s.ServiceAccounts.MaxExpiration, ""+
  239. "The maximum validity duration of a token created by the service account token issuer. If an otherwise valid "+
  240. "TokenRequest with a validity duration larger than this value is requested, a token will be issued with a validity duration of this value.")
  241. }
  242. if s.TokenFile != nil {
  243. fs.StringVar(&s.TokenFile.TokenFile, "token-auth-file", s.TokenFile.TokenFile, ""+
  244. "If set, the file that will be used to secure the secure port of the API server "+
  245. "via token authentication.")
  246. }
  247. if s.WebHook != nil {
  248. fs.StringVar(&s.WebHook.ConfigFile, "authentication-token-webhook-config-file", s.WebHook.ConfigFile, ""+
  249. "File with webhook configuration for token authentication in kubeconfig format. "+
  250. "The API server will query the remote service to determine authentication for bearer tokens.")
  251. fs.DurationVar(&s.WebHook.CacheTTL, "authentication-token-webhook-cache-ttl", s.WebHook.CacheTTL,
  252. "The duration to cache responses from the webhook token authenticator.")
  253. }
  254. }
  255. func (s *BuiltInAuthenticationOptions) ToAuthenticationConfig() kubeauthenticator.Config {
  256. ret := kubeauthenticator.Config{
  257. TokenSuccessCacheTTL: s.TokenSuccessCacheTTL,
  258. TokenFailureCacheTTL: s.TokenFailureCacheTTL,
  259. }
  260. if s.Anonymous != nil {
  261. ret.Anonymous = s.Anonymous.Allow
  262. }
  263. if s.BootstrapToken != nil {
  264. ret.BootstrapToken = s.BootstrapToken.Enable
  265. }
  266. if s.ClientCert != nil {
  267. ret.ClientCAFile = s.ClientCert.ClientCA
  268. }
  269. if s.OIDC != nil {
  270. ret.OIDCCAFile = s.OIDC.CAFile
  271. ret.OIDCClientID = s.OIDC.ClientID
  272. ret.OIDCGroupsClaim = s.OIDC.GroupsClaim
  273. ret.OIDCGroupsPrefix = s.OIDC.GroupsPrefix
  274. ret.OIDCIssuerURL = s.OIDC.IssuerURL
  275. ret.OIDCUsernameClaim = s.OIDC.UsernameClaim
  276. ret.OIDCUsernamePrefix = s.OIDC.UsernamePrefix
  277. ret.OIDCSigningAlgs = s.OIDC.SigningAlgs
  278. ret.OIDCRequiredClaims = s.OIDC.RequiredClaims
  279. }
  280. if s.PasswordFile != nil {
  281. ret.BasicAuthFile = s.PasswordFile.BasicAuthFile
  282. }
  283. if s.RequestHeader != nil {
  284. ret.RequestHeaderConfig = s.RequestHeader.ToAuthenticationRequestHeaderConfig()
  285. }
  286. ret.APIAudiences = s.APIAudiences
  287. if s.ServiceAccounts != nil {
  288. if s.ServiceAccounts.Issuer != "" && len(s.APIAudiences) == 0 {
  289. ret.APIAudiences = authenticator.Audiences{s.ServiceAccounts.Issuer}
  290. }
  291. ret.ServiceAccountKeyFiles = s.ServiceAccounts.KeyFiles
  292. ret.ServiceAccountIssuer = s.ServiceAccounts.Issuer
  293. ret.ServiceAccountLookup = s.ServiceAccounts.Lookup
  294. }
  295. if s.TokenFile != nil {
  296. ret.TokenAuthFile = s.TokenFile.TokenFile
  297. }
  298. if s.WebHook != nil {
  299. ret.WebhookTokenAuthnConfigFile = s.WebHook.ConfigFile
  300. ret.WebhookTokenAuthnCacheTTL = s.WebHook.CacheTTL
  301. if len(s.WebHook.ConfigFile) > 0 && s.WebHook.CacheTTL > 0 {
  302. if s.TokenSuccessCacheTTL > 0 && s.WebHook.CacheTTL < s.TokenSuccessCacheTTL {
  303. 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)
  304. }
  305. if s.TokenFailureCacheTTL > 0 && s.WebHook.CacheTTL < s.TokenFailureCacheTTL {
  306. 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)
  307. }
  308. }
  309. }
  310. return ret
  311. }
  312. func (o *BuiltInAuthenticationOptions) ApplyTo(c *genericapiserver.Config) error {
  313. if o == nil {
  314. return nil
  315. }
  316. var err error
  317. if o.ClientCert != nil {
  318. if err = c.Authentication.ApplyClientCert(o.ClientCert.ClientCA, c.SecureServing); err != nil {
  319. return fmt.Errorf("unable to load client CA file: %v", err)
  320. }
  321. }
  322. if o.RequestHeader != nil {
  323. if err = c.Authentication.ApplyClientCert(o.RequestHeader.ClientCAFile, c.SecureServing); err != nil {
  324. return fmt.Errorf("unable to load client CA file: %v", err)
  325. }
  326. }
  327. c.Authentication.SupportsBasicAuth = o.PasswordFile != nil && len(o.PasswordFile.BasicAuthFile) > 0
  328. c.Authentication.APIAudiences = o.APIAudiences
  329. if o.ServiceAccounts != nil && o.ServiceAccounts.Issuer != "" && len(o.APIAudiences) == 0 {
  330. c.Authentication.APIAudiences = authenticator.Audiences{o.ServiceAccounts.Issuer}
  331. }
  332. return nil
  333. }
  334. // ApplyAuthorization will conditionally modify the authentication options based on the authorization options
  335. func (o *BuiltInAuthenticationOptions) ApplyAuthorization(authorization *BuiltInAuthorizationOptions) {
  336. if o == nil || authorization == nil || o.Anonymous == nil {
  337. return
  338. }
  339. // authorization ModeAlwaysAllow cannot be combined with AnonymousAuth.
  340. // in such a case the AnonymousAuth is stomped to false and you get a message
  341. if o.Anonymous.Allow && sets.NewString(authorization.Modes...).Has(authzmodes.ModeAlwaysAllow) {
  342. klog.Warningf("AnonymousAuth is not allowed with the AlwaysAllow authorizer. Resetting AnonymousAuth to false. You should use a different authorizer")
  343. o.Anonymous.Allow = false
  344. }
  345. }