alpaca icon indicating copy to clipboard operation
alpaca copied to clipboard

Support HTTP Negotiate/Kerberos Authentication

Open samuong opened this issue 5 years ago • 29 comments

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.

samuong avatar Dec 22 '19 00:12 samuong

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

rtfmoz2 avatar Apr 04 '22 08:04 rtfmoz2

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?

samuong avatar Apr 04 '22 22:04 samuong

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: @.***>

rtfmoz2 avatar Apr 04 '22 23:04 rtfmoz2

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.

samuong avatar Apr 06 '22 10:04 samuong

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

samhaque avatar Apr 14 '22 07:04 samhaque

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 avatar Apr 18 '22 02:04 samhaque

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.

samhaque avatar Apr 18 '22 02:04 samhaque

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 avatar Apr 18 '22 02:04 rtfmoz2

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.

rtfmoz2 avatar Apr 18 '22 02:04 rtfmoz2

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 avatar Apr 18 '22 02:04 samhaque

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

samhaque avatar Apr 18 '22 02:04 samhaque

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.

rtfmoz2 avatar Apr 18 '22 02:04 rtfmoz2

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.

samhaque avatar Apr 18 '22 02:04 samhaque

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.

samuong avatar Apr 18 '22 06:04 samuong

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

samhaque avatar Apr 18 '22 07:04 samhaque

Updated comment below.

rtfmoz2 avatar Apr 18 '22 07:04 rtfmoz2

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

rtfmoz2 avatar Apr 18 '22 07:04 rtfmoz2

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

samhaque avatar Apr 18 '22 08:04 samhaque

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: @.***>

rtfmoz2 avatar Apr 18 '22 08:04 rtfmoz2

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 avatar Apr 18 '22 09:04 samhaque

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.

samhaque avatar Apr 18 '22 09:04 samhaque

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?

samuong avatar Apr 19 '22 00:04 samuong

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: @.***>

samhaque avatar Apr 19 '22 01:04 samhaque

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.

samuong avatar Apr 19 '22 23:04 samuong

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

sdt1163 avatar Oct 11 '23 12:10 sdt1163

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.

samuong avatar Oct 22 '23 22:10 samuong

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 avatar Feb 03 '24 12:02 yolkispalkis

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

yolkispalkis avatar Feb 08 '24 12:02 yolkispalkis

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.

samuong avatar Feb 09 '24 06:02 samuong