Skip to content

v36.0.0 regression: exec WebSocket returns 401 Unauthorized on AKS (works in v35.0.0) #2595

@GK-07

Description

@GK-07

What happened?

After upgrading the kubernetes Python client from 35.0.0 to 36.0.0, pod exec via kubernetes.stream.stream() consistently fails against an Azure AKS cluster with 401 Unauthorized during the WebSocket handshake. Regular REST API calls (v1.list_namespaced_pod, v1.read_namespaced_pod, etc.) continue to work with the same kubeconfig and same identity. Downgrading the library to 35.0.0 immediately restores exec functionality with no other changes.

What did you expect to happen?

stream(v1.connect_get_namespaced_pod_exec, ...) should succeed in v36.0.0 the same way it does in v35.0.0, against the same AKS cluster, with the same kubeconfig.

How can we reproduce it (as minimally and precisely as possible)?

  1. AKS cluster (Private Link), kubectl exec works fine from the same host.
  2. kubeconfig uses an exec: auth plugin (kubelogin with Azure AD).
  3. Run the following Python script with kubernetes==36.0.0:
from kubernetes import client, config
from kubernetes.stream import stream

cfg = client.Configuration()
cfg.verify_ssl = False
config.load_kube_config(config_file="/path/to/aks/kubeconfig", client_configuration=cfg)
cfg.proxy = None
v1 = client.CoreV1Api(api_client=client.ApiClient(cfg))

# REST: works in both v35 and v36
print([p.metadata.name for p in v1.list_namespaced_pod("vault").items])

# Exec WebSocket: works in v35, fails in v36 with 401
resp = stream(
    v1.connect_get_namespaced_pod_exec,
    "hashicorp-vault-0", "vault",
    command=["/bin/sh", "-c", "vault status"],
    stderr=True, stdin=False, stdout=True, tty=False,
)
print(resp)

Anything else we need to know?

The traceback from v36.0.0:

File ".../kubernetes/stream/ws_client.py", line 552, in create_websocket
    websocket.connect(url, **connect_opt)
File ".../websocket/_handshake.py", line 158, in _get_resp_headers
    raise WebSocketBadStatusException(
websocket._exceptions.WebSocketBadStatusException: Handshake status 401 Unauthorized -+-+-
{'audit-id': '...', 'cache-control': 'no-cache, private', 'content-type': 'application/json',
 'date': '...', 'content-length': '129', 'connection': 'close'}
-+-+- b'{"kind":"Status","apiVersion":"v1","metadata":{},"status":"Failure",
        "message":"Unauthorized","reason":"Unauthorized","code":401}\n'

The 401 response carries an audit-id, confirming the request reaches the AKS API server and is rejected at the auth layer (not a network/proxy issue).

Root cause (suspected)

PR #2486 (implement pod exec websockets v5) changed the advertised WebSocket subprotocols from a single value to a comma-separated list:

kubernetes/base/stream/ws_client.py — v35.0.0:

header.append("sec-websocket-protocol: v4.channel.k8s.io")

kubernetes/base/stream/ws_client.py — v36.0.0:

header.append("sec-websocket-protocol: %s,%s" % (V5_CHANNEL_PROTOCOL, V4_CHANNEL_PROTOCOL))
# → "sec-websocket-protocol: v5.channel.k8s.io,v4.channel.k8s.io"

The Authorization header handling is identical between the two versions, and the Authorization header is sent. However, when the AKS API server receives multiple subprotocols in Sec-WebSocket-Protocol, it appears to take a different auth code path (presumably the browser-style base64url.bearer.authorization.k8s.io.<token> lookup inside the subprotocol header) and fails to fall back to honoring the standard Authorization header, returning 401 instead of negotiating down to v4.channel.k8s.io.

This makes the regression appear specifically against cluster/control-plane versions that don't natively support the v5 subprotocol.

Workaround

Pinning kubernetes==35.0.0 resolves the issue.

Kubernetes client version

$ pip show kubernetes | grep Version
Version: 36.0.0

Server version (AKS)

*.privatelink.<region>.azmk8s.io (Azure AKS, control plane managed by Azure; happy to share exact Kubernetes minor if needed)

OS

Red Hat Enterprise Linux 9.x (containerized Python 3.12 environment)

Python version

Python 3.12.11

Python client version

kubernetes==36.0.0
websocket-client==1.9.0

Installation method

pip3 install kubernetes

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    kind/bugCategorizes issue or PR as related to a bug.

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions