Change:
<!-- Please use this template while reporting a bug and provide as much info as possible. Not doing so may result in your bug not being addressed in a timely manner. Thanks! If the matter is security related, please disclose it privately via --What happened: When using kubectl, authentication fails with the below error For some reason kubectl is adding a lot of s to the start of the SPN - this can be observed in What you expected to happen: Authentication works as in previous versions of Kubectl How to reproduce it (as minimally and precisely as possible): Create a cluster using Azure AD authentication. Authenticate from a client and wait until the token expires. Use Kubectl and have it automatically refresh the token; it will fail due to the extra 'spn:' entries. Anything else we need to know?: Environment: Kubernetes version (use ): Cloud provider or hardware configuration: on-premise OS (e.g: ): macOS 10.14.6 Kernel (e.g. ): Darwin Hostname 18.7.0 Darwin Kernel Version 18.7.0: Mon Feb 10 21:08:45 PST 2020; root:xnu-4903.278.28~1/RELEASEX8664 x86_64 Install tools: N/A Network plugin and version (if this is a network-related bug): N/A Others:
/sig cli
Kubectl version 1.18.0 Kubernetes version 1.16.6
can you confirm whether your original kubeconfig already has in apiserver-id or not? in the mean time, you may try it can convert your kubeconfig from azure module to exec module and do the token thing outside kubectl
At this point if I remove the tokens and try to reauth i get the error described above. I've tried it both ways with apiserver-id having an initially and not, both lead to the same behavior in the end.
i know what's going on now. will prepare a fix. the root cause is that token's target resource (adal.Token.Resource) needs to have prefix and this prefix gets unexpectedly to as well, which gets written back to kubeconfig. So every time kubectl is run, it adds to apiserver-id
/priority important-soon
I'm seeing this with kubectl 1.17.3 and kubernetes 1.16.7. It's unclear to me what versions are impacted and in what version it is fixed, since only version 1.18 is mentioned.
This still happens on kubectl 1.16.12 and 1.17.8
This still happens on kubectl v1.18.5
same
can you guys share your kubectl version --client and kubeconfig with me? you can find me on kubernetes slack. the slack user name is same as my github handle.
Version 1.17.6 works fine.
Since I was mistaken thinking was still causing this for me, I figured I would just drop a note for others in case it's helpful. Turns out my problem was actually due to and looks to have been resolved as of the v3.3.0 release.
This issue has now reappeared for me - although not necessarily the same root cause. AKS 1.19.0 Authentication works, but it spams frequently.

Discussion:
if expiresOn == "" { return nil, fmt.Errorf("no expiresOn in cfg: %s", cfgExpiresOn) } <ins> tokenAudience := resourceID </ins> if ts.configMode == configModeDefault { <del> resourceID = fmt.Sprintf("spn:%s", resourceID) </del> <ins> tokenAudience = fmt.Sprintf("spn:%s", resourceID) </ins> } return &azureToken{

ExpiresIn:    json.Number(expiresIn), ExpiresOn:    json.Number(expiresOn), NotBefore:    json.Number(expiresOn), <del> Resource:     resourceID, </del> <ins> Resource:     tokenAudience, </ins> Type:         tokenType, }, environment: environment,

