Skip to content

Commit

Permalink
refactor: replace requests with urllib3
Browse files Browse the repository at this point in the history
This simplifies our dependency tree a bit,
since requests is built on urllib3 internally.

It also chips away at a larger plan to replace
requests with urllib3 in sigstore-python, per
sigstore/sigstore-python#1040.

Signed-off-by: William Woodruff <[email protected]>
  • Loading branch information
woodruffw committed Jan 8, 2025
1 parent 1f665ce commit c9a4626
Show file tree
Hide file tree
Showing 3 changed files with 93 additions and 96 deletions.
120 changes: 63 additions & 57 deletions id/_internal/oidc/ambient.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
import shutil
import subprocess # nosec B404

import requests
import urllib3

from ... import AmbientCredentialError, GitHubOidcPermissionCredentialError

Expand Down Expand Up @@ -77,22 +77,24 @@ def detect_github(audience: str) -> str | None:
)

logger.debug("GitHub: requesting OIDC token")
resp = requests.get(
req_url,
params={"audience": audience},
headers={"Authorization": f"bearer {req_token}"},
timeout=30,
)

try:
resp.raise_for_status()
except requests.HTTPError as http_error:
raise AmbientCredentialError(
f"GitHub: OIDC token request failed (code={resp.status_code}, "
f"body={resp.content.decode()!r})"
) from http_error
except requests.Timeout:
resp = urllib3.request(
"GET",
req_url,
fields={"audience": audience},
headers={"Authorization": f"bearer {req_token}"},
timeout=30,
)
except urllib3.exceptions.MaxRetryError:
raise AmbientCredentialError("GitHub: OIDC token request timed out")

if resp.status != 200:
raise AmbientCredentialError(
f"GitHub: OIDC token request failed (code={resp.status}, "
f"body={resp.data.decode()!r})"
)

try:
body = resp.json()
value = body["value"]
Expand Down Expand Up @@ -122,47 +124,50 @@ def detect_gcp(audience: str) -> str | None:
logger.debug("GCP: GOOGLE_SERVICE_ACCOUNT_NAME set; attempting impersonation")

logger.debug("GCP: requesting access token")
resp = requests.get(
_GCP_TOKEN_REQUEST_URL,
params={"scopes": "https://www.googleapis.com/auth/cloud-platform"},
headers={"Metadata-Flavor": "Google"},
timeout=30,
)

try:
resp.raise_for_status()
except requests.HTTPError as http_error:
raise AmbientCredentialError(
f"GCP: access token request failed (code={resp.status_code}, "
f"body={resp.content.decode()!r})"
) from http_error
except requests.Timeout:
resp = urllib3.request(
"GET",
_GCP_TOKEN_REQUEST_URL,
fields={"scopes": "https://www.googleapis.com/auth/cloud-platform"},
headers={"Metadata-Flavor": "Google"},
timeout=30,
)
except urllib3.exceptions.MaxRetryError:
raise AmbientCredentialError("GCP: access token request timed out")

if resp.status != 200:
raise AmbientCredentialError(
f"GCP: access token request failed (code={resp.status}) "
f"body={resp.data.decode()!r}"
)

access_token = resp.json().get("access_token")

if not access_token:
raise AmbientCredentialError("GCP: access token missing from response")

resp = requests.post(
_GCP_GENERATEIDTOKEN_REQUEST_URL.format(service_account_name),
json={"audience": audience, "includeEmail": True},
headers={
"Authorization": f"Bearer {access_token}",
},
timeout=30,
)

logger.debug("GCP: requesting OIDC token")

try:
resp.raise_for_status()
except requests.HTTPError as http_error:
raise AmbientCredentialError(
f"GCP: OIDC token request failed (code={resp.status_code}, "
f"body={resp.content.decode()!r})"
) from http_error
except requests.Timeout:
resp = urllib3.request(
"POST",
_GCP_GENERATEIDTOKEN_REQUEST_URL.format(service_account_name),
json={"audience": audience, "includeEmail": True},
headers={
"Authorization": f"Bearer {access_token}",
},
timeout=30,
)
except urllib3.exceptions.MaxRetryError:
raise AmbientCredentialError("GCP: OIDC token request timed out")

