mirror of
https://github.com/kubernetes/client-go.git
synced 2025-06-20 12:23:22 +00:00
Add client auth plugin for Azure Active Directory
This plugin acquires a fresh access token for apiserver from Azure Active Directory using the device code flow. The access token is saved in the configuration in order to be reused for upcomming accesses to appiserver. In additon the access token is automatically refreshed when expired. Kubernetes-commit: 682d5ec01f37c65117b2496865cc9bf0cd9e0902
This commit is contained in:
parent
fabe644bb3
commit
e12b58626c
16
Godeps/Godeps.json
generated
16
Godeps/Godeps.json
generated
@ -14,6 +14,18 @@
|
||||
"ImportPath": "cloud.google.com/go/internal",
|
||||
"Rev": "3b1ae45394a234c385be014e9a488f2bb6eef821"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/Azure/go-autorest/autorest",
|
||||
"Rev": "d7c034a8af24eda120dd6460bfcd6d9ed14e43ca"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/Azure/go-autorest/autorest/azure",
|
||||
"Rev": "d7c034a8af24eda120dd6460bfcd6d9ed14e43ca"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/Azure/go-autorest/autorest/date",
|
||||
"Rev": "d7c034a8af24eda120dd6460bfcd6d9ed14e43ca"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/PuerkitoBio/purell",
|
||||
"Rev": "8a290539e2e8629dbc4e6bad948158f790ec31f4"
|
||||
@ -58,6 +70,10 @@
|
||||
"ImportPath": "github.com/davecgh/go-spew/spew",
|
||||
"Rev": "5215b55f46b2b919f50a1df0eaa5886afe4e3b3d"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/dgrijalva/jwt-go",
|
||||
"Rev": "01aeca54ebda6e0fbfafd0a524d234159c05ec20"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/docker/distribution/digest",
|
||||
"Rev": "cd27f179f2c10c5d300e6d09025b538c475b0d51"
|
||||
|
@ -12,6 +12,7 @@ go_library(
|
||||
srcs = ["plugins.go"],
|
||||
tags = ["automanaged"],
|
||||
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",
|
||||
],
|
||||
|
29
plugin/pkg/client/auth/azure/BUILD
Normal file
29
plugin/pkg/client/auth/azure/BUILD
Normal file
@ -0,0 +1,29 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["azure_test.go"],
|
||||
library = ":go_default_library",
|
||||
tags = ["automanaged"],
|
||||
deps = ["//vendor/github.com/Azure/go-autorest/autorest/azure:go_default_library"],
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["azure.go"],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//vendor/github.com/Azure/go-autorest/autorest: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/client-go/rest:go_default_library",
|
||||
],
|
||||
)
|
48
plugin/pkg/client/auth/azure/README.md
Normal file
48
plugin/pkg/client/auth/azure/README.md
Normal file
@ -0,0 +1,48 @@
|
||||
# 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.
|
||||
|
||||
|
||||
## 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)
|
||||
|
||||
2. Create a second Azure Active Directory native application for `kubectl`
|
||||
|
||||
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.
|
||||
|
||||
5. Configure the `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
|
||||
|
||||
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 next commands.
|
353
plugin/pkg/client/auth/azure/azure.go
Normal file
353
plugin/pkg/client/auth/azure/azure.go
Normal file
@ -0,0 +1,353 @@
|
||||
/*
|
||||
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 (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"github.com/Azure/go-autorest/autorest"
|
||||
"github.com/Azure/go-autorest/autorest/azure"
|
||||
"github.com/golang/glog"
|
||||
|
||||
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 {
|
||||
glog.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
|
||||
}
|
||||
|
||||
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 {
|
||||
glog.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)
|
||||
}
|
||||
|
||||
type azureToken struct {
|
||||
token azure.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: azure.Token{
|
||||
AccessToken: accessToken,
|
||||
RefreshToken: refreshToken,
|
||||
ExpiresIn: expiresIn,
|
||||
ExpiresOn: expiresOn,
|
||||
NotBefore: 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] = token.token.ExpiresIn
|
||||
newCfg[cfgExpiresOn] = 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 := azure.PublicCloud.OAuthConfigForTenant(token.tenantID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("building the OAuth configuration for token refresh: %v", err)
|
||||
}
|
||||
|
||||
callback := func(t azure.Token) error {
|
||||
return nil
|
||||
}
|
||||
spt, err := azure.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 := ts.environment.OAuthConfigForTenant(ts.tenantID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("building the OAuth configuration for device code authentication: %v", err)
|
||||
}
|
||||
client := &autorest.Client{}
|
||||
deviceCode, err := azure.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 := azure.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
|
||||
}
|
133
plugin/pkg/client/auth/azure/azure_test.go
Normal file
133
plugin/pkg/client/auth/azure/azure_test.go
Normal file
@ -0,0 +1,133 @@
|
||||
/*
|
||||
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 (
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/Azure/go-autorest/autorest/azure"
|
||||
)
|
||||
|
||||
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] = token.token.ExpiresIn
|
||||
cfg[cfgExpiresOn] = token.token.ExpiresOn
|
||||
return cfg
|
||||
}
|
||||
|
||||
func newFackeAzureToken(accessToken string, expiresOn string) azure.Token {
|
||||
return azure.Token{
|
||||
AccessToken: accessToken,
|
||||
RefreshToken: "fake",
|
||||
ExpiresIn: "3600",
|
||||
ExpiresOn: expiresOn,
|
||||
NotBefore: expiresOn,
|
||||
Resource: "fake",
|
||||
Type: "fake",
|
||||
}
|
||||
}
|
@ -18,6 +18,7 @@ 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"
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user