package kubernetes import ( "io/ioutil" "net/http" "sync" "time" "github.com/dexidp/dex/storage/kubernetes/k8sapi" ) // transport is a simple http.Transport wrapper type transport struct { updateReq func(r *http.Request) base http.RoundTripper } func (t transport) RoundTrip(r *http.Request) (*http.Response, error) { // shallow copy of the struct r2 := new(http.Request) *r2 = *r // deep copy of the Header r2.Header = make(http.Header, len(r.Header)) for k, s := range r.Header { r2.Header[k] = append([]string(nil), s...) } t.updateReq(r2) return t.base.RoundTrip(r2) } func wrapRoundTripper(base http.RoundTripper, user k8sapi.AuthInfo, inCluster bool) http.RoundTripper { if inCluster { inClusterTransportHelper := newInClusterTransportHelper(user) return transport{ updateReq: func(r *http.Request) { inClusterTransportHelper.UpdateToken() r.Header.Set("Authorization", "Bearer "+inClusterTransportHelper.GetToken()) }, base: base, } } if user.Token != "" { return transport{ updateReq: func(r *http.Request) { r.Header.Set("Authorization", "Bearer "+user.Token) }, base: base, } } if user.Username != "" && user.Password != "" { return transport{ updateReq: func(r *http.Request) { r.SetBasicAuth(user.Username, user.Password) }, base: base, } } return base } // renewTokenPeriod is the interval after which dex will read the token from a well-known file. // By Kubernetes documentation, this interval should be at least one minute long. // Kubernetes client-go v0.15+ uses 10 seconds long interval. // Dex uses the reasonable value between these two. const renewTokenPeriod = 30 * time.Second // inClusterTransportHelper is capable of safely updating the user token. // BoundServiceAccountTokenVolume feature is enabled in Kubernetes >=1.21 by default. // With this feature, the service account token in the pod becomes periodically updated. // Therefore, Dex needs to re-read the token from the disk after some time to be sure that it uses the valid token. type inClusterTransportHelper struct { mu sync.RWMutex info k8sapi.AuthInfo expiry time.Time now func() time.Time tokenLocation string } func newInClusterTransportHelper(info k8sapi.AuthInfo) *inClusterTransportHelper { user := &inClusterTransportHelper{ info: info, now: time.Now, tokenLocation: "/var/run/secrets/kubernetes.io/serviceaccount/token", } user.UpdateToken() return user } func (c *inClusterTransportHelper) UpdateToken() { c.mu.RLock() exp := c.expiry c.mu.RUnlock() if !c.now().After(exp) { // Do not need to update token yet return } token, err := ioutil.ReadFile(c.tokenLocation) if err != nil { return } c.mu.Lock() defer c.mu.Unlock() c.info.Token = string(token) c.expiry = c.now().Add(renewTokenPeriod) } func (c *inClusterTransportHelper) GetToken() string { c.mu.RLock() defer c.mu.RUnlock() return c.info.Token }