samuong / alpaca

A local HTTP proxy for command-line tools. Supports PAC scripts and NTLM authentication.
Apache License 2.0
184 stars 31 forks source link

Support HTTP Negotiate/Kerberos Authentication #44

Open samuong opened 4 years ago

samuong commented 4 years ago

When Alpaca receives a 407 from an upstream proxy, it just assumes that it's an NTLM proxy and starts to do an NTLM handshake.

In the last few months, I noticed that some of the proxies that I was authenticating to began to send back a "Proxy-Authenticate: Negotiate" header instead of "Proxy-Authenticate: NTLM". This allows the client (Alpaca) to use either NTLM or Kerberos.

Alpaca should check the value of this header, and switch authentication mechanisms accordingly. More information about the Negotiate scheme can be found at https://tools.ietf.org/html/rfc4559 and https://docs.microsoft.com/en-us/dotnet/framework/wcf/feature-details/understanding-http-authentication.

A Kerberos library that might help is https://github.com/jcmturner/gokrb5.

rtfmoz2 commented 2 years ago

macOS supports Kerberos by default out of the box so you should not have to worry about it for that platform. Its happens automatically when the network layer reaches out to connect.

https://support.apple.com/en-au/guide/deployment/depe6a1cda64/web

samuong commented 2 years ago

I'm not sure that's correct. According to your link, only iOS and iPadOS (but not macOS) support "Negotiation challenges", and even that only handles "HTTP 401 Negotiate challenges" not HTTP 407 (proxy authentication) challenges. Am I missing something?

rtfmoz2 commented 2 years ago

If you read the macOS section it says Kerberos is handled at the network layer. Unless I am missing something.

"In macOS, the Kerberos SSO extension proactively acquires a Kerberos TGT upon network state changes to ensure that the user is ready to authenticate when needed."

When you reach out to the proxy Kerberos will automatically occur if its supported. This happens well before layer 7 where proxy discussion occurs.

On Tue, 5 Apr 2022 at 08:58, samuong @.***> wrote:

I'm not sure that's correct. According to your link, only iOS and iPadOS (but not macOS) support "Negotiation challenges", and even that only handles "HTTP 401 Negotiate challenges" not HTTP 407 (proxy authentication) challenges. Am I missing something?

— Reply to this email directly, view it on GitHub https://github.com/samuong/alpaca/issues/44#issuecomment-1088093865, or unsubscribe https://github.com/notifications/unsubscribe-auth/AJYYSIQIBSCKPRP3M2HJPGLVDNX2TANCNFSM4J6JOBLA . You are receiving this because you commented.Message ID: @.***>

samuong commented 2 years ago

Not sure if you've got a proxy that uses Kerberos auth to test against; if so, does this work for you? When I've tried this in the past Alpaca received a 407 from the upstream proxy and I wasn't able to go any further.

samhaque commented 2 years ago

We use Kerberos auth for the proxy, will test and report back if alpaca works

samhaque commented 2 years ago

Here is a screenshot of our redacted logs, as you can see we didn't set the NTLM_CREDENTIALS environment variable and curl request to google still worked with our proxy server which supports both NTLM and Kerberos auth.

image

samhaque commented 2 years ago

Note I am on a Mac running on Apple silicon, and I compiled the latest version of alpaca for my system from the latest commit. Also my Mac had binded with AD first before I ran Alpaca, without the first bind, the Kerberos ticket wouldn't have generated and the proxy would have failed.

rtfmoz2 commented 2 years ago

So we need to turn on some Kerberos output so we can see the auth happening, I’m not sure how that is done.

rtfmoz2 commented 2 years ago

Note I am on a Mac running on Apple silicon, and I compiled the latest version of alpaca for my system from the latest commit. Also my Mac had binded with AD first before I ran Alpaca, without the first bind, the Kerberos ticket wouldn't have generated and the proxy would have failed.

It usually binds after VPN as that’s when it can reach the AD server. We use NoMAD to deal with this where we are. It will get Kerberos tickets after connection. Unfortunately our brain dead proxies don’t support it.

samhaque commented 2 years ago

We are using this I believe: https://docs.jamf.com/jamf-school/documentation/Configuring_Kerberos_Single_Sign-on.html

And our proxy servers do support it but I'm curious if this is some capability golang had built in or how is this working. It's like the authentication is happening at a different network level hence why I don't see anything interesting in the logs.

samhaque commented 2 years ago

We had such a tough time with other languages such as Python/Java/Node which refuses to pick up the Kerberos ticket for proxy requests, alpaca has been a gamechanger for us lol

rtfmoz2 commented 2 years ago

Yeah it’s network layer auth, well before proxies at the application layer. This is what I expect to occur. Not sure @samuong is convinced though.

samhaque commented 2 years ago

If you can add some debug logging to a seperate branch, I can pull it and test it for you to show you more details of the Kerberos ticket being used to authenticate.

