client_builder.go 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  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 controller
  14. import (
  15. "context"
  16. "fmt"
  17. "time"
  18. v1authenticationapi "k8s.io/api/authentication/v1"
  19. "k8s.io/api/core/v1"
  20. apierrors "k8s.io/apimachinery/pkg/api/errors"
  21. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  22. "k8s.io/apimachinery/pkg/fields"
  23. "k8s.io/apimachinery/pkg/runtime"
  24. "k8s.io/apimachinery/pkg/watch"
  25. apiserverserviceaccount "k8s.io/apiserver/pkg/authentication/serviceaccount"
  26. clientset "k8s.io/client-go/kubernetes"
  27. v1authentication "k8s.io/client-go/kubernetes/typed/authentication/v1"
  28. v1core "k8s.io/client-go/kubernetes/typed/core/v1"
  29. restclient "k8s.io/client-go/rest"
  30. "k8s.io/client-go/tools/cache"
  31. watchtools "k8s.io/client-go/tools/watch"
  32. "k8s.io/klog"
  33. "k8s.io/kubernetes/pkg/api/legacyscheme"
  34. api "k8s.io/kubernetes/pkg/apis/core"
  35. "k8s.io/kubernetes/pkg/serviceaccount"
  36. )
  37. // ControllerClientBuilder allows you to get clients and configs for controllers
  38. // Please note a copy also exists in staging/src/k8s.io/cloud-provider/cloud.go
  39. // TODO: Extract this into a separate controller utilities repo (issues/68947)
  40. type ControllerClientBuilder interface {
  41. Config(name string) (*restclient.Config, error)
  42. ConfigOrDie(name string) *restclient.Config
  43. Client(name string) (clientset.Interface, error)
  44. ClientOrDie(name string) clientset.Interface
  45. }
  46. // SimpleControllerClientBuilder returns a fixed client with different user agents
  47. type SimpleControllerClientBuilder struct {
  48. // ClientConfig is a skeleton config to clone and use as the basis for each controller client
  49. ClientConfig *restclient.Config
  50. }
  51. func (b SimpleControllerClientBuilder) Config(name string) (*restclient.Config, error) {
  52. clientConfig := *b.ClientConfig
  53. return restclient.AddUserAgent(&clientConfig, name), nil
  54. }
  55. func (b SimpleControllerClientBuilder) ConfigOrDie(name string) *restclient.Config {
  56. clientConfig, err := b.Config(name)
  57. if err != nil {
  58. klog.Fatal(err)
  59. }
  60. return clientConfig
  61. }
  62. func (b SimpleControllerClientBuilder) Client(name string) (clientset.Interface, error) {
  63. clientConfig, err := b.Config(name)
  64. if err != nil {
  65. return nil, err
  66. }
  67. return clientset.NewForConfig(clientConfig)
  68. }
  69. func (b SimpleControllerClientBuilder) ClientOrDie(name string) clientset.Interface {
  70. client, err := b.Client(name)
  71. if err != nil {
  72. klog.Fatal(err)
  73. }
  74. return client
  75. }
  76. // SAControllerClientBuilder is a ControllerClientBuilder that returns clients identifying as
  77. // service accounts
  78. type SAControllerClientBuilder struct {
  79. // ClientConfig is a skeleton config to clone and use as the basis for each controller client
  80. ClientConfig *restclient.Config
  81. // CoreClient is used to provision service accounts if needed and watch for their associated tokens
  82. // to construct a controller client
  83. CoreClient v1core.CoreV1Interface
  84. // AuthenticationClient is used to check API tokens to make sure they are valid before
  85. // building a controller client from them
  86. AuthenticationClient v1authentication.AuthenticationV1Interface
  87. // Namespace is the namespace used to host the service accounts that will back the
  88. // controllers. It must be highly privileged namespace which normal users cannot inspect.
  89. Namespace string
  90. }
  91. // config returns a complete clientConfig for constructing clients. This is separate in anticipation of composition
  92. // which means that not all clientsets are known here
  93. func (b SAControllerClientBuilder) Config(name string) (*restclient.Config, error) {
  94. sa, err := getOrCreateServiceAccount(b.CoreClient, b.Namespace, name)
  95. if err != nil {
  96. return nil, err
  97. }
  98. var clientConfig *restclient.Config
  99. fieldSelector := fields.SelectorFromSet(map[string]string{
  100. api.SecretTypeField: string(v1.SecretTypeServiceAccountToken),
  101. }).String()
  102. lw := &cache.ListWatch{
  103. ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
  104. options.FieldSelector = fieldSelector
  105. return b.CoreClient.Secrets(b.Namespace).List(options)
  106. },
  107. WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
  108. options.FieldSelector = fieldSelector
  109. return b.CoreClient.Secrets(b.Namespace).Watch(options)
  110. },
  111. }
  112. ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
  113. defer cancel()
  114. _, err = watchtools.UntilWithSync(ctx, lw, &v1.Secret{}, nil,
  115. func(event watch.Event) (bool, error) {
  116. switch event.Type {
  117. case watch.Deleted:
  118. return false, nil
  119. case watch.Error:
  120. return false, fmt.Errorf("error watching")
  121. case watch.Added, watch.Modified:
  122. secret, ok := event.Object.(*v1.Secret)
  123. if !ok {
  124. return false, fmt.Errorf("unexpected object type: %T", event.Object)
  125. }
  126. if !serviceaccount.IsServiceAccountToken(secret, sa) {
  127. return false, nil
  128. }
  129. if len(secret.Data[v1.ServiceAccountTokenKey]) == 0 {
  130. return false, nil
  131. }
  132. validConfig, valid, err := b.getAuthenticatedConfig(sa, string(secret.Data[v1.ServiceAccountTokenKey]))
  133. if err != nil {
  134. klog.Warningf("error validating API token for %s/%s in secret %s: %v", sa.Namespace, sa.Name, secret.Name, err)
  135. // continue watching for good tokens
  136. return false, nil
  137. }
  138. if !valid {
  139. klog.Warningf("secret %s contained an invalid API token for %s/%s", secret.Name, sa.Namespace, sa.Name)
  140. // try to delete the secret containing the invalid token
  141. if err := b.CoreClient.Secrets(secret.Namespace).Delete(secret.Name, &metav1.DeleteOptions{}); err != nil && !apierrors.IsNotFound(err) {
  142. klog.Warningf("error deleting secret %s containing invalid API token for %s/%s: %v", secret.Name, sa.Namespace, sa.Name, err)
  143. }
  144. // continue watching for good tokens
  145. return false, nil
  146. }
  147. clientConfig = validConfig
  148. return true, nil
  149. default:
  150. return false, fmt.Errorf("unexpected event type: %v", event.Type)
  151. }
  152. })
  153. if err != nil {
  154. return nil, fmt.Errorf("unable to get token for service account: %v", err)
  155. }
  156. return clientConfig, nil
  157. }
  158. func (b SAControllerClientBuilder) getAuthenticatedConfig(sa *v1.ServiceAccount, token string) (*restclient.Config, bool, error) {
  159. username := apiserverserviceaccount.MakeUsername(sa.Namespace, sa.Name)
  160. clientConfig := restclient.AnonymousClientConfig(b.ClientConfig)
  161. clientConfig.BearerToken = token
  162. restclient.AddUserAgent(clientConfig, username)
  163. // Try token review first
  164. tokenReview := &v1authenticationapi.TokenReview{Spec: v1authenticationapi.TokenReviewSpec{Token: token}}
  165. if tokenResult, err := b.AuthenticationClient.TokenReviews().Create(tokenReview); err == nil {
  166. if !tokenResult.Status.Authenticated {
  167. klog.Warningf("Token for %s/%s did not authenticate correctly", sa.Namespace, sa.Name)
  168. return nil, false, nil
  169. }
  170. if tokenResult.Status.User.Username != username {
  171. klog.Warningf("Token for %s/%s authenticated as unexpected username: %s", sa.Namespace, sa.Name, tokenResult.Status.User.Username)
  172. return nil, false, nil
  173. }
  174. klog.V(4).Infof("Verified credential for %s/%s", sa.Namespace, sa.Name)
  175. return clientConfig, true, nil
  176. }
  177. // If we couldn't run the token review, the API might be disabled or we might not have permission.
  178. // Try to make a request to /apis with the token. If we get a 401 we should consider the token invalid.
  179. clientConfigCopy := *clientConfig
  180. clientConfigCopy.NegotiatedSerializer = legacyscheme.Codecs
  181. client, err := restclient.UnversionedRESTClientFor(&clientConfigCopy)
  182. if err != nil {
  183. return nil, false, err
  184. }
  185. err = client.Get().AbsPath("/apis").Do().Error()
  186. if apierrors.IsUnauthorized(err) {
  187. klog.Warningf("Token for %s/%s did not authenticate correctly: %v", sa.Namespace, sa.Name, err)
  188. return nil, false, nil
  189. }
  190. return clientConfig, true, nil
  191. }
  192. func (b SAControllerClientBuilder) ConfigOrDie(name string) *restclient.Config {
  193. clientConfig, err := b.Config(name)
  194. if err != nil {
  195. klog.Fatal(err)
  196. }
  197. return clientConfig
  198. }
  199. func (b SAControllerClientBuilder) Client(name string) (clientset.Interface, error) {
  200. clientConfig, err := b.Config(name)
  201. if err != nil {
  202. return nil, err
  203. }
  204. return clientset.NewForConfig(clientConfig)
  205. }
  206. func (b SAControllerClientBuilder) ClientOrDie(name string) clientset.Interface {
  207. client, err := b.Client(name)
  208. if err != nil {
  209. klog.Fatal(err)
  210. }
  211. return client
  212. }