uv icon indicating copy to clipboard operation
uv copied to clipboard

Secure authentication method needed

Open stoney95 opened this issue 1 year ago • 16 comments

I would like to use uv to install dependencies from a private artifactory repository. Authentication for the private repository works with username and password. I would also like to store the index-url in the pyproject.toml. This simplifies the usage of uv for every team-member. The current minimal config looks like the following:

version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.11"
dependencies = []

[tool.uv]
index-url = "https://artifactory.company.com/pypi/simple"
native-tls = true
keyring-provider = "subprocess"

The documentation for authentication lists these options:

  • The URL, e.g., https://<user>:<password>@<hostname>/...
  • A .netrc configuration file
  • A keyring provider (requires opt-in)

The first option is not possible, as the credentials would be shared via git. Using .netrc is only partially possible as it poses a security risk by storing credentials in plain text. I tried this option nonetheless and it worked. But I would like to avoid it due to the plain-text password. The thrid option does not work. I will describe in detail, what I tried and where it failed.

  1. pip install keyring
  2. keyring set artifactory.company.com my_username and entering the credentials as prompted
  3. running uv add <any-package> with the configuration from above failed with
hint: An index URL (https://artifactory.company.com/pypi/simple) could not be queried due to a lack
      of valid authentication credentials (401 Unauthorized).

I changed the configuration value for index-url to https://[email protected]/pypi/simple. By this uv reads the credentials from keyring. As this stores the username in the pyproject.toml the approach does not work. Every team-member uses their own credentials to authenticate with the private artifactory.

  • uv version: uv 0.4.25 (97eb6ab4a 2024-10-21)
  • uv platform: Windows 11

If uv should work with the plain index-url (no username) and keyring, I would like to report this as a bug. In case it doesn't, I would like to request a secure methode for storing credentials while preserving a transferable uv configuration.

stoney95 avatar Nov 04 '24 15:11 stoney95

Keyring alway require a username. If you name your index, you can provide credentials for your index via environment variables and store them however you want:

[[tool.uv.index]]
name = "company"
url = "https://artifactory.company.com/pypi/simple"
default = true

Then you can specify a username with UV_INDEX_COMPANY_USERNAME and a password with UV_INDEX_COMPANY_PASSWORD. See: https://docs.astral.sh/uv/configuration/indexes/#providing-credentials.

charliermarsh avatar Nov 04 '24 15:11 charliermarsh

@charliermarsh, thanks for pointing out the solution using environment variables.

Do you currently plan to provide a solution similar to poetry config http-basic? This uses keyring under the hood, but stores which username to use for each index on the user-level.

Using your example, I would propose the following workflow:

  1. Define the index (making this possible via cli, e.g. uv index add ... would be nice to have)
[[tool.uv.index]]
name = "company"
url = "https://artifactory.company.com/pypi/simple"
default = true
  1. Define credentials with uv index config company "my_username". This will prompt for the password. The username is stored in $XDG_CONFIG/uv/uv.toml, e.g.
[index.company]
username = "my_username"

The password is stored with keyring.

Setting this config up once, makes it possible to run uv add <package>. Which is then pointing to the right index and using credentials automatically

stoney95 avatar Nov 04 '24 16:11 stoney95

Thought this might help. I've made a hacky way to work as a "config" to set the environment variables to authenticate for private pypi repo. My company uses aws so the tokens are short lived, so it's safe to do.

For more info, you would replace PASSWORD with the CodeArtifact auth command to retrieve the password token automatically and set the new credentials when it expires.

#set_credentials.sh
if [ ! -f ~/.uv ]; then
  touch ~/.uv
fi

update_credentials() {
    local PASSWORD="updated_password"
    # Set UV_INDEX_INTERNAL_COMPANY_PASSWORD in the ~/.uv file
    if grep -q "^export UV_INDEX_INTERNAL_COMPANY_PASSWORD=" ~/.uv; then
      # Use sed to replace the value with the local $PASSWORD variable
      sed -i '' "s/^export UV_INDEX_INTERNAL_COMPANY_PASSWORD=.*/export UV_INDEX_INTERNAL_COMPANY_PASSWORD=${PASSWORD}/" ~/.uv
    else
      # Append the line with $PASSWORD if it doesn't already exist
      echo "export UV_INDEX_INTERNAL_COMPANY_USERNAME=aws" >> ~/.uv
      echo "export UV_INDEX_INTERNAL_COMPANY_PASSWORD=${PASSWORD}" >> ~/.uv
    fi
}
update_credentials
source ~/.uv

Run this using

source set_credentials.sh

ljtruong avatar Nov 14 '24 01:11 ljtruong

Do you currently plan to provide a solution similar to poetry config http-basic? This uses keyring under the hood, but stores which username to use for each index on the user-level.

Yeah I'm interested in this, but we'll need to switch from the Python keyring library to something more robust and Rust native which will take time.

zanieb avatar Nov 14 '24 15:11 zanieb

Hello,

I run into the same kind of question: What is the best way of authenticate on private indexes with uv?
If I understand well, the recommended solution today is to use the UV_INDEX_XXX env variables.
However, this is not a very practical solution as .env file is not supported by uv sync, which means we have to set numerous specific variables for uv.

I ended up using the same "hacky" (or at least non-standard) solution as described here, with a set_env.sh script in my repo setting the UV_INDEX_XXX env variables to the appropriate values on both devs and CI machines.

In my case, I see two solutions which would be less "hacky" and much more practical:

  • accepting .env file in the uv sync command,
  • or interpreting env variables in pyproject.toml.

Do you have any of these two planned or do you have something else to recommend?

Thank you 🙏

ThomasHezard avatar Nov 25 '24 12:11 ThomasHezard

accepting .env file in the uv sync command,

Why not activate your .env file with another tool like direnv? Why is it uv's job to read configuration from that file?

interpreting env variables in pyproject.toml

Wouldn't you still need to set the variables? Isn't that part of the complaint?

I believe you can also configure your credentials in the uv.toml, if you prefer.

zanieb avatar Nov 25 '24 23:11 zanieb

interpreting env variables in pyproject.toml

Wouldn't you still need to set the variables? Isn't that part of the complaint?

I believe you can also configure your credentials in the uv.toml, if you prefer.

The thing that makes it a bit more complicated with uv for me (and my team) is that uv has a strict convention about the env variable names for authentification. With other tools allowing using env variables directly in config files, we can easily have a universal configuration across tools that work on both devs and CI machines with simple setup, which is quiet convenient.

accepting .env file in the uv sync command,

Why not activate your .env file with another tool like direnv? Why is it uv's job to read configuration from that file?

I must admit I am not familiar with direnv as I never needed it. It may be a very good solution indeed, I'll have a deeper look at it.

Thank you for the answer 🙏

ThomasHezard avatar Nov 26 '24 15:11 ThomasHezard

With other tools allowing using env variables directly in config files, we can easily have a universal configuration across tools that work on both devs and CI machines with simple setup, which is quiet convenient.

Can you share some example tools? I'd like to look at their approaches.

zanieb avatar Nov 26 '24 15:11 zanieb

Can you share some example tools? I'd like to look at their approaches.

First, I need to say that I don't come from the Python world, I only started using Python at production scale quite recently. My programming background is mostly C++ and iOS/Android development, some UNIX system administration, Docker and k8s deployment, and Matlab quite some time ago.

I used quite a lot of different tools for configuration, environment management, compilation, testing, packaging or deployment, and most of them support reading environment variables within their "configuration files" (or equivalent). A few examples:

  • bundler in gemfiles,
  • fastlane and other Ruby-based tools,
  • cmake and gradle,
  • I'm pretty sure Conan and Bazel support env variables one way or another but I'm not very familiar with these,
  • Docker in Dockerfiles and at runtime,
  • make in makefiles,
  • in Python, I have used setuptools with setup.py, which is native python giving you the ability to use os.environ,
  • gitlab CI yaml
  • helm charts for k8s

Many of these tools support env variables the same way you use it in shell: $MY_ENV_VAR. The ENV['MY_ENV_VAR'] syntax is also present in several of these tools and I'm pretty sure I've seen it in other contexts. cmake has it a bit different with $ENV{MY_ENV_VAR}.

Hope this helps :)

