diff --git a/cmd/mcpRunner.go b/cmd/mcpRunner.go index 1adbc5317..e24153212 100644 --- a/cmd/mcpRunner.go +++ b/cmd/mcpRunner.go @@ -321,7 +321,7 @@ func (s *mcpServer) hubAuthErrorMessage() string { "URL mode cannot auto-renew it — mint a fresh token (e.g. `kubectl create token kubeshark-cli --audience kubeshark-hub`) " + "and restart with --token (or KUBESHARK_HUB_TOKEN)." } - return "Hub rejected the request (401 Unauthorized): authentication failed. Ensure you have RBAC to mint the kubeshark-cli token (or set a valid license)." + return "Hub rejected the request (401 Unauthorized): the minted kubeshark-cli token was not accepted (check the Hub's AUTH_CLI_SERVICE_ACCOUNTS allowlist and the token audience), or no credential was available — if you lack RBAC to mint the token the CLI falls back to the License-Key, so set a valid license." } func (s *mcpServer) fetchHubMCP() *hubMCPResponse { diff --git a/kubernetes/hubtoken.go b/kubernetes/hubtoken.go index b2e8adf13..f50060699 100644 --- a/kubernetes/hubtoken.go +++ b/kubernetes/hubtoken.go @@ -22,6 +22,10 @@ const ( // hubTokenRenewMargin re-mints this long before expiry so requests in // flight always carry a comfortably valid token. hubTokenRenewMargin = 5 * time.Minute + // hubTokenMintRetryInterval throttles re-minting after a failure so a + // cluster where minting can't succeed (no RBAC, SA missing) doesn't pay a + // blocking CreateToken round-trip on every request. + hubTokenMintRetryInterval = 30 * time.Second ) // MintHubToken requests a short-lived ServiceAccount token (audience @@ -67,9 +71,10 @@ type HubTokenRenewer struct { provider *Provider namespace string - mu sync.Mutex - token string - expireAt time.Time + mu sync.Mutex + token string + expireAt time.Time + nextAttempt time.Time // earliest time to retry minting after a failure } // NewHubTokenRenewer builds a renewer that mints kubeshark-cli tokens in the @@ -84,14 +89,19 @@ func NewHubTokenRenewer(provider *Provider, namespace string) *HubTokenRenewer { func (r *HubTokenRenewer) Token() string { r.mu.Lock() defer r.mu.Unlock() - if r.token == "" || time.Now().After(r.expireAt.Add(-hubTokenRenewMargin)) { + needsMint := r.token == "" || time.Now().After(r.expireAt.Add(-hubTokenRenewMargin)) + if needsMint && time.Now().After(r.nextAttempt) { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) tok, expireAt, err := r.provider.mintHubToken(ctx, r.namespace) cancel() if err != nil { + // Throttle retries and fall back to the prior token (possibly "", + // i.e. the License-Key) so a failing cluster doesn't block every + // request on a doomed mint. + r.nextAttempt = time.Now().Add(hubTokenMintRetryInterval) return r.token } - r.token, r.expireAt = tok, expireAt + r.token, r.expireAt, r.nextAttempt = tok, expireAt, time.Time{} } return r.token }