Skip to content

Instantly share code, notes, and snippets.

@eevmanu
Created July 23, 2026 15:08
Show Gist options
  • Select an option

  • Save eevmanu/129ca7b05e7890b3f2df3d8988ab8218 to your computer and use it in GitHub Desktop.

Select an option

Save eevmanu/129ca7b05e7890b3f2df3d8988ab8218 to your computer and use it in GitHub Desktop.
Technical Research & Implementation Report: Nebius CLI & Kubernetes Exec Plugin OAuth Browser Loop Issue

Technical Research & Implementation Report: Nebius CLI & Kubernetes Exec Plugin OAuth Browser Loop Issue

Document Version: 2.1 (Fully Generalized & Portable Edition)
Target File Path: nebius_auth_browser_loop_research.md
Topics Covered: Nebius Managed Kubernetes (mk8s), OAuth 2.0 PKCE Flow, client-go Exec Plugin Spec, Kubeconfig Hardening (--no-browser, interactiveMode: Never), Dynamic CLI & User Retrieval (which nebius, nebius profile current, jq / grep), Service Account Authorized Keys vs. NEBIUS_IAM_TOKEN Environment Variables.


Executive Summary

When using Nebius AI Cloud Managed Kubernetes (mk8s), executing kubectl commands or running background Kubernetes developer tools (e.g., k9s, Lens, VSCode / JetBrains Kubernetes plugins, ArgoCD, custom daemon scripts) can trigger an aggressive infinite loop of opening web browser tabs (Google Chrome/Firefox). These tabs continuously redirect between https://auth.nebius.com/oauth2/authorize?client_id=nebius-cli... and local loopback callback URLs like http://127.0.0.1:<port>.

This issue stems from an architectural conflict between Kubernetes client-go exec credential plugin mechanisms and interactive OAuth 2.0 PKCE authentication fallback in the Nebius CLI (nebius).

When a developer's local profile credentials (IAM access token and OAuth refresh token) expire or are missing, nebius mk8s v1 cluster get-token falls back to an interactive browser-based OAuth PKCE flow by invoking xdg-open. Because background Kubernetes tools poll cluster endpoints repeatedly in parallel or on tight timers, each polling attempt spawns a separate nebius process. Each isolated process launches its own ephemeral HTTP server on a random local port and opens a new browser tab, flooding Chrome within seconds.

This report details the root cause, explains the behavior of all configuration parameters in ~/.kube/config, compares non-interactive authentication methods, and provides safe, fully dynamic commands to resolve the issue permanently across any developer workstation environment.


1. Deep Dive: Mechanism of nebius mk8s v1 cluster get-token

1.1 The Kubernetes client-go Exec Credential Plugin Specification

Kubernetes CLI tools and IDE extensions communicate with cluster API servers via client-go. To support cloud provider-specific authentication, Kubernetes standardizes external credential fetching via the Exec Credential Plugin API (client.authentication.k8s.io/v1 or v1beta1).

When kubectl or an IDE plugin sends an HTTP request to a Kubernetes cluster API server:

  1. client-go inspects the user's ~/.kube/config file under the active user entry.
  2. If an exec block is present, client-go executes the specified binary (e.g., command: ~/.nebius/bin/nebius or $(which nebius) with args: ["mk8s", "v1", "cluster", "get-token", "--profile", "<profile-name>", "--format", "json"]).
  3. client-go reads stdout expecting a structured JSON object (ExecCredential) containing an IAM Bearer token and expiration timestamp:
    {
      "apiVersion": "client.authentication.k8s.io/v1beta1",
      "kind": "ExecCredential",
      "status": {
        "token": "NET...<IAM_ACCESS_TOKEN>",
        "expirationTimestamp": "2026-07-23T21:00:00Z"
      }
    }

1.2 Nebius Profile Check & Fallback Behavior

When nebius mk8s v1 cluster get-token is executed by client-go:

  1. Profile State Inspection: Nebius CLI checks the specified profile (e.g., as returned dynamically by nebius profile current).
  2. Token Cache Check: It checks for an unexpired local IAM access token (valid up to 12 hours).
  3. Silent OAuth Refresh: If the IAM access token is expired, the CLI attempts to silently exchange the cached OAuth refresh token for a fresh IAM access token.
  4. Interactive PKCE Fallback: If the refresh token is missing, expired, or invalid—and no --no-browser flag or Service Account key was provided—nebius assumes an interactive terminal session and initiates an OAuth 2.0 PKCE flow.

1.3 Ephemeral Local Listener & OS Browser Invocation

