diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index f771a616..df595602 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -404,207 +404,207 @@ }, { "ImportPath": "k8s.io/apimachinery/pkg/api/apitesting", - "Rev": "18a5ff3097b4b189511742e39151a153ee16988b" + "Rev": "eb8c8024849baa3681995b2b0bd4da7ea1734ace" }, { "ImportPath": "k8s.io/apimachinery/pkg/api/apitesting/fuzzer", - "Rev": "18a5ff3097b4b189511742e39151a153ee16988b" + "Rev": "eb8c8024849baa3681995b2b0bd4da7ea1734ace" }, { "ImportPath": "k8s.io/apimachinery/pkg/api/apitesting/roundtrip", - "Rev": "18a5ff3097b4b189511742e39151a153ee16988b" + "Rev": "eb8c8024849baa3681995b2b0bd4da7ea1734ace" }, { "ImportPath": "k8s.io/apimachinery/pkg/api/equality", - "Rev": "18a5ff3097b4b189511742e39151a153ee16988b" + "Rev": "eb8c8024849baa3681995b2b0bd4da7ea1734ace" }, { "ImportPath": "k8s.io/apimachinery/pkg/api/errors", - "Rev": "18a5ff3097b4b189511742e39151a153ee16988b" + "Rev": "eb8c8024849baa3681995b2b0bd4da7ea1734ace" }, { "ImportPath": "k8s.io/apimachinery/pkg/api/meta", - "Rev": "18a5ff3097b4b189511742e39151a153ee16988b" + "Rev": "eb8c8024849baa3681995b2b0bd4da7ea1734ace" }, { "ImportPath": "k8s.io/apimachinery/pkg/api/resource", - "Rev": "18a5ff3097b4b189511742e39151a153ee16988b" + "Rev": "eb8c8024849baa3681995b2b0bd4da7ea1734ace" }, { "ImportPath": "k8s.io/apimachinery/pkg/apis/meta/fuzzer", - "Rev": "18a5ff3097b4b189511742e39151a153ee16988b" + "Rev": "eb8c8024849baa3681995b2b0bd4da7ea1734ace" }, { "ImportPath": "k8s.io/apimachinery/pkg/apis/meta/internalversion", - "Rev": "18a5ff3097b4b189511742e39151a153ee16988b" + "Rev": "eb8c8024849baa3681995b2b0bd4da7ea1734ace" }, { "ImportPath": "k8s.io/apimachinery/pkg/apis/meta/v1", - "Rev": "18a5ff3097b4b189511742e39151a153ee16988b" + "Rev": "eb8c8024849baa3681995b2b0bd4da7ea1734ace" }, { "ImportPath": "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured", - "Rev": "18a5ff3097b4b189511742e39151a153ee16988b" + "Rev": "eb8c8024849baa3681995b2b0bd4da7ea1734ace" }, { "ImportPath": "k8s.io/apimachinery/pkg/apis/meta/v1beta1", - "Rev": "18a5ff3097b4b189511742e39151a153ee16988b" + "Rev": "eb8c8024849baa3681995b2b0bd4da7ea1734ace" }, { "ImportPath": "k8s.io/apimachinery/pkg/conversion", - "Rev": "18a5ff3097b4b189511742e39151a153ee16988b" + "Rev": "eb8c8024849baa3681995b2b0bd4da7ea1734ace" }, { "ImportPath": "k8s.io/apimachinery/pkg/conversion/queryparams", - "Rev": "18a5ff3097b4b189511742e39151a153ee16988b" + "Rev": "eb8c8024849baa3681995b2b0bd4da7ea1734ace" }, { "ImportPath": "k8s.io/apimachinery/pkg/fields", - "Rev": "18a5ff3097b4b189511742e39151a153ee16988b" + "Rev": "eb8c8024849baa3681995b2b0bd4da7ea1734ace" }, { "ImportPath": "k8s.io/apimachinery/pkg/labels", - "Rev": "18a5ff3097b4b189511742e39151a153ee16988b" + "Rev": "eb8c8024849baa3681995b2b0bd4da7ea1734ace" }, { "ImportPath": "k8s.io/apimachinery/pkg/runtime", - "Rev": "18a5ff3097b4b189511742e39151a153ee16988b" + "Rev": "eb8c8024849baa3681995b2b0bd4da7ea1734ace" }, { "ImportPath": "k8s.io/apimachinery/pkg/runtime/schema", - "Rev": "18a5ff3097b4b189511742e39151a153ee16988b" + "Rev": "eb8c8024849baa3681995b2b0bd4da7ea1734ace" }, { "ImportPath": "k8s.io/apimachinery/pkg/runtime/serializer", - "Rev": "18a5ff3097b4b189511742e39151a153ee16988b" + "Rev": "eb8c8024849baa3681995b2b0bd4da7ea1734ace" }, { "ImportPath": "k8s.io/apimachinery/pkg/runtime/serializer/json", - "Rev": "18a5ff3097b4b189511742e39151a153ee16988b" + "Rev": "eb8c8024849baa3681995b2b0bd4da7ea1734ace" }, { "ImportPath": "k8s.io/apimachinery/pkg/runtime/serializer/protobuf", - "Rev": "18a5ff3097b4b189511742e39151a153ee16988b" + "Rev": "eb8c8024849baa3681995b2b0bd4da7ea1734ace" }, { "ImportPath": "k8s.io/apimachinery/pkg/runtime/serializer/recognizer", - "Rev": "18a5ff3097b4b189511742e39151a153ee16988b" + "Rev": "eb8c8024849baa3681995b2b0bd4da7ea1734ace" }, { "ImportPath": "k8s.io/apimachinery/pkg/runtime/serializer/streaming", - "Rev": "18a5ff3097b4b189511742e39151a153ee16988b" + "Rev": "eb8c8024849baa3681995b2b0bd4da7ea1734ace" }, { "ImportPath": "k8s.io/apimachinery/pkg/runtime/serializer/versioning", - "Rev": "18a5ff3097b4b189511742e39151a153ee16988b" + "Rev": "eb8c8024849baa3681995b2b0bd4da7ea1734ace" }, { "ImportPath": "k8s.io/apimachinery/pkg/selection", - "Rev": "18a5ff3097b4b189511742e39151a153ee16988b" + "Rev": "eb8c8024849baa3681995b2b0bd4da7ea1734ace" }, { "ImportPath": "k8s.io/apimachinery/pkg/types", - "Rev": "18a5ff3097b4b189511742e39151a153ee16988b" + "Rev": "eb8c8024849baa3681995b2b0bd4da7ea1734ace" }, { "ImportPath": "k8s.io/apimachinery/pkg/util/cache", - "Rev": "18a5ff3097b4b189511742e39151a153ee16988b" + "Rev": "eb8c8024849baa3681995b2b0bd4da7ea1734ace" }, { "ImportPath": "k8s.io/apimachinery/pkg/util/clock", - "Rev": "18a5ff3097b4b189511742e39151a153ee16988b" + "Rev": "eb8c8024849baa3681995b2b0bd4da7ea1734ace" }, { "ImportPath": "k8s.io/apimachinery/pkg/util/diff", - "Rev": "18a5ff3097b4b189511742e39151a153ee16988b" + "Rev": "eb8c8024849baa3681995b2b0bd4da7ea1734ace" }, { "ImportPath": "k8s.io/apimachinery/pkg/util/errors", - "Rev": "18a5ff3097b4b189511742e39151a153ee16988b" + "Rev": "eb8c8024849baa3681995b2b0bd4da7ea1734ace" }, { "ImportPath": "k8s.io/apimachinery/pkg/util/framer", - "Rev": "18a5ff3097b4b189511742e39151a153ee16988b" + "Rev": "eb8c8024849baa3681995b2b0bd4da7ea1734ace" }, { "ImportPath": "k8s.io/apimachinery/pkg/util/httpstream", - "Rev": "18a5ff3097b4b189511742e39151a153ee16988b" + "Rev": "eb8c8024849baa3681995b2b0bd4da7ea1734ace" }, { "ImportPath": "k8s.io/apimachinery/pkg/util/httpstream/spdy", - "Rev": "18a5ff3097b4b189511742e39151a153ee16988b" + "Rev": "eb8c8024849baa3681995b2b0bd4da7ea1734ace" }, { "ImportPath": "k8s.io/apimachinery/pkg/util/intstr", - "Rev": "18a5ff3097b4b189511742e39151a153ee16988b" + "Rev": "eb8c8024849baa3681995b2b0bd4da7ea1734ace" }, { "ImportPath": "k8s.io/apimachinery/pkg/util/json", - "Rev": "18a5ff3097b4b189511742e39151a153ee16988b" + "Rev": "eb8c8024849baa3681995b2b0bd4da7ea1734ace" }, { "ImportPath": "k8s.io/apimachinery/pkg/util/mergepatch", - "Rev": "18a5ff3097b4b189511742e39151a153ee16988b" + "Rev": "eb8c8024849baa3681995b2b0bd4da7ea1734ace" }, { "ImportPath": "k8s.io/apimachinery/pkg/util/naming", - "Rev": "18a5ff3097b4b189511742e39151a153ee16988b" + "Rev": "eb8c8024849baa3681995b2b0bd4da7ea1734ace" }, { "ImportPath": "k8s.io/apimachinery/pkg/util/net", - "Rev": "18a5ff3097b4b189511742e39151a153ee16988b" + "Rev": "eb8c8024849baa3681995b2b0bd4da7ea1734ace" }, { "ImportPath": "k8s.io/apimachinery/pkg/util/remotecommand", - "Rev": "18a5ff3097b4b189511742e39151a153ee16988b" + "Rev": "eb8c8024849baa3681995b2b0bd4da7ea1734ace" }, { "ImportPath": "k8s.io/apimachinery/pkg/util/runtime", - "Rev": "18a5ff3097b4b189511742e39151a153ee16988b" + "Rev": "eb8c8024849baa3681995b2b0bd4da7ea1734ace" }, { "ImportPath": "k8s.io/apimachinery/pkg/util/sets", - "Rev": "18a5ff3097b4b189511742e39151a153ee16988b" + "Rev": "eb8c8024849baa3681995b2b0bd4da7ea1734ace" }, { "ImportPath": "k8s.io/apimachinery/pkg/util/strategicpatch", - "Rev": "18a5ff3097b4b189511742e39151a153ee16988b" + "Rev": "eb8c8024849baa3681995b2b0bd4da7ea1734ace" }, { "ImportPath": "k8s.io/apimachinery/pkg/util/validation", - "Rev": "18a5ff3097b4b189511742e39151a153ee16988b" + "Rev": "eb8c8024849baa3681995b2b0bd4da7ea1734ace" }, { "ImportPath": "k8s.io/apimachinery/pkg/util/validation/field", - "Rev": "18a5ff3097b4b189511742e39151a153ee16988b" + "Rev": "eb8c8024849baa3681995b2b0bd4da7ea1734ace" }, { "ImportPath": "k8s.io/apimachinery/pkg/util/wait", - "Rev": "18a5ff3097b4b189511742e39151a153ee16988b" + "Rev": "eb8c8024849baa3681995b2b0bd4da7ea1734ace" }, { "ImportPath": "k8s.io/apimachinery/pkg/util/yaml", - "Rev": "18a5ff3097b4b189511742e39151a153ee16988b" + "Rev": "eb8c8024849baa3681995b2b0bd4da7ea1734ace" }, { "ImportPath": "k8s.io/apimachinery/pkg/version", - "Rev": "18a5ff3097b4b189511742e39151a153ee16988b" + "Rev": "eb8c8024849baa3681995b2b0bd4da7ea1734ace" }, { "ImportPath": "k8s.io/apimachinery/pkg/watch", - "Rev": "18a5ff3097b4b189511742e39151a153ee16988b" + "Rev": "eb8c8024849baa3681995b2b0bd4da7ea1734ace" }, { "ImportPath": "k8s.io/apimachinery/third_party/forked/golang/json", - "Rev": "18a5ff3097b4b189511742e39151a153ee16988b" + "Rev": "eb8c8024849baa3681995b2b0bd4da7ea1734ace" }, { "ImportPath": "k8s.io/apimachinery/third_party/forked/golang/netutil", - "Rev": "18a5ff3097b4b189511742e39151a153ee16988b" + "Rev": "eb8c8024849baa3681995b2b0bd4da7ea1734ace" }, { "ImportPath": "k8s.io/apimachinery/third_party/forked/golang/reflect", - "Rev": "18a5ff3097b4b189511742e39151a153ee16988b" + "Rev": "eb8c8024849baa3681995b2b0bd4da7ea1734ace" }, { "ImportPath": "k8s.io/klog", diff --git a/rest/config.go b/rest/config.go index 438eb3be..072e7392 100644 --- a/rest/config.go +++ b/rest/config.go @@ -70,6 +70,11 @@ type Config struct { // TODO: demonstrate an OAuth2 compatible client. BearerToken string + // Path to a file containing a BearerToken. + // If set, the contents are periodically read. + // The last successfully read value takes precedence over BearerToken. + BearerTokenFile string + // Impersonate is the configuration that RESTClient will use for impersonation. Impersonate ImpersonationConfig @@ -322,9 +327,8 @@ func InClusterConfig() (*Config, error) { return nil, ErrNotInCluster } - ts := NewCachedFileTokenSource(tokenFile) - - if _, err := ts.Token(); err != nil { + token, err := ioutil.ReadFile(tokenFile) + if err != nil { return nil, err } @@ -340,7 +344,8 @@ func InClusterConfig() (*Config, error) { // TODO: switch to using cluster DNS. Host: "https://" + net.JoinHostPort(host, port), TLSClientConfig: tlsClientConfig, - WrapTransport: TokenSourceWrapTransport(ts), + BearerToken: string(token), + BearerTokenFile: tokenFile, }, nil } @@ -430,12 +435,13 @@ func AnonymousClientConfig(config *Config) *Config { // CopyConfig returns a copy of the given config func CopyConfig(config *Config) *Config { return &Config{ - Host: config.Host, - APIPath: config.APIPath, - ContentConfig: config.ContentConfig, - Username: config.Username, - Password: config.Password, - BearerToken: config.BearerToken, + Host: config.Host, + APIPath: config.APIPath, + ContentConfig: config.ContentConfig, + Username: config.Username, + Password: config.Password, + BearerToken: config.BearerToken, + BearerTokenFile: config.BearerTokenFile, Impersonate: ImpersonationConfig{ Groups: config.Impersonate.Groups, Extra: config.Impersonate.Extra, diff --git a/rest/config_test.go b/rest/config_test.go index 34786428..22c18d77 100644 --- a/rest/config_test.go +++ b/rest/config_test.go @@ -264,6 +264,7 @@ func TestAnonymousConfig(t *testing.T) { // is added to Config, update AnonymousClientConfig to preserve the field otherwise. expected.Impersonate = ImpersonationConfig{} expected.BearerToken = "" + expected.BearerTokenFile = "" expected.Username = "" expected.Password = "" expected.AuthProvider = nil diff --git a/tools/clientcmd/client_config.go b/tools/clientcmd/client_config.go index dea229c9..a7b8c1c6 100644 --- a/tools/clientcmd/client_config.go +++ b/tools/clientcmd/client_config.go @@ -229,11 +229,12 @@ func (config *DirectClientConfig) getUserIdentificationPartialConfig(configAuthI if len(configAuthInfo.Token) > 0 { mergedConfig.BearerToken = configAuthInfo.Token } else if len(configAuthInfo.TokenFile) > 0 { - ts := restclient.NewCachedFileTokenSource(configAuthInfo.TokenFile) - if _, err := ts.Token(); err != nil { + tokenBytes, err := ioutil.ReadFile(configAuthInfo.TokenFile) + if err != nil { return nil, err } - mergedConfig.WrapTransport = restclient.TokenSourceWrapTransport(ts) + mergedConfig.BearerToken = string(tokenBytes) + mergedConfig.BearerTokenFile = configAuthInfo.TokenFile } if len(configAuthInfo.Impersonate) > 0 { mergedConfig.Impersonate = restclient.ImpersonationConfig{ diff --git a/tools/clientcmd/client_config_test.go b/tools/clientcmd/client_config_test.go index 6da850ed..a13f08ae 100644 --- a/tools/clientcmd/client_config_test.go +++ b/tools/clientcmd/client_config_test.go @@ -18,7 +18,6 @@ package clientcmd import ( "io/ioutil" - "net/http" "os" "reflect" "strings" @@ -334,19 +333,7 @@ func TestBasicTokenFile(t *testing.T) { t.Fatalf("Unexpected error: %v", err) } - var out *http.Request - clientConfig.WrapTransport(fakeTransport(func(req *http.Request) (*http.Response, error) { - out = req - return &http.Response{}, nil - })).RoundTrip(&http.Request{}) - - matchStringArg(token, strings.TrimPrefix(out.Header.Get("Authorization"), "Bearer "), t) -} - -type fakeTransport func(*http.Request) (*http.Response, error) - -func (ft fakeTransport) RoundTrip(req *http.Request) (*http.Response, error) { - return ft(req) + matchStringArg(token, clientConfig.BearerToken, t) } func TestPrecedenceTokenFile(t *testing.T) { diff --git a/transport/config.go b/transport/config.go index 4081c23e..acb126d8 100644 --- a/transport/config.go +++ b/transport/config.go @@ -39,6 +39,11 @@ type Config struct { // Bearer token for authentication BearerToken string + // Path to a file containing a BearerToken. + // If set, the contents are periodically read. + // The last successfully read value takes precedence over BearerToken. + BearerTokenFile string + // Impersonate is the config that this Config will impersonate using Impersonate ImpersonationConfig @@ -80,7 +85,7 @@ func (c *Config) HasBasicAuth() bool { // HasTokenAuth returns whether the configuration has token authentication or not. func (c *Config) HasTokenAuth() bool { - return len(c.BearerToken) != 0 + return len(c.BearerToken) != 0 || len(c.BearerTokenFile) != 0 } // HasCertAuth returns whether the configuration has certificate authentication or not. diff --git a/transport/round_trippers.go b/transport/round_trippers.go index da417cf9..117a9c8c 100644 --- a/transport/round_trippers.go +++ b/transport/round_trippers.go @@ -22,6 +22,7 @@ import ( "strings" "time" + "golang.org/x/oauth2" "k8s.io/klog" utilnet "k8s.io/apimachinery/pkg/util/net" @@ -44,7 +45,11 @@ func HTTPWrappersForConfig(config *Config, rt http.RoundTripper) (http.RoundTrip case config.HasBasicAuth() && config.HasTokenAuth(): return nil, fmt.Errorf("username/password or bearer token may be set, but not both") case config.HasTokenAuth(): - rt = NewBearerAuthRoundTripper(config.BearerToken, rt) + var err error + rt, err = NewBearerAuthWithRefreshRoundTripper(config.BearerToken, config.BearerTokenFile, rt) + if err != nil { + return nil, err + } case config.HasBasicAuth(): rt = NewBasicAuthRoundTripper(config.Username, config.Password, rt) } @@ -265,13 +270,35 @@ func (rt *impersonatingRoundTripper) WrappedRoundTripper() http.RoundTripper { r type bearerAuthRoundTripper struct { bearer string + source oauth2.TokenSource rt http.RoundTripper } // NewBearerAuthRoundTripper adds the provided bearer token to a request // unless the authorization header has already been set. func NewBearerAuthRoundTripper(bearer string, rt http.RoundTripper) http.RoundTripper { - return &bearerAuthRoundTripper{bearer, rt} + return &bearerAuthRoundTripper{bearer, nil, rt} +} + +// NewBearerAuthRoundTripper adds the provided bearer token to a request +// unless the authorization header has already been set. +// If tokenFile is non-empty, it is periodically read, +// and the last successfully read content is used as the bearer token. +// If tokenFile is non-empty and bearer is empty, the tokenFile is read +// immediately to populate the initial bearer token. +func NewBearerAuthWithRefreshRoundTripper(bearer string, tokenFile string, rt http.RoundTripper) (http.RoundTripper, error) { + if len(tokenFile) == 0 { + return &bearerAuthRoundTripper{bearer, nil, rt}, nil + } + source := NewCachedFileTokenSource(tokenFile) + if len(bearer) == 0 { + token, err := source.Token() + if err != nil { + return nil, err + } + bearer = token.AccessToken + } + return &bearerAuthRoundTripper{bearer, source, rt}, nil } func (rt *bearerAuthRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { @@ -280,7 +307,13 @@ func (rt *bearerAuthRoundTripper) RoundTrip(req *http.Request) (*http.Response, } req = utilnet.CloneRequest(req) - req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", rt.bearer)) + token := rt.bearer + if rt.source != nil { + if refreshedToken, err := rt.source.Token(); err == nil { + token = refreshedToken.AccessToken + } + } + req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) return rt.rt.RoundTrip(req) } diff --git a/rest/token_source.go b/transport/token_source.go similarity index 99% rename from rest/token_source.go rename to transport/token_source.go index c251b5eb..818baffd 100644 --- a/rest/token_source.go +++ b/transport/token_source.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package rest +package transport import ( "fmt" diff --git a/rest/token_source_test.go b/transport/token_source_test.go similarity index 99% rename from rest/token_source_test.go rename to transport/token_source_test.go index 40851f80..a222495b 100644 --- a/rest/token_source_test.go +++ b/transport/token_source_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package rest +package transport import ( "fmt"