if resp.status != 200:
raise AmbientCredentialError(
f"GCP: OIDC token request failed (code={resp.status}, "
f"body={resp.data.decode()!r})"
)

oidc_token: str = resp.json().get("token")

if not oidc_token:
Expand All @@ -186,25 +191,26 @@ def detect_gcp(audience: str) -> str | None:
return None

logger.debug("GCP: requesting OIDC token")
resp = requests.get(
_GCP_IDENTITY_REQUEST_URL,
params={"audience": audience, "format": "full"},
headers={"Metadata-Flavor": "Google"},
timeout=30,
)

try:
resp.raise_for_status()
except requests.HTTPError as http_error:
raise AmbientCredentialError(
f"GCP: OIDC token request failed (code={resp.status_code}, "
f"body={resp.content.decode()!r})"
) from http_error
except requests.Timeout:
resp = urllib3.request(
"GET",
_GCP_IDENTITY_REQUEST_URL,
fields={"audience": audience, "format": "full"},
headers={"Metadata-Flavor": "Google"},
timeout=30,
)
except urllib3.exceptions.MaxRetryError:
raise AmbientCredentialError("GCP: OIDC token request timed out")

if resp.status != 200:
raise AmbientCredentialError(
f"GCP: OIDC token request failed (code={resp.status}, "
f"body={resp.data.decode()!r})"
)

logger.debug("GCP: successfully requested OIDC token")
return resp.text
return resp.data.decode()


def detect_buildkite(audience: str) -> str | None:
Expand Down
3 changes: 1 addition & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ classifiers = [
"Topic :: Security",
"Topic :: Security :: Cryptography",
]
dependencies = ["requests"]
dependencies = ["urllib3 ~= 2.3"]
requires-python = ">=3.8"

[project.urls]
Expand All @@ -43,7 +43,6 @@ lint = [
# NOTE(ww): ruff is under active development, so we pin conservatively here
# and let Dependabot periodically perform this update.
"ruff < 0.8.2",
"types-requests",
]
dev = ["build", "bump >= 1.3.2", "id[test,lint]"]

Expand Down
66 changes: 29 additions & 37 deletions test/unit/internal/oidc/test_ambient.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@

import pretend
import pytest
from requests import HTTPError, Timeout

from id import detect_credential
from id._internal.oidc import ambient
Expand Down Expand Up @@ -93,22 +92,22 @@ def test_detect_github_request_fails(monkeypatch):
monkeypatch.setenv("ACTIONS_ID_TOKEN_REQUEST_URL", "fakeurl")

resp = pretend.stub(
raise_for_status=pretend.raiser(HTTPError),
status_code=999,
content=b"something",
status=999,
data=b"something",
)
requests = pretend.stub(get=pretend.call_recorder(lambda url, **kw: resp), HTTPError=HTTPError)
monkeypatch.setattr(ambient, "requests", requests)
u3 = pretend.stub(request=pretend.call_recorder(lambda meth, url, **kw: resp))
monkeypatch.setattr(ambient, "urllib3", u3)

with pytest.raises(
ambient.AmbientCredentialError,
match=r"GitHub: OIDC token request failed \(code=999, body='something'\)",
):
ambient.detect_github("some-audience")
assert requests.get.calls == [
assert u3.request.calls == [
pretend.call(
"GET",
"fakeurl",
params={"audience": "some-audience"},
fields={"audience": "some-audience"},
headers={"Authorization": "bearer faketoken"},
timeout=30,
)
Expand All @@ -120,47 +119,38 @@ def test_detect_github_request_timeout(monkeypatch):
monkeypatch.setenv("ACTIONS_ID_TOKEN_REQUEST_TOKEN", "faketoken")
monkeypatch.setenv("ACTIONS_ID_TOKEN_REQUEST_URL", "fakeurl")

resp = pretend.stub(raise_for_status=pretend.raiser(Timeout))
requests = pretend.stub(
get=pretend.call_recorder(lambda url, **kw: resp),
HTTPError=HTTPError,
Timeout=Timeout,
u3 = pretend.stub(
request=pretend.raiser(ValueError), exceptions=pretend.stub(MaxRetryError=ValueError)
)
monkeypatch.setattr(ambient, "requests", requests)

monkeypatch.setattr(ambient, "urllib3", u3)

with pytest.raises(
ambient.AmbientCredentialError,
match=r"GitHub: OIDC token request timed out",
):
ambient.detect_github("some-audience")
assert requests.get.calls == [
pretend.call(
"fakeurl",
params={"audience": "some-audience"},
headers={"Authorization": "bearer faketoken"},
timeout=30,
)
]


def test_detect_github_invalid_json_payload(monkeypatch):
monkeypatch.setenv("GITHUB_ACTIONS", "true")
monkeypatch.setenv("ACTIONS_ID_TOKEN_REQUEST_TOKEN", "faketoken")
monkeypatch.setenv("ACTIONS_ID_TOKEN_REQUEST_URL", "fakeurl")

resp = pretend.stub(raise_for_status=lambda: None, json=pretend.raiser(json.JSONDecodeError))
requests = pretend.stub(get=pretend.call_recorder(lambda url, **kw: resp))
monkeypatch.setattr(ambient, "requests", requests)
resp = pretend.stub(status=200, json=pretend.raiser(json.JSONDecodeError))
request = pretend.call_recorder(lambda meth, url, **kw: resp)
monkeypatch.setattr(ambient.urllib3, "request", request)

with pytest.raises(
ambient.AmbientCredentialError,
match="GitHub: malformed or incomplete JSON",
):
ambient.detect_github("some-audience")
assert requests.get.calls == [
assert request.calls == [
pretend.call(
"GET",
"fakeurl",
params={"audience": "some-audience"},
fields={"audience": "some-audience"},
headers={"Authorization": "bearer faketoken"},
timeout=30,
)
Expand All @@ -173,19 +163,20 @@ def test_detect_github_bad_payload(monkeypatch, payload):
monkeypatch.setenv("ACTIONS_ID_TOKEN_REQUEST_TOKEN", "faketoken")
monkeypatch.setenv("ACTIONS_ID_TOKEN_REQUEST_URL", "fakeurl")

resp = pretend.stub(raise_for_status=lambda: None, json=pretend.call_recorder(lambda: payload))
requests = pretend.stub(get=pretend.call_recorder(lambda url, **kw: resp))
monkeypatch.setattr(ambient, "requests", requests)
resp = pretend.stub(status=200, json=pretend.call_recorder(lambda: payload))
u3 = pretend.stub(request=pretend.call_recorder(lambda meth, url, **kw: resp))
monkeypatch.setattr(ambient, "urllib3", u3)

with pytest.raises(
ambient.AmbientCredentialError,
match="GitHub: malformed or incomplete JSON",
):
ambient.detect_github("some-audience")
assert requests.get.calls == [
assert u3.request.calls == [
pretend.call(
"GET",
"fakeurl",
params={"audience": "some-audience"},
fields={"audience": "some-audience"},
headers={"Authorization": "bearer faketoken"},
timeout=30,
)
Expand All @@ -199,17 +190,18 @@ def test_detect_github(monkeypatch):
monkeypatch.setenv("ACTIONS_ID_TOKEN_REQUEST_URL", "fakeurl")

resp = pretend.stub(
raise_for_status=lambda: None,
status=200,
json=pretend.call_recorder(lambda: {"value": "fakejwt"}),
)
requests = pretend.stub(get=pretend.call_recorder(lambda url, **kw: resp))
monkeypatch.setattr(ambient, "requests", requests)
u3 = pretend.stub(request=pretend.call_recorder(lambda meth, url, **kw: resp))
monkeypatch.setattr(ambient, "urllib3", u3)

assert ambient.detect_github("some-audience") == "fakejwt"
assert requests.get.calls == [
assert u3.request.calls == [
pretend.call(
"GET",
"fakeurl",
params={"audience": "some-audience"},
fields={"audience": "some-audience"},
headers={"Authorization": "bearer faketoken"},
timeout=30,
)
Expand Down

0 comments on commit c9a4626

Please sign in to comment.