samuong commented 2 years ago

I was expecting the ticket to be attached to an HTTP request - i.e. I thought it was application-level auth, not a network-level. Basically the client sends a request and receives a "407 Proxy Authentication Required" response (with the Proxy-Authenticate: Negotiate header) and then tries to send the request again with the Kerberos service ticket encoded in the Proxy-Authorization header.

If there is some network-level auth going on, awesome, that means we don't need to do anything :). But I'm not sure how to log what's going on at the network-level - you might need a Wireshark to see what's going on at that level?

@samhaque I've added logging of the application-level response statuses to the log-407s branch in case you wanted to give that a try.

samhaque commented 2 years ago

As you can see in the log, authentication is happening before application layer, there is no proxy challenge request due to the OS handling all of that. I am on OSX 12.3 Monterey.

Quite the effort to get a screenshot of the log, they blocked put/post request to GitHub for our org and this was unnecessarily annoying to get it through...

image

rtfmoz2 commented 2 years ago

Updated comment below.

rtfmoz2 commented 2 years ago

After you use the proxy run the following command and paste the sanitised result here. Also if you know the proxy address and name that would be useful so we can see if they appear in the list.

sudo klist -kt

samhaque commented 2 years ago

After you use the proxy run the following command and paste the sanitised result here. Also if you know the proxy address and name that would be useful so we can see if they appear in the list.

sudo klist -kt

The kt flag didn't work but I just did a Sudo klist and as you can see the proxy server is there as one of the entries and that's what alpaca connects to when I curl https://google.com in the next screenshot

image

image

rtfmoz2 commented 2 years ago

Yep so that confirms Kerberos tickets exist for that host. Now if we can get that list via python @samuong can confirm which proxy shots he does not need auth for

On Mon, 18 Apr 2022 at 6:49 pm, Samiul Haque @.***> wrote:

After you use the proxy run the following command and paste the sanitised result here. Also if you know the proxy address and name that would be useful so we can see if they appear in the list.

sudo klist -kt

The kt flag didn't work but I just did a Sudo klist and as you can see the proxy server is there as one of the entries and that's what alpaca connects to when I curl https://google.com in the next screenshot

[image: image] https://user-images.githubusercontent.com/13789262/163783083-195615c4-11c8-4e3c-bd92-944dce6249dc.png

[image: image] https://user-images.githubusercontent.com/13789262/163783106-b0f9dcfd-450d-4e4a-b204-458c7f1c5722.jpeg

— Reply to this email directly, view it on GitHub https://github.com/samuong/alpaca/issues/44#issuecomment-1101230316, or unsubscribe https://github.com/notifications/unsubscribe-auth/AJYYSIVFGAXVJLK4PBGNV5LVFUOXZANCNFSM4J6JOBLA . You are receiving this because you commented.Message ID: @.***>

samhaque commented 2 years ago

You can do something with the os exec package like:

jsonOut := exec.command("klist","--json")

Then unmarshal the json into a struct for use.

In fact the gokrb5 package does something similar. Even with python there is no native library, its using Python's os module to call the command, so might as well do it in go.

samhaque commented 2 years ago

The ideal solution would be keeping a struct of valid Principals until the Expiry date as seen in the screenshot. Then for every proxy request check if the requested FQDN is in list in the struct, if it is don't need to add proxy authorization header as that is handled by the OS, if it is not in our list then we fallback and attach the proxy authorization header.

samuong commented 2 years ago

Thanks for testing guys, that's really interesting.

Alpaca will only do an NTLM handshake if the proxy sends back a 407 Proxy Authentication Required response - all requests are initially attempted unauthenticated. So my understanding from what you're saying is that macOS does the network-level auth, and then if (and only if) that fails then Alpaca will fall back to NTLM.

Is there any need for Alpaca to try to keep track of which proxy SPNs appear in the output of klist?

samhaque commented 2 years ago

If alpaca engages only when the proxy returns a 407, we don't need to to keep track. To fully test this can a log statement be made to print something like 407 proxy header found? Just so we cover all edge cases and document the behaviour.