During the PKCE flow:

  1. Nebius CLI binds to a random unprivileged local TCP port (e.g., 43125) and starts a temporary HTTP web server at http://127.0.0.1:43125/.
  2. It constructs an OAuth authorization URL: https://auth.nebius.com/oauth2/authorize?client_id=nebius-cli&response_type=code&redirect_uri=http://127.0.0.1:43125&code_challenge=<CHALLENGE>&code_challenge_method=S256
  3. Default Behavior (Without --no-browser): The CLI executes xdg-open (Linux), open (macOS), or start (Windows) to launch the default browser (Google Chrome) pointing to the authorization URL.
  4. Headless Behavior (With --no-browser): The CLI does not call xdg-open and does not launch Chrome. Instead, it prints a text prompt containing the URL to standard error (stderr) and waits or exits immediately if interactiveMode: Never is set.

2. Root Cause Analysis: The Infinite Tab-Flooding Cascade

2.1 Polling Mechanics in Background Kubernetes Tools

Modern Kubernetes UI and CLI tools do not issue single queries; they continuously monitor cluster state:

  • k9s: Refreshes active resource lists (pods, nodes, services) every 2 to 5 seconds.
  • Lens / IDE Plugins (VSCode, JetBrains): Run background goroutines polling metrics, logs, and CRDs concurrently across multiple namespaces.
  • ArgoCD / Helm / Custom Scripts: Run automated background reconciliation loops.

2.2 Step-by-Step Failure Cascade

