mirror of
https://github.com/ceph/ceph-csi.git
synced 2025-06-14 18:53:35 +00:00
37
vendor/k8s.io/client-go/plugin/pkg/client/auth/BUILD
generated
vendored
37
vendor/k8s.io/client-go/plugin/pkg/client/auth/BUILD
generated
vendored
@ -1,37 +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"],
|
||||
importpath = "k8s.io/client-go/plugin/pkg/client/auth",
|
||||
deps = [
|
||||
"//vendor/k8s.io/client-go/plugin/pkg/client/auth/azure:go_default_library",
|
||||
"//vendor/k8s.io/client-go/plugin/pkg/client/auth/gcp:go_default_library",
|
||||
"//vendor/k8s.io/client-go/plugin/pkg/client/auth/oidc:go_default_library",
|
||||
"//vendor/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/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"],
|
||||
)
|
7
vendor/k8s.io/client-go/plugin/pkg/client/auth/OWNERS
generated
vendored
Normal file
7
vendor/k8s.io/client-go/plugin/pkg/client/auth/OWNERS
generated
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
approvers:
|
||||
- sig-auth-authenticators-approvers
|
||||
reviewers:
|
||||
- sig-auth-authenticators-reviewers
|
||||
labels:
|
||||
- sig/auth
|
||||
|
41
vendor/k8s.io/client-go/plugin/pkg/client/auth/azure/BUILD
generated
vendored
41
vendor/k8s.io/client-go/plugin/pkg/client/auth/azure/BUILD
generated
vendored
@ -1,41 +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"],
|
||||
importpath = "k8s.io/client-go/plugin/pkg/client/auth/azure",
|
||||
deps = [
|
||||
"//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/github.com/golang/glog:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/net:go_default_library",
|
||||
"//vendor/k8s.io/client-go/rest:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
18
vendor/k8s.io/client-go/plugin/pkg/client/auth/azure/README.md
generated
vendored
18
vendor/k8s.io/client-go/plugin/pkg/client/auth/azure/README.md
generated
vendored
@ -1,15 +1,14 @@
|
||||
# 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 stored them in the kubectl configuration. In addition it will refresh and update the tokens in configuration when expired.
|
||||
|
||||
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)
|
||||
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`
|
||||
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
|
||||
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
|
||||
|
||||
@ -21,8 +20,9 @@ This plugin provides an integration with Azure Active Directory device flow. If
|
||||
|
||||
* 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 the `kubectl` to use the `azure` authentication provider
|
||||
5. Configure `kubectl` to use the `azure` authentication provider
|
||||
|
||||
```
|
||||
kubectl config set-credentials "USER_NAME" --auth-provider=azure \
|
||||
@ -35,7 +35,8 @@ This plugin provides an integration with Azure Active Directory device flow. If
|
||||
* 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
|
||||
* 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
|
||||
|
||||
@ -45,4 +46,5 @@ This plugin provides an integration with Azure Active Directory device flow. If
|
||||
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 next commands.
|
||||
* 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.
|
||||
|
19
vendor/k8s.io/client-go/plugin/pkg/client/auth/azure/azure.go
generated
vendored
19
vendor/k8s.io/client-go/plugin/pkg/client/auth/azure/azure.go
generated
vendored
@ -17,6 +17,7 @@ limitations under the License.
|
||||
package azure
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
@ -26,7 +27,7 @@ import (
|
||||
"github.com/Azure/go-autorest/autorest"
|
||||
"github.com/Azure/go-autorest/autorest/adal"
|
||||
"github.com/Azure/go-autorest/autorest/azure"
|
||||
"github.com/golang/glog"
|
||||
"k8s.io/klog"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/net"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
@ -49,7 +50,7 @@ const (
|
||||
|
||||
func init() {
|
||||
if err := restclient.RegisterAuthProviderPlugin("azure", newAzureAuthProvider); err != nil {
|
||||
glog.Fatalf("Failed to register azure auth plugin: %v", err)
|
||||
klog.Fatalf("Failed to register azure auth plugin: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
@ -123,7 +124,7 @@ func (r *azureRoundTripper) RoundTrip(req *http.Request) (*http.Response, error)
|
||||
|
||||
token, err := r.tokenSource.Token()
|
||||
if err != nil {
|
||||
glog.Errorf("Failed to acquire a token: %v", err)
|
||||
klog.Errorf("Failed to acquire a token: %v", err)
|
||||
return nil, fmt.Errorf("acquiring a token for authorization header: %v", err)
|
||||
}
|
||||
|
||||
@ -243,9 +244,9 @@ func (ts *azureTokenSource) retrieveTokenFromCfg() (*azureToken, error) {
|
||||
token: adal.Token{
|
||||
AccessToken: accessToken,
|
||||
RefreshToken: refreshToken,
|
||||
ExpiresIn: expiresIn,
|
||||
ExpiresOn: expiresOn,
|
||||
NotBefore: expiresOn,
|
||||
ExpiresIn: json.Number(expiresIn),
|
||||
ExpiresOn: json.Number(expiresOn),
|
||||
NotBefore: json.Number(expiresOn),
|
||||
Resource: fmt.Sprintf("spn:%s", apiserverID),
|
||||
Type: tokenType,
|
||||
},
|
||||
@ -262,8 +263,8 @@ func (ts *azureTokenSource) storeTokenInCfg(token *azureToken) error {
|
||||
newCfg[cfgClientID] = token.clientID
|
||||
newCfg[cfgTenantID] = token.tenantID
|
||||
newCfg[cfgApiserverID] = token.apiserverID
|
||||
newCfg[cfgExpiresIn] = token.token.ExpiresIn
|
||||
newCfg[cfgExpiresOn] = token.token.ExpiresOn
|
||||
newCfg[cfgExpiresIn] = string(token.token.ExpiresIn)
|
||||
newCfg[cfgExpiresOn] = string(token.token.ExpiresOn)
|
||||
|
||||
err := ts.persister.Persist(newCfg)
|
||||
if err != nil {
|
||||
@ -297,7 +298,7 @@ func (ts *azureTokenSource) refreshToken(token *azureToken) (*azureToken, error)
|
||||
}
|
||||
|
||||
return &azureToken{
|
||||
token: spt.Token,
|
||||
token: spt.Token(),
|
||||
clientID: token.clientID,
|
||||
tenantID: token.tenantID,
|
||||
apiserverID: token.apiserverID,
|
||||
|
9
vendor/k8s.io/client-go/plugin/pkg/client/auth/azure/azure_test.go
generated
vendored
9
vendor/k8s.io/client-go/plugin/pkg/client/auth/azure/azure_test.go
generated
vendored
@ -17,6 +17,7 @@ limitations under the License.
|
||||
package azure
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
@ -115,8 +116,8 @@ func token2Cfg(token *azureToken) map[string]string {
|
||||
cfg[cfgClientID] = token.clientID
|
||||
cfg[cfgTenantID] = token.tenantID
|
||||
cfg[cfgApiserverID] = token.apiserverID
|
||||
cfg[cfgExpiresIn] = token.token.ExpiresIn
|
||||
cfg[cfgExpiresOn] = token.token.ExpiresOn
|
||||
cfg[cfgExpiresIn] = string(token.token.ExpiresIn)
|
||||
cfg[cfgExpiresOn] = string(token.token.ExpiresOn)
|
||||
return cfg
|
||||
}
|
||||
|
||||
@ -125,8 +126,8 @@ func newFackeAzureToken(accessToken string, expiresOn string) adal.Token {
|
||||
AccessToken: accessToken,
|
||||
RefreshToken: "fake",
|
||||
ExpiresIn: "3600",
|
||||
ExpiresOn: expiresOn,
|
||||
NotBefore: expiresOn,
|
||||
ExpiresOn: json.Number(expiresOn),
|
||||
NotBefore: json.Number(expiresOn),
|
||||
Resource: "fake",
|
||||
Type: "fake",
|
||||
}
|
||||
|
361
vendor/k8s.io/client-go/plugin/pkg/client/auth/exec/exec.go
generated
vendored
Normal file
361
vendor/k8s.io/client-go/plugin/pkg/client/auth/exec/exec.go
generated
vendored
Normal file
@ -0,0 +1,361 @@
|
||||
/*
|
||||
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
|
||||
}
|
748
vendor/k8s.io/client-go/plugin/pkg/client/auth/exec/exec_test.go
generated
vendored
Normal file
748
vendor/k8s.io/client-go/plugin/pkg/client/auth/exec/exec_test.go
generated
vendored
Normal file
@ -0,0 +1,748 @@
|
||||
/*
|
||||
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})
|
||||
}
|
18
vendor/k8s.io/client-go/plugin/pkg/client/auth/exec/testdata/test-plugin.sh
generated
vendored
Executable file
18
vendor/k8s.io/client-go/plugin/pkg/client/auth/exec/testdata/test-plugin.sh
generated
vendored
Executable file
@ -0,0 +1,18 @@
|
||||
#!/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"
|
43
vendor/k8s.io/client-go/plugin/pkg/client/auth/gcp/BUILD
generated
vendored
43
vendor/k8s.io/client-go/plugin/pkg/client/auth/gcp/BUILD
generated
vendored
@ -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"],
|
||||
importpath = "k8s.io/client-go/plugin/pkg/client/auth/gcp",
|
||||
deps = [
|
||||
"//vendor/github.com/golang/glog:go_default_library",
|
||||
"//vendor/golang.org/x/net/context:go_default_library",
|
||||
"//vendor/golang.org/x/oauth2:go_default_library",
|
||||
"//vendor/golang.org/x/oauth2/google:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/net:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/yaml:go_default_library",
|
||||
"//vendor/k8s.io/client-go/rest:go_default_library",
|
||||
"//vendor/k8s.io/client-go/util/jsonpath:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
37
vendor/k8s.io/client-go/plugin/pkg/client/auth/gcp/gcp.go
generated
vendored
37
vendor/k8s.io/client-go/plugin/pkg/client/auth/gcp/gcp.go
generated
vendored
@ -18,6 +18,7 @@ package gcp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
@ -26,19 +27,18 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"golang.org/x/net/context"
|
||||
"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 {
|
||||
glog.Fatalf("Failed to register gcp auth plugin: %v", err)
|
||||
klog.Fatalf("Failed to register gcp auth plugin: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
@ -174,7 +174,13 @@ func parseScopes(gcpConfig map[string]string) []string {
|
||||
}
|
||||
|
||||
func (g *gcpAuthProvider) WrapTransport(rt http.RoundTripper) http.RoundTripper {
|
||||
return &conditionalTransport{&oauth2.Transport{Source: g.tokenSource, Base: rt}, g.persister}
|
||||
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 }
|
||||
@ -217,7 +223,7 @@ func (t *cachedTokenSource) Token() (*oauth2.Token, error) {
|
||||
cache := t.update(tok)
|
||||
if t.persister != nil {
|
||||
if err := t.persister.Persist(cache); err != nil {
|
||||
glog.V(4).Infof("Failed to persist token: %v", err)
|
||||
klog.V(4).Infof("Failed to persist token: %v", err)
|
||||
}
|
||||
}
|
||||
return tok, nil
|
||||
@ -247,6 +253,19 @@ func (t *cachedTokenSource) update(tok *oauth2.Token) map[string]string {
|
||||
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
|
||||
@ -310,7 +329,7 @@ func (c *commandTokenSource) parseTokenCmdOutput(output []byte) (*oauth2.Token,
|
||||
}
|
||||
var expiry time.Time
|
||||
if t, err := time.Parse(c.timeFmt, expiryStr); err != nil {
|
||||
glog.V(4).Infof("Failed to parse token expiry from %s (fmt=%s): %v", expiryStr, c.timeFmt, err)
|
||||
klog.V(4).Infof("Failed to parse token expiry from %s (fmt=%s): %v", expiryStr, c.timeFmt, err)
|
||||
} else {
|
||||
expiry = t
|
||||
}
|
||||
@ -337,6 +356,7 @@ func parseJSONPath(input interface{}, name, template string) (string, error) {
|
||||
type conditionalTransport struct {
|
||||
oauthTransport *oauth2.Transport
|
||||
persister restclient.AuthProviderConfigPersister
|
||||
resetCache map[string]string
|
||||
}
|
||||
|
||||
var _ net.RoundTripperWrapper = &conditionalTransport{}
|
||||
@ -353,9 +373,8 @@ func (t *conditionalTransport) RoundTrip(req *http.Request) (*http.Response, err
|
||||
}
|
||||
|
||||
if res.StatusCode == 401 {
|
||||
glog.V(4).Infof("The credentials that were supplied are invalid for the target cluster")
|
||||
emptyCache := make(map[string]string)
|
||||
t.persister.Persist(emptyCache)
|
||||
klog.V(4).Infof("The credentials that were supplied are invalid for the target cluster")
|
||||
t.persister.Persist(t.resetCache)
|
||||
}
|
||||
|
||||
return res, nil
|
||||
|
70
vendor/k8s.io/client-go/plugin/pkg/client/auth/gcp/gcp_test.go
generated
vendored
70
vendor/k8s.io/client-go/plugin/pkg/client/auth/gcp/gcp_test.go
generated
vendored
@ -442,37 +442,61 @@ func (t *MockTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
return t.res, nil
|
||||
}
|
||||
|
||||
func TestClearingCredentials(t *testing.T) {
|
||||
func Test_cmdTokenSource_roundTrip(t *testing.T) {
|
||||
|
||||
accessToken := "fakeToken"
|
||||
fakeExpiry := time.Now().Add(time.Hour)
|
||||
|
||||
cache := map[string]string{
|
||||
"access-token": "fakeToken",
|
||||
"expiry": fakeExpiry.String(),
|
||||
fakeExpiryStr := fakeExpiry.Format(time.RFC3339Nano)
|
||||
fs := &fakeTokenSource{
|
||||
token: &oauth2.Token{
|
||||
AccessToken: accessToken,
|
||||
Expiry: fakeExpiry,
|
||||
},
|
||||
}
|
||||
|
||||
cts := cachedTokenSource{
|
||||
source: nil,
|
||||
accessToken: cache["access-token"],
|
||||
expiry: fakeExpiry,
|
||||
persister: nil,
|
||||
cache: nil,
|
||||
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
|
||||
cache map[string]string
|
||||
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},
|
||||
cache,
|
||||
make(map[string]string),
|
||||
simpleCacheUpdated,
|
||||
},
|
||||
{
|
||||
"Authorized, nonempty defaultCache",
|
||||
http.Response{StatusCode: 200},
|
||||
cmdCache,
|
||||
cmdCacheUpdated,
|
||||
},
|
||||
}
|
||||
|
||||
@ -480,17 +504,23 @@ func TestClearingCredentials(t *testing.T) {
|
||||
req := http.Request{Header: http.Header{}}
|
||||
|
||||
for _, tc := range tests {
|
||||
authProvider := gcpAuthProvider{&cts, persister}
|
||||
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))
|
||||
persister.Persist(cache)
|
||||
// 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.cache) {
|
||||
t.Errorf("got cache %v, want %v", got, tc.cache)
|
||||
if got := persister.read(); !reflect.DeepEqual(got, tc.expectedCache) {
|
||||
t.Errorf("got cache %v, want %v", got, tc.expectedCache)
|
||||
}
|
||||
}
|
||||
|
||||
|
38
vendor/k8s.io/client-go/plugin/pkg/client/auth/oidc/BUILD
generated
vendored
38
vendor/k8s.io/client-go/plugin/pkg/client/auth/oidc/BUILD
generated
vendored
@ -1,38 +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"],
|
||||
importpath = "k8s.io/client-go/plugin/pkg/client/auth/oidc",
|
||||
deps = [
|
||||
"//vendor/github.com/golang/glog:go_default_library",
|
||||
"//vendor/golang.org/x/oauth2:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/net:go_default_library",
|
||||
"//vendor/k8s.io/client-go/rest:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
1
vendor/k8s.io/client-go/plugin/pkg/client/auth/oidc/OWNERS
generated
vendored
1
vendor/k8s.io/client-go/plugin/pkg/client/auth/oidc/OWNERS
generated
vendored
@ -2,3 +2,4 @@ approvers:
|
||||
- ericchiang
|
||||
reviewers:
|
||||
- ericchiang
|
||||
- rithujohn191
|
||||
|
8
vendor/k8s.io/client-go/plugin/pkg/client/auth/oidc/oidc.go
generated
vendored
8
vendor/k8s.io/client-go/plugin/pkg/client/auth/oidc/oidc.go
generated
vendored
@ -28,10 +28,10 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"golang.org/x/oauth2"
|
||||
"k8s.io/apimachinery/pkg/util/net"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
"k8s.io/klog"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -49,7 +49,7 @@ const (
|
||||
|
||||
func init() {
|
||||
if err := restclient.RegisterAuthProviderPlugin("oidc", newOIDCAuthProvider); err != nil {
|
||||
glog.Fatalf("Failed to register oidc auth plugin: %v", err)
|
||||
klog.Fatalf("Failed to register oidc auth plugin: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
@ -124,7 +124,7 @@ func newOIDCAuthProvider(_ string, cfg map[string]string, persister restclient.A
|
||||
}
|
||||
|
||||
if len(cfg[cfgExtraScopes]) > 0 {
|
||||
glog.V(2).Infof("%s auth provider field depricated, refresh request don't send scopes",
|
||||
klog.V(2).Infof("%s auth provider field depricated, refresh request don't send scopes",
|
||||
cfgExtraScopes)
|
||||
}
|
||||
|
||||
@ -279,7 +279,7 @@ func (p *oidcAuthProvider) idToken() (string, error) {
|
||||
|
||||
// Persist new config and if successful, update the in memory config.
|
||||
if err = p.persister.Persist(newCfg); err != nil {
|
||||
return "", fmt.Errorf("could not perist new tokens: %v", err)
|
||||
return "", fmt.Errorf("could not persist new tokens: %v", err)
|
||||
}
|
||||
p.cfg = newCfg
|
||||
|
||||
|
38
vendor/k8s.io/client-go/plugin/pkg/client/auth/openstack/BUILD
generated
vendored
38
vendor/k8s.io/client-go/plugin/pkg/client/auth/openstack/BUILD
generated
vendored
@ -1,38 +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"],
|
||||
importpath = "k8s.io/client-go/plugin/pkg/client/auth/openstack",
|
||||
deps = [
|
||||
"//vendor/github.com/golang/glog:go_default_library",
|
||||
"//vendor/github.com/gophercloud/gophercloud/openstack:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/net:go_default_library",
|
||||
"//vendor/k8s.io/client-go/rest:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
55
vendor/k8s.io/client-go/plugin/pkg/client/auth/openstack/openstack.go
generated
vendored
55
vendor/k8s.io/client-go/plugin/pkg/client/auth/openstack/openstack.go
generated
vendored
@ -22,8 +22,9 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/gophercloud/gophercloud"
|
||||
"github.com/gophercloud/gophercloud/openstack"
|
||||
"k8s.io/klog"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/net"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
@ -31,7 +32,7 @@ import (
|
||||
|
||||
func init() {
|
||||
if err := restclient.RegisterAuthProviderPlugin("openstack", newOpenstackAuthProvider); err != nil {
|
||||
glog.Fatalf("Failed to register openstack auth plugin: %s", err)
|
||||
klog.Fatalf("Failed to register openstack auth plugin: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
@ -42,8 +43,7 @@ const DefaultTTLDuration = 10 * time.Minute
|
||||
// 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
|
||||
|
||||
ttl time.Duration
|
||||
tokenGetter TokenGetter
|
||||
}
|
||||
|
||||
@ -52,13 +52,23 @@ type TokenGetter interface {
|
||||
Token() (string, error)
|
||||
}
|
||||
|
||||
type tokenGetter struct{}
|
||||
type tokenGetter struct {
|
||||
authOpt *gophercloud.AuthOptions
|
||||
}
|
||||
|
||||
// Token creates a token by authenticate with keystone.
|
||||
func (*tokenGetter) Token() (string, error) {
|
||||
options, err := openstack.AuthOptionsFromEnv()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to read openstack env vars: %s", err)
|
||||
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 {
|
||||
@ -85,7 +95,7 @@ func (c *cachedGetter) Token() (string, error) {
|
||||
|
||||
var err error
|
||||
// no token or exceeds the TTL
|
||||
if c.token == "" || time.Now().Sub(c.born) > c.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)
|
||||
@ -116,7 +126,7 @@ func (t *tokenRoundTripper) RoundTrip(req *http.Request) (*http.Response, error)
|
||||
if err == nil {
|
||||
req.Header.Set("Authorization", "Bearer "+token)
|
||||
} else {
|
||||
glog.V(4).Infof("failed to get token: %s", err)
|
||||
klog.V(4).Infof("failed to get token: %s", err)
|
||||
}
|
||||
|
||||
return t.RoundTripper.RoundTrip(req)
|
||||
@ -126,10 +136,11 @@ func (t *tokenRoundTripper) WrappedRoundTripper() http.RoundTripper { return t.R
|
||||
|
||||
// newOpenstackAuthProvider creates an auth provider which works with openstack
|
||||
// environment.
|
||||
func newOpenstackAuthProvider(clusterAddress string, config map[string]string, persister restclient.AuthProviderConfigPersister) (restclient.AuthProvider, error) {
|
||||
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
|
||||
@ -145,11 +156,27 @@ func newOpenstackAuthProvider(clusterAddress string, config map[string]string, p
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: read/persist client configuration(OS_XXX env vars) in config
|
||||
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: &tokenGetter{},
|
||||
tokenGetter: &getter,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
57
vendor/k8s.io/client-go/plugin/pkg/client/auth/openstack/openstack_test.go
generated
vendored
57
vendor/k8s.io/client-go/plugin/pkg/client/auth/openstack/openstack_test.go
generated
vendored
@ -114,3 +114,60 @@ func TestOpenstackAuthProvider(t *testing.T) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user