config.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331
  1. /*
  2. Copyright 2014 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 authenticator
  14. import (
  15. "time"
  16. "github.com/go-openapi/spec"
  17. "k8s.io/apiserver/pkg/authentication/authenticator"
  18. "k8s.io/apiserver/pkg/authentication/authenticatorfactory"
  19. "k8s.io/apiserver/pkg/authentication/group"
  20. "k8s.io/apiserver/pkg/authentication/request/anonymous"
  21. "k8s.io/apiserver/pkg/authentication/request/bearertoken"
  22. "k8s.io/apiserver/pkg/authentication/request/headerrequest"
  23. "k8s.io/apiserver/pkg/authentication/request/union"
  24. "k8s.io/apiserver/pkg/authentication/request/websocket"
  25. "k8s.io/apiserver/pkg/authentication/request/x509"
  26. tokencache "k8s.io/apiserver/pkg/authentication/token/cache"
  27. "k8s.io/apiserver/pkg/authentication/token/tokenfile"
  28. tokenunion "k8s.io/apiserver/pkg/authentication/token/union"
  29. utilfeature "k8s.io/apiserver/pkg/util/feature"
  30. "k8s.io/apiserver/plugin/pkg/authenticator/password/passwordfile"
  31. "k8s.io/apiserver/plugin/pkg/authenticator/request/basicauth"
  32. "k8s.io/apiserver/plugin/pkg/authenticator/token/oidc"
  33. "k8s.io/apiserver/plugin/pkg/authenticator/token/webhook"
  34. // Initialize all known client auth plugins.
  35. _ "k8s.io/client-go/plugin/pkg/client/auth"
  36. certutil "k8s.io/client-go/util/cert"
  37. "k8s.io/client-go/util/keyutil"
  38. "k8s.io/kubernetes/pkg/features"
  39. "k8s.io/kubernetes/pkg/serviceaccount"
  40. )
  41. // Config contains the data on how to authenticate a request to the Kube API Server
  42. type Config struct {
  43. Anonymous bool
  44. BasicAuthFile string
  45. BootstrapToken bool
  46. ClientCAFile string
  47. TokenAuthFile string
  48. OIDCIssuerURL string
  49. OIDCClientID string
  50. OIDCCAFile string
  51. OIDCUsernameClaim string
  52. OIDCUsernamePrefix string
  53. OIDCGroupsClaim string
  54. OIDCGroupsPrefix string
  55. OIDCSigningAlgs []string
  56. OIDCRequiredClaims map[string]string
  57. ServiceAccountKeyFiles []string
  58. ServiceAccountLookup bool
  59. ServiceAccountIssuer string
  60. APIAudiences authenticator.Audiences
  61. WebhookTokenAuthnConfigFile string
  62. WebhookTokenAuthnCacheTTL time.Duration
  63. TokenSuccessCacheTTL time.Duration
  64. TokenFailureCacheTTL time.Duration
  65. RequestHeaderConfig *authenticatorfactory.RequestHeaderConfig
  66. // TODO, this is the only non-serializable part of the entire config. Factor it out into a clientconfig
  67. ServiceAccountTokenGetter serviceaccount.ServiceAccountTokenGetter
  68. BootstrapTokenAuthenticator authenticator.Token
  69. }
  70. // New returns an authenticator.Request or an error that supports the standard
  71. // Kubernetes authentication mechanisms.
  72. func (config Config) New() (authenticator.Request, *spec.SecurityDefinitions, error) {
  73. var authenticators []authenticator.Request
  74. var tokenAuthenticators []authenticator.Token
  75. securityDefinitions := spec.SecurityDefinitions{}
  76. // front-proxy, BasicAuth methods, local first, then remote
  77. // Add the front proxy authenticator if requested
  78. if config.RequestHeaderConfig != nil {
  79. requestHeaderAuthenticator, err := headerrequest.NewSecure(
  80. config.RequestHeaderConfig.ClientCA,
  81. config.RequestHeaderConfig.AllowedClientNames,
  82. config.RequestHeaderConfig.UsernameHeaders,
  83. config.RequestHeaderConfig.GroupHeaders,
  84. config.RequestHeaderConfig.ExtraHeaderPrefixes,
  85. )
  86. if err != nil {
  87. return nil, nil, err
  88. }
  89. authenticators = append(authenticators, authenticator.WrapAudienceAgnosticRequest(config.APIAudiences, requestHeaderAuthenticator))
  90. }
  91. // basic auth
  92. if len(config.BasicAuthFile) > 0 {
  93. basicAuth, err := newAuthenticatorFromBasicAuthFile(config.BasicAuthFile)
  94. if err != nil {
  95. return nil, nil, err
  96. }
  97. authenticators = append(authenticators, authenticator.WrapAudienceAgnosticRequest(config.APIAudiences, basicAuth))
  98. securityDefinitions["HTTPBasic"] = &spec.SecurityScheme{
  99. SecuritySchemeProps: spec.SecuritySchemeProps{
  100. Type: "basic",
  101. Description: "HTTP Basic authentication",
  102. },
  103. }
  104. }
  105. // X509 methods
  106. if len(config.ClientCAFile) > 0 {
  107. certAuth, err := newAuthenticatorFromClientCAFile(config.ClientCAFile)
  108. if err != nil {
  109. return nil, nil, err
  110. }
  111. authenticators = append(authenticators, certAuth)
  112. }
  113. // Bearer token methods, local first, then remote
  114. if len(config.TokenAuthFile) > 0 {
  115. tokenAuth, err := newAuthenticatorFromTokenFile(config.TokenAuthFile)
  116. if err != nil {
  117. return nil, nil, err
  118. }
  119. tokenAuthenticators = append(tokenAuthenticators, authenticator.WrapAudienceAgnosticToken(config.APIAudiences, tokenAuth))
  120. }
  121. if len(config.ServiceAccountKeyFiles) > 0 {
  122. serviceAccountAuth, err := newLegacyServiceAccountAuthenticator(config.ServiceAccountKeyFiles, config.ServiceAccountLookup, config.APIAudiences, config.ServiceAccountTokenGetter)
  123. if err != nil {
  124. return nil, nil, err
  125. }
  126. tokenAuthenticators = append(tokenAuthenticators, serviceAccountAuth)
  127. }
  128. if utilfeature.DefaultFeatureGate.Enabled(features.TokenRequest) && config.ServiceAccountIssuer != "" {
  129. serviceAccountAuth, err := newServiceAccountAuthenticator(config.ServiceAccountIssuer, config.ServiceAccountKeyFiles, config.APIAudiences, config.ServiceAccountTokenGetter)
  130. if err != nil {
  131. return nil, nil, err
  132. }
  133. tokenAuthenticators = append(tokenAuthenticators, serviceAccountAuth)
  134. }
  135. if config.BootstrapToken {
  136. if config.BootstrapTokenAuthenticator != nil {
  137. // TODO: This can sometimes be nil because of
  138. tokenAuthenticators = append(tokenAuthenticators, authenticator.WrapAudienceAgnosticToken(config.APIAudiences, config.BootstrapTokenAuthenticator))
  139. }
  140. }
  141. // NOTE(ericchiang): Keep the OpenID Connect after Service Accounts.
  142. //
  143. // Because both plugins verify JWTs whichever comes first in the union experiences
  144. // cache misses for all requests using the other. While the service account plugin
  145. // simply returns an error, the OpenID Connect plugin may query the provider to
  146. // update the keys, causing performance hits.
  147. if len(config.OIDCIssuerURL) > 0 && len(config.OIDCClientID) > 0 {
  148. oidcAuth, err := newAuthenticatorFromOIDCIssuerURL(oidc.Options{
  149. IssuerURL: config.OIDCIssuerURL,
  150. ClientID: config.OIDCClientID,
  151. APIAudiences: config.APIAudiences,
  152. CAFile: config.OIDCCAFile,
  153. UsernameClaim: config.OIDCUsernameClaim,
  154. UsernamePrefix: config.OIDCUsernamePrefix,
  155. GroupsClaim: config.OIDCGroupsClaim,
  156. GroupsPrefix: config.OIDCGroupsPrefix,
  157. SupportedSigningAlgs: config.OIDCSigningAlgs,
  158. RequiredClaims: config.OIDCRequiredClaims,
  159. })
  160. if err != nil {
  161. return nil, nil, err
  162. }
  163. tokenAuthenticators = append(tokenAuthenticators, oidcAuth)
  164. }
  165. if len(config.WebhookTokenAuthnConfigFile) > 0 {
  166. webhookTokenAuth, err := newWebhookTokenAuthenticator(config.WebhookTokenAuthnConfigFile, config.WebhookTokenAuthnCacheTTL, config.APIAudiences)
  167. if err != nil {
  168. return nil, nil, err
  169. }
  170. tokenAuthenticators = append(tokenAuthenticators, webhookTokenAuth)
  171. }
  172. if len(tokenAuthenticators) > 0 {
  173. // Union the token authenticators
  174. tokenAuth := tokenunion.New(tokenAuthenticators...)
  175. // Optionally cache authentication results
  176. if config.TokenSuccessCacheTTL > 0 || config.TokenFailureCacheTTL > 0 {
  177. tokenAuth = tokencache.New(tokenAuth, true, config.TokenSuccessCacheTTL, config.TokenFailureCacheTTL)
  178. }
  179. authenticators = append(authenticators, bearertoken.New(tokenAuth), websocket.NewProtocolAuthenticator(tokenAuth))
  180. securityDefinitions["BearerToken"] = &spec.SecurityScheme{
  181. SecuritySchemeProps: spec.SecuritySchemeProps{
  182. Type: "apiKey",
  183. Name: "authorization",
  184. In: "header",
  185. Description: "Bearer Token authentication",
  186. },
  187. }
  188. }
  189. if len(authenticators) == 0 {
  190. if config.Anonymous {
  191. return anonymous.NewAuthenticator(), &securityDefinitions, nil
  192. }
  193. return nil, &securityDefinitions, nil
  194. }
  195. authenticator := union.New(authenticators...)
  196. authenticator = group.NewAuthenticatedGroupAdder(authenticator)
  197. if config.Anonymous {
  198. // If the authenticator chain returns an error, return an error (don't consider a bad bearer token
  199. // or invalid username/password combination anonymous).
  200. authenticator = union.NewFailOnError(authenticator, anonymous.NewAuthenticator())
  201. }
  202. return authenticator, &securityDefinitions, nil
  203. }
  204. // IsValidServiceAccountKeyFile returns true if a valid public RSA key can be read from the given file
  205. func IsValidServiceAccountKeyFile(file string) bool {
  206. _, err := keyutil.PublicKeysFromFile(file)
  207. return err == nil
  208. }
  209. // newAuthenticatorFromBasicAuthFile returns an authenticator.Request or an error
  210. func newAuthenticatorFromBasicAuthFile(basicAuthFile string) (authenticator.Request, error) {
  211. basicAuthenticator, err := passwordfile.NewCSV(basicAuthFile)
  212. if err != nil {
  213. return nil, err
  214. }
  215. return basicauth.New(basicAuthenticator), nil
  216. }
  217. // newAuthenticatorFromTokenFile returns an authenticator.Token or an error
  218. func newAuthenticatorFromTokenFile(tokenAuthFile string) (authenticator.Token, error) {
  219. tokenAuthenticator, err := tokenfile.NewCSV(tokenAuthFile)
  220. if err != nil {
  221. return nil, err
  222. }
  223. return tokenAuthenticator, nil
  224. }
  225. // newAuthenticatorFromOIDCIssuerURL returns an authenticator.Token or an error.
  226. func newAuthenticatorFromOIDCIssuerURL(opts oidc.Options) (authenticator.Token, error) {
  227. const noUsernamePrefix = "-"
  228. if opts.UsernamePrefix == "" && opts.UsernameClaim != "email" {
  229. // Old behavior. If a usernamePrefix isn't provided, prefix all claims other than "email"
  230. // with the issuerURL.
  231. //
  232. // See https://github.com/kubernetes/kubernetes/issues/31380
  233. opts.UsernamePrefix = opts.IssuerURL + "#"
  234. }
  235. if opts.UsernamePrefix == noUsernamePrefix {
  236. // Special value indicating usernames shouldn't be prefixed.
  237. opts.UsernamePrefix = ""
  238. }
  239. tokenAuthenticator, err := oidc.New(opts)
  240. if err != nil {
  241. return nil, err
  242. }
  243. return tokenAuthenticator, nil
  244. }
  245. // newLegacyServiceAccountAuthenticator returns an authenticator.Token or an error
  246. func newLegacyServiceAccountAuthenticator(keyfiles []string, lookup bool, apiAudiences authenticator.Audiences, serviceAccountGetter serviceaccount.ServiceAccountTokenGetter) (authenticator.Token, error) {
  247. allPublicKeys := []interface{}{}
  248. for _, keyfile := range keyfiles {
  249. publicKeys, err := keyutil.PublicKeysFromFile(keyfile)
  250. if err != nil {
  251. return nil, err
  252. }
  253. allPublicKeys = append(allPublicKeys, publicKeys...)
  254. }
  255. tokenAuthenticator := serviceaccount.JWTTokenAuthenticator(serviceaccount.LegacyIssuer, allPublicKeys, apiAudiences, serviceaccount.NewLegacyValidator(lookup, serviceAccountGetter))
  256. return tokenAuthenticator, nil
  257. }
  258. // newServiceAccountAuthenticator returns an authenticator.Token or an error
  259. func newServiceAccountAuthenticator(iss string, keyfiles []string, apiAudiences authenticator.Audiences, serviceAccountGetter serviceaccount.ServiceAccountTokenGetter) (authenticator.Token, error) {
  260. allPublicKeys := []interface{}{}
  261. for _, keyfile := range keyfiles {
  262. publicKeys, err := keyutil.PublicKeysFromFile(keyfile)
  263. if err != nil {
  264. return nil, err
  265. }
  266. allPublicKeys = append(allPublicKeys, publicKeys...)
  267. }
  268. tokenAuthenticator := serviceaccount.JWTTokenAuthenticator(iss, allPublicKeys, apiAudiences, serviceaccount.NewValidator(serviceAccountGetter))
  269. return tokenAuthenticator, nil
  270. }
  271. // newAuthenticatorFromClientCAFile returns an authenticator.Request or an error
  272. func newAuthenticatorFromClientCAFile(clientCAFile string) (authenticator.Request, error) {
  273. roots, err := certutil.NewPool(clientCAFile)
  274. if err != nil {
  275. return nil, err
  276. }
  277. opts := x509.DefaultVerifyOptions()
  278. opts.Roots = roots
  279. return x509.New(opts, x509.CommonNameUserConversion), nil
  280. }
  281. func newWebhookTokenAuthenticator(webhookConfigFile string, ttl time.Duration, implicitAuds authenticator.Audiences) (authenticator.Token, error) {
  282. webhookTokenAuthenticator, err := webhook.New(webhookConfigFile, implicitAuds)
  283. if err != nil {
  284. return nil, err
  285. }
  286. return tokencache.New(webhookTokenAuthenticator, false, ttl, ttl), nil
  287. }