ThomasHezard avatar Nov 28 '24 22:11 ThomasHezard

Do you currently plan to provide a solution similar to poetry config http-basic? This uses keyring under the hood, but stores which username to use for each index on the user-level.

Yeah I'm interested in this, but we'll need to switch from the Python keyring library to something more robust and Rust native which will take time.

Would keyring-rs be an option: https://docs.rs/keyring/latest/keyring/ to speed up transition?

farndt avatar Dec 03 '24 23:12 farndt

Yeah we'd probably use that, though I haven't audited it.

Thanks @ThomasHezard !

zanieb avatar Dec 04 '24 00:12 zanieb

I would suggest the following implementation for Keyring:

  1. define index in pyproject.toml
[[tool.uv.index]]
name = “service-name”
url = “https://artifactory.company.com/*/simple”
  1. keyring set artifactory.company.com my_username -> enter password

  2. uv reads username and password using Keyring get_credentials service-name

This would allow teams to use keyring without making changes to pyproject.toml. Due to lack of encryption and simple logging it should be avoided to use environment variables as storage for credentials.

This approach could be quickly implemented temporarily with keyring as a Python package. When switching to Keyring-RS at a later date, the process would not need to change.

farndt avatar Dec 05 '24 23:12 farndt

@zanieb, what are your concerns about keyring-rs? I would be interested to contribute the keyring credentials feature and would like to understand the constraints

