blob: b8cadd382af8aba3daf5420c7cf78044bee189eb [file] [log] [blame]
Scott Bakere7144bc2019-10-01 14:16:47 -07001/*
2Copyright 2018 The Kubernetes Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8 http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
girishke7ca43b2019-10-10 12:30:03 +000017package transport
Scott Bakere7144bc2019-10-01 14:16:47 -070018
19import (
20 "fmt"
21 "io/ioutil"
22 "net/http"
23 "strings"
24 "sync"
25 "time"
26
27 "golang.org/x/oauth2"
28 "k8s.io/klog"
29)
30
31// TokenSourceWrapTransport returns a WrapTransport that injects bearer tokens
32// authentication from an oauth2.TokenSource.
33func TokenSourceWrapTransport(ts oauth2.TokenSource) func(http.RoundTripper) http.RoundTripper {
34 return func(rt http.RoundTripper) http.RoundTripper {
35 return &tokenSourceTransport{
36 base: rt,
37 ort: &oauth2.Transport{
38 Source: ts,
39 Base: rt,
40 },
41 }
42 }
43}
44
45// NewCachedFileTokenSource returns a oauth2.TokenSource reads a token from a
46// file at a specified path and periodically reloads it.
47func NewCachedFileTokenSource(path string) oauth2.TokenSource {
48 return &cachingTokenSource{
49 now: time.Now,
girishke7ca43b2019-10-10 12:30:03 +000050 leeway: 10 * time.Second,
Scott Bakere7144bc2019-10-01 14:16:47 -070051 base: &fileTokenSource{
52 path: path,
girishke7ca43b2019-10-10 12:30:03 +000053 // This period was picked because it is half of the duration between when the kubelet
54 // refreshes a projected service account token and when the original token expires.
55 // Default token lifetime is 10 minutes, and the kubelet starts refreshing at 80% of lifetime.
56 // This should induce re-reading at a frequency that works with the token volume source.
57 period: time.Minute,
Scott Bakere7144bc2019-10-01 14:16:47 -070058 },
59 }
60}
61
girishke7ca43b2019-10-10 12:30:03 +000062// NewCachedTokenSource returns a oauth2.TokenSource reads a token from a
63// designed TokenSource. The ts would provide the source of token.
64func NewCachedTokenSource(ts oauth2.TokenSource) oauth2.TokenSource {
65 return &cachingTokenSource{
66 now: time.Now,
67 base: ts,
68 }
69}
70
Scott Bakere7144bc2019-10-01 14:16:47 -070071type tokenSourceTransport struct {
72 base http.RoundTripper
73 ort http.RoundTripper
74}
75
76func (tst *tokenSourceTransport) RoundTrip(req *http.Request) (*http.Response, error) {
77 // This is to allow --token to override other bearer token providers.
78 if req.Header.Get("Authorization") != "" {
79 return tst.base.RoundTrip(req)
80 }
81 return tst.ort.RoundTrip(req)
82}
83
84type fileTokenSource struct {
85 path string
86 period time.Duration
87}
88
89var _ = oauth2.TokenSource(&fileTokenSource{})
90
91func (ts *fileTokenSource) Token() (*oauth2.Token, error) {
92 tokb, err := ioutil.ReadFile(ts.path)
93 if err != nil {
94 return nil, fmt.Errorf("failed to read token file %q: %v", ts.path, err)
95 }
96 tok := strings.TrimSpace(string(tokb))
97 if len(tok) == 0 {
98 return nil, fmt.Errorf("read empty token from file %q", ts.path)
99 }
100
101 return &oauth2.Token{
102 AccessToken: tok,
103 Expiry: time.Now().Add(ts.period),
104 }, nil
105}
106
107type cachingTokenSource struct {
108 base oauth2.TokenSource
109 leeway time.Duration
110
111 sync.RWMutex
112 tok *oauth2.Token
113
114 // for testing
115 now func() time.Time
116}
117
118var _ = oauth2.TokenSource(&cachingTokenSource{})
119
120func (ts *cachingTokenSource) Token() (*oauth2.Token, error) {
121 now := ts.now()
122 // fast path
123 ts.RLock()
124 tok := ts.tok
125 ts.RUnlock()
126
127 if tok != nil && tok.Expiry.Add(-1*ts.leeway).After(now) {
128 return tok, nil
129 }
130
131 // slow path
132 ts.Lock()
133 defer ts.Unlock()
134 if tok := ts.tok; tok != nil && tok.Expiry.Add(-1*ts.leeway).After(now) {
135 return tok, nil
136 }
137
138 tok, err := ts.base.Token()
139 if err != nil {
140 if ts.tok == nil {
141 return nil, err
142 }
143 klog.Errorf("Unable to rotate token: %v", err)
144 return ts.tok, nil
145 }
146
147 ts.tok = tok
148 return tok, nil
149}