import ( "encoding/json" "errors" <ins> "fmt" </ins> "net/http" "strconv" "strings"

expectedConfigModes := []string{"1", "0"} for i, configMode := range configModes { <ins> t.Run(fmt.Sprintf("validate token from cfg with configMode %v", configMode), func(t *testing.T) { const ( serverID     = "fakeServerID" clientID     = "fakeClientID" tenantID     = "fakeTenantID" accessToken  = "fakeToken" environment  = "fakeEnvironment" refreshToken = "fakeToken" expiresIn    = "foo" expiresOn    = "foo" ) cfg := map[string]string{ cfgConfigMode:   string(configMode), cfgApiserverID:  serverID, cfgClientID:     clientID, cfgTenantID:     tenantID, cfgEnvironment:  environment, cfgAccessToken:  accessToken, cfgRefreshToken: refreshToken, cfgExpiresIn:    expiresIn, cfgExpiresOn:    expiresOn, } fakeSource := fakeTokenSource{token: newFakeAzureToken("fakeToken", time.Now().Add(3600*time.Second))} persiter := &fakePersister{cache: make(map[string]string)} tokenCache := newAzureTokenCache() tokenSource := newAzureTokenSource(&fakeSource, tokenCache, cfg, configMode, persiter) azTokenSource := tokenSource.(*azureTokenSource) token, err := azTokenSource.retrieveTokenFromCfg() if err != nil { t.Errorf("failed to retrieve the token form cfg: %s", err) } if token.apiserverID != serverID { t.Errorf("expecting token.apiserverID: %s, actual: %s", serverID, token.apiserverID) } if token.clientID != clientID { t.Errorf("expecting token.clientID: %s, actual: %s", clientID, token.clientID) } if token.tenantID != tenantID { t.Errorf("expecting token.tenantID: %s, actual: %s", tenantID, token.tenantID) } expectedAudience := serverID if configMode == configModeDefault { expectedAudience = fmt.Sprintf("spn:%s", serverID) } if token.token.Resource != expectedAudience { t.Errorf("expecting adal token.Resource: %s, actual: %s", expectedAudience, token.token.Resource) } }) </ins> t.Run("validate token against cache", func(t *testing.T) { fakeAccessToken := "fake token 1" fakeSource := fakeTokenSource{token: newFakeAzureToken(fakeAccessToken, time.Now().Add(3600*time.Second))}

} func TestAzureTokenSourceScenarios(t *testing.T) { <del> configMode := configModeDefault </del> expiredToken := newFakeAzureToken("expired token", time.Now().Add(-time.Second)) extendedToken := newFakeAzureToken("extend token", time.Now().Add(1000*time.Second)) fakeToken := newFakeAzureToken("fake token", time.Now().Add(1000*time.Second))

}, } for _, tc := range tests { <del> t.Run(tc.name, func(t *testing.T) { persister := newFakePersister() cfg := map[string]string{} if tc.configToken != nil { cfg = token2Cfg(tc.configToken) } tokenCache := newAzureTokenCache() if tc.cachedToken != nil { tokenCache.setToken(azureTokenKey, tc.cachedToken) } </del> <ins> configModes := []configMode{configModeOmitSPNPrefix, configModeDefault} </ins> <del> fakeSource := fakeTokenSource{ token:        tc.sourceToken, tokenErr:     tc.tokenErr, refreshToken: tc.refreshToken, refreshErr:   tc.refreshErr, } </del> <ins> for _, configMode := range configModes { t.Run(fmt.Sprintf("%s with configMode: %v", tc.name, configMode), func(t *testing.T) { persister := newFakePersister() </ins> <del> tokenSource := newAzureTokenSource(&fakeSource, tokenCache, cfg, configMode, &persister) token, err := tokenSource.Token() </del> <ins> cfg := map[string]string{ cfgConfigMode: string(configMode), } if tc.configToken != nil { cfg = token2Cfg(tc.configToken) } </ins> <del> if fakeSource.tokenCalls != tc.tokenCalls { t.Errorf("expecting tokenCalls: %v, got: %v", tc.tokenCalls, fakeSource.tokenCalls) } </del> <ins> tokenCache := newAzureTokenCache() if tc.cachedToken != nil { tokenCache.setToken(azureTokenKey, tc.cachedToken) } </ins> <del> if fakeSource.refreshCalls != tc.refreshCalls { t.Errorf("expecting refreshCalls: %v, got: %v", tc.refreshCalls, fakeSource.refreshCalls) } </del> <ins> fakeSource := fakeTokenSource{ token:        tc.sourceToken, tokenErr:     tc.tokenErr, refreshToken: tc.refreshToken, refreshErr:   tc.refreshErr, } </ins> <del> if persister.calls != tc.persistCalls { t.Errorf("expecting persister calls: %v, got: %v", tc.persistCalls, persister.calls) } </del> <ins> tokenSource := newAzureTokenSource(&fakeSource, tokenCache, cfg, configMode, &persister) token, err := tokenSource.Token() </ins> <del> if tc.expectErr != "" { if !strings.Contains(err.Error(), tc.expectErr) { t.Errorf("expecting error %v, got %v", tc.expectErr, err) </del> <ins> if token != nil && fakeSource.token != nil && token.apiserverID != fakeSource.token.apiserverID { t.Errorf("expecting apiservierID: %s, got: %s", fakeSource.token.apiserverID, token.apiserverID) </ins> } <del> if token != nil { t.Errorf("token should be nil in err situation, got %v", token) </del> <ins> if fakeSource.tokenCalls != tc.tokenCalls { t.Errorf("expecting tokenCalls: %v, got: %v", tc.tokenCalls, fakeSource.tokenCalls) </ins> } <del> } else { if err != nil { t.Fatalf("error should be nil, got %v", err) </del> <ins> if fakeSource.refreshCalls != tc.refreshCalls { t.Errorf("expecting refreshCalls: %v, got: %v", tc.refreshCalls, fakeSource.refreshCalls) </ins> } <del> if token.token.AccessToken != tc.expectToken.token.AccessToken { t.Errorf("token should have accessToken %v, got %v", token.token.AccessToken, tc.expectToken.token.AccessToken) </del> <ins> if persister.calls != tc.persistCalls { t.Errorf("expecting persister calls: %v, got: %v", tc.persistCalls, persister.calls) </ins> } <del> } }) </del> <ins> if tc.expectErr != "" { if !strings.Contains(err.Error(), tc.expectErr) { t.Errorf("expecting error %v, got %v", tc.expectErr, err) } if token != nil { t.Errorf("token should be nil in err situation, got %v", token) } } else { if err != nil { t.Fatalf("error should be nil, got %v", err) } if token.token.AccessToken != tc.expectToken.token.AccessToken { t.Errorf("token should have accessToken %v, got %v", token.token.AccessToken, tc.expectToken.token.AccessToken) } } }) } </ins> } }