OneAPI client constructs invalid OAuth and API base URLs for GOV and GOV-US clouds
Description
The ZscalerClient (OneAPI client) fails entirely when cloud is set to gov or govus. Two methods produce incorrect URLs for these clouds:
-
OAuth._get_auth_url — appends .zslogin{cloud}.net to the vanity domain regardless of cloud type. For government clouds, the identity provider uses .zidentitygov.net (GOV) and .zidentitygov.us (GOV-US), not the standard .zslogin.net family.
-
RequestExecutor.get_base_url — constructs https://api.{cloud}.zsapi.net for non-production clouds. The GOV OneAPI gateway is at https://api.zscalergov.net and GOV-US is at https://api.zscalergov.us; neither matches the .zsapi.net pattern.
The result is an immediate SSLError / ConnectionError on every API call because the constructed hostnames do not exist.
Reproduction
from zscaler import ZscalerClient
# GOV cloud — fails immediately on any API call
client = ZscalerClient({
"clientId": "<your-gov-client-id>",
"clientSecret": "<your-gov-client-secret>",
"vanityDomain": "zsgovlab-net", # short-form prefix (without .zidentitygov.net)
"cloud": "gov",
"customerId": "<your-zpa-customer-id>",
})
rules, response, error = client.zpa.policies.list_rules("access")
# Error: SSLError connecting to zsgovlab-net.zslogingov.net (does not exist)
# GOV-US cloud — same failure
client_us = ZscalerClient({
"clientId": "<your-govus-client-id>",
"clientSecret": "<your-govus-client-secret>",
"vanityDomain": "zsgovlab-us", # short-form prefix (without .zidentitygov.us)
"cloud": "govus",
"customerId": "<your-zpa-customer-id>",
})
rules, response, error = client_us.zpa.policies.list_rules("access")
# Error: SSLError connecting to zsgovlab-us.zslogingovus.net (does not exist)
Exact URLs the SDK constructs vs. what they should be:
| Cloud |
Component |
SDK produces (broken) |
Correct URL |
gov |
Auth token endpoint |
https://zsgovlab-net.zslogingov.net/oauth2/v1/token |
https://zsgovlab-net.zidentitygov.net/oauth2/v1/token |
gov |
API base URL |
https://api.gov.zsapi.net |
https://api.zscalergov.net |
govus |
Auth token endpoint |
https://zsgovlab-us.zslogingovus.net/oauth2/v1/token |
https://zsgovlab-us.zidentitygov.us/oauth2/v1/token |
govus |
API base URL |
https://api.govus.zsapi.net |
https://api.zscalergov.us |
Both broken hostnames (zslogingov.net, zslogingovus.net, api.gov.zsapi.net, api.govus.zsapi.net) do not resolve in DNS.
Expected behavior
ZscalerClient with cloud="gov" or cloud="govus" should authenticate against the correct government identity provider and route API calls to the correct government OneAPI gateway:
- GOV — Auth:
https://{vanityDomain}.zidentitygov.net/oauth2/v1/token, API: https://api.zscalergov.net
- GOV-US — Auth:
https://{vanityDomain}.zidentitygov.us/oauth2/v1/token, API: https://api.zscalergov.us
Is it a regression?
Unknown. The gov and govus cloud values exist in the legacy ZPA client (LegacyZPAClientHelper) but appear never to have been implemented in the OneAPI (ZscalerClient) path. This is a missing feature that manifests as a broken URL rather than a clear error.
Debug Logs
With ZSCALER_SDK_LOG=true and ZSCALER_SDK_VERBOSE=true, the following error is emitted immediately on the first API call:
ERROR:root:Failed to get access token: HTTPSConnectionPool(host='zsgovlab-net.zslogingov.net', port=443):
Max retries exceeded with url: /oauth2/v1/token
(Caused by SSLError(SSLError(1, '[SSL: SSLV3_ALERT_HANDSHAKE_FAILURE] sslv3 alert handshake failure (_ssl.c:1129)')))
For GOV-US:
ERROR:root:Failed to get access token: HTTPSConnectionPool(host='zsgovlab-us.zslogingovus.net', port=443):
Max retries exceeded with url: /oauth2/v1/token
(Caused by NewConnectionError('Failed to establish a new connection: [Errno 8] nodename nor servname provided'))
Proposed Fix
File: zscaler/oneapi_oauth_client.py — OAuth._get_auth_url
# Current (broken)
def _get_auth_url(self, vanity_domain: str, cloud: str) -> str:
if cloud == "production":
return f"https://{vanity_domain}.zslogin.net/oauth2/v1/token"
else:
return f"https://{vanity_domain}.zslogin{cloud}.net/oauth2/v1/token"
# Fixed
def _get_auth_url(self, vanity_domain: str, cloud: str) -> str:
if cloud == "production":
return f"https://{vanity_domain}.zslogin.net/oauth2/v1/token"
elif cloud == "gov":
return f"https://{vanity_domain}.zidentitygov.net/oauth2/v1/token"
elif cloud == "govus":
return f"https://{vanity_domain}.zidentitygov.us/oauth2/v1/token"
else:
return f"https://{vanity_domain}.zslogin{cloud}.net/oauth2/v1/token"
File: zscaler/request_executor.py — RequestExecutor.get_base_url
# Current (broken) — the relevant branch
if self.cloud and self.cloud != "production":
return f"https://api.{self.cloud}.zsapi.net"
# Fixed — add gov/govus cases before the generic branch
if self.cloud == "gov":
return "https://api.zscalergov.net"
elif self.cloud == "govus":
return "https://api.zscalergov.us"
elif self.cloud and self.cloud != "production":
return f"https://api.{self.cloud}.zsapi.net"
The same gov/govus guard is also needed in the zidentity and zins branches of get_base_url for completeness:
def get_base_url(self, endpoint: str) -> str:
GOV_BASE_URLS = {
"gov": "https://api.zscalergov.net",
"govus": "https://api.zscalergov.us",
}
if "/zscsb" in endpoint:
return f"https://csbapi.{self.sandbox_cloud}.net"
if "/zidentity" in endpoint or "/admin/api/v1" in endpoint:
if not self.vanity_domain:
raise ValueError("vanityDomain is required for zidentity service")
if self.cloud in GOV_BASE_URLS:
# GOV identity admin uses the vanity domain directly (no zslogin suffix)
return f"https://{self.vanity_domain}-admin/admin/api/v1"
if self.cloud and self.cloud != "production":
return f"https://{self.vanity_domain}-admin.zslogin{self.cloud}.net/admin/api/v1"
return f"https://{self.vanity_domain}-admin.zslogin.net/admin/api/v1"
if self.cloud in GOV_BASE_URLS:
return GOV_BASE_URLS[self.cloud]
if "/zins" in endpoint:
if self.cloud and self.cloud != "production":
return f"https://api.{self.cloud}.zsapi.net"
return self.BASE_URL
if self.cloud and self.cloud != "production":
return f"https://api.{self.cloud}.zsapi.net"
return self.BASE_URL
Other Information
- OS: macOS 14 (Darwin 24.6.0)
- Python: 3.9
- SDK Version: 1.9.16 (latest)
- Clouds affected:
gov (Zscaler GOV — api.zscalergov.net) and govus (Zscaler GOV-US — api.zscalergov.us)
- Clouds unaffected:
production and all other standard clouds (.zsapi.net family)
Additional Context
Government cloud tenants use a separate identity provider (zidentitygov.net / zidentitygov.us) rather than the standard zslogin.net provider. The vanity domain for these tenants is in the form {org-name}.zidentitygov.{tld}, not {org-name}.zslogin.net.
Workaround (until fixed): monkey-patch both methods at import time before constructing ZscalerClient.
from zscaler.oneapi_oauth_client import OAuth
from zscaler.request_executor import RequestExecutor
_orig_auth = OAuth._get_auth_url
def _patched_auth(self, vanity_domain, cloud):
if cloud == "gov":
return f"https://{vanity_domain}.zidentitygov.net/oauth2/v1/token"
if cloud == "govus":
return f"https://{vanity_domain}.zidentitygov.us/oauth2/v1/token"
return _orig_auth(self, vanity_domain, cloud)
OAuth._get_auth_url = _patched_auth
_orig_base = RequestExecutor.get_base_url
def _patched_base(self, endpoint):
cloud = getattr(self, "cloud", "")
if cloud == "gov":
return "https://api.zscalergov.net"
if cloud == "govus":
return "https://api.zscalergov.us"
return _orig_base(self, endpoint)
RequestExecutor.get_base_url = _patched_base
OneAPI client constructs invalid OAuth and API base URLs for GOV and GOV-US clouds
Description
The
ZscalerClient(OneAPI client) fails entirely whencloudis set togovorgovus. Two methods produce incorrect URLs for these clouds:OAuth._get_auth_url— appends.zslogin{cloud}.netto the vanity domain regardless of cloud type. For government clouds, the identity provider uses.zidentitygov.net(GOV) and.zidentitygov.us(GOV-US), not the standard.zslogin.netfamily.RequestExecutor.get_base_url— constructshttps://api.{cloud}.zsapi.netfor non-production clouds. The GOV OneAPI gateway is athttps://api.zscalergov.netand GOV-US is athttps://api.zscalergov.us; neither matches the.zsapi.netpattern.The result is an immediate
SSLError/ConnectionErroron every API call because the constructed hostnames do not exist.Reproduction
Exact URLs the SDK constructs vs. what they should be:
govhttps://zsgovlab-net.zslogingov.net/oauth2/v1/tokenhttps://zsgovlab-net.zidentitygov.net/oauth2/v1/tokengovhttps://api.gov.zsapi.nethttps://api.zscalergov.netgovushttps://zsgovlab-us.zslogingovus.net/oauth2/v1/tokenhttps://zsgovlab-us.zidentitygov.us/oauth2/v1/tokengovushttps://api.govus.zsapi.nethttps://api.zscalergov.usBoth broken hostnames (
zslogingov.net,zslogingovus.net,api.gov.zsapi.net,api.govus.zsapi.net) do not resolve in DNS.Expected behavior
ZscalerClientwithcloud="gov"orcloud="govus"should authenticate against the correct government identity provider and route API calls to the correct government OneAPI gateway:https://{vanityDomain}.zidentitygov.net/oauth2/v1/token, API:https://api.zscalergov.nethttps://{vanityDomain}.zidentitygov.us/oauth2/v1/token, API:https://api.zscalergov.usIs it a regression?
Unknown. The
govandgovuscloud values exist in the legacy ZPA client (LegacyZPAClientHelper) but appear never to have been implemented in the OneAPI (ZscalerClient) path. This is a missing feature that manifests as a broken URL rather than a clear error.Debug Logs
With
ZSCALER_SDK_LOG=trueandZSCALER_SDK_VERBOSE=true, the following error is emitted immediately on the first API call:For GOV-US:
Proposed Fix
File:
zscaler/oneapi_oauth_client.py—OAuth._get_auth_urlFile:
zscaler/request_executor.py—RequestExecutor.get_base_urlThe same
gov/govusguard is also needed in thezidentityandzinsbranches ofget_base_urlfor completeness:Other Information
gov(Zscaler GOV —api.zscalergov.net) andgovus(Zscaler GOV-US —api.zscalergov.us)productionand all other standard clouds (.zsapi.netfamily)Additional Context
Government cloud tenants use a separate identity provider (
zidentitygov.net/zidentitygov.us) rather than the standardzslogin.netprovider. The vanity domain for these tenants is in the form{org-name}.zidentitygov.{tld}, not{org-name}.zslogin.net.Workaround (until fixed): monkey-patch both methods at import time before constructing
ZscalerClient.