[Background Tool (k9s / Lens / Extension)]
        │
        ├── (1) Polls K8s API every 2s
        │
        ├── (2) Invokes client-go -> runs `nebius mk8s v1 cluster get-token` (Process #1)
        │         │
        │         ├── Token expired -> Starts HTTP Server on 127.0.0.1:41001
        │         └── Calls `xdg-open` -> Opens Chrome Tab #1 (Port 41001)
        │
        ├── (3) 2s later: Poll #2 runs `nebius mk8s v1 cluster get-token` (Process #2)
        │         │
        │         ├── Isolated Process -> Starts HTTP Server on 127.0.0.1:41002
        │         └── Calls `xdg-open` -> Opens Chrome Tab #2 (Port 41002)
        │
        ├── (4) 2s later: Poll #3 runs (Process #3) -> Opens Chrome Tab #3 (Port 41003)
        │
        └── ... Repetitive loop spawns 10-50+ tabs until system resource exhaustion!
  1. Expired Session State: The developer's profile has an expired IAM token and invalid refresh token.
  2. Process #1 Executed: Lens/k9s triggers nebius get-token. Process #1 starts an HTTP listener on 127.0.0.1:41001, calls xdg-open, and opens Tab #1 in Chrome.
  3. Parallel Polling (Processes #2, #3...): Background tools do not wait for Process #1 to complete browser login. Subsequent polling cycles spawn additional instances of nebius get-token.
  4. Port Divergence: Each spawned process is isolated. Process #2 cannot bind to port 41001, so it binds to 41002 and executes xdg-open again, opening Tab #2.
  5. Tab Explosion: Within 30 seconds, 15 to 30 separate CLI processes are active, each opening new tabs pointing to different http://127.0.0.1:<port> callbacks.

3. Dissecting the Kubeconfig exec: Configuration Parameters

When configuring ~/.kube/config, the exec: block contains four specific fields. Here is why each exists and whether it is required:

users:
- name: <nebius-cluster-user>
  user:
    exec:
      apiVersion: client.authentication.k8s.io/v1beta1
      command: ~/.nebius/bin/nebius # or resolved via $(which nebius)
      args:
      - mk8s
      - v1
      - cluster
      - get-token
      - --profile
      - <profile-name>
      - --no-browser
      - --format
      - json
      interactiveMode: Never
      provideClusterInfo: false
      env: null

Parameter Analysis & Coupling Matrix

Parameter Belongs To Purpose Required for Tab Suppression?
--no-browser Nebius CLI Flag Instructs Nebius CLI to never call xdg-open or launch browser windows automatically when authentication fails/expires. ESSENTIAL
interactiveMode: Never Kubernetes Spec Instructs client-go that the credential plugin runs in headless mode and MUST NOT attempt interactive TTY prompts. ESSENTIAL
provideClusterInfo: false Kubernetes Spec Toggles passing cluster API server metadata via KUBERNETES_EXEC_INFO env var. Nebius CLI ignores this variable. OPTIONAL (Boilerplate)
env: null YAML Syntax Explicitly indicates an empty list of custom environment variables. OPTIONAL (Boilerplate)

Important

Crucial Distinction for --no-browser:
--no-browser prevents nebius from executing xdg-open (launching Chrome tabs). However, if the token is expired, nebius will still print a text authorization URL to stderr ("To complete the authentication process, open the following link..."). Seeing this text URL in terminal logs does NOT mean Chrome was launched; it is simply a text prompt written to stderr.


4. Dynamic Kubeconfig Update Command (Fully Portable)

To ensure scripts and commands are completely portable across any system, username, binary path, or profile name, retrieve all variables dynamically:

4.1 Dynamic Parameter Extraction Helpers

# 1. Retrieve the full absolute path of the Nebius CLI binary dynamically:
NEBIUS_BIN=$(which nebius || echo "$HOME/.nebius/bin/nebius")

# 2. Retrieve the active Nebius profile name dynamically:
NEBIUS_PROFILE=$(nebius profile current)

# 3. Retrieve the Nebius cluster user entry from kubeconfig dynamically (no positional indexing):
KUBE_USER=$(kubectl config view -o json | jq -r '.users[].name | select(contains("nebius"))')

4.2 Fully Portable One-Liner Update Command

Backup ~/.kube/config and execute the update using nested subshell command substitution:

# Step 1: Backup kubeconfig
cp ~/.kube/config ~/.kube/config.bak

# Step 2: Apply updated exec configuration with dynamic parameters
kubectl config set-credentials "$(kubectl config view -o json | jq -r '.users[].name | select(contains("nebius"))')" \
  --exec-api-version=client.authentication.k8s.io/v1beta1 \
  --exec-command="$(which nebius || echo "$HOME/.nebius/bin/nebius")" \
  --exec-arg=mk8s \
  --exec-arg=v1 \
  --exec-arg=cluster \
  --exec-arg=get-token \
  --exec-arg=--profile \
  --exec-arg="$(nebius profile current)" \
  --exec-arg=--no-browser \
  --exec-arg=--format \
  --exec-arg=json

5. Non-Interactive Authentication Comparison: Solution 3 vs. Solution 4

For environments where interactive login is undesirable, Nebius supports two distinct non-interactive authentication methods. While both avoid browser tabs, they operate at completely different layers of the identity stack:

┌─────────────────────────────────────────────────────────────────────────────┐
│                          IDENTITY STACK COMPARISON                          │
├──────────────────────────────┬──────────────────────────────────────────────┤
│ Solution 3 (Service Account) │ Long-lived PRIVATE KEY FILE on disk          │
│                              │ -> CLI auto-generates short-lived tokens     │
├──────────────────────────────┼──────────────────────────────────────────────┤
│ Solution 4 (Env Var)         │ Ephemeral TOKEN STRING in shell memory       │
│                              │ -> Hard lifetime limit of 12 hours           │
└──────────────────────────────┴──────────────────────────────────────────────┘

Detailed Side-by-Side Comparison

Feature Solution 3: Service Account Key File (sa-key.json) Solution 4: Environment Variable (NEBIUS_IAM_TOKEN)
Credential Type Authorized RSA/ECDSA Private Key JSON file stored on disk (~/.nebius/sa-key.json). Raw IAM Access Token string stored in shell memory (export NEBIUS_IAM_TOKEN=...).
Execution Flow nebius reads sa-key.json, signs JWT requests locally, and fetches new IAM access tokens automatically. nebius detects NEBIUS_IAM_TOKEN, skips profile/key lookups entirely, and uses the raw token directly.
Token Lifespan Permanent access (Key file never expires until revoked in Nebius IAM Console). Max 12 hours (Hard lifetime limit for raw Nebius IAM access tokens).
User Interaction Zero interaction. Set up once and forget permanently. Must manually re-export export NEBIUS_IAM_TOKEN=$(nebius iam get-access-token) every 12 hours.
Primary Use Case Personal Developer Laptops & IDEs: Permanent hands-off background K8s access without browser logins. CI/CD Pipelines & Transient Runners: Short-lived automated build/test jobs where saving persistent key files on disk is insecure.

6. Developer Laptop Workflow & Re-Authentication Procedure

With Solution 2 applied (--no-browser and interactiveMode: Never in ~/.kube/config), your day-to-day developer workflow changes as follows:

Day-to-Day Operation (Normal State)

  1. You run kubectl, k9s, Lens, or VSCode extensions normally.
  2. Background tools poll the cluster silently.
  3. Zero browser tabs pop open.

When Your Session Tokens Expire (Renewal State)

  1. kubectl or k9s reports Unauthorized or Authentication token expired inside its own UI.
  2. No browser tabs open automatically.
  3. To renew your session at your convenience, run one single command in your terminal using dynamic profile resolution:
    nebius auth login --profile "$(nebius profile current)"
  4. A single browser tab opens once. You complete login, and a fresh OAuth refresh token is stored in your active profile.
  5. All kubectl, k9s, and IDE background tasks immediately resume working silently.

7. Primary Sources & References

  1. Kubernetes Client Authentication Exec Plugin Specification:
  2. Nebius Official Documentation & CLI Reference:
    • Managed Kubernetes (mk8s): nebius mk8s cluster get-credentials, nebius mk8s v1 cluster get-token
    • Identity & Access Management: nebius auth login, nebius profile current, nebius iam key create, nebius config profile set
    • Portal: docs.nebius.com / nebius.ai
  3. OAuth 2.0 PKCE & Client-Go Execution Behavior:
    • RFC 7636: Proof Key for Code Exchange by OAuth Public Clients
    • client-go Exec Credential Provider implementation (stdout JSON parsing, stderr logging, TTY detection).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment