uv: `uv pip install` returning 403 from private pypi cloud instance backed by s3

I am using a private pypi cloud instance backed by s3 (with no auth on my end). Public packages are resolved normally, but uv pip cannot resolve packages hosted on the private cloud instance.

  • pip3 install my-private-package --index-url https://my-pip-instance.example.com/ succeeded with no problems.
  • Running uv pip install with the --no-cache option did not change the result.
  • I can retrieve the artifact from the url that uv pip was trying to fetch. This URL returns a 302 to the url that is giving me a 403
$ uv pip install my-private-package --index-url https://my-pip-instance.example.com/

error: Failed to build editables
  Caused by: Failed to build editable: file:///Users/phil/repos/philiplinden/scratch
  Caused by: Failed to install requirements from build-system.requires (resolve)
  Caused by: No solution found when resolving: setuptools
  Caused by: Failed to download: setuptools==69.1.1
  Caused by: HTTP status client error (403 Forbidden) for url (https://s3-url.amazonaws.com/example/setuptools-69.1.1-py3-none-any.whl?AWSAccessKeyId=xxx&Signature=xxx%3D&Expires=1709153725](https://s3-url.amazonaws.com/ffdf/setuptools/setuptools-69.1.1-py3-none-any.whl?AWSAccessKeyId=xxx&Signature=xxx%3D&Expires=1709153725)))

Relates to https://github.com/astral-sh/uv/issues/1709 and https://github.com/astral-sh/uv/pull/1902

Version: 0.1.6, 0.1.11

Verbose output (anonymized)

 uv_client::flat_index::from_entries 
 uv_installer::downloader::build_editables 
      0.355282s   0ms DEBUG uv_distribution::source Building (editable) file:///Users/phil/repos/philiplinden/scratch
   uv_dispatch::setup_build package_id="file:///Users/phil/repos/philiplinden/scratch", subdirectory=None
     uv_resolver::resolver::solve 
          0.361346s   0ms DEBUG uv_resolver::resolver Solving with target Python version 3.11.6
       uv_resolver::resolver::choose_version package=root
       uv_resolver::resolver::get_dependencies package=root, version=0a0.dev0
            0.361500s   0ms DEBUG uv_resolver::resolver Adding direct dependency: setuptools*
       uv_resolver::resolver::choose_version package=setuptools
         uv_resolver::resolver::package_wait package_name=setuptools
     uv_resolver::resolver::process_request request=Versions setuptools
       uv_client::registry_client::simple_api package=setuptools
         uv_client::cached_client::get_cacheable 
           uv_client::cached_client::read_and_parse_cache file=/Users/phil/Library/Caches/uv/simple-v1/8aba338bd0495f93/setuptools.rkyv
     uv_resolver::resolver::process_request request=Prefetch setuptools *
              0.366930s   5ms DEBUG uv_client::cached_client Found stale response for: https://my-pip-instance.example.com/simple/setuptools/
              0.366959s   5ms DEBUG uv_client::cached_client Sending revalidation request for: https://my-pip-instance.example.com/setuptools/
           uv_client::cached_client::revalidation_request url="https://my-pip-instance.example.com/setuptools/"
              1.016439s 654ms DEBUG uv_client::cached_client Found modified response for: https://my-pip-instance.example.com/simple/setuptools/
           uv_client::cached_client::new_cache file=/Users/phil/Library/Caches/uv/simple-v1/8aba338bd0495f93/setuptools.rkyv
           uv_client::registry_client::parse_simple_api package=setuptools
             uv_client::html::parse url=https://my-pip-instance.example.com/setuptools/
 uv_resolver::version_map::from_metadata 
       uv_distribution::distribution_database::get_or_build_wheel_metadata dist=setuptools==69.1.1
         uv_client::registry_client::wheel_metadata built_dist=setuptools==69.1.1
           uv_client::cached_client::get_serde 
             uv_client::cached_client::get_cacheable 
               uv_client::cached_client::read_and_parse_cache file=/Users/phil/Library/Caches/uv/wheels-v0/index/8aba338bd0495f93/setuptools/setuptools-69.1.1-py3-none-any.msgpack
            1.322675s 961ms DEBUG uv_resolver::resolver Searching for a compatible version of setuptools (*)
            1.322694s 961ms DEBUG uv_resolver::resolver Selecting: setuptools==69.1.1 (setuptools-69.1.1-py3-none-any.whl)
       uv_resolver::resolver::get_dependencies package=setuptools, version=69.1.1
         uv_resolver::resolver::distributions_wait package_id=setuptools-69.1.1
                  1.322754s   0ms DEBUG uv_client::cached_client No cache entry for: https://my-pip-instance.example.com/api/package/setuptools/setuptools-69.1.1-py3-none-any.whl#sha256=02fa291a0471b3a18b2b2481ed902af520c69e8ae0919c13da936542754b4c56
               uv_client::cached_client::fresh_request url="https://my-pip-instance.example.com/api/package/setuptools/setuptools-69.1.1-py3-none-any.whl#sha256=02fa291a0471b3a18b2b2481ed902af520c69e8ae0919c13da936542754b4c56"
error: Failed to build editables
  Caused by: Failed to build editable: file:///Users/phil/repos/philiplinden/scratch
  Caused by: Failed to install requirements from build-system.requires (resolve)
  Caused by: No solution found when resolving: setuptools
  Caused by: Failed to download: setuptools==69.1.1
  Caused by: HTTP status client error (403 Forbidden) for url (https://my-pip-instance.amazonaws.com/ffdf/setuptools/setuptools-69.1.1-py3-none-any.whl?AWSAccessKeyId=xxx&Signature=xxx&Expires=1709157923)

About this issue

  • Original URL
  • State: open
  • Created 4 months ago
  • Comments: 25 (15 by maintainers)

Most upvoted comments

I made a proof-of-concept PR to work around the issue. I do that by passing a modified response to the range reader so that it uses the “original” (gemfury) link and not the 302-redirected S3 link. Works for my local repro test case 😄

I emailed Gemfury.

We are experiencing the same issue. I wonder if it’s because we have an older Gemfury account. This option is enabled under our organization settings:

Screen Shot 2024-04-04 at 14 43 23

I don’t remember opting into that explicitly. Is that disabled in your account?

EDIT: Disabling this option changed the package source to a https://gemfury.s3-accelerate.dualstack.amazonaws.com/gems/... CDN URL instead of S3, but I still get the same error.

Yep, you’re right, @charliermarsh. I just tried hard-coding in my credentials at that point in the code, and then I got a “Request already has an authorization header” error instead. My clue was not a clue after all 😢

But yes, I can reproduce. I believe I have the same issue as @amarckal, which is that when I use curl against pypi.fury.io to fetch my private package, I get a 302 redirect to a url like this:

https://s3.amazonaws.com/gemfury/gems/<redacted-path>/sdxp_0_7_3_py3_none_any_whl?x-acct=<redacted-acct>&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=<redacted-cred>%2F20240404%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20240404T180859Z&X-Amz-Expires=900&X-Amz-SignedHeaders=host&X-Amz-Signature=<redacted-sig>

(I put <redacted-[whatever]> in places that could be sensitive).

When I run ./target/debug/uv pip install -vv --no-cache --extra-index-url https://{$GEMFURY_READ_TOKEN}@pypi.fury.io/oda/ sdxp==0.7.3, the last part of the output is (with some <redacted-[whatevers]> here as well:

           uv_client::cached_client::read_and_parse_cache file=/private/var/folders/2k/by15c_cs40l9swcck47g6lpm0000gn/T/.tmplRVtUe/wheels-v0/index/5f5b51aad86993d4/sdxp/sdxp-0.7.3-py3-none-any.msgpack
 uv_client::cached_client::from_path_sync path="/private/var/folders/2k/by15c_cs40l9swcck47g6lpm0000gn/T/.tmplRVtUe/wheels-v0/index/5f5b51aad86993d4/sdxp/sdxp-0.7.3-py3-none-any.msgpack"
                1.665639s   0ms TRACE uv_client::cached_client No cache entry exists for /private/var/folders/2k/by15c_cs40l9swcck47g6lpm0000gn/T/.tmplRVtUe/wheels-v0/index/5f5b51aad86993d4/sdxp/sdxp-0.7.3-py3-none-any.msgpack
              1.665827s   1ms DEBUG uv_client::cached_client No cache entry for: https://pypi.fury.io/oda/-/ver_Fz9wq/sdxp-0.7.3-py3-none-any.whl#sha256=869326637eef5de7d4312b82ecf9ba85fcd9e038273d54c0bbe7602d3b8529ad
           uv_client::cached_client::fresh_request url="https://pypi.fury.io/oda/-/ver_Fz9wq/sdxp-0.7.3-py3-none-any.whl#sha256=869326637eef5de7d4312b82ecf9ba85fcd9e038273d54c0bbe7602d3b8529ad"
                1.666044s   0ms TRACE uv_client::cached_client Sending fresh HEAD request for https://pypi.fury.io/oda/-/ver_Fz9wq/sdxp-0.7.3-py3-none-any.whl#sha256=869326637eef5de7d4312b82ecf9ba85fcd9e038273d54c0bbe7602d3b8529ad
                1.666269s   0ms DEBUG uv_auth::middleware Adding authentication to already-seen URL: https://pypi.fury.io/oda/-/ver_Fz9wq/sdxp-0.7.3-py3-none-any.whl#sha256=869326637eef5de7d4312b82ecf9ba85fcd9e038273d54c0bbe7602d3b8529ad
                1.895293s 229ms TRACE uv_client::httpcache cached request https://pypi.fury.io/oda/-/ver_Fz9wq/sdxp-0.7.3-py3-none-any.whl#sha256=869326637eef5de7d4312b82ecf9ba85fcd9e038273d54c0bbe7602d3b8529ad is storable because its response has a heuristically cacheable status code 200
           uv_client::cached_client::new_cache file=/private/var/folders/2k/by15c_cs40l9swcck47g6lpm0000gn/T/.tmplRVtUe/wheels-v0/index/5f5b51aad86993d4/sdxp/sdxp-0.7.3-py3-none-any.msgpack
           uv_client::registry_client::read_metadata_range_request wheel=sdxp-0.7.3-py3-none-any.whl
                1.896366s   0ms TRACE uv_client::registry_client Getting metadata for sdxp-0.7.3-py3-none-any.whl by range request
    1.897265s DEBUG uv_auth::middleware No credentials found for: https://s3.amazonaws.com/gemfury/gems/<redacted-path>/sdxp_0_7_3_py3_none_any_whl?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=<redacted-cred>%2F20240404%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20240404T181433Z&X-Amz-Expires=900&X-Amz-SignedHeaders=host&X-Amz-Signature=<redacted-sig>
error: Failed to download: sdxp==0.7.3
  Caused by: Failed to unzip wheel: sdxp-0.7.3-py3-none-any.whl
  Caused by: an upstream reader returned an error: io error occurred: Request error: HTTP status client error (403 Forbidden) for url (https://s3.amazonaws.com/gemfury/gems/<redacted-path>/sdxp_0_7_3_py3_none_any_whl?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=<redacted-cred>%2F20240404%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20240404T181433Z&X-Amz-Expires=900&X-Amz-SignedHeaders=host&X-Amz-Signature=<redacted-sig>)
  Caused by: io error occurred: Request error: HTTP status client error (403 Forbidden) for url (<the-same-url-again>)
  Caused by: Request error: HTTP status client error (403 Forbidden) for url (<the-same-url-again>)
  Caused by: HTTP status client error (403 Forbidden) for url (<the-same-url-again>)

If I isolate just the url in this output, there’s another possible clue:

https://s3.amazonaws.com/gemfury/gems/<redacted-path>/sdxp_0_7_3_py3_none_any_whl?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=<redacted-cred>%2F20240404%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20240404T181433Z&X-Amz-Expires=900&X-Amz-SignedHeaders=host&X-Amz-Signature=<redacted-sig> (<-- this is the uv one)

compared with the curl one from above:

https://s3.amazonaws.com/gemfury/gems/<redacted-path>/sdxp_0_7_3_py3_none_any_whl?x-acct=<redacted-acct>&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=<redacted-cred>%2F20240404%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20240404T180859Z&X-Amz-Expires=900&X-Amz-SignedHeaders=host&X-Amz-Signature=<redacted-sig> (<-- this is the curl one)

The curl url has a x-acct=<stuff> query param, while the uv one doesn’t. I have no idea why…