@farndt, can you share some documentation about get_credentials without a username? afaik, you need a service and a username to reference keyring entries.

stoney95 avatar Dec 07 '24 10:12 stoney95

No concerns; we just audit new dependencies for trustworthiness, correctness, etc.

Feel free to contribute support if you're interested. I think the first step is to have --keyring-provider <something> use the crate as a backend?

zanieb avatar Dec 07 '24 16:12 zanieb

I would suggest the following:

  • uv add loads the credentials for all indexes into the cache
  • This uses [Index.credentials](https://github.com/astral-sh/uv/blob/main/crates/uv-distribution-types/src/index.rs#L141-L152)
    • This currently only supports env variables UV_INDEX_XXX and [Credentials.from_url](https://github.com/astral-sh/uv/blob/main/crates/uv-auth/src/credentials.rs#L119-L122)
    • I would suggest to add logic for loading username from auth.toml in uv cache dir and checking keyring in Index.credentials
  • To configure the credentials I would add a new command index.
    • This provides uv index auth <index-name>.
    • It uses the already existing [keyring provider](https://github.com/astral-sh/uv/blob/main/crates/uv-auth/src/keyring.rs)

As there is already an implementation for keyring within uv, I would rely on it for this topic. Using keyring-rs can then be dealt with independently, if desired.

stoney95 avatar Dec 08 '24 00:12 stoney95

@zanieb, what are your concerns about keyring-rs? I would be interested to contribute the keyring credentials feature and would like to understand the constraints

@farndt, can you share some documentation about get_credentials without a username? afaik, you need a service and a username to reference keyring entries.

@stoney95 Coming from python keyring: https://github.com/jaraco/keyring?tab=readme-ov-file#api it has get_credential(service, username) but username can be anything and you will recieve username related to service.

I am not sure but you could recieve username as attribute of the service. For the request may the username os variable can be used? https://github.com/hwchen/keyring-rs/blob/master/src/windows.rs comments at begin of file (line 24)

The [get_attributes](crate::Entry::get_attributes) call will return the values in the
`username`, `comment`, and `target_alias` fields (using those strings as the attribute names),```

farndt avatar Dec 10 '24 14:12 farndt

Hello! I encountered a similar issue when trying to install from a wheel that is publicly available. I've tried [[tool.uv.index]] as well but had no luck. To reproduce:

uv pip install pytorch3d --extra-index-url https://dl.fbaipublicfiles.com/pytorch3d/packaging/wheels/py310_cu121_pyt241/pytorch3d-0.7.8-cp310-cp310-linux_x86_64.whl
  × No solution found when resolving dependencies:
  ╰─▶ Because only the following versions of pytorch3d are available:
          pytorch3d==0.1.1
          pytorch3d==0.2.0
          pytorch3d==0.2.5
          pytorch3d==0.3.0
          pytorch3d==0.4.0
          pytorch3d==0.5.0
          pytorch3d==0.6.1
          pytorch3d==0.6.2
          pytorch3d==0.7.0
          pytorch3d==0.7.1
          pytorch3d==0.7.2
          pytorch3d==0.7.3
          pytorch3d==0.7.4
      and pytorch3d<=0.6.2 has no wheels with a matching Python ABI tag, we can conclude
      that pytorch3d<=0.6.2 cannot be used.
      And because pytorch3d==0.7.0 has no wheels with a matching platform tag and
      pytorch3d==0.7.1 has no wheels with a matching Python ABI tag, we can conclude that
      pytorch3d<0.7.2 cannot be used.
      And because pytorch3d>=0.7.2 has no wheels with a matching platform tag and you
      require pytorch3d, we can conclude that your requirements are unsatisfiable.

      hint: An index URL
      (https://dl.fbaipublicfiles.com/pytorch3d/packaging/wheels/py310_cu121_pyt241/pytorch3d-0.7.8-cp310-cp310-linux_x86_64.whl)
      could not be queried due to a lack of valid authentication credentials (403
      Forbidden).

However on the same machine, we can download the wheel with wget:

wget https://dl.fbaipublicfiles.com/pytorch3d/packaging/wheels/py310_cu121_pyt241/pytorch3d-0.7.8-cp310-cp310-linux_x86_64.whl
--2025-02-05 09:15:31--  https://dl.fbaipublicfiles.com/pytorch3d/packaging/wheels/py310_cu121_pyt241/pytorch3d-0.7.8-cp310-cp310-linux_x86_64.whl
Resolving dl.fbaipublicfiles.com (dl.fbaipublicfiles.com)... 108.156.184.100, 108.156.184.129, 108.156.184.78, ...
Connecting to dl.fbaipublicfiles.com (dl.fbaipublicfiles.com)|108.156.184.100|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 20521514 (20M) [binary/octet-stream]
Saving to: ‘pytorch3d-0.7.8-cp310-cp310-linux_x86_64.whl’

pytorch3d-0.7.8-cp310-c 100%[=============================>]  19.57M  16.9MB/s    in 1.2s

2025-02-05 09:15:33 (16.9 MB/s) - ‘pytorch3d-0.7.8-cp310-cp310-linux_x86_64.whl’ saved [20521514/20521514]

Could we get some help? Thanks in advance!

zyaoj avatar Feb 05 '25 09:02 zyaoj

I can confirm that I can download the file. I can't really speculate as to why they'd throw a 403 there. This looks unrelated to the topic in this issue though, please see #9452

zanieb avatar Feb 05 '25 15:02 zanieb

I'm facing a similar issue as @stoney95 where we're using artifactory with every team member & CI pipeline having unique username & password credentials. An additional wrinkle is that each team has a separate artifactory repo, so managing dependencies from different teams is already a bit of a pain (independent of uv).

I'm trying to encourage folks to move to uv from poetry and this seems to be the first major hiccup I've run into where things are decidedly worse than poetry. At least without something like config.http-basic. All options I can think of require an additional step or tool on top of learning uv which is a pain.

Our options:

  • Use keychain
    • Requires specifying username in index url. Works okay for users who want to set up indexes in a global uv.toml config and use uv pip in place of pip, but is a non-starter for indexes in pyproject.toml
    • Our repos are on the same host (or VIP or whatever) & keychain auth is at the host level so multilple repos aren't too bad
    • requires keychain
  • Use UV_INDEX_{name}_USERNAME and _PASSWORD
    • Works for projects, but needs additional config. We can do something like:
      export UV_INDEX_A_USERNAME=${ARTIFACTORY_USER}
      export UV_INDEX_A_PASSWORD=${ARTIFACToRY_PASS}
      export UV_INDEX_B_USERNAME=${ARTIFACTORY_USER}
      export UV_INDEX_B_PASSWORD=${ARTIFACTORY_PASS}
      
      but we need to manually source that file each time we work with the project, or adopt a tool like direnv (which I'd never heard of before this thread
    • Requires making changes in two places whenever adding an index (pyproject.toml and wherever env vars are configured)

lendle avatar Jun 27 '25 15:06 lendle

I'm trying to use keychain on my Mac.

I did:

UV_PREVIEW_FEATURES=native-auth uv auth login https://www.reportlab.com/pypi/

entered login and password and I can see in keychain an entry:uv:https://www.reportlab.com/pypi/

Then I tried:

UV_PREVIEW_FEATURES=native-auth uv sync

but it failed with:

$ UV_PREVIEW_FEATURES=native-auth uv sync                                                                                                                        on git:main|✚1-1…9
Resolved 86 packages in 2ms
  × Failed to download `preppy==5.0.1`
  ├─▶ Failed to fetch: `https://www.reportlab.com/pypi/packages/preppy-5.0.1-py3-none-any.whl`
  ╰─▶ HTTP status client error (401 Unauthorized) for url (https://www.reportlab.com/pypi/packages/preppy-5.0.1-py3-none-any.whl/)
  help: `preppy` (v5.0.1) was included because `pdfgen-showdown` (v0.1.0) depends on `rlextra` (v4.4.3.1) which depends on `preppy`

If I register in plain (~/.local/share/uv/credentials/credentials.toml), it works. If I use:

export UV_INDEX_REPORTLAB_USERNAME="..."
export UV_INDEX_REPORTLAB_PASSWORD="..."

it works as well.

I'm simply failing to get it to work with UV_PREVIEW_FEATURES=native-auth.

alanwilter avatar Sep 04 '25 17:09 alanwilter

The native store doesn't support "prefix" matches yet. I think we need to fix that before you example will work. Can you login to www.reportlab.com instead? (We do support domain-level matches)

p.s. Thank you for trying the feature and taking the time to provide feedback!

zanieb avatar Sep 04 '25 17:09 zanieb

Yes, I can login to www.reportlab.com using the same credentials I use above. I'm not sure if I understand:

The native store doesn't support "prefix" matches yet or We do support domain-level matches

But let me know when you guys think Mac keychain should be working.

alanwilter avatar Sep 05 '25 07:09 alanwilter

The native store doesn't support "prefix" matches yet. I think we need to fix that before you example will work. Can you login to www.reportlab.com instead? (We do support domain-level matches)

p.s. Thank you for trying the feature and taking the time to provide feedback!

I can confirm the issue and tried the domain login you suggested, means using UV_PREVIEW_FEATURES=native-auth uv auth login www.my_company.com

Then I try: UV_PREVIEW_FEATURES=native-auth uv -v sync and see in the debug output, that uv is looking for plain text credentials: DEBUG Loaded credential file /home/... ... DEBUG Checking text store for credentials for https://www.my_company.com/... ... ╰─▶ Missing credentials for https://www.my_company.com/...

If I login with plain text storage, UV_PREVIEW_FEATURES=native-auth uv -v sync also works.

RStreitfeld avatar Sep 12 '25 16:09 RStreitfeld

Thanks @RStreitfeld I'll look into that!

zanieb avatar Sep 12 '25 16:09 zanieb