-
Notifications
You must be signed in to change notification settings - Fork 53
Add Windows Kerberos/Negotiate proxy authentication (SSPI) #180
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
base: master
Are you sure you want to change the base?
Changes from all commits
8d33e5f
110b80b
36720a6
344879b
0d6be23
034a8de
d4b36f9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,12 +12,13 @@ | |
| // See the License for the specific language governing permissions and | ||
| // limitations under the License. | ||
|
|
||
| //go:build !darwin | ||
| //go:build !darwin && !windows | ||
|
|
||
| package main | ||
|
|
||
| // newNegotiateAuthenticator is a stub for non-macOS platforms. Kerberos | ||
| // authentication via GSS.framework is only available on macOS. | ||
| // newNegotiateAuthenticator is a stub for platforms without a Kerberos | ||
| // backend. Kerberos/Negotiate is implemented on macOS (GSS.framework, | ||
| // kerberos_darwin.go) and Windows (SSPI, kerberos_windows.go). | ||
|
Comment on lines
+20
to
+21
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. let's remove the second sentence of this comment, so that it doesn't churn every time we add a new platform? |
||
| func newNegotiateAuthenticator() proxyAuthenticator { | ||
| return nil | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,103 @@ | ||
| // Copyright 2026 The Alpaca Authors | ||
| // | ||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||
| // you may not use this file except in compliance with the License. | ||
| // You may obtain a copy of the License at | ||
| // | ||
| // http://www.apache.org/licenses/LICENSE-2.0 | ||
| // | ||
| // Unless required by applicable law or agreed to in writing, software | ||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| // See the License for the specific language governing permissions and | ||
| // limitations under the License. | ||
|
|
||
| //go:build darwin || windows | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the various kerberos files were a bit confusing to me at first glance, can we rename them to:
|
||
|
|
||
| package main | ||
|
|
||
| import ( | ||
| "encoding/base64" | ||
| "fmt" | ||
| "log" | ||
| "net/http" | ||
| "net/url" | ||
| ) | ||
|
|
||
| // negotiateAuthenticator implements proxyAuthenticator using SPNEGO | ||
| // (Kerberos/Negotiate). The platform backends supply the two functions | ||
| // it depends on: checkKerberosTicket and generateSPNEGOToken. macOS supplies | ||
| // them via GSS.framework (kerberos_darwin.go), Windows via SSPI | ||
| // (kerberos_windows.go). | ||
| // | ||
| // It does NOT enforce a host allowlist itself; that's the picker's job | ||
| // (see *authChain.allowedHost), which applies uniformly to Basic, NTLM, | ||
| // and Negotiate. The only per-method applicability check Negotiate | ||
| // enforces is "do we currently have a Kerberos ticket?", re-checked on | ||
| // every 407 so a ticket that arrives mid-session is honoured | ||
| // automatically without an alpaca restart. | ||
| type negotiateAuthenticator struct { | ||
| // hasTicket is the ticket-availability check used by applicableTo | ||
| // at picker time. Defaults to checkKerberosTicket; tests inject | ||
| // their own to avoid depending on the developer's real Kerberos | ||
| // state. | ||
| hasTicket func() bool | ||
| } | ||
|
|
||
| func (n *negotiateAuthenticator) scheme() string { return "Negotiate" } | ||
|
|
||
| // applicableTo enforces two policies at picker time: | ||
| // | ||
| // 1. The proxy host must be non-empty (we cannot generate an SPN | ||
| // without it). | ||
| // 2. A usable credential must currently be available, as reported by the | ||
| // platform's checkKerberosTicket. We re-check on every 407 because the | ||
| // credential may have expired or been revoked since alpaca started; if | ||
| // it has, returning false here causes the picker to omit Negotiate and | ||
| // fall through to NTLM / Basic instead of failing the chain. | ||
| // | ||
| // Host policy (the ALPACA_PROXY_AUTH_ALLOWLIST gate) is enforced at the | ||
| // picker level in *authChain.pick, uniformly across Basic, NTLM, and | ||
| // Negotiate, so this method intentionally doesn't repeat that check. | ||
| // | ||
| // Returning false is silent fall-through; the chain proceeds to the | ||
| // next configured authenticator. | ||
| func (n *negotiateAuthenticator) applicableTo(proxyHost string) bool { | ||
| if proxyHost == "" { | ||
| return false | ||
| } | ||
| check := n.hasTicket | ||
| if check == nil { | ||
| check = checkKerberosTicket | ||
| } | ||
| if !check() { | ||
| log.Printf("Kerberos ticket no longer valid; skipping Negotiate for %s", | ||
| proxyHost) | ||
| return false | ||
| } | ||
| return true | ||
| } | ||
|
|
||
| // do performs Negotiate/SPNEGO proxy authentication. It generates a SPNEGO | ||
| // token for the upstream proxy and sends the request with a | ||
| // Proxy-Authorization: Negotiate header. | ||
| func (n *negotiateAuthenticator) do(req *http.Request, rt http.RoundTripper) (*http.Response, error) { | ||
| // Get the proxy host from the request context. | ||
| proxyHost := "" | ||
| if value := req.Context().Value(contextKeyProxy); value != nil { | ||
| proxy := value.(*url.URL) | ||
| proxyHost = proxy.Hostname() | ||
| } | ||
| if proxyHost == "" { | ||
| return nil, fmt.Errorf("cannot determine proxy host for Negotiate auth") | ||
| } | ||
|
|
||
| token, err := generateSPNEGOToken(proxyHost) | ||
| if err != nil { | ||
| log.Printf("Error generating SPNEGO token for %s: %v", proxyHost, err) | ||
| return nil, err | ||
| } | ||
|
|
||
| req.Header.Set("Proxy-Authorization", "Negotiate "+base64.StdEncoding.EncodeToString(token)) | ||
| return rt.RoundTrip(req) | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Something I'm noticing is that Claude is adding a lot of documentation in both markdown files and in comments, and often even repeating comments in multiple places. This is creating a few problems:
So there are a few things we should instruct coding agents to do, with regards to comments:
If you've got any other prompts, let's discuss adding them too. Are you able to ask Claude to remove comments based on these guidelines and see if and how much this helps?
Also I'm not sure where this belongs, maybe in another section...