Skip to content

HTTP_PROXY/HTTPS_PROXY ignored by load_kube_config(): proxy scheme resolved before the host is known #2681

Closed

Description

@yurnov

Split out of #2679 at @yliaog's request — that issue covered the kubeconfig proxy-url field, fixed in #2680. This one is about the environment variables.

What happened (please include outputs or screenshots):

HTTP_PROXY and HTTPS_PROXY are ignored when the client configuration is built by load_kube_config(). Configuration.proxy stays None and requests go direct.

Only ALL_PROXY works. That makes the commonly suggested workaround for proxy setups ("just export HTTPS_PROXY") ineffective on the load_kube_config() path, which is the path almost every user takes.

What you expected to happen:

HTTPS_PROXY (or HTTP_PROXY) is honoured for the cluster endpoint, as it is for curl, kubectl, and client-go.

How to reproduce it (as minimally and precisely as possible):

$ HTTPS_PROXY=http://127.0.0.1:8888 python -c "
from kubernetes.client import Configuration
print(repr(Configuration().proxy))
print(repr(Configuration(host='https://example:6443').proxy))"
None
'http://127.0.0.1:8888'

The same asymmetry end to end, against a cluster reachable only through a proxy (the setup from #2679):

env var set Configuration.proxy after load_kube_config() API call
HTTPS_PROXY None fails
HTTP_PROXY None fails
ALL_PROXY set works

Root cause

Configuration.__init__ resolves the proxy from the environment by the scheme of self.host:

# urllib3 does not read proxy environment variables itself:
# https://github.com/urllib3/urllib3/issues/1785
if proxy is None or no_proxy is None:
proxies = getproxies()
if proxy is None:
scheme = urlparse(self.host).scheme
proxy = proxies.get(scheme) or proxies.get("all")
if no_proxy is None:
no_proxy = proxies.get("no")

scheme = urlparse(self.host).scheme
proxy = proxies.get(scheme) or proxies.get("all")

But host is a property, not the constructor argument:

@property
def host(self) -> str:
"""Return generated host."""
return self.get_host_from_settings(self.server_index, variables=self.server_variables)

@property
def host(self) -> str:
    """Return generated host."""
    return self.get_host_from_settings(self.server_index, variables=self.server_variables)

and server_index is set to 0 whenever no host argument is passed:

self.server_index = 0 if server_index is None and host is None else server_index

self.server_index = 0 if server_index is None and host is None else server_index

With server_index = 0, host returns get_host_settings()[0]['url'], which is "" for this spec. So urlparse("").scheme is '', proxies.get('') misses, and only the proxies.get("all") fallback can match — which is why ALL_PROXY is the one variable that works.

load_kube_config() constructs the object with no host:

if client_configuration is None:
config = type.__call__(Configuration)
loader.load_and_set(config)
Configuration.set_default(config)

if client_configuration is None:
    config = type.__call__(Configuration)
    loader.load_and_set(config)

so it always takes the broken path. The loader assigns the real server afterwards — and the host setter does reset server_index to None — but by then the proxy has already been resolved in __init__. Passing host= explicitly is what makes the working case above work.

Possible fixes

Two options, and I'd like guidance on which is preferred before writing a patch:

  1. Re-evaluate the environment proxy in the loader once host is known — a change in kubernetes/base/config/kube_config.py, which is developer-written and patchable in this repo.
  2. Fix the resolution order in Configuration.__init__ so it uses the effective base path rather than the generated host property. This is in kubernetes/client/configuration.py, which is generated (kubernetes/.openapi-generator/FILES), so it would need whatever hotfix/regeneration flow you prefer.

Happy to implement either. Given #2147's history I'd rather not touch generated files without agreement first.

Anything else we need to know?:

Related: #2679 / #2680 (kubeconfig proxy-url, fixed), #2520 (no_proxy env var handling).

Environment:

  • Python client version: master (9c3456a351594b59a19862d9caec1e78e7b4e287), also reproduced on released 36.0.3
  • Python version: 3.12.3
  • OS: Linux

/kind bug

Activity

  1. yliaog commented on Aug 14, 2026

    @yliaog
    Contributor

    @yurnov option 2. is preferred if it is done in the openapi generator repo, so the change won't be overridden by the next openapi generator run for future releases. also the fix might be applicable to other clients that uses the same openapi generator.

    option 1 is also acceptable if option 2 is not viable.

  2. yliaog commented on Aug 14, 2026

    @yliaog
    Contributor

    btw, would this issue also address #2520 ?

  3. yurnov commented on Aug 16, 2026

    @yurnov
    ContributorAuthor

    btw, would this issue also address #2520 ?

    Partly - they overlap but they are not the same bug.

    The specific defect reported in #2520, self.no_proxy = None overwriting the value just read from the environment, is already gone: @roycaihw was right that #2459/#2513 fixed it, and the current template reads no_proxy = proxies.get("no") once. rest.py also gained should_bypass_proxies(), so the value is actually applied. On master today:

    $ HTTPS_PROXY=http://proxy.local:8080 NO_PROXY=.local python -c "
    from kubernetes.client import Configuration
    print(repr(Configuration().no_proxy))"
    '.local'
    

    What #2520 could not have noticed is that its own repro was masked by this issue: on the load_kube_config() path proxy was always None, so nothing was routed through a proxy and no_proxy never had anything to bypass. That is why the reporter's snippet still prints proxy: None today.

    So #2681 is what makes the no_proxy handling reachable rather than what fixes it. With the lazy-proxy patch applied, the full env-only path behaves correctly:

    host HTTPS_PROXY NO_PROXY pool manager
    https://api.example.com:6443 set .internal ProxyManager
    https://api.svc.internal:6443 set .internal PoolManager

    I'd suggest keeping #2520 open until this lands and then closing it, so there is something to point at that demonstrates the end-to-end behaviour.

    (Note #2520 is lifecycle/rotten and @Ymax27 self-assigned in August — worth a /remove-lifecycle rotten if you agree it should stay open.)

  4. yurnov commented on Aug 16, 2026

    @yurnov
    ContributorAuthor

    /assign

  5. yurnov commented on Aug 16, 2026

    @yurnov
    ContributorAuthor

    Opened upstream as OpenAPITools/openapi-generator#24716 (option 2).

    The proxy is re-resolved in the host setter, so construction is unchanged and the existing tests pass as-is. Once it merges it reaches us with a bump of OPENAPI_GENERATOR_COMMIT in kubernetes-client/gen.

  6. yurnov commented on Aug 18, 2026

    @yurnov
    ContributorAuthor

    Upstream merged: OpenAPITools/openapi-generator#24716 (07be218c).

    @yliaog on kubernetes-client/gen#308 you offered the same choice for proxy_ssl_context — rev OPENAPI_GENERATOR_COMMIT in a new PR, or wait for the 7.25.0 release. The two are now the same decision: #24539 landed in the same range, so one bump carries both this and #2387.

    Waiting looks cheap: the 7.25.0 milestone is due 2026-08-20 (23 open / 126 closed), and it would let python.sh pin a tag instead of the current SHA.

    Either way the bump is not isolated. openapi/python.sh is at 830e9d15, 161 commits back; seven of those touch the python templates:

    07be218c [python] re-resolve the environment proxy when the host is assigned (#24716)
    9b9e7f07 Move shared git push template to _common (#24565)
    ecb8bf16 remove tornado from python generators (#24564)
    0d686ead [python] Support proxy_ssl_context for the urllib3-based client (#24539)
    4bb1c741 [python] Account for content-type parameters on request preparation (#23992)
    de177e50 [python] fix null handling in generated models (#24357)
    6f052bcf [python] serialize booleans as lowercase in all parameter styles (#24359)
    

    plus PythonClientCodegen.java. model_generic.mustache and api_client.mustache change too, so the regenerated diff spans the model files, not just configuration.py.

    Both local patches also stop applying. scripts/rest_client_patch.diff anchors on

                    is_json = (
                        not content_type
                        or re.search('json', content_type, re.IGNORECASE)

    which #23992 rewrote to contenttype_matches(...), and scripts/client_go_retry_patch.diff is applied with --unidiff-zero, so its line anchors shift as soon as configuration.py grows. Mechanical to regenerate, but it makes the python-side PR larger than a version bump.

    Which raises the main question: since 37.0.0 regenerates against release-1.37 anyway — and that refreshes those patches regardless — do you want the generator bump folded into that regeneration, or landed on its own beforehand? Folding it in means one regeneration instead of two; doing it separately gets the fix onto master sooner and keeps the spec bump and the generator bump reviewable apart.

    Two smaller ones:

    Happy to do the work whichever way you prefer.

    cc @emmanuel-adu — #2387 rides the same bump.

  7. yliaog commented on Aug 18, 2026

    @yliaog
    Contributor

    i think it is clearer to separate the python 37.0.0 release from the generator release 1.25.0. I prefer to bump to generator 1.25.0 as soon as it releases.

    btw, does your PR fix #2520 ? could you close that issue once the master has the 1.25.0 generator bump?

  8. yurnov commented on Aug 19, 2026

    @yurnov
    ContributorAuthor

    @yliaog understood — separate PR, on the release. I'll open the kubernetes-client/gen PR bumping openapi/python.sh to the 7.25.0 tag as soon as it ships, then the regeneration PR here. 7.25.0 is due 2026-08-20 with 23 issues still open on the milestone, so it may slip a few days; I'll watch for it. Should openapi/python-aio.sh move to the same tag in that PR? The async client has no env-proxy block, so it doesn't need it for this, but leaving the two pins split seems worse than bumping both.

    On #2520 — yes, it's covered once this lands. To be precise about why, since it's not quite "this PR fixes it": the literal defect reported there, self.no_proxy = None overwriting the value just read from the environment, was already fixed by #2459/#2513. It stayed unobservable because proxy was always None on the load_kube_config() path, so nothing was routed through a proxy for no_proxy to bypass. This bump is what makes the env path work end to end, so #2520 has nothing left in it afterwards. It's lifecycle/rotten and assigned to @Ymax27 — I'll post there once master has the bump, but I don't think I have the permissions to close it myself.

    Would adjusting the proxy resolution logic to check for the environment variables before resolving the host potentially honor the HTTP_PROXY and HTTPS_PROXY values correctly?

    @craftydisso the environment is already read first — __init__ calls getproxies() before anything else. The missing input isn't the environment, it's the scheme:

    proxies.get(urlparse(self.host).scheme) or proxies.get("all")

    That has to know whether the target is http or https to choose between HTTP_PROXY and HTTPS_PROXY. At that point self.host is "", not None — server_index falls back to 0 and the k8s spec's server URL is empty — so urlparse("").scheme is "" and every lookup except the ALL_PROXY fallback misses. load_kube_config() assigns the host afterwards.

    So no reordering inside __init__ can fix it; the host genuinely doesn't exist yet. That's why the upstream fix (OpenAPITools/openapi-generator#24716, merged) re-resolves in the host setter, the first point at which the scheme is known.

  9. yliaog commented on Aug 19, 2026

    @yliaog
    Contributor

    thanks @yurnov , please move openapi/python-aio.sh to the same tag, so that sync and asyncio client are kept in sync to the same openapi generator release.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

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

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions