client_builder_dynamic.go 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. /*
  2. Copyright 2018 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. "net/http"
  18. "sync"
  19. "time"
  20. "golang.org/x/oauth2"
  21. v1authenticationapi "k8s.io/api/authentication/v1"
  22. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  23. "k8s.io/apimachinery/pkg/util/clock"
  24. "k8s.io/apimachinery/pkg/util/wait"
  25. apiserverserviceaccount "k8s.io/apiserver/pkg/authentication/serviceaccount"
  26. clientset "k8s.io/client-go/kubernetes"
  27. v1core "k8s.io/client-go/kubernetes/typed/core/v1"
  28. restclient "k8s.io/client-go/rest"
  29. "k8s.io/client-go/transport"
  30. "k8s.io/klog"
  31. utilpointer "k8s.io/utils/pointer"
  32. )
  33. var (
  34. // defaultExpirationSeconds defines the duration of a TokenRequest in seconds.
  35. defaultExpirationSeconds = int64(3600)
  36. // defaultLeewayPercent defines the percentage of expiration left before the client trigger a token rotation.
  37. // range[0, 100]
  38. defaultLeewayPercent = 20
  39. )
  40. type DynamicControllerClientBuilder struct {
  41. // ClientConfig is a skeleton config to clone and use as the basis for each controller client
  42. ClientConfig *restclient.Config
  43. // CoreClient is used to provision service accounts if needed and watch for their associated tokens
  44. // to construct a controller client
  45. CoreClient v1core.CoreV1Interface
  46. // Namespace is the namespace used to host the service accounts that will back the
  47. // controllers. It must be highly privileged namespace which normal users cannot inspect.
  48. Namespace string
  49. // roundTripperFuncMap is a cache stores the corresponding roundtripper func for each
  50. // service account
  51. roundTripperFuncMap map[string]func(http.RoundTripper) http.RoundTripper
  52. // expirationSeconds defines the token expiration seconds
  53. expirationSeconds int64
  54. // leewayPercent defines the percentage of expiration left before the client trigger a token rotation.
  55. leewayPercent int
  56. mutex sync.Mutex
  57. clock clock.Clock
  58. }
  59. func NewDynamicClientBuilder(clientConfig *restclient.Config, coreClient v1core.CoreV1Interface, ns string) ControllerClientBuilder {
  60. builder := &DynamicControllerClientBuilder{
  61. ClientConfig: clientConfig,
  62. CoreClient: coreClient,
  63. Namespace: ns,
  64. roundTripperFuncMap: map[string]func(http.RoundTripper) http.RoundTripper{},
  65. expirationSeconds: defaultExpirationSeconds,
  66. leewayPercent: defaultLeewayPercent,
  67. clock: clock.RealClock{},
  68. }
  69. return builder
  70. }
  71. // this function only for test purpose, don't call it
  72. func NewTestDynamicClientBuilder(clientConfig *restclient.Config, coreClient v1core.CoreV1Interface, ns string, expirationSeconds int64, leewayPercent int) ControllerClientBuilder {
  73. builder := &DynamicControllerClientBuilder{
  74. ClientConfig: clientConfig,
  75. CoreClient: coreClient,
  76. Namespace: ns,
  77. roundTripperFuncMap: map[string]func(http.RoundTripper) http.RoundTripper{},
  78. expirationSeconds: expirationSeconds,
  79. leewayPercent: leewayPercent,
  80. clock: clock.RealClock{},
  81. }
  82. return builder
  83. }
  84. func (t *DynamicControllerClientBuilder) Config(saName string) (*restclient.Config, error) {
  85. _, err := getOrCreateServiceAccount(t.CoreClient, t.Namespace, saName)
  86. if err != nil {
  87. return nil, err
  88. }
  89. configCopy := constructClient(t.Namespace, saName, t.ClientConfig)
  90. t.mutex.Lock()
  91. defer t.mutex.Unlock()
  92. rt, ok := t.roundTripperFuncMap[saName]
  93. if ok {
  94. configCopy.WrapTransport = rt
  95. } else {
  96. cachedTokenSource := transport.NewCachedTokenSource(&tokenSourceImpl{
  97. namespace: t.Namespace,
  98. serviceAccountName: saName,
  99. coreClient: t.CoreClient,
  100. expirationSeconds: t.expirationSeconds,
  101. leewayPercent: t.leewayPercent,
  102. })
  103. configCopy.WrapTransport = transport.TokenSourceWrapTransport(cachedTokenSource)
  104. t.roundTripperFuncMap[saName] = configCopy.WrapTransport
  105. }
  106. return &configCopy, nil
  107. }
  108. func (t *DynamicControllerClientBuilder) ConfigOrDie(name string) *restclient.Config {
  109. clientConfig, err := t.Config(name)
  110. if err != nil {
  111. klog.Fatal(err)
  112. }
  113. return clientConfig
  114. }
  115. func (t *DynamicControllerClientBuilder) Client(name string) (clientset.Interface, error) {
  116. clientConfig, err := t.Config(name)
  117. if err != nil {
  118. return nil, err
  119. }
  120. return clientset.NewForConfig(clientConfig)
  121. }
  122. func (t *DynamicControllerClientBuilder) ClientOrDie(name string) clientset.Interface {
  123. client, err := t.Client(name)
  124. if err != nil {
  125. klog.Fatal(err)
  126. }
  127. return client
  128. }
  129. type tokenSourceImpl struct {
  130. namespace string
  131. serviceAccountName string
  132. coreClient v1core.CoreV1Interface
  133. expirationSeconds int64
  134. leewayPercent int
  135. }
  136. func (ts *tokenSourceImpl) Token() (*oauth2.Token, error) {
  137. var retTokenRequest *v1authenticationapi.TokenRequest
  138. backoff := wait.Backoff{
  139. Duration: 500 * time.Millisecond,
  140. Factor: 2, // double the timeout for every failure
  141. Steps: 4,
  142. }
  143. if err := wait.ExponentialBackoff(backoff, func() (bool, error) {
  144. if _, inErr := getOrCreateServiceAccount(ts.coreClient, ts.namespace, ts.serviceAccountName); inErr != nil {
  145. klog.Warningf("get or create service account failed: %v", inErr)
  146. return false, nil
  147. }
  148. tr, inErr := ts.coreClient.ServiceAccounts(ts.namespace).CreateToken(context.TODO(), ts.serviceAccountName, &v1authenticationapi.TokenRequest{
  149. Spec: v1authenticationapi.TokenRequestSpec{
  150. ExpirationSeconds: utilpointer.Int64Ptr(ts.expirationSeconds),
  151. },
  152. }, metav1.CreateOptions{})
  153. if inErr != nil {
  154. klog.Warningf("get token failed: %v", inErr)
  155. return false, nil
  156. }
  157. retTokenRequest = tr
  158. return true, nil
  159. }); err != nil {
  160. return nil, fmt.Errorf("failed to get token for %s/%s: %v", ts.namespace, ts.serviceAccountName, err)
  161. }
  162. if retTokenRequest.Spec.ExpirationSeconds == nil {
  163. return nil, fmt.Errorf("nil pointer of expiration in token request")
  164. }
  165. lifetime := retTokenRequest.Status.ExpirationTimestamp.Time.Sub(time.Now())
  166. if lifetime < time.Minute*10 {
  167. // possible clock skew issue, pin to minimum token lifetime
  168. lifetime = time.Minute * 10
  169. }
  170. leeway := time.Duration(int64(lifetime) * int64(ts.leewayPercent) / 100)
  171. expiry := time.Now().Add(lifetime).Add(-1 * leeway)
  172. return &oauth2.Token{
  173. AccessToken: retTokenRequest.Status.Token,
  174. TokenType: "Bearer",
  175. Expiry: expiry,
  176. }, nil
  177. }
  178. func constructClient(saNamespace, saName string, config *restclient.Config) restclient.Config {
  179. username := apiserverserviceaccount.MakeUsername(saNamespace, saName)
  180. ret := *restclient.AnonymousClientConfig(config)
  181. restclient.AddUserAgent(&ret, username)
  182. return ret
  183. }