Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proxy auth with custom HTTP transport #11433

Closed
wants to merge 3 commits into from
Closed

Conversation

hhirtz
Copy link

@hhirtz hhirtz commented Nov 28, 2022

What does this PR try to resolve?

This PR makes cargo authenticate against proxies when needed.
Closes #7330

Add three settings:

  • http.proxy-auth
  • http.proxy-username
  • http.proxy-password

proxy-auth defaults to "auto" so that most use cases work out of the box. I don't know why you would disable authentication support, but I added a "disable" option in this case. You can also force the use of a mechanism and abort if the proxy doesn't advertise it.

proxy-username and proxy-password: String vs Option<String>?
curl doesn't negotiate (SPNEGO, here "gss") if handle.proxy_username and handle.proxy_password aren't called. This authentication mechanism doesn't require a user/pass in cargo config. This shouldn't hurt (?) when the proxy doesn't require authentication.

About curl-rust's spnego and ntlm features:
https://github.com/alexcrichton/curl-rust/blob/b50c5d355aab69483752db51815c5a679b29d6c4/curl-sys/Cargo.toml#L54-L60
This is the last blocker I think. If we require those features, then vendored curl builds will require "a suitable GSS-API library or SSPI on Windows" [0] for the "gss" setting to work (which is what corporate proxies are using most of the time). I think this PR is blocked until we can either vendor those dependencies, or we change the official cargo builds to link dynamically to libcurl.

The official cargo builds seems to be made with vendored curl.

How should we test and review this PR?

Complete testing is a bit tedious since you'd need e.g. squid [1] to test each authentication mechanism. With such proxy, run cargo update with http-proxy environment variables (plus --config http.proxy-{username,password}= if needed).

[0] https://docs.rs/curl/0.4/curl/easy/struct.Auth.html#method.gssnegotiate
[1] http://www.squid-cache.org/

@rustbot
Copy link
Collaborator

rustbot commented Nov 28, 2022

Thanks for the pull request, and welcome! The Rust team is excited to review your changes, and you should hear from @ehuss (or someone else) soon.

Please see the contribution instructions for more information.

@rustbot rustbot added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Nov 28, 2022
Add three settings:
- http.proxy-auth
- http.proxy-username
- http.proxy-password

proxy-auth defaults to "auto" so that most use cases work out of the
box. I don't know why you would disable authentication support, but I
added a "disable" option in this case. You can also force the use of a
mechanism and abort if the proxy doesn't advertise it.

proxy-username and proxy-password: String vs Option<String>?
curl doesn't negotiate (SPNEGO, here "gss") if handle.proxy_username and
handle.proxy_password aren't called. This authentication mechanism
doesn't require a user/pass in cargo config. This shouldn't hurt (?)
when the proxy doesn't require authentication.

About curl-rust's spnego and ntlm features:
https://github.com/alexcrichton/curl-rust/blob/b50c5d355aab69483752db51815c5a679b29d6c4/curl-sys/Cargo.toml#L54-L60
**This is the last blocker I think.** If we require those features, then
vendored curl builds will require "a suitable GSS-API library or SSPI on
Windows" [0] for the "gss" setting to work (which is what corporate
proxies are using most of the time). I think this PR is blocked until we
can either  vendor those dependencies, or we change the official cargo
builds to link dynamically to libcurl.
Comment on lines 2251 to 2254
#[serde(default)]
pub proxy_username: String,
#[serde(default)]
pub proxy_password: String,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer the username and password be Options rather than empty strings.

Suggested change
#[serde(default)]
pub proxy_username: String,
#[serde(default)]
pub proxy_password: String,
pub proxy_username: Option<String>,
pub proxy_password: Option<String>,

