Remove NTLM support

Our NTLM support has been known to be broken in various situations for a
while, specifically on Windows.  The core team is unable to troubleshoot
these problems, and nobody has stepped up to maintain the NTLM support.
In addition, NTLM uses cryptography and security techniques that are
known to be insecure, such as the algorithms DES, MD4, and MD5, as well
as simple, unsalted hashes of passwords.

Since we now support Kerberos, most users should be able to replace
their use of NTLM with Kerberos instead.  Users have reported this
working on Windows and it is known to work well on at least Debian as
well.  Drop support for NTLM and remove it from the codebase.
This commit is contained in:
brian m. carlson 2021-01-12 22:44:28 +00:00
parent a32af909e7
commit dcfd29419e
No known key found for this signature in database
GPG Key ID: 2D0C9BC12F82B3A1
15 changed files with 14 additions and 677 deletions

@ -7,7 +7,6 @@ const (
BasicAccess AccessMode = "basic"
PrivateAccess AccessMode = "private"
NegotiateAccess AccessMode = "negotiate"
NTLMAccess AccessMode = "ntlm"
EmptyAccess AccessMode = ""
)

@ -20,7 +20,7 @@ var (
// DoWithAuth sends an HTTP request to get an HTTP response. It attempts to add
// authentication from netrc or git's credential helpers if necessary,
// supporting basic and ntlm authentication.
// supporting basic authentication.
func (c *Client) DoWithAuth(remote string, access creds.Access, req *http.Request) (*http.Response, error) {
res, err := c.doWithAuth(remote, access, req, nil)
@ -87,9 +87,7 @@ func (c *Client) doWithAuth(remote string, access creds.Access, req *http.Reques
}
func (c *Client) doWithCreds(req *http.Request, credWrapper creds.CredentialHelperWrapper, access creds.Access, via []*http.Request) (*http.Response, error) {
if access.Mode() == creds.NTLMAccess {
return c.doWithNTLM(req, credWrapper)
} else if access.Mode() == creds.NegotiateAccess {
if access.Mode() == creds.NegotiateAccess {
return c.doWithNegotiate(req, credWrapper)
}
@ -142,7 +140,7 @@ func (c *Client) getCreds(remote string, access creds.Access, req *http.Request)
operation := getReqOperation(req)
apiEndpoint := ef.Endpoint(operation, remote)
if access.Mode() != creds.NTLMAccess && access.Mode() != creds.NegotiateAccess {
if access.Mode() != creds.NegotiateAccess {
if requestHasAuth(req) || access.Mode() == creds.NoneAccess {
return creds.CredentialHelperWrapper{CredentialHelper: creds.NullCreds, Input: nil, Url: nil, Creds: nil}, nil
}
@ -165,7 +163,7 @@ func (c *Client) getCreds(remote string, access creds.Access, req *http.Request)
return credWrapper, err
}
// NTLM and Negotiate only
// Negotiate only
credsURL, err := url.Parse(apiEndpoint.Url)
if err != nil {
@ -292,7 +290,6 @@ func setRequestAuthFromURL(req *http.Request, u *url.URL) bool {
}
func setRequestAuth(req *http.Request, user, pass string) {
// better not be NTLM!
if len(user) == 0 && len(pass) == 0 {
return
}
@ -323,7 +320,7 @@ func getAuthAccess(res *http.Response) creds.AccessMode {
}
switch creds.AccessMode(pieces[0]) {
case creds.NegotiateAccess, creds.NTLMAccess:
case creds.NegotiateAccess:
return creds.AccessMode(pieces[0])
}
}

@ -28,10 +28,6 @@ func TestAuthenticateHeaderAccess(t *testing.T) {
"basic 123": creds.BasicAccess,
"basic": creds.BasicAccess,
"unknown": creds.BasicAccess,
"NTLM": creds.NTLMAccess,
"ntlm": creds.NTLMAccess,
"NTLM 1 2 3": creds.NTLMAccess,
"ntlm 1 2 3": creds.NTLMAccess,
"NEGOTIATE": creds.NegotiateAccess,
"negotiate": creds.NegotiateAccess,
"NEGOTIATE 1 2 3": creds.NegotiateAccess,
@ -442,26 +438,6 @@ func TestGetCreds(t *testing.T) {
},
},
},
"ntlm": getCredsTest{
Remote: "origin",
Method: "GET",
Href: "https://git-server.com/repo/lfs/locks",
Endpoint: "https://git-server.com/repo/lfs",
Config: map[string]string{
"lfs.url": "https://git-server.com/repo/lfs",
"lfs.https://git-server.com/repo/lfs.access": "ntlm",
},
Expected: getCredsExpected{
Access: creds.NTLMAccess,
CredsURL: "https://git-server.com/repo/lfs",
Creds: map[string]string{
"protocol": "https",
"host": "git-server.com",
"username": "git-server.com",
"password": "monkey",
},
},
},
"custom auth": getCredsTest{
Remote: "origin",
Method: "GET",
@ -663,11 +639,6 @@ func TestGetCreds(t *testing.T) {
assert.Equal(t, test.Expected.Authorization, req.Header.Get("Authorization"), "authorization")
if test.Expected.Creds != nil {
if desc == "ntlm" {
// For NTLM we initially try with no provided credentials to test SSPI and then prompt. We want to test both sets.
assert.Nil(t, credWrapper.Creds, "creds")
credWrapper.FillCreds()
}
assert.EqualValues(t, test.Expected.Creds, credWrapper.Creds)
} else {
assert.Nil(t, credWrapper.Creds, "creds")

@ -476,10 +476,10 @@ func TestSetAccess(t *testing.T) {
assert.Equal(t, creds.NoneAccess, access.Mode())
assert.Equal(t, url, access.URL())
finder.SetAccess(access.Upgrade(creds.NTLMAccess))
finder.SetAccess(access.Upgrade(creds.NegotiateAccess))
newAccess := finder.AccessFor(url)
assert.Equal(t, creds.NTLMAccess, newAccess.Mode())
assert.Equal(t, creds.NegotiateAccess, newAccess.Mode())
assert.Equal(t, url, newAccess.URL())
}
@ -493,10 +493,10 @@ func TestChangeAccess(t *testing.T) {
assert.Equal(t, creds.BasicAccess, access.Mode())
assert.Equal(t, url, access.URL())
finder.SetAccess(access.Upgrade(creds.NTLMAccess))
finder.SetAccess(access.Upgrade(creds.NegotiateAccess))
newAccess := finder.AccessFor(url)
assert.Equal(t, creds.NTLMAccess, newAccess.Mode())
assert.Equal(t, creds.NegotiateAccess, newAccess.Mode())
assert.Equal(t, url, newAccess.URL())
}

@ -4,36 +4,12 @@ import (
"net/http"
"github.com/git-lfs/git-lfs/creds"
"github.com/git-lfs/git-lfs/errors"
"github.com/rubyist/tracerx"
)
func (c *Client) doWithNegotiate(req *http.Request, credWrapper creds.CredentialHelperWrapper) (*http.Response, error) {
// There are two possibilities here if we're using Negotiate
// authentication. One is that we're using Kerberos, which we try
// first. The other is that we're using NTLM, which we try second with
// single sign-on credentials. Finally, if that also fails, we fall
// back to prompting for credentials with NTLM and trying that.
res, err := c.doWithAccess(req, "", nil, creds.NegotiateAccess)
if err == nil || errors.IsAuthError(err) {
if res.StatusCode != 401 {
return res, nil
}
}
// If we received an error, fall back to NTLM. That will be the case if
// we don't have any cached credentials, which on Unix will look like a
// failed attempt to read a file in /tmp, not a standard auth error.
tracerx.Printf("attempting NTLM as fallback")
res, err = c.doWithAccess(req, "", nil, creds.NTLMAccess)
if err != nil && !errors.IsAuthError(err) {
return res, err
}
if res.StatusCode != 401 {
return res, nil
}
return c.ntlmReAuth(req, credWrapper, true)
// first. The other is that we're using NTLM, which we no longer
// support. Fail in that case.
return c.doWithAccess(req, "", nil, creds.NegotiateAccess)
}

@ -2,21 +2,16 @@ package lfsapi
import (
"fmt"
"sync"
"github.com/git-lfs/git-lfs/creds"
"github.com/git-lfs/git-lfs/errors"
"github.com/git-lfs/git-lfs/lfshttp"
"github.com/git-lfs/go-ntlm/ntlm"
)
type Client struct {
Endpoints EndpointFinder
Credentials creds.CredentialHelper
ntlmSessions map[string]ntlm.ClientSession
ntlmMu sync.Mutex
credContext *creds.CredentialHelperContext
client *lfshttp.Client

@ -1,154 +0,0 @@
package lfsapi
import (
"encoding/base64"
"fmt"
"io"
"io/ioutil"
"net/http"
"strings"
"github.com/git-lfs/git-lfs/creds"
"github.com/git-lfs/git-lfs/errors"
)
type ntmlCredentials struct {
domain string
username string
password string
}
func (c *Client) doWithNTLM(req *http.Request, credWrapper creds.CredentialHelperWrapper) (*http.Response, error) {
res, err := c.do(req, "", nil)
if err != nil && !errors.IsAuthError(err) {
return res, err
}
if res.StatusCode != 401 {
return res, nil
}
return c.ntlmReAuth(req, credWrapper, true)
}
// If the status is 401 then we need to re-authenticate
func (c *Client) ntlmReAuth(req *http.Request, credWrapper creds.CredentialHelperWrapper, retry bool) (*http.Response, error) {
// Try SSPI first.
if c.ntlmSupportsSSPI() == true {
res, err := c.ntlmAuthenticateRequest(req, nil)
if err != nil && !errors.IsAuthError(err) {
return res, err
}
// If SSPI succeeded, then we can move on.
if res.StatusCode < 300 && res.StatusCode > 199 {
return res, nil
}
}
// If SSPI failed, then we need to try the normal.
credWrapper.FillCreds()
ntmlCreds, err := ntlmGetCredentials(credWrapper.Creds)
if err != nil {
return nil, err
}
res, err := c.ntlmAuthenticateRequest(req, ntmlCreds)
if err != nil && !errors.IsAuthError(err) {
return res, err
}
switch res.StatusCode {
case 401:
credWrapper.CredentialHelper.Reject(credWrapper.Creds)
if retry {
return c.ntlmReAuth(req, credWrapper, false)
}
case 403:
credWrapper.CredentialHelper.Reject(credWrapper.Creds)
default:
if res.StatusCode < 300 && res.StatusCode > 199 {
credWrapper.CredentialHelper.Approve(credWrapper.Creds)
}
}
return res, nil
}
func (c *Client) ntlmSendType1Message(req *http.Request, message []byte) (*http.Response, []byte, error) {
res, err := c.ntlmSendMessage(req, message)
if err != nil && !errors.IsAuthError(err) {
return res, nil, err
}
io.Copy(ioutil.Discard, res.Body)
res.Body.Close()
by, err := parseChallengeResponse(res)
return res, by, err
}
func (c *Client) ntlmSendType3Message(req *http.Request, authenticate []byte) (*http.Response, error) {
return c.ntlmSendMessage(req, authenticate)
}
func (c *Client) ntlmSendMessage(req *http.Request, message []byte) (*http.Response, error) {
body, err := rewoundRequestBody(req)
if err != nil {
return nil, err
}
req.Body = body
msg := base64.StdEncoding.EncodeToString(message)
req.Header.Set("Authorization", "NTLM "+msg)
return c.do(req, "", nil)
}
func parseChallengeResponse(res *http.Response) ([]byte, error) {
header := res.Header.Get("Www-Authenticate")
if len(header) < 6 {
return nil, fmt.Errorf("invalid NTLM challenge response: %q", header)
}
//parse out the "NTLM " at the beginning of the response
challenge := header[5:]
val, err := base64.StdEncoding.DecodeString(challenge)
if err != nil {
return nil, err
}
return []byte(val), nil
}
func rewoundRequestBody(req *http.Request) (io.ReadCloser, error) {
if req.Body == nil {
return nil, nil
}
body, ok := req.Body.(ReadSeekCloser)
if !ok {
return nil, fmt.Errorf("Request body must implement io.ReadCloser and io.Seeker. Got: %T", body)
}
_, err := body.Seek(0, io.SeekStart)
return body, err
}
func ntlmGetCredentials(creds creds.Creds) (*ntmlCredentials, error) {
username := creds["username"]
password := creds["password"]
if username == "" && password == "" {
return nil, nil
}
splits := strings.Split(username, "\\")
if len(splits) != 2 {
return nil, fmt.Errorf("Your user name must be of the form DOMAIN\\user. It is currently '%s'", username)
}
domain := strings.ToUpper(splits[0])
username = splits[1]
return &ntmlCredentials{domain: domain, username: username, password: password}, nil
}

@ -1,73 +0,0 @@
// +build !windows
package lfsapi
import (
"encoding/base64"
"fmt"
"net/http"
"github.com/git-lfs/go-ntlm/ntlm"
)
func (c *Client) ntlmSupportsSSPI() bool {
return false
}
func (c *Client) ntlmAuthenticateRequest(req *http.Request, creds *ntmlCredentials) (*http.Response, error) {
negotiate, err := base64.StdEncoding.DecodeString(ntlmNegotiateMessage)
if err != nil {
return nil, err
}
chRes, challengeMsg, err := c.ntlmSendType1Message(req, negotiate)
if err != nil {
return chRes, err
}
challenge, err := ntlm.ParseChallengeMessage(challengeMsg)
if err != nil {
return nil, err
}
session, err := c.ntlmClientSession(creds)
if err != nil {
return nil, err
}
session.ProcessChallengeMessage(challenge)
authenticate, err := session.GenerateAuthenticateMessage()
if err != nil {
return nil, err
}
return c.ntlmSendType3Message(req, authenticate.Bytes())
}
func (c *Client) ntlmClientSession(creds *ntmlCredentials) (ntlm.ClientSession, error) {
c.ntlmMu.Lock()
defer c.ntlmMu.Unlock()
if creds == nil {
return nil, fmt.Errorf("Your user name must be of the form DOMAIN\\user. Single-sign-on is not supported.")
}
if c.ntlmSessions == nil {
c.ntlmSessions = make(map[string]ntlm.ClientSession)
}
if ses, ok := c.ntlmSessions[creds.domain]; ok {
return ses, nil
}
session, err := ntlm.CreateClientSession(ntlm.Version2, ntlm.ConnectionOrientedMode)
if err != nil {
return nil, err
}
session.SetUserInfo(creds.username, creds.password, creds.domain)
c.ntlmSessions[creds.domain] = session
return session, nil
}
const ntlmNegotiateMessage = "TlRMTVNTUAABAAAAB7IIogwADAAzAAAACwALACgAAAAKAAAoAAAAD1dJTExISS1NQUlOTk9SVEhBTUVSSUNB"

@ -1,36 +0,0 @@
// +build !windows
package lfsapi
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestNtlmClientSession(t *testing.T) {
cli, err := NewClient(nil)
require.Nil(t, err)
creds := &ntmlCredentials{domain: "MOOSEDOMAIN", username: "canadian", password: "MooseAntlersYeah"}
session1, err := cli.ntlmClientSession(creds)
assert.Nil(t, err)
assert.NotNil(t, session1)
// The second call should ignore creds and give the session we just created.
badCreds := &ntmlCredentials{domain: "MOOSEDOMAIN", username: "badusername", password: "MooseAntlersYeah"}
session2, err := cli.ntlmClientSession(badCreds)
assert.Nil(t, err)
assert.NotNil(t, session2)
assert.EqualValues(t, session1, session2)
}
func TestNtlmClientSessionBadCreds(t *testing.T) {
cli, err := NewClient(nil)
require.Nil(t, err)
// Single-Sign-On is not supported on *nix
_, err = cli.ntlmClientSession(nil)
assert.NotNil(t, err)
}

@ -1,47 +0,0 @@
// +build windows
package lfsapi
import (
"net/http"
"github.com/alexbrainman/sspi"
"github.com/alexbrainman/sspi/ntlm"
)
func (c *Client) ntlmSupportsSSPI() bool {
return true
}
func (c *Client) ntlmAuthenticateRequest(req *http.Request, creds *ntmlCredentials) (*http.Response, error) {
var sspiCreds *sspi.Credentials
var err error
if creds == nil {
sspiCreds, err = ntlm.AcquireCurrentUserCredentials()
} else {
sspiCreds, err = ntlm.AcquireUserCredentials(creds.domain, creds.username, creds.password)
}
if err != nil {
return nil, err
}
defer sspiCreds.Release()
secctx, negotiate, err := ntlm.NewClientContext(sspiCreds)
if err != nil {
return nil, err
}
defer secctx.Release()
chRes, challengeMsg, err := c.ntlmSendType1Message(req, negotiate)
if err != nil {
return chRes, err
}
authenticateMsg, err := secctx.Update(challengeMsg)
if err != nil {
return nil, err
}
return c.ntlmSendType3Message(req, authenticateMsg)
}

@ -1,196 +0,0 @@
package lfsapi
import (
"encoding/base64"
"io/ioutil"
"net/http"
"net/http/httptest"
"net/url"
"runtime"
"strings"
"sync/atomic"
"testing"
"github.com/git-lfs/git-lfs/creds"
"github.com/git-lfs/git-lfs/lfshttp"
"github.com/git-lfs/go-ntlm/ntlm"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestNtlmAuth(t *testing.T) {
session, err := ntlm.CreateServerSession(ntlm.Version2, ntlm.ConnectionOrientedMode)
require.Nil(t, err)
session.SetUserInfo("ntlmuser", "ntlmpass", "NTLMDOMAIN")
var called uint32
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
reqIndex := atomic.LoadUint32(&called)
atomic.AddUint32(&called, 1)
if called == 4 && runtime.GOOS != "windows" {
// We don't want to hit the second class 1 path if we are on *nix.
atomic.AddUint32(&called, 1)
}
authHeader := req.Header.Get("Authorization")
t.Logf("REQUEST %d: %s %s", reqIndex, req.Method, req.URL)
t.Logf("AUTH: %q", authHeader)
// assert full body is sent each time
by, err := ioutil.ReadAll(req.Body)
req.Body.Close()
if assert.Nil(t, err) {
assert.Equal(t, "ntlm", string(by))
}
switch called {
case 1:
w.Header().Set("Www-Authenticate", "ntlm")
w.WriteHeader(401)
case 2, 4:
assert.True(t, strings.HasPrefix(req.Header.Get("Authorization"), "NTLM "))
neg := authHeader[5:] // strip "ntlm " prefix
_, err := base64.StdEncoding.DecodeString(neg)
if !assert.Nil(t, err) {
t.Logf("neg base64 error: %+v", err)
w.WriteHeader(500)
return
}
// ntlm implementation can't currently parse the negotiate message
ch, err := session.GenerateChallengeMessage()
if !assert.Nil(t, err) {
t.Logf("challenge gen error: %+v", err)
w.WriteHeader(500)
return
}
chMsg := base64.StdEncoding.EncodeToString(ch.Bytes())
w.Header().Set("Www-Authenticate", "ntlm "+chMsg)
w.WriteHeader(401)
default: // should be an auth msg
authHeader := req.Header.Get("Authorization")
assert.True(t, strings.HasPrefix(strings.ToUpper(authHeader), "NTLM "))
auth := authHeader[5:] // strip "ntlm " prefix
val, err := base64.StdEncoding.DecodeString(auth)
if !assert.Nil(t, err) {
t.Logf("auth base64 error: %+v", err)
w.WriteHeader(500)
return
}
authMsg, err := ntlm.ParseAuthenticateMessage(val, 2)
if !assert.Nil(t, err) {
t.Logf("auth parse error: %+v", err)
w.WriteHeader(500)
return
}
if called == 3 && runtime.GOOS == "windows" {
// This is the SSPI call that should return unauth so that standard NTLM can run.
w.WriteHeader(401)
return
}
err = session.ProcessAuthenticateMessage(authMsg)
if !assert.Nil(t, err) {
t.Logf("auth process error: %+v", err)
w.WriteHeader(500)
return
}
w.WriteHeader(200)
}
}))
defer srv.Close()
req, err := http.NewRequest("POST", srv.URL+"/ntlm", NewByteBody([]byte("ntlm")))
require.Nil(t, err)
credHelper := newMockCredentialHelper()
cli, err := NewClient(lfshttp.NewContext(nil, nil, map[string]string{
"lfs.url": srv.URL + "/ntlm",
"lfs." + srv.URL + "/ntlm.access": "ntlm",
}))
cli.Credentials = credHelper
require.Nil(t, err)
// ntlm support pulls domain and login info from git credentials
srvURL, err := url.Parse(srv.URL)
require.Nil(t, err)
cred := creds.Creds{
"protocol": srvURL.Scheme,
"host": srvURL.Host,
"username": "ntlmdomain\\ntlmuser",
"password": "ntlmpass",
}
credHelper.Approve(cred)
res, err := cli.DoWithAuth("remote", cli.Endpoints.AccessFor(srv.URL+"/ntlm"), req)
require.Nil(t, err)
assert.Equal(t, 200, res.StatusCode)
assert.True(t, credHelper.IsApproved(cred))
}
func TestNtlmGetCredentials(t *testing.T) {
cred := creds.Creds{"username": "MOOSEDOMAIN\\canadian", "password": "MooseAntlersYeah"}
ntmlCreds, err := ntlmGetCredentials(cred)
assert.Nil(t, err)
assert.NotNil(t, ntmlCreds)
assert.Equal(t, "MOOSEDOMAIN", ntmlCreds.domain)
assert.Equal(t, "canadian", ntmlCreds.username)
assert.Equal(t, "MooseAntlersYeah", ntmlCreds.password)
cred = creds.Creds{"username": "", "password": ""}
ntmlCreds, err = ntlmGetCredentials(cred)
assert.Nil(t, err)
assert.Nil(t, ntmlCreds)
}
func TestNtlmGetCredentialsBadCreds(t *testing.T) {
cred := creds.Creds{"username": "badusername", "password": "MooseAntlersYeah"}
_, err := ntlmGetCredentials(cred)
assert.NotNil(t, err)
}
func TestNtlmHeaderParseValid(t *testing.T) {
res := http.Response{}
res.Header = make(map[string][]string)
res.Header.Add("Www-Authenticate", "NTLM "+base64.StdEncoding.EncodeToString([]byte("I am a moose")))
bytes, err := parseChallengeResponse(&res)
assert.Nil(t, err)
assert.False(t, strings.HasPrefix(string(bytes), "NTLM"))
}
func TestNtlmHeaderParseInvalidLength(t *testing.T) {
res := http.Response{}
res.Header = make(map[string][]string)
res.Header.Add("Www-Authenticate", "NTL")
ret, err := parseChallengeResponse(&res)
assert.NotNil(t, err)
assert.Nil(t, ret)
}
func TestNtlmHeaderParseInvalid(t *testing.T) {
res := http.Response{}
res.Header = make(map[string][]string)
res.Header.Add("Www-Authenticate", base64.StdEncoding.EncodeToString([]byte("NTLM I am a moose")))
ret, err := parseChallengeResponse(&res)
assert.NotNil(t, err)
assert.Nil(t, ret)
}
func assertRequestsEqual(t *testing.T, req1 *http.Request, req2 *http.Request, req1Body []byte) {
assert.Equal(t, req1.Method, req2.Method)
for k, v := range req1.Header {
assert.Equal(t, v, req2.Header[k])
}
if req1.Body == nil {
assert.Nil(t, req2.Body)
} else {
bytes2, _ := ioutil.ReadAll(req2.Body)
assert.Equal(t, req1Body, bytes2)
}
}

@ -34,8 +34,6 @@ import (
"strings"
"sync"
"time"
"github.com/git-lfs/go-ntlm/ntlm"
)
var (
@ -88,13 +86,6 @@ func main() {
}
serverClientCert.StartTLS()
ntlmSession, err := ntlm.CreateServerSession(ntlm.Version2, ntlm.ConnectionOrientedMode)
if err != nil {
fmt.Println("Error creating ntlm session:", err)
os.Exit(1)
}
ntlmSession.SetUserInfo("ntlmuser", "ntlmpass", "NTLMDOMAIN")
stopch := make(chan bool)
mux.HandleFunc("/shutdown", func(w http.ResponseWriter, r *http.Request) {
@ -120,7 +111,7 @@ func main() {
}
if strings.Contains(r.URL.Path, "/info/lfs") {
if !skipIfBadAuth(w, r, id, ntlmSession) {
if !skipIfBadAuth(w, r, id) {
lfsHandler(w, r, id)
}
@ -1571,12 +1562,8 @@ func skipIfNoCookie(w http.ResponseWriter, r *http.Request, id string) bool {
return true
}
func skipIfBadAuth(w http.ResponseWriter, r *http.Request, id string, ntlmSession ntlm.ServerSession) bool {
func skipIfBadAuth(w http.ResponseWriter, r *http.Request, id string) bool {
auth := r.Header.Get("Authorization")
if strings.Contains(r.URL.Path, "ntlm") {
return false
}
if auth == "" {
w.WriteHeader(401)
return true
@ -1608,49 +1595,6 @@ func skipIfBadAuth(w http.ResponseWriter, r *http.Request, id string, ntlmSessio
return true
}
func handleNTLM(w http.ResponseWriter, r *http.Request, authHeader string, session ntlm.ServerSession) {
if strings.HasPrefix(strings.ToUpper(authHeader), "BASIC ") {
authHeader = ""
}
switch authHeader {
case "":
w.Header().Set("Www-Authenticate", "ntlm")
w.WriteHeader(401)
// ntlmNegotiateMessage from httputil pkg
case "NTLM TlRMTVNTUAABAAAAB7IIogwADAAzAAAACwALACgAAAAKAAAoAAAAD1dJTExISS1NQUlOTk9SVEhBTUVSSUNB":
ch, err := session.GenerateChallengeMessage()
if err != nil {
writeLFSError(w, 500, err.Error())
return
}
chMsg := base64.StdEncoding.EncodeToString(ch.Bytes())
w.Header().Set("Www-Authenticate", "ntlm "+chMsg)
w.WriteHeader(401)
default:
if !strings.HasPrefix(strings.ToUpper(authHeader), "NTLM ") {
writeLFSError(w, 500, "bad authorization header: "+authHeader)
return
}
auth := authHeader[5:] // strip "ntlm " prefix
val, err := base64.StdEncoding.DecodeString(auth)
if err != nil {
writeLFSError(w, 500, "base64 decode error: "+err.Error())
return
}
_, err = ntlm.ParseAuthenticateMessage(val, 2)
if err != nil {
writeLFSError(w, 500, "auth parse error: "+err.Error())
return
}
}
}
func init() {
oidHandlers = make(map[string]string)
for _, content := range contentHandlers {

@ -123,25 +123,3 @@ begin_test "batch transfers with ssh endpoint"
git push origin main 2>&1
)
end_test
begin_test "batch transfers with ntlm server"
(
set -e
reponame="ntlmtest"
setup_remote_repo "$reponame"
printf "ntlmdomain\\\ntlmuser:ntlmpass" > "$CREDSDIR/127.0.0.1--$reponame"
clone_repo "$reponame" "$reponame"
contents="test"
oid="$(calc_oid "$contents")"
git lfs track "*.dat"
printf "%s" "$contents" > test.dat
git add .gitattributes test.dat
git commit -m "initial commit"
GIT_CURL_VERBOSE=1 git push origin main 2>&1
)
end_test

@ -19,18 +19,6 @@ func TestManifestIsConfigurable(t *testing.T) {
assert.Equal(t, 3, m.MaxRetries())
}
func TestManifestChecksNTLM(t *testing.T) {
cli, err := lfsapi.NewClient(lfshttp.NewContext(nil, nil, map[string]string{
"lfs.url": "http://foo",
"lfs.http://foo.access": "ntlm",
"lfs.concurrenttransfers": "3",
}))
require.Nil(t, err)
m := NewManifest(nil, cli, "", "")
assert.Equal(t, 8, m.MaxRetries())
}
func TestManifestClampsValidValues(t *testing.T) {
cli, err := lfsapi.NewClient(lfshttp.NewContext(nil, nil, map[string]string{
"lfs.transfer.maxretries": "-1",

@ -7,7 +7,6 @@ import (
"sync"
"time"
"github.com/git-lfs/git-lfs/creds"
"github.com/git-lfs/git-lfs/errors"
"github.com/git-lfs/git-lfs/git"
"github.com/git-lfs/git-lfs/lfshttp"
@ -906,10 +905,6 @@ func (q *TransferQueue) ensureAdapterBegun(e lfshttp.Endpoint) error {
func (q *TransferQueue) toAdapterCfg(e lfshttp.Endpoint) AdapterConfig {
apiClient := q.manifest.APIClient()
concurrency := q.manifest.ConcurrentTransfers()
access := apiClient.Endpoints.AccessFor(e.Url)
if access.Mode() == creds.NTLMAccess {
concurrency = 1
}
return &adapterConfig{
concurrentTransfers: concurrency,