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:
parent
a32af909e7
commit
dcfd29419e
@ -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
|
||||
|
154
lfsapi/ntlm.go
154
lfsapi/ntlm.go
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user