From: samuong @.> Sent: Monday, April 18, 2022 8:58:42 PM To: samuong/alpaca @.> Cc: Samiul Haque @.>; Mention @.> Subject: Re: [samuong/alpaca] Support HTTP Negotiate/Kerberos Authentication (#44)

Thanks for testing guys, that's really interesting.

Alpaca will only do an NTLM handshake if the proxy sends back a 407 Proxy Authentication Required response - all requests are initially attempted unauthenticated. So my understanding from what you're saying is that macOS does the network-level auth, and then if (and only if) that fails then Alpaca will fall back to NTLM.

Is there any need for Alpaca to try to keep track of which proxy SPNs appear in the output of klist?

— Reply to this email directly, view it on GitHubhttps://can01.safelinks.protection.outlook.com/?url=https%3A%2F%2Fgithub.com%2Fsamuong%2Falpaca%2Fissues%2F44%23issuecomment-1101891770&data=04%7C01%7Csammy.haque%40mail.utoronto.ca%7C7152f07a755b44bb142908da219fbfd4%7C78aac2262f034b4d9037b46d56c55210%7C0%7C0%7C637859267246593373%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C3000&sdata=l6hPI4if7WEZ5giq9BXhLLRx3f%2B5sB%2BmG15XvBnFfNg%3D&reserved=0, or unsubscribehttps://can01.safelinks.protection.outlook.com/?url=https%3A%2F%2Fgithub.com%2Fnotifications%2Funsubscribe-auth%2FADJGQTRUJVYW6CD77YSQ2QDVFYAMFANCNFSM4J6JOBLA&data=04%7C01%7Csammy.haque%40mail.utoronto.ca%7C7152f07a755b44bb142908da219fbfd4%7C78aac2262f034b4d9037b46d56c55210%7C0%7C0%7C637859267246593373%7CUnknown%7CTWFpbGZsb3d8eyJWIjoiMC4wLjAwMDAiLCJQIjoiV2luMzIiLCJBTiI6Ik1haWwiLCJXVCI6Mn0%3D%7C3000&sdata=xMt7w%2Bzm4HtRIFUzlPlnr6TY8PwgNkfxPPF7sbyJmHQ%3D&reserved=0. You are receiving this because you were mentioned.Message ID: @.***>

samuong commented 2 years ago

Yep, I think that makes sense, we should be logging that anyway. I'll have a go at tidying up the log-407s branch so that it does that.

sdt1163 commented 9 months ago

Hi, Any update about this issue ? When i use alpaca with NTLM auth, I've got on log the 407 messages, but connection does not work. Is there a workaround ? I want to use alpaca on my linux rhel8 in front of 2 proxies (bluecoat with NTLM auth, and a Squid), connection to both is manage with a PAC file. Thanks

samuong commented 8 months ago

Hi @sdt1163, if your proxies accept NTLMv2 authentication, then this should work with Alpaca. If not, I suspect you're either using an older version of NTLM, or there's an issue with configuration. Are you able to let me know more about your setup? I.e. what version of NTLM does your Blue Coat proxy require, what authentication (if any) does Squid require, how have you set up Alpaca and what do you see in the logs?

If you're asking about Kerberos, so far most people who I've spoken to about this are using macOS in a Microsoft AD environment, and apparently this is handled at the OS-level (see the comments above). I don't know whether this is true on Linux/RHEL systems, and unfortunately I don't have a way to test it. Any contributions would be appreciated here, including help with testing.

yolkispalkis commented 5 months ago

Any contributions would be appreciated here, including help with testing.

I can help with kerberos testing, I am looking for a cntlm replacement with a patch to support kerberos

yolkispalkis commented 5 months ago

There are two ways to use Kerberos: with an account and password, or by using a CCache. For passwordless (using CCache) Kerberos Auth, I think it should be

``` cfgPath := "/etc/krb5.conf" cfg, err := config.Load(cfgPath) if err != nil { return nil, fmt.Errorf("could not load krb5.conf: %v", err) } u, err := user.Current() if err != nil { return nil, fmt.Errorf("could not get current user: %v", err) } ccpath := "/tmp/krb5cc_" + u.Uid ccache, err := credentials.LoadCCache(ccpath) if err != nil { return nil, fmt.Errorf("could not load CCache: %v", err) } cl, err := client.NewFromCCache(ccache, cfg, client.DisablePAFXFAST(true)) if err != nil { return nil, fmt.Errorf("could not create new client from CCache: %v", err) } spn := "HTTP/..." s := spnego.SPNEGOClient(cl, spn) if err := s.AcquireCred(); err != nil { return nil, fmt.Errorf("could not acquire client credential: %v", err) } st, err := s.InitSecContext() if err != nil { return nil, fmt.Errorf("could not initialize context: %v", err) } nb, err := st.Marshal() if err != nil { return nil, fmt.Errorf("could not marshal SPNEGO: %v", err) } hs := "Negotiate " + base64.StdEncoding.EncodeToString(nb) req.Header.Set("Proxy-Authorization", hs) return rt.RoundTrip(req) ```

I apologise for the ugly code, it's the first time I've used GO

samuong commented 4 months ago

Hi @yolkispalkis, thanks for offering to help test, and for taking a stab at the code. I'll try to carve out a bit of time over the next few weeks to set up something like MIT Kerberos or Heimdal locally, so that I can develop against it, but I can't really guarantee anything.

If you're comfortable doing so, one thing you could try is to just delete all the code inside the authenticator.do() function, and replace it with your code. Then try it within your Kerberos environment and see if it works.