cli/mcp: throttle hub-token re-mint on failure; accurate proxy 401 message

Review follow-up. HubTokenRenewer.Token() re-minted on every request while
the token was empty, so a cluster where minting can't succeed (no RBAC, SA
missing) paid a blocking ~10s CreateToken round-trip per request (under the
lock). Add a 30s retry throttle: on mint failure, back off and fall back to
the prior token / License-Key. Also correct the proxy-mode 401 message — a
token-bearing 401 means the minted token was rejected (allowlist/audience),
not an RBAC-to-mint failure.
This commit is contained in:
Volodymyr Stoiko
2026-06-16 19:16:16 +00:00
parent 04d7b04c19
commit 89979e3aec
2 changed files with 16 additions and 6 deletions

View File

@@ -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 {

View File

@@ -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
}