diff --git a/cmd/kubectl/kubectl.go b/cmd/kubectl/kubectl.go index 35149cc8c8..7dda64cf22 100644 --- a/cmd/kubectl/kubectl.go +++ b/cmd/kubectl/kubectl.go @@ -28,9 +28,6 @@ import ( utilflag "k8s.io/apiserver/pkg/util/flag" "k8s.io/kubernetes/pkg/kubectl/cmd" "k8s.io/kubernetes/pkg/kubectl/util/logs" - - // Import to initialize client auth plugins. - _ "k8s.io/client-go/plugin/pkg/client/auth" ) func main() { diff --git a/pkg/kubeapiserver/authenticator/config.go b/pkg/kubeapiserver/authenticator/config.go index 7f2dbf08d9..58e2022da5 100644 --- a/pkg/kubeapiserver/authenticator/config.go +++ b/pkg/kubeapiserver/authenticator/config.go @@ -33,7 +33,6 @@ import ( "k8s.io/apiserver/plugin/pkg/authenticator/request/basicauth" "k8s.io/apiserver/plugin/pkg/authenticator/token/webhook" // Initialize all known client auth plugins. - _ "k8s.io/client-go/plugin/pkg/client/auth" certutil "k8s.io/client-go/util/cert" "k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/serviceaccount" diff --git a/staging/src/k8s.io/client-go/plugin/pkg/client/auth/BUILD b/staging/src/k8s.io/client-go/plugin/pkg/client/auth/BUILD deleted file mode 100644 index 93f82807f8..0000000000 --- a/staging/src/k8s.io/client-go/plugin/pkg/client/auth/BUILD +++ /dev/null @@ -1,39 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", -) - -go_library( - name = "go_default_library", - srcs = ["plugins.go"], - importmap = "k8s.io/kubernetes/vendor/k8s.io/client-go/plugin/pkg/client/auth", - importpath = "k8s.io/client-go/plugin/pkg/client/auth", - deps = [ - "//staging/src/k8s.io/client-go/plugin/pkg/client/auth/azure:go_default_library", - "//staging/src/k8s.io/client-go/plugin/pkg/client/auth/gcp:go_default_library", - "//staging/src/k8s.io/client-go/plugin/pkg/client/auth/oidc:go_default_library", - "//staging/src/k8s.io/client-go/plugin/pkg/client/auth/openstack:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [ - ":package-srcs", - "//staging/src/k8s.io/client-go/plugin/pkg/client/auth/azure:all-srcs", - "//staging/src/k8s.io/client-go/plugin/pkg/client/auth/exec:all-srcs", - "//staging/src/k8s.io/client-go/plugin/pkg/client/auth/gcp:all-srcs", - "//staging/src/k8s.io/client-go/plugin/pkg/client/auth/oidc:all-srcs", - "//staging/src/k8s.io/client-go/plugin/pkg/client/auth/openstack:all-srcs", - ], - tags = ["automanaged"], -) diff --git a/staging/src/k8s.io/client-go/plugin/pkg/client/auth/OWNERS b/staging/src/k8s.io/client-go/plugin/pkg/client/auth/OWNERS deleted file mode 100644 index c607d2aa8c..0000000000 --- a/staging/src/k8s.io/client-go/plugin/pkg/client/auth/OWNERS +++ /dev/null @@ -1,7 +0,0 @@ -approvers: -- sig-auth-authenticators-approvers -reviewers: -- sig-auth-authenticators-reviewers -labels: -- sig/auth - diff --git a/staging/src/k8s.io/client-go/plugin/pkg/client/auth/azure/BUILD b/staging/src/k8s.io/client-go/plugin/pkg/client/auth/azure/BUILD deleted file mode 100644 index 6a28475a76..0000000000 --- a/staging/src/k8s.io/client-go/plugin/pkg/client/auth/azure/BUILD +++ /dev/null @@ -1,42 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", - "go_test", -) - -go_test( - name = "go_default_test", - srcs = ["azure_test.go"], - embed = [":go_default_library"], - deps = ["//vendor/github.com/Azure/go-autorest/autorest/adal:go_default_library"], -) - -go_library( - name = "go_default_library", - srcs = ["azure.go"], - importmap = "k8s.io/kubernetes/vendor/k8s.io/client-go/plugin/pkg/client/auth/azure", - importpath = "k8s.io/client-go/plugin/pkg/client/auth/azure", - deps = [ - "//staging/src/k8s.io/apimachinery/pkg/util/net:go_default_library", - "//staging/src/k8s.io/client-go/rest:go_default_library", - "//vendor/github.com/Azure/go-autorest/autorest:go_default_library", - "//vendor/github.com/Azure/go-autorest/autorest/adal:go_default_library", - "//vendor/github.com/Azure/go-autorest/autorest/azure:go_default_library", - "//vendor/k8s.io/klog:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], -) diff --git a/staging/src/k8s.io/client-go/plugin/pkg/client/auth/azure/README.md b/staging/src/k8s.io/client-go/plugin/pkg/client/auth/azure/README.md deleted file mode 100644 index e4ba791ea7..0000000000 --- a/staging/src/k8s.io/client-go/plugin/pkg/client/auth/azure/README.md +++ /dev/null @@ -1,50 +0,0 @@ -# Azure Active Directory plugin for client authentication - -This plugin provides an integration with Azure Active Directory device flow. If no tokens are present in the kubectl configuration, it will prompt a device code which can be used to login in a browser. After login it will automatically fetch the tokens and store them in the kubectl configuration. In addition it will refresh and update the tokens in the configuration when expired. - -## Usage - -1. Create an Azure Active Directory *Web App / API* application for `apiserver` following these [instructions](https://docs.microsoft.com/en-us/azure/active-directory/active-directory-app-registration). The callback URL does not matter (just cannot be empty). - -2. Create a second Azure Active Directory native application for `kubectl`. The callback URL does not matter (just cannot be empty). - -3. On `kubectl` application's configuration page in Azure portal grant permissions to `apiserver` application by clicking on *Required Permissions*, click the *Add* button and search for the apiserver application created in step 1. Select "Access apiserver" under the *DELEGATED PERMISSIONS*. Once added click the *Grant Permissions* button to apply the changes. - -4. Configure the `apiserver` to use the Azure Active Directory as an OIDC provider with following options - - ``` - --oidc-client-id="spn:APISERVER_APPLICATION_ID" \ - --oidc-issuer-url="https://sts.windows.net/TENANT_ID/" - --oidc-username-claim="sub" - ``` - - * Replace the `APISERVER_APPLICATION_ID` with the application ID of `apiserver` application - * Replace `TENANT_ID` with your tenant ID. -   * For a list of alternative username claims that are supported by the OIDC issuer check the JSON response at `https://sts.windows.net/TENANT_ID/.well-known/openid-configuration`. - -5. Configure `kubectl` to use the `azure` authentication provider - - ``` - kubectl config set-credentials "USER_NAME" --auth-provider=azure \ - --auth-provider-arg=environment=AzurePublicCloud \ - --auth-provider-arg=client-id=APPLICATION_ID \ - --auth-provider-arg=tenant-id=TENANT_ID \ - --auth-provider-arg=apiserver-id=APISERVER_APPLICATION_ID - ``` - - * Supported environments: `AzurePublicCloud`, `AzureUSGovernmentCloud`, `AzureChinaCloud`, `AzureGermanCloud` - * Replace `USER_NAME` and `TENANT_ID` with your user name and tenant ID - * Replace `APPLICATION_ID` with the application ID of your`kubectl` application ID - * Replace `APISERVER_APPLICATION_ID` with the application ID of your `apiserver` application ID - * Be sure to also (create and) select a context that uses above user - - 6. The access token is acquired when first `kubectl` command is executed - - ``` - kubectl get pods - - To sign in, use a web browser to open the page https://aka.ms/devicelogin and enter the code DEC7D48GA to authenticate. - ``` - - * After signing in a web browser, the token is stored in the configuration, and it will be reused when executing further commands. - * The resulting username in Kubernetes depends on your [configuration of the `--oidc-username-claim` and `--oidc-username-prefix` flags on the API server](https://kubernetes.io/docs/admin/authentication/#configuring-the-api-server). If you are using any authorization method you need to give permissions to that user, e.g. by binding the user to a role in the case of RBAC. diff --git a/staging/src/k8s.io/client-go/plugin/pkg/client/auth/azure/azure.go b/staging/src/k8s.io/client-go/plugin/pkg/client/auth/azure/azure.go deleted file mode 100644 index d42449fc25..0000000000 --- a/staging/src/k8s.io/client-go/plugin/pkg/client/auth/azure/azure.go +++ /dev/null @@ -1,360 +0,0 @@ -/* -Copyright 2017 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package azure - -import ( - "encoding/json" - "errors" - "fmt" - "net/http" - "os" - "sync" - - "github.com/Azure/go-autorest/autorest" - "github.com/Azure/go-autorest/autorest/adal" - "github.com/Azure/go-autorest/autorest/azure" - "k8s.io/klog" - - "k8s.io/apimachinery/pkg/util/net" - restclient "k8s.io/client-go/rest" -) - -const ( - azureTokenKey = "azureTokenKey" - tokenType = "Bearer" - authHeader = "Authorization" - - cfgClientID = "client-id" - cfgTenantID = "tenant-id" - cfgAccessToken = "access-token" - cfgRefreshToken = "refresh-token" - cfgExpiresIn = "expires-in" - cfgExpiresOn = "expires-on" - cfgEnvironment = "environment" - cfgApiserverID = "apiserver-id" -) - -func init() { - if err := restclient.RegisterAuthProviderPlugin("azure", newAzureAuthProvider); err != nil { - klog.Fatalf("Failed to register azure auth plugin: %v", err) - } -} - -var cache = newAzureTokenCache() - -type azureTokenCache struct { - lock sync.Mutex - cache map[string]*azureToken -} - -func newAzureTokenCache() *azureTokenCache { - return &azureTokenCache{cache: make(map[string]*azureToken)} -} - -func (c *azureTokenCache) getToken(tokenKey string) *azureToken { - c.lock.Lock() - defer c.lock.Unlock() - return c.cache[tokenKey] -} - -func (c *azureTokenCache) setToken(tokenKey string, token *azureToken) { - c.lock.Lock() - defer c.lock.Unlock() - c.cache[tokenKey] = token -} - -func newAzureAuthProvider(_ string, cfg map[string]string, persister restclient.AuthProviderConfigPersister) (restclient.AuthProvider, error) { - var ts tokenSource - - environment, err := azure.EnvironmentFromName(cfg[cfgEnvironment]) - if err != nil { - environment = azure.PublicCloud - } - ts, err = newAzureTokenSourceDeviceCode(environment, cfg[cfgClientID], cfg[cfgTenantID], cfg[cfgApiserverID]) - if err != nil { - return nil, fmt.Errorf("creating a new azure token source for device code authentication: %v", err) - } - cacheSource := newAzureTokenSource(ts, cache, cfg, persister) - - return &azureAuthProvider{ - tokenSource: cacheSource, - }, nil -} - -type azureAuthProvider struct { - tokenSource tokenSource -} - -func (p *azureAuthProvider) Login() error { - return errors.New("not yet implemented") -} - -func (p *azureAuthProvider) WrapTransport(rt http.RoundTripper) http.RoundTripper { - return &azureRoundTripper{ - tokenSource: p.tokenSource, - roundTripper: rt, - } -} - -type azureRoundTripper struct { - tokenSource tokenSource - roundTripper http.RoundTripper -} - -var _ net.RoundTripperWrapper = &azureRoundTripper{} - -func (r *azureRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { - if len(req.Header.Get(authHeader)) != 0 { - return r.roundTripper.RoundTrip(req) - } - - token, err := r.tokenSource.Token() - if err != nil { - klog.Errorf("Failed to acquire a token: %v", err) - return nil, fmt.Errorf("acquiring a token for authorization header: %v", err) - } - - // clone the request in order to avoid modifying the headers of the original request - req2 := new(http.Request) - *req2 = *req - req2.Header = make(http.Header, len(req.Header)) - for k, s := range req.Header { - req2.Header[k] = append([]string(nil), s...) - } - - req2.Header.Set(authHeader, fmt.Sprintf("%s %s", tokenType, token.token.AccessToken)) - - return r.roundTripper.RoundTrip(req2) -} - -func (r *azureRoundTripper) WrappedRoundTripper() http.RoundTripper { return r.roundTripper } - -type azureToken struct { - token adal.Token - clientID string - tenantID string - apiserverID string -} - -type tokenSource interface { - Token() (*azureToken, error) -} - -type azureTokenSource struct { - source tokenSource - cache *azureTokenCache - lock sync.Mutex - cfg map[string]string - persister restclient.AuthProviderConfigPersister -} - -func newAzureTokenSource(source tokenSource, cache *azureTokenCache, cfg map[string]string, persister restclient.AuthProviderConfigPersister) tokenSource { - return &azureTokenSource{ - source: source, - cache: cache, - cfg: cfg, - persister: persister, - } -} - -// Token fetches a token from the cache of configuration if present otherwise -// acquires a new token from the configured source. Automatically refreshes -// the token if expired. -func (ts *azureTokenSource) Token() (*azureToken, error) { - ts.lock.Lock() - defer ts.lock.Unlock() - - var err error - token := ts.cache.getToken(azureTokenKey) - if token == nil { - token, err = ts.retrieveTokenFromCfg() - if err != nil { - token, err = ts.source.Token() - if err != nil { - return nil, fmt.Errorf("acquiring a new fresh token: %v", err) - } - } - if !token.token.IsExpired() { - ts.cache.setToken(azureTokenKey, token) - err = ts.storeTokenInCfg(token) - if err != nil { - return nil, fmt.Errorf("storing the token in configuration: %v", err) - } - } - } - if token.token.IsExpired() { - token, err = ts.refreshToken(token) - if err != nil { - return nil, fmt.Errorf("refreshing the expired token: %v", err) - } - ts.cache.setToken(azureTokenKey, token) - err = ts.storeTokenInCfg(token) - if err != nil { - return nil, fmt.Errorf("storing the refreshed token in configuration: %v", err) - } - } - return token, nil -} - -func (ts *azureTokenSource) retrieveTokenFromCfg() (*azureToken, error) { - accessToken := ts.cfg[cfgAccessToken] - if accessToken == "" { - return nil, fmt.Errorf("no access token in cfg: %s", cfgAccessToken) - } - refreshToken := ts.cfg[cfgRefreshToken] - if refreshToken == "" { - return nil, fmt.Errorf("no refresh token in cfg: %s", cfgRefreshToken) - } - clientID := ts.cfg[cfgClientID] - if clientID == "" { - return nil, fmt.Errorf("no client ID in cfg: %s", cfgClientID) - } - tenantID := ts.cfg[cfgTenantID] - if tenantID == "" { - return nil, fmt.Errorf("no tenant ID in cfg: %s", cfgTenantID) - } - apiserverID := ts.cfg[cfgApiserverID] - if apiserverID == "" { - return nil, fmt.Errorf("no apiserver ID in cfg: %s", apiserverID) - } - expiresIn := ts.cfg[cfgExpiresIn] - if expiresIn == "" { - return nil, fmt.Errorf("no expiresIn in cfg: %s", cfgExpiresIn) - } - expiresOn := ts.cfg[cfgExpiresOn] - if expiresOn == "" { - return nil, fmt.Errorf("no expiresOn in cfg: %s", cfgExpiresOn) - } - - return &azureToken{ - token: adal.Token{ - AccessToken: accessToken, - RefreshToken: refreshToken, - ExpiresIn: json.Number(expiresIn), - ExpiresOn: json.Number(expiresOn), - NotBefore: json.Number(expiresOn), - Resource: fmt.Sprintf("spn:%s", apiserverID), - Type: tokenType, - }, - clientID: clientID, - tenantID: tenantID, - apiserverID: apiserverID, - }, nil -} - -func (ts *azureTokenSource) storeTokenInCfg(token *azureToken) error { - newCfg := make(map[string]string) - newCfg[cfgAccessToken] = token.token.AccessToken - newCfg[cfgRefreshToken] = token.token.RefreshToken - newCfg[cfgClientID] = token.clientID - newCfg[cfgTenantID] = token.tenantID - newCfg[cfgApiserverID] = token.apiserverID - newCfg[cfgExpiresIn] = string(token.token.ExpiresIn) - newCfg[cfgExpiresOn] = string(token.token.ExpiresOn) - - err := ts.persister.Persist(newCfg) - if err != nil { - return fmt.Errorf("persisting the configuration: %v", err) - } - ts.cfg = newCfg - return nil -} - -func (ts *azureTokenSource) refreshToken(token *azureToken) (*azureToken, error) { - oauthConfig, err := adal.NewOAuthConfig(azure.PublicCloud.ActiveDirectoryEndpoint, token.tenantID) - if err != nil { - return nil, fmt.Errorf("building the OAuth configuration for token refresh: %v", err) - } - - callback := func(t adal.Token) error { - return nil - } - spt, err := adal.NewServicePrincipalTokenFromManualToken( - *oauthConfig, - token.clientID, - token.apiserverID, - token.token, - callback) - if err != nil { - return nil, fmt.Errorf("creating new service principal for token refresh: %v", err) - } - - if err := spt.Refresh(); err != nil { - return nil, fmt.Errorf("refreshing token: %v", err) - } - - return &azureToken{ - token: spt.Token(), - clientID: token.clientID, - tenantID: token.tenantID, - apiserverID: token.apiserverID, - }, nil -} - -type azureTokenSourceDeviceCode struct { - environment azure.Environment - clientID string - tenantID string - apiserverID string -} - -func newAzureTokenSourceDeviceCode(environment azure.Environment, clientID string, tenantID string, apiserverID string) (tokenSource, error) { - if clientID == "" { - return nil, errors.New("client-id is empty") - } - if tenantID == "" { - return nil, errors.New("tenant-id is empty") - } - if apiserverID == "" { - return nil, errors.New("apiserver-id is empty") - } - return &azureTokenSourceDeviceCode{ - environment: environment, - clientID: clientID, - tenantID: tenantID, - apiserverID: apiserverID, - }, nil -} - -func (ts *azureTokenSourceDeviceCode) Token() (*azureToken, error) { - oauthConfig, err := adal.NewOAuthConfig(ts.environment.ActiveDirectoryEndpoint, ts.tenantID) - if err != nil { - return nil, fmt.Errorf("building the OAuth configuration for device code authentication: %v", err) - } - client := &autorest.Client{} - deviceCode, err := adal.InitiateDeviceAuth(client, *oauthConfig, ts.clientID, ts.apiserverID) - if err != nil { - return nil, fmt.Errorf("initialing the device code authentication: %v", err) - } - - _, err = fmt.Fprintln(os.Stderr, *deviceCode.Message) - if err != nil { - return nil, fmt.Errorf("prompting the device code message: %v", err) - } - - token, err := adal.WaitForUserCompletion(client, deviceCode) - if err != nil { - return nil, fmt.Errorf("waiting for device code authentication to complete: %v", err) - } - - return &azureToken{ - token: *token, - clientID: ts.clientID, - tenantID: ts.tenantID, - apiserverID: ts.apiserverID, - }, nil -} diff --git a/staging/src/k8s.io/client-go/plugin/pkg/client/auth/azure/azure_test.go b/staging/src/k8s.io/client-go/plugin/pkg/client/auth/azure/azure_test.go deleted file mode 100644 index 810a097ded..0000000000 --- a/staging/src/k8s.io/client-go/plugin/pkg/client/auth/azure/azure_test.go +++ /dev/null @@ -1,134 +0,0 @@ -/* -Copyright 2017 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package azure - -import ( - "encoding/json" - "strconv" - "strings" - "sync" - "testing" - "time" - - "github.com/Azure/go-autorest/autorest/adal" -) - -func TestAzureTokenSource(t *testing.T) { - fakeAccessToken := "fake token 1" - fakeSource := fakeTokenSource{ - accessToken: fakeAccessToken, - expiresOn: strconv.FormatInt(time.Now().Add(3600*time.Second).Unix(), 10), - } - cfg := make(map[string]string) - persiter := &fakePersister{cache: make(map[string]string)} - tokenCache := newAzureTokenCache() - tokenSource := newAzureTokenSource(&fakeSource, tokenCache, cfg, persiter) - token, err := tokenSource.Token() - if err != nil { - t.Errorf("failed to retrieve the token form cache: %v", err) - } - - wantCacheLen := 1 - if len(tokenCache.cache) != wantCacheLen { - t.Errorf("Token() cache length error: got %v, want %v", len(tokenCache.cache), wantCacheLen) - } - - if token != tokenCache.cache[azureTokenKey] { - t.Error("Token() returned token != cached token") - } - - wantCfg := token2Cfg(token) - persistedCfg := persiter.Cache() - for k, v := range persistedCfg { - if strings.Compare(v, wantCfg[k]) != 0 { - t.Errorf("Token() persisted cfg %s: got %v, want %v", k, v, wantCfg[k]) - } - } - - fakeSource.accessToken = "fake token 2" - token, err = tokenSource.Token() - if err != nil { - t.Errorf("failed to retrieve the cached token: %v", err) - } - - if token.token.AccessToken != fakeAccessToken { - t.Errorf("Token() didn't return the cached token") - } -} - -type fakePersister struct { - lock sync.Mutex - cache map[string]string -} - -func (p *fakePersister) Persist(cache map[string]string) error { - p.lock.Lock() - defer p.lock.Unlock() - p.cache = map[string]string{} - for k, v := range cache { - p.cache[k] = v - } - return nil -} - -func (p *fakePersister) Cache() map[string]string { - ret := map[string]string{} - p.lock.Lock() - defer p.lock.Unlock() - for k, v := range p.cache { - ret[k] = v - } - return ret -} - -type fakeTokenSource struct { - expiresOn string - accessToken string -} - -func (ts *fakeTokenSource) Token() (*azureToken, error) { - return &azureToken{ - token: newFackeAzureToken(ts.accessToken, ts.expiresOn), - clientID: "fake", - tenantID: "fake", - apiserverID: "fake", - }, nil -} - -func token2Cfg(token *azureToken) map[string]string { - cfg := make(map[string]string) - cfg[cfgAccessToken] = token.token.AccessToken - cfg[cfgRefreshToken] = token.token.RefreshToken - cfg[cfgClientID] = token.clientID - cfg[cfgTenantID] = token.tenantID - cfg[cfgApiserverID] = token.apiserverID - cfg[cfgExpiresIn] = string(token.token.ExpiresIn) - cfg[cfgExpiresOn] = string(token.token.ExpiresOn) - return cfg -} - -func newFackeAzureToken(accessToken string, expiresOn string) adal.Token { - return adal.Token{ - AccessToken: accessToken, - RefreshToken: "fake", - ExpiresIn: "3600", - ExpiresOn: json.Number(expiresOn), - NotBefore: json.Number(expiresOn), - Resource: "fake", - Type: "fake", - } -} diff --git a/staging/src/k8s.io/client-go/plugin/pkg/client/auth/exec/BUILD b/staging/src/k8s.io/client-go/plugin/pkg/client/auth/exec/BUILD deleted file mode 100644 index 6941559833..0000000000 --- a/staging/src/k8s.io/client-go/plugin/pkg/client/auth/exec/BUILD +++ /dev/null @@ -1,52 +0,0 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") - -go_library( - name = "go_default_library", - srcs = ["exec.go"], - importmap = "k8s.io/kubernetes/vendor/k8s.io/client-go/plugin/pkg/client/auth/exec", - importpath = "k8s.io/client-go/plugin/pkg/client/auth/exec", - visibility = ["//visibility:public"], - deps = [ - "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library", - "//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", - "//staging/src/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library", - "//staging/src/k8s.io/apimachinery/pkg/util/runtime:go_default_library", - "//staging/src/k8s.io/client-go/pkg/apis/clientauthentication:go_default_library", - "//staging/src/k8s.io/client-go/pkg/apis/clientauthentication/v1alpha1:go_default_library", - "//staging/src/k8s.io/client-go/pkg/apis/clientauthentication/v1beta1:go_default_library", - "//staging/src/k8s.io/client-go/tools/clientcmd/api:go_default_library", - "//staging/src/k8s.io/client-go/transport:go_default_library", - "//staging/src/k8s.io/client-go/util/connrotation:go_default_library", - "//vendor/golang.org/x/crypto/ssh/terminal:go_default_library", - "//vendor/k8s.io/klog:go_default_library", - ], -) - -go_test( - name = "go_default_test", - srcs = ["exec_test.go"], - data = glob(["testdata/**"]), - embed = [":go_default_library"], - deps = [ - "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library", - "//staging/src/k8s.io/client-go/pkg/apis/clientauthentication:go_default_library", - "//staging/src/k8s.io/client-go/tools/clientcmd/api:go_default_library", - "//staging/src/k8s.io/client-go/transport:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], - visibility = ["//visibility:public"], -) diff --git a/staging/src/k8s.io/client-go/plugin/pkg/client/auth/exec/exec.go b/staging/src/k8s.io/client-go/plugin/pkg/client/auth/exec/exec.go deleted file mode 100644 index 4d72526583..0000000000 --- a/staging/src/k8s.io/client-go/plugin/pkg/client/auth/exec/exec.go +++ /dev/null @@ -1,361 +0,0 @@ -/* -Copyright 2018 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package exec - -import ( - "bytes" - "context" - "crypto/tls" - "errors" - "fmt" - "io" - "net" - "net/http" - "os" - "os/exec" - "reflect" - "sync" - "time" - - "golang.org/x/crypto/ssh/terminal" - "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/runtime/serializer" - utilruntime "k8s.io/apimachinery/pkg/util/runtime" - "k8s.io/client-go/pkg/apis/clientauthentication" - "k8s.io/client-go/pkg/apis/clientauthentication/v1alpha1" - "k8s.io/client-go/pkg/apis/clientauthentication/v1beta1" - "k8s.io/client-go/tools/clientcmd/api" - "k8s.io/client-go/transport" - "k8s.io/client-go/util/connrotation" - "k8s.io/klog" -) - -const execInfoEnv = "KUBERNETES_EXEC_INFO" - -var scheme = runtime.NewScheme() -var codecs = serializer.NewCodecFactory(scheme) - -func init() { - v1.AddToGroupVersion(scheme, schema.GroupVersion{Version: "v1"}) - utilruntime.Must(v1alpha1.AddToScheme(scheme)) - utilruntime.Must(v1beta1.AddToScheme(scheme)) - utilruntime.Must(clientauthentication.AddToScheme(scheme)) -} - -var ( - // Since transports can be constantly re-initialized by programs like kubectl, - // keep a cache of initialized authenticators keyed by a hash of their config. - globalCache = newCache() - // The list of API versions we accept. - apiVersions = map[string]schema.GroupVersion{ - v1alpha1.SchemeGroupVersion.String(): v1alpha1.SchemeGroupVersion, - v1beta1.SchemeGroupVersion.String(): v1beta1.SchemeGroupVersion, - } -) - -func newCache() *cache { - return &cache{m: make(map[string]*Authenticator)} -} - -func cacheKey(c *api.ExecConfig) string { - return fmt.Sprintf("%#v", c) -} - -type cache struct { - mu sync.Mutex - m map[string]*Authenticator -} - -func (c *cache) get(s string) (*Authenticator, bool) { - c.mu.Lock() - defer c.mu.Unlock() - a, ok := c.m[s] - return a, ok -} - -// put inserts an authenticator into the cache. If an authenticator is already -// associated with the key, the first one is returned instead. -func (c *cache) put(s string, a *Authenticator) *Authenticator { - c.mu.Lock() - defer c.mu.Unlock() - existing, ok := c.m[s] - if ok { - return existing - } - c.m[s] = a - return a -} - -// GetAuthenticator returns an exec-based plugin for providing client credentials. -func GetAuthenticator(config *api.ExecConfig) (*Authenticator, error) { - return newAuthenticator(globalCache, config) -} - -func newAuthenticator(c *cache, config *api.ExecConfig) (*Authenticator, error) { - key := cacheKey(config) - if a, ok := c.get(key); ok { - return a, nil - } - - gv, ok := apiVersions[config.APIVersion] - if !ok { - return nil, fmt.Errorf("exec plugin: invalid apiVersion %q", config.APIVersion) - } - - a := &Authenticator{ - cmd: config.Command, - args: config.Args, - group: gv, - - stdin: os.Stdin, - stderr: os.Stderr, - interactive: terminal.IsTerminal(int(os.Stdout.Fd())), - now: time.Now, - environ: os.Environ, - } - - for _, env := range config.Env { - a.env = append(a.env, env.Name+"="+env.Value) - } - - return c.put(key, a), nil -} - -// Authenticator is a client credential provider that rotates credentials by executing a plugin. -// The plugin input and output are defined by the API group client.authentication.k8s.io. -type Authenticator struct { - // Set by the config - cmd string - args []string - group schema.GroupVersion - env []string - - // Stubbable for testing - stdin io.Reader - stderr io.Writer - interactive bool - now func() time.Time - environ func() []string - - // Cached results. - // - // The mutex also guards calling the plugin. Since the plugin could be - // interactive we want to make sure it's only called once. - mu sync.Mutex - cachedCreds *credentials - exp time.Time - - onRotate func() -} - -type credentials struct { - token string - cert *tls.Certificate -} - -// UpdateTransportConfig updates the transport.Config to use credentials -// returned by the plugin. -func (a *Authenticator) UpdateTransportConfig(c *transport.Config) error { - wt := c.WrapTransport - c.WrapTransport = func(rt http.RoundTripper) http.RoundTripper { - if wt != nil { - rt = wt(rt) - } - return &roundTripper{a, rt} - } - - if c.TLS.GetCert != nil { - return errors.New("can't add TLS certificate callback: transport.Config.TLS.GetCert already set") - } - c.TLS.GetCert = a.cert - - var dial func(ctx context.Context, network, addr string) (net.Conn, error) - if c.Dial != nil { - dial = c.Dial - } else { - dial = (&net.Dialer{Timeout: 30 * time.Second, KeepAlive: 30 * time.Second}).DialContext - } - d := connrotation.NewDialer(dial) - a.onRotate = d.CloseAll - c.Dial = d.DialContext - - return nil -} - -type roundTripper struct { - a *Authenticator - base http.RoundTripper -} - -func (r *roundTripper) RoundTrip(req *http.Request) (*http.Response, error) { - // If a user has already set credentials, use that. This makes commands like - // "kubectl get --token (token) pods" work. - if req.Header.Get("Authorization") != "" { - return r.base.RoundTrip(req) - } - - creds, err := r.a.getCreds() - if err != nil { - return nil, fmt.Errorf("getting credentials: %v", err) - } - if creds.token != "" { - req.Header.Set("Authorization", "Bearer "+creds.token) - } - - res, err := r.base.RoundTrip(req) - if err != nil { - return nil, err - } - if res.StatusCode == http.StatusUnauthorized { - resp := &clientauthentication.Response{ - Header: res.Header, - Code: int32(res.StatusCode), - } - if err := r.a.maybeRefreshCreds(creds, resp); err != nil { - klog.Errorf("refreshing credentials: %v", err) - } - } - return res, nil -} - -func (a *Authenticator) credsExpired() bool { - if a.exp.IsZero() { - return false - } - return a.now().After(a.exp) -} - -func (a *Authenticator) cert() (*tls.Certificate, error) { - creds, err := a.getCreds() - if err != nil { - return nil, err - } - return creds.cert, nil -} - -func (a *Authenticator) getCreds() (*credentials, error) { - a.mu.Lock() - defer a.mu.Unlock() - if a.cachedCreds != nil && !a.credsExpired() { - return a.cachedCreds, nil - } - - if err := a.refreshCredsLocked(nil); err != nil { - return nil, err - } - return a.cachedCreds, nil -} - -// maybeRefreshCreds executes the plugin to force a rotation of the -// credentials, unless they were rotated already. -func (a *Authenticator) maybeRefreshCreds(creds *credentials, r *clientauthentication.Response) error { - a.mu.Lock() - defer a.mu.Unlock() - - // Since we're not making a new pointer to a.cachedCreds in getCreds, no - // need to do deep comparison. - if creds != a.cachedCreds { - // Credentials already rotated. - return nil - } - - return a.refreshCredsLocked(r) -} - -// refreshCredsLocked executes the plugin and reads the credentials from -// stdout. It must be called while holding the Authenticator's mutex. -func (a *Authenticator) refreshCredsLocked(r *clientauthentication.Response) error { - cred := &clientauthentication.ExecCredential{ - Spec: clientauthentication.ExecCredentialSpec{ - Response: r, - Interactive: a.interactive, - }, - } - - env := append(a.environ(), a.env...) - if a.group == v1alpha1.SchemeGroupVersion { - // Input spec disabled for beta due to lack of use. Possibly re-enable this later if - // someone wants it back. - // - // See: https://github.com/kubernetes/kubernetes/issues/61796 - data, err := runtime.Encode(codecs.LegacyCodec(a.group), cred) - if err != nil { - return fmt.Errorf("encode ExecCredentials: %v", err) - } - env = append(env, fmt.Sprintf("%s=%s", execInfoEnv, data)) - } - - stdout := &bytes.Buffer{} - cmd := exec.Command(a.cmd, a.args...) - cmd.Env = env - cmd.Stderr = a.stderr - cmd.Stdout = stdout - if a.interactive { - cmd.Stdin = a.stdin - } - - if err := cmd.Run(); err != nil { - return fmt.Errorf("exec: %v", err) - } - - _, gvk, err := codecs.UniversalDecoder(a.group).Decode(stdout.Bytes(), nil, cred) - if err != nil { - return fmt.Errorf("decoding stdout: %v", err) - } - if gvk.Group != a.group.Group || gvk.Version != a.group.Version { - return fmt.Errorf("exec plugin is configured to use API version %s, plugin returned version %s", - a.group, schema.GroupVersion{Group: gvk.Group, Version: gvk.Version}) - } - - if cred.Status == nil { - return fmt.Errorf("exec plugin didn't return a status field") - } - if cred.Status.Token == "" && cred.Status.ClientCertificateData == "" && cred.Status.ClientKeyData == "" { - return fmt.Errorf("exec plugin didn't return a token or cert/key pair") - } - if (cred.Status.ClientCertificateData == "") != (cred.Status.ClientKeyData == "") { - return fmt.Errorf("exec plugin returned only certificate or key, not both") - } - - if cred.Status.ExpirationTimestamp != nil { - a.exp = cred.Status.ExpirationTimestamp.Time - } else { - a.exp = time.Time{} - } - - newCreds := &credentials{ - token: cred.Status.Token, - } - if cred.Status.ClientKeyData != "" && cred.Status.ClientCertificateData != "" { - cert, err := tls.X509KeyPair([]byte(cred.Status.ClientCertificateData), []byte(cred.Status.ClientKeyData)) - if err != nil { - return fmt.Errorf("failed parsing client key/certificate: %v", err) - } - newCreds.cert = &cert - } - - oldCreds := a.cachedCreds - a.cachedCreds = newCreds - // Only close all connections when TLS cert rotates. Token rotation doesn't - // need the extra noise. - if a.onRotate != nil && oldCreds != nil && !reflect.DeepEqual(oldCreds.cert, a.cachedCreds.cert) { - a.onRotate() - } - return nil -} diff --git a/staging/src/k8s.io/client-go/plugin/pkg/client/auth/exec/exec_test.go b/staging/src/k8s.io/client-go/plugin/pkg/client/auth/exec/exec_test.go deleted file mode 100644 index e3398e821e..0000000000 --- a/staging/src/k8s.io/client-go/plugin/pkg/client/auth/exec/exec_test.go +++ /dev/null @@ -1,748 +0,0 @@ -/* -Copyright 2018 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package exec - -import ( - "bytes" - "crypto/ecdsa" - "crypto/elliptic" - "crypto/rand" - "crypto/tls" - "crypto/x509" - "crypto/x509/pkix" - "encoding/json" - "encoding/pem" - "fmt" - "io/ioutil" - "math/big" - "net/http" - "net/http/httptest" - "reflect" - "strings" - "testing" - "time" - - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/client-go/pkg/apis/clientauthentication" - "k8s.io/client-go/tools/clientcmd/api" - "k8s.io/client-go/transport" -) - -var ( - certData = []byte(`-----BEGIN CERTIFICATE----- -MIIC6jCCAdSgAwIBAgIBCzALBgkqhkiG9w0BAQswIzEhMB8GA1UEAwwYMTAuMTMu -MTI5LjEwNkAxNDIxMzU5MDU4MB4XDTE1MDExNTIyMDEzMVoXDTE2MDExNTIyMDEz -MlowGzEZMBcGA1UEAxMQb3BlbnNoaWZ0LWNsaWVudDCCASIwDQYJKoZIhvcNAQEB -BQADggEPADCCAQoCggEBAKtdhz0+uCLXw5cSYns9rU/XifFSpb/x24WDdrm72S/v -b9BPYsAStiP148buylr1SOuNi8sTAZmlVDDIpIVwMLff+o2rKYDicn9fjbrTxTOj -lI4pHJBH+JU3AJ0tbajupioh70jwFS0oYpwtneg2zcnE2Z4l6mhrj2okrc5Q1/X2 -I2HChtIU4JYTisObtin10QKJX01CLfYXJLa8upWzKZ4/GOcHG+eAV3jXWoXidtjb -1Usw70amoTZ6mIVCkiu1QwCoa8+ycojGfZhvqMsAp1536ZcCul+Na+AbCv4zKS7F -kQQaImVrXdUiFansIoofGlw/JNuoKK6ssVpS5Ic3pgcCAwEAAaM1MDMwDgYDVR0P -AQH/BAQDAgCgMBMGA1UdJQQMMAoGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwCwYJ -KoZIhvcNAQELA4IBAQCKLREH7bXtXtZ+8vI6cjD7W3QikiArGqbl36bAhhWsJLp/ -p/ndKz39iFNaiZ3GlwIURWOOKx3y3GA0x9m8FR+Llthf0EQ8sUjnwaknWs0Y6DQ3 -jjPFZOpV3KPCFrdMJ3++E3MgwFC/Ih/N2ebFX9EcV9Vcc6oVWMdwT0fsrhu683rq -6GSR/3iVX1G/pmOiuaR0fNUaCyCfYrnI4zHBDgSfnlm3vIvN2lrsR/DQBakNL8DJ -HBgKxMGeUPoneBv+c8DMXIL0EhaFXRlBv9QW45/GiAIOuyFJ0i6hCtGZpJjq4OpQ -BRjCI+izPzFTjsxD4aORE+WOkyWFCGPWKfNejfw0 ------END CERTIFICATE-----`) - keyData = []byte(`-----BEGIN RSA PRIVATE KEY----- -MIIEowIBAAKCAQEAq12HPT64ItfDlxJiez2tT9eJ8VKlv/HbhYN2ubvZL+9v0E9i -wBK2I/Xjxu7KWvVI642LyxMBmaVUMMikhXAwt9/6jaspgOJyf1+NutPFM6OUjikc -kEf4lTcAnS1tqO6mKiHvSPAVLShinC2d6DbNycTZniXqaGuPaiStzlDX9fYjYcKG -0hTglhOKw5u2KfXRAolfTUIt9hcktry6lbMpnj8Y5wcb54BXeNdaheJ22NvVSzDv -RqahNnqYhUKSK7VDAKhrz7JyiMZ9mG+oywCnXnfplwK6X41r4BsK/jMpLsWRBBoi -ZWtd1SIVqewiih8aXD8k26gorqyxWlLkhzemBwIDAQABAoIBAD2XYRs3JrGHQUpU -FkdbVKZkvrSY0vAZOqBTLuH0zUv4UATb8487anGkWBjRDLQCgxH+jucPTrztekQK -aW94clo0S3aNtV4YhbSYIHWs1a0It0UdK6ID7CmdWkAj6s0T8W8lQT7C46mWYVLm -5mFnCTHi6aB42jZrqmEpC7sivWwuU0xqj3Ml8kkxQCGmyc9JjmCB4OrFFC8NNt6M -ObvQkUI6Z3nO4phTbpxkE1/9dT0MmPIF7GhHVzJMS+EyyRYUDllZ0wvVSOM3qZT0 -JMUaBerkNwm9foKJ1+dv2nMKZZbJajv7suUDCfU44mVeaEO+4kmTKSGCGjjTBGkr -7L1ySDECgYEA5ElIMhpdBzIivCuBIH8LlUeuzd93pqssO1G2Xg0jHtfM4tz7fyeI -cr90dc8gpli24dkSxzLeg3Tn3wIj/Bu64m2TpZPZEIlukYvgdgArmRIPQVxerYey -OkrfTNkxU1HXsYjLCdGcGXs5lmb+K/kuTcFxaMOs7jZi7La+jEONwf8CgYEAwCs/ -rUOOA0klDsWWisbivOiNPII79c9McZCNBqncCBfMUoiGe8uWDEO4TFHN60vFuVk9 -8PkwpCfvaBUX+ajvbafIfHxsnfk1M04WLGCeqQ/ym5Q4sQoQOcC1b1y9qc/xEWfg -nIUuia0ukYRpl7qQa3tNg+BNFyjypW8zukUAC/kCgYB1/Kojuxx5q5/oQVPrx73k -2bevD+B3c+DYh9MJqSCNwFtUpYIWpggPxoQan4LwdsmO0PKzocb/ilyNFj4i/vII -NToqSc/WjDFpaDIKyuu9oWfhECye45NqLWhb/6VOuu4QA/Nsj7luMhIBehnEAHW+ -GkzTKM8oD1PxpEG3nPKXYQKBgQC6AuMPRt3XBl1NkCrpSBy/uObFlFaP2Enpf39S -3OZ0Gv0XQrnSaL1kP8TMcz68rMrGX8DaWYsgytstR4W+jyy7WvZwsUu+GjTJ5aMG -77uEcEBpIi9CBzivfn7hPccE8ZgqPf+n4i6q66yxBJflW5xhvafJqDtW2LcPNbW/ -bvzdmQKBgExALRUXpq+5dbmkdXBHtvXdRDZ6rVmrnjy4nI5bPw+1GqQqk6uAR6B/ -F6NmLCQOO4PDG/cuatNHIr2FrwTmGdEL6ObLUGWn9Oer9gJhHVqqsY5I4sEPo4XX -stR0Yiw0buV6DL/moUO0HIM9Bjh96HJp+LxiIS6UCdIhMPp5HoQa ------END RSA PRIVATE KEY-----`) - validCert *tls.Certificate -) - -func init() { - cert, err := tls.X509KeyPair(certData, keyData) - if err != nil { - panic(err) - } - validCert = &cert -} - -func TestCacheKey(t *testing.T) { - c1 := &api.ExecConfig{ - Command: "foo-bar", - Args: []string{"1", "2"}, - Env: []api.ExecEnvVar{ - {Name: "3", Value: "4"}, - {Name: "5", Value: "6"}, - {Name: "7", Value: "8"}, - }, - APIVersion: "client.authentication.k8s.io/v1alpha1", - } - c2 := &api.ExecConfig{ - Command: "foo-bar", - Args: []string{"1", "2"}, - Env: []api.ExecEnvVar{ - {Name: "3", Value: "4"}, - {Name: "5", Value: "6"}, - {Name: "7", Value: "8"}, - }, - APIVersion: "client.authentication.k8s.io/v1alpha1", - } - c3 := &api.ExecConfig{ - Command: "foo-bar", - Args: []string{"1", "2"}, - Env: []api.ExecEnvVar{ - {Name: "3", Value: "4"}, - {Name: "5", Value: "6"}, - }, - APIVersion: "client.authentication.k8s.io/v1alpha1", - } - key1 := cacheKey(c1) - key2 := cacheKey(c2) - key3 := cacheKey(c3) - if key1 != key2 { - t.Error("key1 and key2 didn't match") - } - if key1 == key3 { - t.Error("key1 and key3 matched") - } - if key2 == key3 { - t.Error("key2 and key3 matched") - } -} - -func compJSON(t *testing.T, got, want []byte) { - t.Helper() - gotJSON := &bytes.Buffer{} - wantJSON := &bytes.Buffer{} - - if err := json.Indent(gotJSON, got, "", " "); err != nil { - t.Errorf("got invalid JSON: %v", err) - } - if err := json.Indent(wantJSON, want, "", " "); err != nil { - t.Errorf("want invalid JSON: %v", err) - } - g := strings.TrimSpace(gotJSON.String()) - w := strings.TrimSpace(wantJSON.String()) - if g != w { - t.Errorf("wanted %q, got %q", w, g) - } -} - -func TestRefreshCreds(t *testing.T) { - tests := []struct { - name string - config api.ExecConfig - output string - interactive bool - response *clientauthentication.Response - wantInput string - wantCreds credentials - wantExpiry time.Time - wantErr bool - }{ - { - name: "basic-request", - config: api.ExecConfig{ - APIVersion: "client.authentication.k8s.io/v1alpha1", - }, - wantInput: `{ - "kind":"ExecCredential", - "apiVersion":"client.authentication.k8s.io/v1alpha1", - "spec": {} - }`, - output: `{ - "kind": "ExecCredential", - "apiVersion": "client.authentication.k8s.io/v1alpha1", - "status": { - "token": "foo-bar" - } - }`, - wantCreds: credentials{token: "foo-bar"}, - }, - { - name: "interactive", - config: api.ExecConfig{ - APIVersion: "client.authentication.k8s.io/v1alpha1", - }, - interactive: true, - wantInput: `{ - "kind":"ExecCredential", - "apiVersion":"client.authentication.k8s.io/v1alpha1", - "spec": { - "interactive": true - } - }`, - output: `{ - "kind": "ExecCredential", - "apiVersion": "client.authentication.k8s.io/v1alpha1", - "status": { - "token": "foo-bar" - } - }`, - wantCreds: credentials{token: "foo-bar"}, - }, - { - name: "response", - config: api.ExecConfig{ - APIVersion: "client.authentication.k8s.io/v1alpha1", - }, - response: &clientauthentication.Response{ - Header: map[string][]string{ - "WWW-Authenticate": {`Basic realm="Access to the staging site", charset="UTF-8"`}, - }, - Code: 401, - }, - wantInput: `{ - "kind":"ExecCredential", - "apiVersion":"client.authentication.k8s.io/v1alpha1", - "spec": { - "response": { - "header": { - "WWW-Authenticate": [ - "Basic realm=\"Access to the staging site\", charset=\"UTF-8\"" - ] - }, - "code": 401 - } - } - }`, - output: `{ - "kind": "ExecCredential", - "apiVersion": "client.authentication.k8s.io/v1alpha1", - "status": { - "token": "foo-bar" - } - }`, - wantCreds: credentials{token: "foo-bar"}, - }, - { - name: "expiry", - config: api.ExecConfig{ - APIVersion: "client.authentication.k8s.io/v1alpha1", - }, - wantInput: `{ - "kind":"ExecCredential", - "apiVersion":"client.authentication.k8s.io/v1alpha1", - "spec": {} - }`, - output: `{ - "kind": "ExecCredential", - "apiVersion": "client.authentication.k8s.io/v1alpha1", - "status": { - "token": "foo-bar", - "expirationTimestamp": "2006-01-02T15:04:05Z" - } - }`, - wantExpiry: time.Date(2006, 01, 02, 15, 04, 05, 0, time.UTC), - wantCreds: credentials{token: "foo-bar"}, - }, - { - name: "no-group-version", - config: api.ExecConfig{ - APIVersion: "client.authentication.k8s.io/v1alpha1", - }, - wantInput: `{ - "kind":"ExecCredential", - "apiVersion":"client.authentication.k8s.io/v1alpha1", - "spec": {} - }`, - output: `{ - "kind": "ExecCredential", - "status": { - "token": "foo-bar" - } - }`, - wantErr: true, - }, - { - name: "no-status", - config: api.ExecConfig{ - APIVersion: "client.authentication.k8s.io/v1alpha1", - }, - wantInput: `{ - "kind":"ExecCredential", - "apiVersion":"client.authentication.k8s.io/v1alpha1", - "spec": {} - }`, - output: `{ - "kind": "ExecCredential", - "apiVersion":"client.authentication.k8s.io/v1alpha1" - }`, - wantErr: true, - }, - { - name: "no-creds", - config: api.ExecConfig{ - APIVersion: "client.authentication.k8s.io/v1alpha1", - }, - wantInput: `{ - "kind":"ExecCredential", - "apiVersion":"client.authentication.k8s.io/v1alpha1", - "spec": {} - }`, - output: `{ - "kind": "ExecCredential", - "apiVersion":"client.authentication.k8s.io/v1alpha1", - "status": {} - }`, - wantErr: true, - }, - { - name: "TLS credentials", - config: api.ExecConfig{ - APIVersion: "client.authentication.k8s.io/v1alpha1", - }, - wantInput: `{ - "kind":"ExecCredential", - "apiVersion":"client.authentication.k8s.io/v1alpha1", - "spec": {} - }`, - output: fmt.Sprintf(`{ - "kind": "ExecCredential", - "apiVersion": "client.authentication.k8s.io/v1alpha1", - "status": { - "clientKeyData": %q, - "clientCertificateData": %q - } - }`, keyData, certData), - wantCreds: credentials{cert: validCert}, - }, - { - name: "bad TLS credentials", - config: api.ExecConfig{ - APIVersion: "client.authentication.k8s.io/v1alpha1", - }, - wantInput: `{ - "kind":"ExecCredential", - "apiVersion":"client.authentication.k8s.io/v1alpha1", - "spec": {} - }`, - output: `{ - "kind": "ExecCredential", - "apiVersion": "client.authentication.k8s.io/v1alpha1", - "status": { - "clientKeyData": "foo", - "clientCertificateData": "bar" - } - }`, - wantErr: true, - }, - { - name: "cert but no key", - config: api.ExecConfig{ - APIVersion: "client.authentication.k8s.io/v1alpha1", - }, - wantInput: `{ - "kind":"ExecCredential", - "apiVersion":"client.authentication.k8s.io/v1alpha1", - "spec": {} - }`, - output: fmt.Sprintf(`{ - "kind": "ExecCredential", - "apiVersion": "client.authentication.k8s.io/v1alpha1", - "status": { - "clientCertificateData": %q - } - }`, certData), - wantErr: true, - }, - { - name: "beta-basic-request", - config: api.ExecConfig{ - APIVersion: "client.authentication.k8s.io/v1beta1", - }, - output: `{ - "kind": "ExecCredential", - "apiVersion": "client.authentication.k8s.io/v1beta1", - "status": { - "token": "foo-bar" - } - }`, - wantCreds: credentials{token: "foo-bar"}, - }, - { - name: "beta-expiry", - config: api.ExecConfig{ - APIVersion: "client.authentication.k8s.io/v1beta1", - }, - output: `{ - "kind": "ExecCredential", - "apiVersion": "client.authentication.k8s.io/v1beta1", - "status": { - "token": "foo-bar", - "expirationTimestamp": "2006-01-02T15:04:05Z" - } - }`, - wantExpiry: time.Date(2006, 01, 02, 15, 04, 05, 0, time.UTC), - wantCreds: credentials{token: "foo-bar"}, - }, - { - name: "beta-no-group-version", - config: api.ExecConfig{ - APIVersion: "client.authentication.k8s.io/v1beta1", - }, - output: `{ - "kind": "ExecCredential", - "status": { - "token": "foo-bar" - } - }`, - wantErr: true, - }, - { - name: "beta-no-status", - config: api.ExecConfig{ - APIVersion: "client.authentication.k8s.io/v1beta1", - }, - output: `{ - "kind": "ExecCredential", - "apiVersion":"client.authentication.k8s.io/v1beta1" - }`, - wantErr: true, - }, - { - name: "beta-no-token", - config: api.ExecConfig{ - APIVersion: "client.authentication.k8s.io/v1beta1", - }, - output: `{ - "kind": "ExecCredential", - "apiVersion":"client.authentication.k8s.io/v1beta1", - "status": {} - }`, - wantErr: true, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - c := test.config - - c.Command = "./testdata/test-plugin.sh" - c.Env = append(c.Env, api.ExecEnvVar{ - Name: "TEST_OUTPUT", - Value: test.output, - }) - - a, err := newAuthenticator(newCache(), &c) - if err != nil { - t.Fatal(err) - } - - stderr := &bytes.Buffer{} - a.stderr = stderr - a.interactive = test.interactive - a.environ = func() []string { return nil } - - if err := a.refreshCredsLocked(test.response); err != nil { - if !test.wantErr { - t.Errorf("get token %v", err) - } - return - } - if test.wantErr { - t.Fatal("expected error getting token") - } - - if !reflect.DeepEqual(a.cachedCreds, &test.wantCreds) { - t.Errorf("expected credentials %+v got %+v", &test.wantCreds, a.cachedCreds) - } - - if !a.exp.Equal(test.wantExpiry) { - t.Errorf("expected expiry %v got %v", test.wantExpiry, a.exp) - } - - if test.wantInput == "" { - if got := strings.TrimSpace(stderr.String()); got != "" { - t.Errorf("expected no input parameters, got %q", got) - } - return - } - - compJSON(t, stderr.Bytes(), []byte(test.wantInput)) - }) - } -} - -func TestRoundTripper(t *testing.T) { - wantToken := "" - - n := time.Now() - now := func() time.Time { return n } - - env := []string{""} - environ := func() []string { - s := make([]string, len(env)) - copy(s, env) - return s - } - - setOutput := func(s string) { - env[0] = "TEST_OUTPUT=" + s - } - - handler := func(w http.ResponseWriter, r *http.Request) { - gotToken := "" - parts := strings.Split(r.Header.Get("Authorization"), " ") - if len(parts) > 1 && strings.EqualFold(parts[0], "bearer") { - gotToken = parts[1] - } - - if wantToken != gotToken { - http.Error(w, "Unauthorized", http.StatusUnauthorized) - return - } - fmt.Fprintln(w, "ok") - } - server := httptest.NewServer(http.HandlerFunc(handler)) - - c := api.ExecConfig{ - Command: "./testdata/test-plugin.sh", - APIVersion: "client.authentication.k8s.io/v1alpha1", - } - a, err := newAuthenticator(newCache(), &c) - if err != nil { - t.Fatal(err) - } - a.environ = environ - a.now = now - a.stderr = ioutil.Discard - - tc := &transport.Config{} - if err := a.UpdateTransportConfig(tc); err != nil { - t.Fatal(err) - } - client := http.Client{ - Transport: tc.WrapTransport(http.DefaultTransport), - } - - get := func(t *testing.T, statusCode int) { - t.Helper() - resp, err := client.Get(server.URL) - if err != nil { - t.Fatal(err) - } - defer resp.Body.Close() - if resp.StatusCode != statusCode { - t.Errorf("wanted status %d got %d", statusCode, resp.StatusCode) - } - } - - setOutput(`{ - "kind": "ExecCredential", - "apiVersion": "client.authentication.k8s.io/v1alpha1", - "status": { - "token": "token1" - } - }`) - wantToken = "token1" - get(t, http.StatusOK) - - setOutput(`{ - "kind": "ExecCredential", - "apiVersion": "client.authentication.k8s.io/v1alpha1", - "status": { - "token": "token2" - } - }`) - // Previous token should be cached - get(t, http.StatusOK) - - wantToken = "token2" - // Token is still cached, hits unauthorized but causes token to rotate. - get(t, http.StatusUnauthorized) - // Follow up request uses the rotated token. - get(t, http.StatusOK) - - setOutput(`{ - "kind": "ExecCredential", - "apiVersion": "client.authentication.k8s.io/v1alpha1", - "status": { - "token": "token3", - "expirationTimestamp": "` + now().Add(time.Hour).Format(time.RFC3339Nano) + `" - } - }`) - wantToken = "token3" - // Token is still cached, hit's unauthorized but causes rotation to token with an expiry. - get(t, http.StatusUnauthorized) - get(t, http.StatusOK) - - // Move time forward 2 hours, "token3" is now expired. - n = n.Add(time.Hour * 2) - setOutput(`{ - "kind": "ExecCredential", - "apiVersion": "client.authentication.k8s.io/v1alpha1", - "status": { - "token": "token4", - "expirationTimestamp": "` + now().Add(time.Hour).Format(time.RFC3339Nano) + `" - } - }`) - wantToken = "token4" - // Old token is expired, should refresh automatically without hitting a 401. - get(t, http.StatusOK) -} - -func TestTLSCredentials(t *testing.T) { - now := time.Now() - - certPool := x509.NewCertPool() - cert, key := genClientCert(t) - if !certPool.AppendCertsFromPEM(cert) { - t.Fatal("failed to add client cert to CertPool") - } - - server := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - fmt.Fprintln(w, "ok") - })) - server.TLS = &tls.Config{ - ClientAuth: tls.RequireAndVerifyClientCert, - ClientCAs: certPool, - } - server.StartTLS() - defer server.Close() - - a, err := newAuthenticator(newCache(), &api.ExecConfig{ - Command: "./testdata/test-plugin.sh", - APIVersion: "client.authentication.k8s.io/v1alpha1", - }) - if err != nil { - t.Fatal(err) - } - var output *clientauthentication.ExecCredential - a.environ = func() []string { - data, err := runtime.Encode(codecs.LegacyCodec(a.group), output) - if err != nil { - t.Fatal(err) - } - return []string{"TEST_OUTPUT=" + string(data)} - } - a.now = func() time.Time { return now } - a.stderr = ioutil.Discard - - // We're not interested in server's cert, this test is about client cert. - tc := &transport.Config{TLS: transport.TLSConfig{Insecure: true}} - if err := a.UpdateTransportConfig(tc); err != nil { - t.Fatal(err) - } - - get := func(t *testing.T, desc string, wantErr bool) { - t.Run(desc, func(t *testing.T) { - tlsCfg, err := transport.TLSConfigFor(tc) - if err != nil { - t.Fatal("TLSConfigFor:", err) - } - client := http.Client{ - Transport: &http.Transport{TLSClientConfig: tlsCfg}, - } - resp, err := client.Get(server.URL) - switch { - case err != nil && !wantErr: - t.Errorf("got client.Get error: %q, want nil", err) - case err == nil && wantErr: - t.Error("got nil client.Get error, want non-nil") - } - if err == nil { - resp.Body.Close() - } - }) - } - - output = &clientauthentication.ExecCredential{ - Status: &clientauthentication.ExecCredentialStatus{ - ClientCertificateData: string(cert), - ClientKeyData: string(key), - ExpirationTimestamp: &v1.Time{now.Add(time.Hour)}, - }, - } - get(t, "valid TLS cert", false) - - // Advance time to force re-exec. - nCert, nKey := genClientCert(t) - now = now.Add(time.Hour * 2) - output = &clientauthentication.ExecCredential{ - Status: &clientauthentication.ExecCredentialStatus{ - ClientCertificateData: string(nCert), - ClientKeyData: string(nKey), - ExpirationTimestamp: &v1.Time{now.Add(time.Hour)}, - }, - } - get(t, "untrusted TLS cert", true) - - now = now.Add(time.Hour * 2) - output = &clientauthentication.ExecCredential{ - Status: &clientauthentication.ExecCredentialStatus{ - ClientCertificateData: string(cert), - ClientKeyData: string(key), - ExpirationTimestamp: &v1.Time{now.Add(time.Hour)}, - }, - } - get(t, "valid TLS cert again", false) -} - -// genClientCert generates an x509 certificate for testing. Certificate and key -// are returned in PEM encoding. -func genClientCert(t *testing.T) ([]byte, []byte) { - key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - if err != nil { - t.Fatal(err) - } - keyRaw, err := x509.MarshalECPrivateKey(key) - if err != nil { - t.Fatal(err) - } - serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) - serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) - if err != nil { - t.Fatal(err) - } - cert := &x509.Certificate{ - SerialNumber: serialNumber, - Subject: pkix.Name{Organization: []string{"Acme Co"}}, - NotBefore: time.Now(), - NotAfter: time.Now().Add(24 * time.Hour), - - KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, - ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, - BasicConstraintsValid: true, - } - certRaw, err := x509.CreateCertificate(rand.Reader, cert, cert, key.Public(), key) - if err != nil { - t.Fatal(err) - } - return pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certRaw}), - pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: keyRaw}) -} diff --git a/staging/src/k8s.io/client-go/plugin/pkg/client/auth/exec/testdata/test-plugin.sh b/staging/src/k8s.io/client-go/plugin/pkg/client/auth/exec/testdata/test-plugin.sh deleted file mode 100755 index aa7daad5fd..0000000000 --- a/staging/src/k8s.io/client-go/plugin/pkg/client/auth/exec/testdata/test-plugin.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/bash -e - -# Copyright 2018 The Kubernetes Authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - ->&2 echo "$KUBERNETES_EXEC_INFO" -echo "$TEST_OUTPUT" diff --git a/staging/src/k8s.io/client-go/plugin/pkg/client/auth/gcp/BUILD b/staging/src/k8s.io/client-go/plugin/pkg/client/auth/gcp/BUILD deleted file mode 100644 index 1254922771..0000000000 --- a/staging/src/k8s.io/client-go/plugin/pkg/client/auth/gcp/BUILD +++ /dev/null @@ -1,43 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", - "go_test", -) - -go_test( - name = "go_default_test", - srcs = ["gcp_test.go"], - embed = [":go_default_library"], - deps = ["//vendor/golang.org/x/oauth2:go_default_library"], -) - -go_library( - name = "go_default_library", - srcs = ["gcp.go"], - importmap = "k8s.io/kubernetes/vendor/k8s.io/client-go/plugin/pkg/client/auth/gcp", - importpath = "k8s.io/client-go/plugin/pkg/client/auth/gcp", - deps = [ - "//staging/src/k8s.io/apimachinery/pkg/util/net:go_default_library", - "//staging/src/k8s.io/apimachinery/pkg/util/yaml:go_default_library", - "//staging/src/k8s.io/client-go/rest:go_default_library", - "//staging/src/k8s.io/client-go/util/jsonpath:go_default_library", - "//vendor/golang.org/x/oauth2:go_default_library", - "//vendor/golang.org/x/oauth2/google:go_default_library", - "//vendor/k8s.io/klog:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], -) diff --git a/staging/src/k8s.io/client-go/plugin/pkg/client/auth/gcp/OWNERS b/staging/src/k8s.io/client-go/plugin/pkg/client/auth/gcp/OWNERS deleted file mode 100644 index 6cf8db8ac9..0000000000 --- a/staging/src/k8s.io/client-go/plugin/pkg/client/auth/gcp/OWNERS +++ /dev/null @@ -1,6 +0,0 @@ -approvers: -- cjcullen -- jlowdermilk -reviewers: -- cjcullen -- jlowdermilk diff --git a/staging/src/k8s.io/client-go/plugin/pkg/client/auth/gcp/gcp.go b/staging/src/k8s.io/client-go/plugin/pkg/client/auth/gcp/gcp.go deleted file mode 100644 index e44c2adabb..0000000000 --- a/staging/src/k8s.io/client-go/plugin/pkg/client/auth/gcp/gcp.go +++ /dev/null @@ -1,383 +0,0 @@ -/* -Copyright 2016 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package gcp - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "net/http" - "os/exec" - "strings" - "sync" - "time" - - "golang.org/x/oauth2" - "golang.org/x/oauth2/google" - "k8s.io/apimachinery/pkg/util/net" - "k8s.io/apimachinery/pkg/util/yaml" - restclient "k8s.io/client-go/rest" - "k8s.io/client-go/util/jsonpath" - "k8s.io/klog" -) - -func init() { - if err := restclient.RegisterAuthProviderPlugin("gcp", newGCPAuthProvider); err != nil { - klog.Fatalf("Failed to register gcp auth plugin: %v", err) - } -} - -var ( - // Stubbable for testing - execCommand = exec.Command - - // defaultScopes: - // - cloud-platform is the base scope to authenticate to GCP. - // - userinfo.email is used to authenticate to GKE APIs with gserviceaccount - // email instead of numeric uniqueID. - defaultScopes = []string{ - "https://www.googleapis.com/auth/cloud-platform", - "https://www.googleapis.com/auth/userinfo.email"} -) - -// gcpAuthProvider is an auth provider plugin that uses GCP credentials to provide -// tokens for kubectl to authenticate itself to the apiserver. A sample json config -// is provided below with all recognized options described. -// -// { -// 'auth-provider': { -// # Required -// "name": "gcp", -// -// 'config': { -// # Authentication options -// # These options are used while getting a token. -// -// # comma-separated list of GCP API scopes. default value of this field -// # is "https://www.googleapis.com/auth/cloud-platform,https://www.googleapis.com/auth/userinfo.email". -// # to override the API scopes, specify this field explicitly. -// "scopes": "https://www.googleapis.com/auth/cloud-platform" -// -// # Caching options -// -// # Raw string data representing cached access token. -// "access-token": "ya29.CjWdA4GiBPTt", -// # RFC3339Nano expiration timestamp for cached access token. -// "expiry": "2016-10-31 22:31:9.123", -// -// # Command execution options -// # These options direct the plugin to execute a specified command and parse -// # token and expiry time from the output of the command. -// -// # Command to execute for access token. Command output will be parsed as JSON. -// # If "cmd-args" is not present, this value will be split on whitespace, with -// # the first element interpreted as the command, remaining elements as args. -// "cmd-path": "/usr/bin/gcloud", -// -// # Arguments to pass to command to execute for access token. -// "cmd-args": "config config-helper --output=json" -// -// # JSONPath to the string field that represents the access token in -// # command output. If omitted, defaults to "{.access_token}". -// "token-key": "{.credential.access_token}", -// -// # JSONPath to the string field that represents expiration timestamp -// # of the access token in the command output. If omitted, defaults to -// # "{.token_expiry}" -// "expiry-key": ""{.credential.token_expiry}", -// -// # golang reference time in the format that the expiration timestamp uses. -// # If omitted, defaults to time.RFC3339Nano -// "time-fmt": "2006-01-02 15:04:05.999999999" -// } -// } -// } -// -type gcpAuthProvider struct { - tokenSource oauth2.TokenSource - persister restclient.AuthProviderConfigPersister -} - -func newGCPAuthProvider(_ string, gcpConfig map[string]string, persister restclient.AuthProviderConfigPersister) (restclient.AuthProvider, error) { - ts, err := tokenSource(isCmdTokenSource(gcpConfig), gcpConfig) - if err != nil { - return nil, err - } - cts, err := newCachedTokenSource(gcpConfig["access-token"], gcpConfig["expiry"], persister, ts, gcpConfig) - if err != nil { - return nil, err - } - return &gcpAuthProvider{cts, persister}, nil -} - -func isCmdTokenSource(gcpConfig map[string]string) bool { - _, ok := gcpConfig["cmd-path"] - return ok -} - -func tokenSource(isCmd bool, gcpConfig map[string]string) (oauth2.TokenSource, error) { - // Command-based token source - if isCmd { - cmd := gcpConfig["cmd-path"] - if len(cmd) == 0 { - return nil, fmt.Errorf("missing access token cmd") - } - if gcpConfig["scopes"] != "" { - return nil, fmt.Errorf("scopes can only be used when kubectl is using a gcp service account key") - } - var args []string - if cmdArgs, ok := gcpConfig["cmd-args"]; ok { - args = strings.Fields(cmdArgs) - } else { - fields := strings.Fields(cmd) - cmd = fields[0] - args = fields[1:] - } - return newCmdTokenSource(cmd, args, gcpConfig["token-key"], gcpConfig["expiry-key"], gcpConfig["time-fmt"]), nil - } - - // Google Application Credentials-based token source - scopes := parseScopes(gcpConfig) - ts, err := google.DefaultTokenSource(context.Background(), scopes...) - if err != nil { - return nil, fmt.Errorf("cannot construct google default token source: %v", err) - } - return ts, nil -} - -// parseScopes constructs a list of scopes that should be included in token source -// from the config map. -func parseScopes(gcpConfig map[string]string) []string { - scopes, ok := gcpConfig["scopes"] - if !ok { - return defaultScopes - } - if scopes == "" { - return []string{} - } - return strings.Split(gcpConfig["scopes"], ",") -} - -func (g *gcpAuthProvider) WrapTransport(rt http.RoundTripper) http.RoundTripper { - var resetCache map[string]string - if cts, ok := g.tokenSource.(*cachedTokenSource); ok { - resetCache = cts.baseCache() - } else { - resetCache = make(map[string]string) - } - return &conditionalTransport{&oauth2.Transport{Source: g.tokenSource, Base: rt}, g.persister, resetCache} -} - -func (g *gcpAuthProvider) Login() error { return nil } - -type cachedTokenSource struct { - lk sync.Mutex - source oauth2.TokenSource - accessToken string - expiry time.Time - persister restclient.AuthProviderConfigPersister - cache map[string]string -} - -func newCachedTokenSource(accessToken, expiry string, persister restclient.AuthProviderConfigPersister, ts oauth2.TokenSource, cache map[string]string) (*cachedTokenSource, error) { - var expiryTime time.Time - if parsedTime, err := time.Parse(time.RFC3339Nano, expiry); err == nil { - expiryTime = parsedTime - } - if cache == nil { - cache = make(map[string]string) - } - return &cachedTokenSource{ - source: ts, - accessToken: accessToken, - expiry: expiryTime, - persister: persister, - cache: cache, - }, nil -} - -func (t *cachedTokenSource) Token() (*oauth2.Token, error) { - tok := t.cachedToken() - if tok.Valid() && !tok.Expiry.IsZero() { - return tok, nil - } - tok, err := t.source.Token() - if err != nil { - return nil, err - } - cache := t.update(tok) - if t.persister != nil { - if err := t.persister.Persist(cache); err != nil { - klog.V(4).Infof("Failed to persist token: %v", err) - } - } - return tok, nil -} - -func (t *cachedTokenSource) cachedToken() *oauth2.Token { - t.lk.Lock() - defer t.lk.Unlock() - return &oauth2.Token{ - AccessToken: t.accessToken, - TokenType: "Bearer", - Expiry: t.expiry, - } -} - -func (t *cachedTokenSource) update(tok *oauth2.Token) map[string]string { - t.lk.Lock() - defer t.lk.Unlock() - t.accessToken = tok.AccessToken - t.expiry = tok.Expiry - ret := map[string]string{} - for k, v := range t.cache { - ret[k] = v - } - ret["access-token"] = t.accessToken - ret["expiry"] = t.expiry.Format(time.RFC3339Nano) - return ret -} - -// baseCache is the base configuration value for this TokenSource, without any cached ephemeral tokens. -func (t *cachedTokenSource) baseCache() map[string]string { - t.lk.Lock() - defer t.lk.Unlock() - ret := map[string]string{} - for k, v := range t.cache { - ret[k] = v - } - delete(ret, "access-token") - delete(ret, "expiry") - return ret -} - -type commandTokenSource struct { - cmd string - args []string - tokenKey string - expiryKey string - timeFmt string -} - -func newCmdTokenSource(cmd string, args []string, tokenKey, expiryKey, timeFmt string) *commandTokenSource { - if len(timeFmt) == 0 { - timeFmt = time.RFC3339Nano - } - if len(tokenKey) == 0 { - tokenKey = "{.access_token}" - } - if len(expiryKey) == 0 { - expiryKey = "{.token_expiry}" - } - return &commandTokenSource{ - cmd: cmd, - args: args, - tokenKey: tokenKey, - expiryKey: expiryKey, - timeFmt: timeFmt, - } -} - -func (c *commandTokenSource) Token() (*oauth2.Token, error) { - fullCmd := strings.Join(append([]string{c.cmd}, c.args...), " ") - cmd := execCommand(c.cmd, c.args...) - var stderr bytes.Buffer - cmd.Stderr = &stderr - output, err := cmd.Output() - if err != nil { - return nil, fmt.Errorf("error executing access token command %q: err=%v output=%s stderr=%s", fullCmd, err, output, string(stderr.Bytes())) - } - token, err := c.parseTokenCmdOutput(output) - if err != nil { - return nil, fmt.Errorf("error parsing output for access token command %q: %v", fullCmd, err) - } - return token, nil -} - -func (c *commandTokenSource) parseTokenCmdOutput(output []byte) (*oauth2.Token, error) { - output, err := yaml.ToJSON(output) - if err != nil { - return nil, err - } - var data interface{} - if err := json.Unmarshal(output, &data); err != nil { - return nil, err - } - - accessToken, err := parseJSONPath(data, "token-key", c.tokenKey) - if err != nil { - return nil, fmt.Errorf("error parsing token-key %q from %q: %v", c.tokenKey, string(output), err) - } - expiryStr, err := parseJSONPath(data, "expiry-key", c.expiryKey) - if err != nil { - return nil, fmt.Errorf("error parsing expiry-key %q from %q: %v", c.expiryKey, string(output), err) - } - var expiry time.Time - if t, err := time.Parse(c.timeFmt, expiryStr); err != nil { - klog.V(4).Infof("Failed to parse token expiry from %s (fmt=%s): %v", expiryStr, c.timeFmt, err) - } else { - expiry = t - } - - return &oauth2.Token{ - AccessToken: accessToken, - TokenType: "Bearer", - Expiry: expiry, - }, nil -} - -func parseJSONPath(input interface{}, name, template string) (string, error) { - j := jsonpath.New(name) - buf := new(bytes.Buffer) - if err := j.Parse(template); err != nil { - return "", err - } - if err := j.Execute(buf, input); err != nil { - return "", err - } - return buf.String(), nil -} - -type conditionalTransport struct { - oauthTransport *oauth2.Transport - persister restclient.AuthProviderConfigPersister - resetCache map[string]string -} - -var _ net.RoundTripperWrapper = &conditionalTransport{} - -func (t *conditionalTransport) RoundTrip(req *http.Request) (*http.Response, error) { - if len(req.Header.Get("Authorization")) != 0 { - return t.oauthTransport.Base.RoundTrip(req) - } - - res, err := t.oauthTransport.RoundTrip(req) - - if err != nil { - return nil, err - } - - if res.StatusCode == 401 { - klog.V(4).Infof("The credentials that were supplied are invalid for the target cluster") - t.persister.Persist(t.resetCache) - } - - return res, nil -} - -func (t *conditionalTransport) WrappedRoundTripper() http.RoundTripper { return t.oauthTransport.Base } diff --git a/staging/src/k8s.io/client-go/plugin/pkg/client/auth/gcp/gcp_test.go b/staging/src/k8s.io/client-go/plugin/pkg/client/auth/gcp/gcp_test.go deleted file mode 100644 index c8fbb35161..0000000000 --- a/staging/src/k8s.io/client-go/plugin/pkg/client/auth/gcp/gcp_test.go +++ /dev/null @@ -1,527 +0,0 @@ -/* -Copyright 2016 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package gcp - -import ( - "fmt" - "io/ioutil" - "net/http" - "os" - "os/exec" - "reflect" - "strings" - "sync" - "testing" - "time" - - "golang.org/x/oauth2" -) - -type fakeOutput struct { - args []string - output string -} - -var ( - wantCmd []string - // Output for fakeExec, keyed by command - execOutputs = map[string]fakeOutput{ - "/default/no/args": { - args: []string{}, - output: `{ - "access_token": "faketoken", - "token_expiry": "2016-10-31T22:31:09.123000000Z" -}`}, - "/default/legacy/args": { - args: []string{"arg1", "arg2", "arg3"}, - output: `{ - "access_token": "faketoken", - "token_expiry": "2016-10-31T22:31:09.123000000Z" -}`}, - "/space in path/customkeys": { - args: []string{"can", "haz", "auth"}, - output: `{ - "token": "faketoken", - "token_expiry": { - "datetime": "2016-10-31 22:31:09.123" - } -}`}, - "missing/tokenkey/noargs": { - args: []string{}, - output: `{ - "broken": "faketoken", - "token_expiry": { - "datetime": "2016-10-31 22:31:09.123000000Z" - } -}`}, - "missing/expirykey/legacyargs": { - args: []string{"split", "on", "whitespace"}, - output: `{ - "access_token": "faketoken", - "expires": "2016-10-31T22:31:09.123000000Z" -}`}, - "invalid expiry/timestamp": { - args: []string{"foo", "--bar", "--baz=abc,def"}, - output: `{ - "access_token": "faketoken", - "token_expiry": "sometime soon, idk" -}`}, - "badjson": { - args: []string{}, - output: `{ - "access_token": "faketoken", - "token_expiry": "sometime soon, idk" - ------ -`}, - } -) - -func fakeExec(command string, args ...string) *exec.Cmd { - cs := []string{"-test.run=TestHelperProcess", "--", command} - cs = append(cs, args...) - cmd := exec.Command(os.Args[0], cs...) - cmd.Env = []string{"GO_WANT_HELPER_PROCESS=1"} - return cmd -} - -func TestHelperProcess(t *testing.T) { - if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" { - return - } - // Strip out the leading args used to exec into this function. - gotCmd := os.Args[3] - gotArgs := os.Args[4:] - output, ok := execOutputs[gotCmd] - if !ok { - fmt.Fprintf(os.Stdout, "unexpected call cmd=%q args=%v\n", gotCmd, gotArgs) - os.Exit(1) - } else if !reflect.DeepEqual(output.args, gotArgs) { - fmt.Fprintf(os.Stdout, "call cmd=%q got args %v, want: %v\n", gotCmd, gotArgs, output.args) - os.Exit(1) - } - fmt.Fprintf(os.Stdout, output.output) - os.Exit(0) -} - -func Test_isCmdTokenSource(t *testing.T) { - c1 := map[string]string{"cmd-path": "foo"} - if v := isCmdTokenSource(c1); !v { - t.Fatalf("cmd-path present in config (%+v), but got %v", c1, v) - } - - c2 := map[string]string{"cmd-args": "foo bar"} - if v := isCmdTokenSource(c2); v { - t.Fatalf("cmd-path not present in config (%+v), but got %v", c2, v) - } -} - -func Test_tokenSource_cmd(t *testing.T) { - if _, err := tokenSource(true, map[string]string{}); err == nil { - t.Fatalf("expected error, cmd-args not present in config") - } - - c := map[string]string{ - "cmd-path": "foo", - "cmd-args": "bar"} - ts, err := tokenSource(true, c) - if err != nil { - t.Fatalf("failed to return cmd token source: %+v", err) - } - if ts == nil { - t.Fatal("returned nil token source") - } - if _, ok := ts.(*commandTokenSource); !ok { - t.Fatalf("returned token source type:(%T) expected:(*commandTokenSource)", ts) - } -} - -func Test_tokenSource_cmdCannotBeUsedWithScopes(t *testing.T) { - c := map[string]string{ - "cmd-path": "foo", - "scopes": "A,B"} - if _, err := tokenSource(true, c); err == nil { - t.Fatal("expected error when scopes is used with cmd-path") - } -} - -func Test_tokenSource_applicationDefaultCredentials_fails(t *testing.T) { - // try to use empty ADC file - fakeTokenFile, err := ioutil.TempFile("", "adctoken") - if err != nil { - t.Fatalf("failed to create fake token file: +%v", err) - } - fakeTokenFile.Close() - defer os.Remove(fakeTokenFile.Name()) - - os.Setenv("GOOGLE_APPLICATION_CREDENTIALS", fakeTokenFile.Name()) - defer os.Unsetenv("GOOGLE_APPLICATION_CREDENTIALS") - if _, err := tokenSource(false, map[string]string{}); err == nil { - t.Fatalf("expected error because specified ADC token file is not a JSON") - } -} - -func Test_tokenSource_applicationDefaultCredentials(t *testing.T) { - fakeTokenFile, err := ioutil.TempFile("", "adctoken") - if err != nil { - t.Fatalf("failed to create fake token file: +%v", err) - } - fakeTokenFile.Close() - defer os.Remove(fakeTokenFile.Name()) - if err := ioutil.WriteFile(fakeTokenFile.Name(), []byte(`{"type":"service_account"}`), 0600); err != nil { - t.Fatalf("failed to write to fake token file: %+v", err) - } - - os.Setenv("GOOGLE_APPLICATION_CREDENTIALS", fakeTokenFile.Name()) - defer os.Unsetenv("GOOGLE_APPLICATION_CREDENTIALS") - ts, err := tokenSource(false, map[string]string{}) - if err != nil { - t.Fatalf("failed to get a token source: %+v", err) - } - if ts == nil { - t.Fatal("returned nil token source") - } -} - -func Test_parseScopes(t *testing.T) { - cases := []struct { - in map[string]string - out []string - }{ - { - map[string]string{}, - []string{ - "https://www.googleapis.com/auth/cloud-platform", - "https://www.googleapis.com/auth/userinfo.email"}, - }, - { - map[string]string{"scopes": ""}, - []string{}, - }, - { - map[string]string{"scopes": "A,B,C"}, - []string{"A", "B", "C"}, - }, - } - - for _, c := range cases { - got := parseScopes(c.in) - if !reflect.DeepEqual(got, c.out) { - t.Errorf("expected=%v, got=%v", c.out, got) - } - } -} - -func errEquiv(got, want error) bool { - if got == want { - return true - } - if got != nil && want != nil { - return strings.Contains(got.Error(), want.Error()) - } - return false -} - -func TestCmdTokenSource(t *testing.T) { - execCommand = fakeExec - fakeExpiry := time.Date(2016, 10, 31, 22, 31, 9, 123000000, time.UTC) - customFmt := "2006-01-02 15:04:05.999999999" - - tests := []struct { - name string - gcpConfig map[string]string - tok *oauth2.Token - newErr, tokenErr error - }{ - { - "default", - map[string]string{ - "cmd-path": "/default/no/args", - }, - &oauth2.Token{ - AccessToken: "faketoken", - TokenType: "Bearer", - Expiry: fakeExpiry, - }, - nil, - nil, - }, - { - "default legacy args", - map[string]string{ - "cmd-path": "/default/legacy/args arg1 arg2 arg3", - }, - &oauth2.Token{ - AccessToken: "faketoken", - TokenType: "Bearer", - Expiry: fakeExpiry, - }, - nil, - nil, - }, - - { - "custom keys", - map[string]string{ - "cmd-path": "/space in path/customkeys", - "cmd-args": "can haz auth", - "token-key": "{.token}", - "expiry-key": "{.token_expiry.datetime}", - "time-fmt": customFmt, - }, - &oauth2.Token{ - AccessToken: "faketoken", - TokenType: "Bearer", - Expiry: fakeExpiry, - }, - nil, - nil, - }, - { - "missing cmd", - map[string]string{ - "cmd-path": "", - }, - nil, - fmt.Errorf("missing access token cmd"), - nil, - }, - { - "missing token-key", - map[string]string{ - "cmd-path": "missing/tokenkey/noargs", - "token-key": "{.token}", - }, - nil, - nil, - fmt.Errorf("error parsing token-key %q", "{.token}"), - }, - - { - "missing expiry-key", - map[string]string{ - "cmd-path": "missing/expirykey/legacyargs split on whitespace", - "expiry-key": "{.expiry}", - }, - nil, - nil, - fmt.Errorf("error parsing expiry-key %q", "{.expiry}"), - }, - { - "invalid expiry timestamp", - map[string]string{ - "cmd-path": "invalid expiry/timestamp", - "cmd-args": "foo --bar --baz=abc,def", - }, - &oauth2.Token{ - AccessToken: "faketoken", - TokenType: "Bearer", - Expiry: time.Time{}, - }, - nil, - nil, - }, - { - "bad JSON", - map[string]string{ - "cmd-path": "badjson", - }, - nil, - nil, - fmt.Errorf("invalid character '-' after object key:value pair"), - }, - } - - for _, tc := range tests { - provider, err := newGCPAuthProvider("", tc.gcpConfig, nil /* persister */) - if !errEquiv(err, tc.newErr) { - t.Errorf("%q newGCPAuthProvider error: got %v, want %v", tc.name, err, tc.newErr) - continue - } - if err != nil { - continue - } - ts := provider.(*gcpAuthProvider).tokenSource.(*cachedTokenSource).source.(*commandTokenSource) - wantCmd = append([]string{ts.cmd}, ts.args...) - tok, err := ts.Token() - if !errEquiv(err, tc.tokenErr) { - t.Errorf("%q Token() error: got %v, want %v", tc.name, err, tc.tokenErr) - } - if !reflect.DeepEqual(tok, tc.tok) { - t.Errorf("%q Token() got %v, want %v", tc.name, tok, tc.tok) - } - } -} - -type fakePersister struct { - lk sync.Mutex - cache map[string]string -} - -func (f *fakePersister) Persist(cache map[string]string) error { - f.lk.Lock() - defer f.lk.Unlock() - f.cache = map[string]string{} - for k, v := range cache { - f.cache[k] = v - } - return nil -} - -func (f *fakePersister) read() map[string]string { - ret := map[string]string{} - f.lk.Lock() - defer f.lk.Unlock() - for k, v := range f.cache { - ret[k] = v - } - return ret -} - -type fakeTokenSource struct { - token *oauth2.Token - err error -} - -func (f *fakeTokenSource) Token() (*oauth2.Token, error) { - return f.token, f.err -} - -func TestCachedTokenSource(t *testing.T) { - tok := &oauth2.Token{AccessToken: "fakeaccesstoken"} - persister := &fakePersister{} - source := &fakeTokenSource{ - token: tok, - err: nil, - } - cache := map[string]string{ - "foo": "bar", - "baz": "bazinga", - } - ts, err := newCachedTokenSource("fakeaccesstoken", "", persister, source, cache) - if err != nil { - t.Fatal(err) - } - var wg sync.WaitGroup - wg.Add(10) - for i := 0; i < 10; i++ { - go func() { - _, err := ts.Token() - if err != nil { - t.Errorf("unexpected error: %s", err) - } - wg.Done() - }() - } - wg.Wait() - cache["access-token"] = "fakeaccesstoken" - cache["expiry"] = tok.Expiry.Format(time.RFC3339Nano) - if got := persister.read(); !reflect.DeepEqual(got, cache) { - t.Errorf("got cache %v, want %v", got, cache) - } -} - -type MockTransport struct { - res *http.Response -} - -func (t *MockTransport) RoundTrip(req *http.Request) (*http.Response, error) { - return t.res, nil -} - -func Test_cmdTokenSource_roundTrip(t *testing.T) { - - accessToken := "fakeToken" - fakeExpiry := time.Now().Add(time.Hour) - fakeExpiryStr := fakeExpiry.Format(time.RFC3339Nano) - fs := &fakeTokenSource{ - token: &oauth2.Token{ - AccessToken: accessToken, - Expiry: fakeExpiry, - }, - } - - cmdCache := map[string]string{ - "cmd-path": "/path/to/tokensource/cmd", - "cmd-args": "--output=json", - } - cmdCacheUpdated := map[string]string{ - "cmd-path": "/path/to/tokensource/cmd", - "cmd-args": "--output=json", - "access-token": accessToken, - "expiry": fakeExpiryStr, - } - simpleCacheUpdated := map[string]string{ - "access-token": accessToken, - "expiry": fakeExpiryStr, - } - - tests := []struct { - name string - res http.Response - baseCache, expectedCache map[string]string - }{ - { - "Unauthorized", - http.Response{StatusCode: 401}, - make(map[string]string), - make(map[string]string), - }, - { - "Unauthorized, nonempty defaultCache", - http.Response{StatusCode: 401}, - cmdCache, - cmdCache, - }, - { - "Authorized", - http.Response{StatusCode: 200}, - make(map[string]string), - simpleCacheUpdated, - }, - { - "Authorized, nonempty defaultCache", - http.Response{StatusCode: 200}, - cmdCache, - cmdCacheUpdated, - }, - } - - persister := &fakePersister{} - req := http.Request{Header: http.Header{}} - - for _, tc := range tests { - cts, err := newCachedTokenSource(accessToken, fakeExpiry.String(), persister, fs, tc.baseCache) - if err != nil { - t.Fatalf("unexpected error from newCachedTokenSource: %v", err) - } - authProvider := gcpAuthProvider{cts, persister} - - fakeTransport := MockTransport{&tc.res} - transport := (authProvider.WrapTransport(&fakeTransport)) - // call Token to persist/update cache - if _, err := cts.Token(); err != nil { - t.Fatalf("unexpected error from cachedTokenSource.Token(): %v", err) - } - - transport.RoundTrip(&req) - - if got := persister.read(); !reflect.DeepEqual(got, tc.expectedCache) { - t.Errorf("got cache %v, want %v", got, tc.expectedCache) - } - } - -} diff --git a/staging/src/k8s.io/client-go/plugin/pkg/client/auth/oidc/BUILD b/staging/src/k8s.io/client-go/plugin/pkg/client/auth/oidc/BUILD deleted file mode 100644 index f87a2d6589..0000000000 --- a/staging/src/k8s.io/client-go/plugin/pkg/client/auth/oidc/BUILD +++ /dev/null @@ -1,39 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", - "go_test", -) - -go_test( - name = "go_default_test", - srcs = ["oidc_test.go"], - embed = [":go_default_library"], -) - -go_library( - name = "go_default_library", - srcs = ["oidc.go"], - importmap = "k8s.io/kubernetes/vendor/k8s.io/client-go/plugin/pkg/client/auth/oidc", - importpath = "k8s.io/client-go/plugin/pkg/client/auth/oidc", - deps = [ - "//staging/src/k8s.io/apimachinery/pkg/util/net:go_default_library", - "//staging/src/k8s.io/client-go/rest:go_default_library", - "//vendor/golang.org/x/oauth2:go_default_library", - "//vendor/k8s.io/klog:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], -) diff --git a/staging/src/k8s.io/client-go/plugin/pkg/client/auth/oidc/OWNERS b/staging/src/k8s.io/client-go/plugin/pkg/client/auth/oidc/OWNERS deleted file mode 100644 index e0f3c6ca98..0000000000 --- a/staging/src/k8s.io/client-go/plugin/pkg/client/auth/oidc/OWNERS +++ /dev/null @@ -1,5 +0,0 @@ -approvers: -- ericchiang -reviewers: -- ericchiang -- rithujohn191 diff --git a/staging/src/k8s.io/client-go/plugin/pkg/client/auth/oidc/oidc.go b/staging/src/k8s.io/client-go/plugin/pkg/client/auth/oidc/oidc.go deleted file mode 100644 index 1383a97c62..0000000000 --- a/staging/src/k8s.io/client-go/plugin/pkg/client/auth/oidc/oidc.go +++ /dev/null @@ -1,379 +0,0 @@ -/* -Copyright 2016 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package oidc - -import ( - "context" - "encoding/base64" - "encoding/json" - "errors" - "fmt" - "io/ioutil" - "net/http" - "strings" - "sync" - "time" - - "golang.org/x/oauth2" - "k8s.io/apimachinery/pkg/util/net" - restclient "k8s.io/client-go/rest" - "k8s.io/klog" -) - -const ( - cfgIssuerUrl = "idp-issuer-url" - cfgClientID = "client-id" - cfgClientSecret = "client-secret" - cfgCertificateAuthority = "idp-certificate-authority" - cfgCertificateAuthorityData = "idp-certificate-authority-data" - cfgIDToken = "id-token" - cfgRefreshToken = "refresh-token" - - // Unused. Scopes aren't sent during refreshing. - cfgExtraScopes = "extra-scopes" -) - -func init() { - if err := restclient.RegisterAuthProviderPlugin("oidc", newOIDCAuthProvider); err != nil { - klog.Fatalf("Failed to register oidc auth plugin: %v", err) - } -} - -// expiryDelta determines how earlier a token should be considered -// expired than its actual expiration time. It is used to avoid late -// expirations due to client-server time mismatches. -// -// NOTE(ericchiang): this is take from golang.org/x/oauth2 -const expiryDelta = 10 * time.Second - -var cache = newClientCache() - -// Like TLS transports, keep a cache of OIDC clients indexed by issuer URL. This ensures -// current requests from different clients don't concurrently attempt to refresh the same -// set of credentials. -type clientCache struct { - mu sync.RWMutex - - cache map[cacheKey]*oidcAuthProvider -} - -func newClientCache() *clientCache { - return &clientCache{cache: make(map[cacheKey]*oidcAuthProvider)} -} - -type cacheKey struct { - // Canonical issuer URL string of the provider. - issuerURL string - clientID string -} - -func (c *clientCache) getClient(issuer, clientID string) (*oidcAuthProvider, bool) { - c.mu.RLock() - defer c.mu.RUnlock() - client, ok := c.cache[cacheKey{issuer, clientID}] - return client, ok -} - -// setClient attempts to put the client in the cache but may return any clients -// with the same keys set before. This is so there's only ever one client for a provider. -func (c *clientCache) setClient(issuer, clientID string, client *oidcAuthProvider) *oidcAuthProvider { - c.mu.Lock() - defer c.mu.Unlock() - key := cacheKey{issuer, clientID} - - // If another client has already initialized a client for the given provider we want - // to use that client instead of the one we're trying to set. This is so all transports - // share a client and can coordinate around the same mutex when refreshing and writing - // to the kubeconfig. - if oldClient, ok := c.cache[key]; ok { - return oldClient - } - - c.cache[key] = client - return client -} - -func newOIDCAuthProvider(_ string, cfg map[string]string, persister restclient.AuthProviderConfigPersister) (restclient.AuthProvider, error) { - issuer := cfg[cfgIssuerUrl] - if issuer == "" { - return nil, fmt.Errorf("Must provide %s", cfgIssuerUrl) - } - - clientID := cfg[cfgClientID] - if clientID == "" { - return nil, fmt.Errorf("Must provide %s", cfgClientID) - } - - // Check cache for existing provider. - if provider, ok := cache.getClient(issuer, clientID); ok { - return provider, nil - } - - if len(cfg[cfgExtraScopes]) > 0 { - klog.V(2).Infof("%s auth provider field depricated, refresh request don't send scopes", - cfgExtraScopes) - } - - var certAuthData []byte - var err error - if cfg[cfgCertificateAuthorityData] != "" { - certAuthData, err = base64.StdEncoding.DecodeString(cfg[cfgCertificateAuthorityData]) - if err != nil { - return nil, err - } - } - - clientConfig := restclient.Config{ - TLSClientConfig: restclient.TLSClientConfig{ - CAFile: cfg[cfgCertificateAuthority], - CAData: certAuthData, - }, - } - - trans, err := restclient.TransportFor(&clientConfig) - if err != nil { - return nil, err - } - hc := &http.Client{Transport: trans} - - provider := &oidcAuthProvider{ - client: hc, - now: time.Now, - cfg: cfg, - persister: persister, - } - - return cache.setClient(issuer, clientID, provider), nil -} - -type oidcAuthProvider struct { - client *http.Client - - // Method for determining the current time. - now func() time.Time - - // Mutex guards persisting to the kubeconfig file and allows synchronized - // updates to the in-memory config. It also ensures concurrent calls to - // the RoundTripper only trigger a single refresh request. - mu sync.Mutex - cfg map[string]string - persister restclient.AuthProviderConfigPersister -} - -func (p *oidcAuthProvider) WrapTransport(rt http.RoundTripper) http.RoundTripper { - return &roundTripper{ - wrapped: rt, - provider: p, - } -} - -func (p *oidcAuthProvider) Login() error { - return errors.New("not yet implemented") -} - -type roundTripper struct { - provider *oidcAuthProvider - wrapped http.RoundTripper -} - -var _ net.RoundTripperWrapper = &roundTripper{} - -func (r *roundTripper) RoundTrip(req *http.Request) (*http.Response, error) { - if len(req.Header.Get("Authorization")) != 0 { - return r.wrapped.RoundTrip(req) - } - token, err := r.provider.idToken() - if err != nil { - return nil, err - } - - // shallow copy of the struct - r2 := new(http.Request) - *r2 = *req - // deep copy of the Header so we don't modify the original - // request's Header (as per RoundTripper contract). - r2.Header = make(http.Header) - for k, s := range req.Header { - r2.Header[k] = s - } - r2.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) - - return r.wrapped.RoundTrip(r2) -} - -func (t *roundTripper) WrappedRoundTripper() http.RoundTripper { return t.wrapped } - -func (p *oidcAuthProvider) idToken() (string, error) { - p.mu.Lock() - defer p.mu.Unlock() - - if idToken, ok := p.cfg[cfgIDToken]; ok && len(idToken) > 0 { - valid, err := idTokenExpired(p.now, idToken) - if err != nil { - return "", err - } - if valid { - // If the cached id token is still valid use it. - return idToken, nil - } - } - - // Try to request a new token using the refresh token. - rt, ok := p.cfg[cfgRefreshToken] - if !ok || len(rt) == 0 { - return "", errors.New("No valid id-token, and cannot refresh without refresh-token") - } - - // Determine provider's OAuth2 token endpoint. - tokenURL, err := tokenEndpoint(p.client, p.cfg[cfgIssuerUrl]) - if err != nil { - return "", err - } - - config := oauth2.Config{ - ClientID: p.cfg[cfgClientID], - ClientSecret: p.cfg[cfgClientSecret], - Endpoint: oauth2.Endpoint{TokenURL: tokenURL}, - } - - ctx := context.WithValue(context.Background(), oauth2.HTTPClient, p.client) - token, err := config.TokenSource(ctx, &oauth2.Token{RefreshToken: rt}).Token() - if err != nil { - return "", fmt.Errorf("failed to refresh token: %v", err) - } - - idToken, ok := token.Extra("id_token").(string) - if !ok { - // id_token isn't a required part of a refresh token response, so some - // providers (Okta) don't return this value. - // - // See https://github.com/kubernetes/kubernetes/issues/36847 - return "", fmt.Errorf("token response did not contain an id_token, either the scope \"openid\" wasn't requested upon login, or the provider doesn't support id_tokens as part of the refresh response.") - } - - // Create a new config to persist. - newCfg := make(map[string]string) - for key, val := range p.cfg { - newCfg[key] = val - } - - // Update the refresh token if the server returned another one. - if token.RefreshToken != "" && token.RefreshToken != rt { - newCfg[cfgRefreshToken] = token.RefreshToken - } - newCfg[cfgIDToken] = idToken - - // Persist new config and if successful, update the in memory config. - if err = p.persister.Persist(newCfg); err != nil { - return "", fmt.Errorf("could not persist new tokens: %v", err) - } - p.cfg = newCfg - - return idToken, nil -} - -// tokenEndpoint uses OpenID Connect discovery to determine the OAuth2 token -// endpoint for the provider, the endpoint the client will use the refresh -// token against. -func tokenEndpoint(client *http.Client, issuer string) (string, error) { - // Well known URL for getting OpenID Connect metadata. - // - // https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig - wellKnown := strings.TrimSuffix(issuer, "/") + "/.well-known/openid-configuration" - resp, err := client.Get(wellKnown) - if err != nil { - return "", err - } - defer resp.Body.Close() - - body, err := ioutil.ReadAll(resp.Body) - if err != nil { - return "", err - } - if resp.StatusCode != http.StatusOK { - // Don't produce an error that's too huge (e.g. if we get HTML back for some reason). - const n = 80 - if len(body) > n { - body = append(body[:n], []byte("...")...) - } - return "", fmt.Errorf("oidc: failed to query metadata endpoint %s: %q", resp.Status, body) - } - - // Metadata object. We only care about the token_endpoint, the thing endpoint - // we'll be refreshing against. - // - // https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata - var metadata struct { - TokenURL string `json:"token_endpoint"` - } - if err := json.Unmarshal(body, &metadata); err != nil { - return "", fmt.Errorf("oidc: failed to decode provider discovery object: %v", err) - } - if metadata.TokenURL == "" { - return "", fmt.Errorf("oidc: discovery object doesn't contain a token_endpoint") - } - return metadata.TokenURL, nil -} - -func idTokenExpired(now func() time.Time, idToken string) (bool, error) { - parts := strings.Split(idToken, ".") - if len(parts) != 3 { - return false, fmt.Errorf("ID Token is not a valid JWT") - } - - payload, err := base64.RawURLEncoding.DecodeString(parts[1]) - if err != nil { - return false, err - } - var claims struct { - Expiry jsonTime `json:"exp"` - } - if err := json.Unmarshal(payload, &claims); err != nil { - return false, fmt.Errorf("parsing claims: %v", err) - } - - return now().Add(expiryDelta).Before(time.Time(claims.Expiry)), nil -} - -// jsonTime is a json.Unmarshaler that parses a unix timestamp. -// Because JSON numbers don't differentiate between ints and floats, -// we want to ensure we can parse either. -type jsonTime time.Time - -func (j *jsonTime) UnmarshalJSON(b []byte) error { - var n json.Number - if err := json.Unmarshal(b, &n); err != nil { - return err - } - var unix int64 - - if t, err := n.Int64(); err == nil { - unix = t - } else { - f, err := n.Float64() - if err != nil { - return err - } - unix = int64(f) - } - *j = jsonTime(time.Unix(unix, 0)) - return nil -} - -func (j jsonTime) MarshalJSON() ([]byte, error) { - return json.Marshal(time.Time(j).Unix()) -} diff --git a/staging/src/k8s.io/client-go/plugin/pkg/client/auth/oidc/oidc_test.go b/staging/src/k8s.io/client-go/plugin/pkg/client/auth/oidc/oidc_test.go deleted file mode 100644 index c14a3a8490..0000000000 --- a/staging/src/k8s.io/client-go/plugin/pkg/client/auth/oidc/oidc_test.go +++ /dev/null @@ -1,138 +0,0 @@ -/* -Copyright 2016 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package oidc - -import ( - "encoding/base64" - "encoding/json" - "fmt" - "testing" - "time" -) - -func TestJSONTime(t *testing.T) { - data := `{ - "t1": 1493851263, - "t2": 1.493851263e9 - }` - - var v struct { - T1 jsonTime `json:"t1"` - T2 jsonTime `json:"t2"` - } - if err := json.Unmarshal([]byte(data), &v); err != nil { - t.Fatal(err) - } - wantT1 := time.Unix(1493851263, 0) - wantT2 := time.Unix(1493851263, 0) - gotT1 := time.Time(v.T1) - gotT2 := time.Time(v.T2) - - if !wantT1.Equal(gotT1) { - t.Errorf("t1 value: wanted %s got %s", wantT1, gotT1) - } - if !wantT2.Equal(gotT2) { - t.Errorf("t2 value: wanted %s got %s", wantT2, gotT2) - } -} - -func encodeJWT(header, payload, sig string) string { - e := func(s string) string { - return base64.RawURLEncoding.EncodeToString([]byte(s)) - } - return e(header) + "." + e(payload) + "." + e(sig) -} - -func TestExpired(t *testing.T) { - now := time.Now() - - nowFunc := func() time.Time { return now } - - tests := []struct { - name string - idToken string - wantErr bool - wantExpired bool - }{ - { - name: "valid", - idToken: encodeJWT( - "{}", - fmt.Sprintf(`{"exp":%d}`, now.Add(time.Hour).Unix()), - "blah", // signature isn't veified. - ), - }, - { - name: "expired", - idToken: encodeJWT( - "{}", - fmt.Sprintf(`{"exp":%d}`, now.Add(-time.Hour).Unix()), - "blah", // signature isn't veified. - ), - wantExpired: true, - }, - { - name: "bad exp claim", - idToken: encodeJWT( - "{}", - `{"exp":"foobar"}`, - "blah", // signature isn't veified. - ), - wantErr: true, - }, - { - name: "not an id token", - idToken: "notanidtoken", - wantErr: true, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - valid, err := idTokenExpired(nowFunc, test.idToken) - if err != nil { - if !test.wantErr { - t.Errorf("parse error: %v", err) - } - return - } - if test.wantExpired == valid { - t.Errorf("wanted expired %t, got %t", test.wantExpired, !valid) - } - }) - } -} - -func TestClientCache(t *testing.T) { - cache := newClientCache() - - if _, ok := cache.getClient("issuer1", "id1"); ok { - t.Fatalf("got client before putting one in the cache") - } - - cli1 := new(oidcAuthProvider) - cli2 := new(oidcAuthProvider) - - gotcli := cache.setClient("issuer1", "id1", cli1) - if cli1 != gotcli { - t.Fatalf("set first client and got a different one") - } - - gotcli = cache.setClient("issuer1", "id1", cli2) - if cli1 != gotcli { - t.Fatalf("set a second client and didn't get the first") - } -} diff --git a/staging/src/k8s.io/client-go/plugin/pkg/client/auth/openstack/BUILD b/staging/src/k8s.io/client-go/plugin/pkg/client/auth/openstack/BUILD deleted file mode 100644 index 488af00d32..0000000000 --- a/staging/src/k8s.io/client-go/plugin/pkg/client/auth/openstack/BUILD +++ /dev/null @@ -1,40 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", - "go_test", -) - -go_test( - name = "go_default_test", - srcs = ["openstack_test.go"], - embed = [":go_default_library"], -) - -go_library( - name = "go_default_library", - srcs = ["openstack.go"], - importmap = "k8s.io/kubernetes/vendor/k8s.io/client-go/plugin/pkg/client/auth/openstack", - importpath = "k8s.io/client-go/plugin/pkg/client/auth/openstack", - deps = [ - "//staging/src/k8s.io/apimachinery/pkg/util/net:go_default_library", - "//staging/src/k8s.io/client-go/rest:go_default_library", - "//vendor/github.com/gophercloud/gophercloud:go_default_library", - "//vendor/github.com/gophercloud/gophercloud/openstack:go_default_library", - "//vendor/k8s.io/klog:go_default_library", - ], -) - -filegroup( - name = "package-srcs", - srcs = glob(["**"]), - tags = ["automanaged"], - visibility = ["//visibility:private"], -) - -filegroup( - name = "all-srcs", - srcs = [":package-srcs"], - tags = ["automanaged"], -) diff --git a/staging/src/k8s.io/client-go/plugin/pkg/client/auth/openstack/openstack.go b/staging/src/k8s.io/client-go/plugin/pkg/client/auth/openstack/openstack.go deleted file mode 100644 index fab5104ef6..0000000000 --- a/staging/src/k8s.io/client-go/plugin/pkg/client/auth/openstack/openstack.go +++ /dev/null @@ -1,193 +0,0 @@ -/* -Copyright 2017 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package openstack - -import ( - "fmt" - "net/http" - "sync" - "time" - - "github.com/gophercloud/gophercloud" - "github.com/gophercloud/gophercloud/openstack" - "k8s.io/klog" - - "k8s.io/apimachinery/pkg/util/net" - restclient "k8s.io/client-go/rest" -) - -func init() { - if err := restclient.RegisterAuthProviderPlugin("openstack", newOpenstackAuthProvider); err != nil { - klog.Fatalf("Failed to register openstack auth plugin: %s", err) - } -} - -// DefaultTTLDuration is the time before a token gets expired. -const DefaultTTLDuration = 10 * time.Minute - -// openstackAuthProvider is an authprovider for openstack. this provider reads -// the environment variables to determine the client identity, and generates a -// token which will be inserted into the request header later. -type openstackAuthProvider struct { - ttl time.Duration - tokenGetter TokenGetter -} - -// TokenGetter returns a bearer token that can be inserted into request. -type TokenGetter interface { - Token() (string, error) -} - -type tokenGetter struct { - authOpt *gophercloud.AuthOptions -} - -// Token creates a token by authenticate with keystone. -func (t *tokenGetter) Token() (string, error) { - var options gophercloud.AuthOptions - var err error - if t.authOpt == nil { - // reads the config from the environment - klog.V(4).Info("reading openstack config from the environment variables") - options, err = openstack.AuthOptionsFromEnv() - if err != nil { - return "", fmt.Errorf("failed to read openstack env vars: %s", err) - } - } else { - options = *t.authOpt - } - client, err := openstack.AuthenticatedClient(options) - if err != nil { - return "", fmt.Errorf("authentication failed: %s", err) - } - return client.TokenID, nil -} - -// cachedGetter caches a token until it gets expired, after the expiration, it will -// generate another token and cache it. -type cachedGetter struct { - mutex sync.Mutex - tokenGetter TokenGetter - - token string - born time.Time - ttl time.Duration -} - -// Token returns the current available token, create a new one if expired. -func (c *cachedGetter) Token() (string, error) { - c.mutex.Lock() - defer c.mutex.Unlock() - - var err error - // no token or exceeds the TTL - if c.token == "" || time.Since(c.born) > c.ttl { - c.token, err = c.tokenGetter.Token() - if err != nil { - return "", fmt.Errorf("failed to get token: %s", err) - } - c.born = time.Now() - } - return c.token, nil -} - -// tokenRoundTripper implements the RoundTripper interface: adding the bearer token -// into the request header. -type tokenRoundTripper struct { - http.RoundTripper - - tokenGetter TokenGetter -} - -var _ net.RoundTripperWrapper = &tokenRoundTripper{} - -// RoundTrip adds the bearer token into the request. -func (t *tokenRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { - // if the authorization header already present, use it. - if req.Header.Get("Authorization") != "" { - return t.RoundTripper.RoundTrip(req) - } - - token, err := t.tokenGetter.Token() - if err == nil { - req.Header.Set("Authorization", "Bearer "+token) - } else { - klog.V(4).Infof("failed to get token: %s", err) - } - - return t.RoundTripper.RoundTrip(req) -} - -func (t *tokenRoundTripper) WrappedRoundTripper() http.RoundTripper { return t.RoundTripper } - -// newOpenstackAuthProvider creates an auth provider which works with openstack -// environment. -func newOpenstackAuthProvider(_ string, config map[string]string, persister restclient.AuthProviderConfigPersister) (restclient.AuthProvider, error) { - var ttlDuration time.Duration - var err error - - klog.Warningf("WARNING: in-tree openstack auth plugin is now deprecated. please use the \"client-keystone-auth\" kubectl/client-go credential plugin instead") - ttl, found := config["ttl"] - if !found { - ttlDuration = DefaultTTLDuration - // persist to config - config["ttl"] = ttlDuration.String() - if err = persister.Persist(config); err != nil { - return nil, fmt.Errorf("failed to persist config: %s", err) - } - } else { - ttlDuration, err = time.ParseDuration(ttl) - if err != nil { - return nil, fmt.Errorf("failed to parse ttl config: %s", err) - } - } - - authOpt := gophercloud.AuthOptions{ - IdentityEndpoint: config["identityEndpoint"], - Username: config["username"], - Password: config["password"], - DomainName: config["name"], - TenantID: config["tenantId"], - TenantName: config["tenantName"], - } - - getter := tokenGetter{} - // not empty - if (authOpt != gophercloud.AuthOptions{}) { - if len(authOpt.IdentityEndpoint) == 0 { - return nil, fmt.Errorf("empty %q in the config for openstack auth provider", "identityEndpoint") - } - getter.authOpt = &authOpt - } - - return &openstackAuthProvider{ - ttl: ttlDuration, - tokenGetter: &getter, - }, nil -} - -func (oap *openstackAuthProvider) WrapTransport(rt http.RoundTripper) http.RoundTripper { - return &tokenRoundTripper{ - RoundTripper: rt, - tokenGetter: &cachedGetter{ - tokenGetter: oap.tokenGetter, - ttl: oap.ttl, - }, - } -} - -func (oap *openstackAuthProvider) Login() error { return nil } diff --git a/staging/src/k8s.io/client-go/plugin/pkg/client/auth/openstack/openstack_test.go b/staging/src/k8s.io/client-go/plugin/pkg/client/auth/openstack/openstack_test.go deleted file mode 100644 index 24d55b9fca..0000000000 --- a/staging/src/k8s.io/client-go/plugin/pkg/client/auth/openstack/openstack_test.go +++ /dev/null @@ -1,173 +0,0 @@ -/* -Copyright 2017 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package openstack - -import ( - "math/rand" - "net/http" - "testing" - "time" -) - -// testTokenGetter is a simple random token getter. -type testTokenGetter struct{} - -const LetterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" - -func RandStringBytes(n int) string { - b := make([]byte, n) - for i := range b { - b[i] = LetterBytes[rand.Intn(len(LetterBytes))] - } - return string(b) -} - -func (*testTokenGetter) Token() (string, error) { - return RandStringBytes(32), nil -} - -// testRoundTripper is mocked roundtripper which responds with unauthorized when -// there is no authorization header, otherwise returns status ok. -type testRoundTripper struct{} - -func (trt *testRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { - authHeader := req.Header.Get("Authorization") - if authHeader == "" || authHeader == "Bearer " { - return &http.Response{ - StatusCode: http.StatusUnauthorized, - }, nil - } - return &http.Response{StatusCode: http.StatusOK}, nil -} - -func TestOpenstackAuthProvider(t *testing.T) { - trt := &tokenRoundTripper{ - RoundTripper: &testRoundTripper{}, - } - - tests := []struct { - name string - ttl time.Duration - interval time.Duration - same bool - }{ - { - name: "normal", - ttl: 2 * time.Second, - interval: 1 * time.Second, - same: true, - }, - { - name: "expire", - ttl: 1 * time.Second, - interval: 2 * time.Second, - same: false, - }, - } - - for _, test := range tests { - trt.tokenGetter = &cachedGetter{ - tokenGetter: &testTokenGetter{}, - ttl: test.ttl, - } - - req, err := http.NewRequest(http.MethodPost, "https://test-api-server.com", nil) - if err != nil { - t.Errorf("failed to new request: %s", err) - } - trt.RoundTrip(req) - header := req.Header.Get("Authorization") - if header == "" { - t.Errorf("expect to see token in header, but is absent") - } - - time.Sleep(test.interval) - - req, err = http.NewRequest(http.MethodPost, "https://test-api-server.com", nil) - if err != nil { - t.Errorf("failed to new request: %s", err) - } - trt.RoundTrip(req) - newHeader := req.Header.Get("Authorization") - if newHeader == "" { - t.Errorf("expect to see token in header, but is absent") - } - - same := newHeader == header - if same != test.same { - t.Errorf("expect to get %t when compare header, but saw %t", test.same, same) - } - } - -} - -type fakePersister struct{} - -func (i *fakePersister) Persist(map[string]string) error { - return nil -} - -func TestNewOpenstackAuthProvider(t *testing.T) { - tests := []struct { - name string - config map[string]string - expectError bool - }{ - { - name: "normal config without openstack configurations", - config: map[string]string{ - "ttl": "1s", - "foo": "bar", - }, - }, - { - name: "openstack auth provider: missing identityEndpoint", - config: map[string]string{ - "ttl": "1s", - "foo": "bar", - "username": "xyz", - "password": "123", - "tenantName": "admin", - }, - expectError: true, - }, - { - name: "openstack auth provider", - config: map[string]string{ - "ttl": "1s", - "foo": "bar", - "identityEndpoint": "http://controller:35357/v3", - "username": "xyz", - "password": "123", - "tenantName": "admin", - }, - }, - } - - for _, test := range tests { - _, err := newOpenstackAuthProvider("test", test.config, &fakePersister{}) - if err != nil { - if !test.expectError { - t.Errorf("unexpected error: %v", err) - } - } else { - if test.expectError { - t.Error("expect error, but nil") - } - } - } -} diff --git a/staging/src/k8s.io/client-go/plugin/pkg/client/auth/plugins.go b/staging/src/k8s.io/client-go/plugin/pkg/client/auth/plugins.go deleted file mode 100644 index 42085d7ae1..0000000000 --- a/staging/src/k8s.io/client-go/plugin/pkg/client/auth/plugins.go +++ /dev/null @@ -1,25 +0,0 @@ -/* -Copyright 2016 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package auth - -import ( - // Initialize all known client auth plugins. - _ "k8s.io/client-go/plugin/pkg/client/auth/azure" - _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" - _ "k8s.io/client-go/plugin/pkg/client/auth/oidc" - _ "k8s.io/client-go/plugin/pkg/client/auth/openstack" -) diff --git a/staging/src/k8s.io/client-go/rest/transport.go b/staging/src/k8s.io/client-go/rest/transport.go index 25c1801b67..8260f78e74 100644 --- a/staging/src/k8s.io/client-go/rest/transport.go +++ b/staging/src/k8s.io/client-go/rest/transport.go @@ -21,7 +21,6 @@ import ( "errors" "net/http" - "k8s.io/client-go/plugin/pkg/client/auth/exec" "k8s.io/client-go/transport" ) @@ -89,15 +88,6 @@ func (c *Config) TransportConfig() (*transport.Config, error) { return nil, errors.New("execProvider and authProvider cannot be used in combination") } - if c.ExecProvider != nil { - provider, err := exec.GetAuthenticator(c.ExecProvider) - if err != nil { - return nil, err - } - if err := provider.UpdateTransportConfig(conf); err != nil { - return nil, err - } - } if c.AuthProvider != nil { provider, err := GetAuthProvider(c.Host, c.AuthProvider, c.AuthConfigPersister) if err != nil {