impl CargoHttpProxyAuth {
pub fn to_easy(&self) -> Auth {
let mut easy = Auth::new();
let easy = match self {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid the easy.clone() below by not redefining easy here. Also consider calling it auth instead of easy

Suggested change
let easy = match self {
match self {

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not possible to avoid the clone because match self { .. } is a &mut Auth. Also Auth does not implement Copy.

https://docs.rs/curl/0.4.44/curl/easy/struct.Auth.html

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't need to capture the result of the match in a variable. The basic(), digest(), etc methods mutate the Auth struct, so you can return the one you created with Auth::new() without cloning.

    pub fn to_easy(&self) -> Auth {
        let mut auth = Auth::new();
        match self {
            Self::Auto => auth.basic(true).digest(true).gssnegotiate(true).ntlm(true),
            Self::Disable => &auth,
            Self::Basic => auth.basic(true),
            Self::Digest => auth.digest(true),
            Self::Gss => auth.gssnegotiate(true),
            Self::Ntlm => auth.ntlm(true),
        };
        auth
    }

#[serde(rename_all = "kebab-case")]
pub enum CargoHttpProxyAuth {
#[default]
Auto,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the default of Auto introduce any additional HTTP requests for the non-proxy case?

The curl docs indicate that it might:

If more than one bit is set, libcurl will first query the site to see what authentication methods it supports and then pick the best one you allow it to use. For some methods, this will induce an extra network round-trip

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change focuses on proxy authentication specifically, not authentication to arbitrary HTTP servers. The Auth API is used for both problems, thus this paragraph I think. In the non-proxy case there is no additional request to be made.

This could well send an additional request to a proxy that doesn't need authentication, however I don't think it would make a noticeable difference in performance compared to, e.g. the time it takes to update the registry or download kilobytes of crates (?).

this is true! my bad!

<rust-lang#11433 (comment)>
handle.proxy_username and handle.proxy_password still need to be called
when options are "None", otherwise curl won't negotiate in cases it
should (eg when using "gss")

<rust-lang#11433 (comment)>
@@ -560,6 +560,9 @@ pub fn configure_http_handle(config: &Config, handle: &mut Easy) -> CargoResult<
if let Some(proxy) = http_proxy(config)? {
handle.proxy(&proxy)?;
}
handle.proxy_auth(&http.proxy_auth.to_easy())?;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is CargoHttpProxyAuth::Disable should we avoid calling any of the proxy_ methods?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not calling handle.proxy_auth is the same as calling it with Auth::new().basic(true) [0,1] (support only basic authentication). The idea with "disable" is to tell curl to not negotiate even if the user has a login/pwd in cargo config file.

We could avoid calling proxy_username and proxy_password but I don't think it would change anything, at least from the official curl docs.

[0] https://curl.se/libcurl/c/CURLOPT_PROXYAUTH.html
[1] https://docs.rs/curl/latest/curl/easy/struct.Easy2.html#method.proxy_auth

@@ -560,6 +560,9 @@ pub fn configure_http_handle(config: &Config, handle: &mut Easy) -> CargoResult<
if let Some(proxy) = http_proxy(config)? {
handle.proxy(&proxy)?;
}
handle.proxy_auth(&http.proxy_auth.to_easy())?;
handle.proxy_username(http.proxy_username.as_deref().unwrap_or(""))?;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a comment about why these need to be called with empty string (instead of just not calling them)?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment is in the commit message [0] (second paragraph). Do you want another one in the code?

[0] e7a5905

@ehuss
Copy link
Contributor

ehuss commented Dec 13, 2022

Unfortunately this seems like a relatively meaty change, so it can be difficult to nail everything down.

@hhirtz Is there a specific authentication scheme that you personally are interested in using? I was wondering if there is some way to narrow the scope of what is being added here. For example, if cargo needs to move to a different backend, then it would need to support all the mechanisms added here, which can be a large burden. Additionally, it looks like building curl with all the appropriate support looks potentially complicated. Exactly what will be supported on all of Cargo's platforms with the current build system?

Another issue is that we try to avoid including secrets in the configuration files. Adding the password here seems like an anti-pattern, so I think we may need to look at some alternate approach. I'm not sure if that involves using credentials.toml or something else.

In general, it can also be difficult for me to review and understand a change like this since I am not deeply familiar with all the different proxy authentication mechanisms. I think to move forward, I would need some kind of ELI5 introduction to what is being added here to help evaluate how these will be used and the consequences.

@ehuss ehuss added S-waiting-on-author Status: The marked PR is awaiting some action (such as code changes) from the PR author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Dec 19, 2022
@bors
Copy link
Contributor

bors commented Feb 14, 2023

☔ The latest upstream changes (presumably #11646) made this pull request unmergeable. Please resolve the merge conflicts.

@ehuss
Copy link
Contributor

ehuss commented May 9, 2023

I'm going to close since this hasn't seen any movement in a while. If you want to pick this back up, I would suggest following up on #7330 with a specific design proposal that addresses any of the concerns raised above. Thanks!

@ehuss ehuss closed this May 9, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-waiting-on-author Status: The marked PR is awaiting some action (such as code changes) from the PR author.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Proxy authentication mechanism support in cargo
5 participants