Git Lfs NTLM Work

* Switched the NTLM toggle to use the same url access pattern as private batch auth
* Updated the NTLM session to pull the credentials from the cred helper
This commit is contained in:
William Hipschman 2015-10-05 15:26:39 -04:00
parent 21159f8baa
commit d71daeeb04
5 changed files with 118 additions and 95 deletions

@ -14,6 +14,7 @@ import (
"path/filepath" "path/filepath"
"regexp" "regexp"
"strconv" "strconv"
"strings"
"github.com/github/git-lfs/vendor/_nuts/github.com/rubyist/tracerx" "github.com/github/git-lfs/vendor/_nuts/github.com/rubyist/tracerx"
) )
@ -227,10 +228,17 @@ func Batch(objects []*objectResource, operation string) ([]*objectResource, erro
return nil, newRetriableError(err) return nil, newRetriableError(err)
} }
if IsAuthError(err) { tracerx.Printf("BATCH---------HEADER---: %s", res.Header["Www-Authenticate"][0][0:4])
Config.SetAccess("basic") if IsAuthError(err){
tracerx.Printf("api: batch not authorized, submitting with auth") if strings.ToLower(res.Header["Www-Authenticate"][0][0:4]) == "ntlm" {
return Batch(objects, operation) Config.SetAccess("ntlm")
tracerx.Printf("api: response indicates ntlm, submitting with ntlm auth")
return Batch(objects, operation)
} else {
Config.SetAccess("basic")
tracerx.Printf("api: batch not authorized, submitting with auth")
return Batch(objects, operation)
}
} }
switch res.StatusCode { switch res.StatusCode {
@ -286,9 +294,15 @@ func UploadCheck(oidPath string) (*objectResource, error) {
if err != nil { if err != nil {
if IsAuthError(err) { if IsAuthError(err) {
Config.SetAccess("basic") if strings.ToLower(res.Header["Www-Authenticate"][0][0:4]) == "ntlm" {
tracerx.Printf("api: upload check not authorized, submitting with auth") Config.SetAccess("ntlm")
return UploadCheck(oidPath) tracerx.Printf("api: response indicates ntlm, submitting with ntlm auth")
return UploadCheck(oidPath)
} else{
Config.SetAccess("basic")
tracerx.Printf("api: upload check not authorized, submitting with auth")
return UploadCheck(oidPath)
}
} }
return nil, newRetriableError(err) return nil, newRetriableError(err)
@ -460,9 +474,17 @@ func doAPIRequest(req *http.Request, useCreds bool) (*http.Response, error) {
// doHttpRequest runs the given HTTP request. LFS or Storage API requests should // doHttpRequest runs the given HTTP request. LFS or Storage API requests should
// use doApiBatchRequest() or doStorageRequest() instead. // use doApiBatchRequest() or doStorageRequest() instead.
func doHttpRequest(req *http.Request, creds Creds) (*http.Response, error) { func doHttpRequest(req *http.Request, creds Creds) (*http.Response, error) {
res, err := Config.HttpClient().Do(req)
if Config.NTLM() {
tracerx.Printf("ENTER doHttpRequest")
defer tracerx.Printf("LEAVE doHttpRequest")
var(
res *http.Response
err error
)
if Config.NtlmAccess() {
res, err = DoNTLMRequest(req, true) res, err = DoNTLMRequest(req, true)
} else { } else {
res, err = Config.HttpClient().Do(req) res, err = Config.HttpClient().Do(req)
@ -478,7 +500,13 @@ func doHttpRequest(req *http.Request, creds Creds) (*http.Response, error) {
} }
if err != nil { if err != nil {
err = Error(err) if IsAuthError(err) && res.Header["Www-Authenticate"][0][0:4] == "ntlm" {
Config.SetAccess("ntlm")
tracerx.Printf("api: response indicates ntlm, submitting with ntlm auth")
doHttpRequest(req, creds)
} else {
err = Error(err)
}
} else { } else {
err = handleResponse(res, creds) err = handleResponse(res, creds)
} }
@ -497,7 +525,7 @@ func doHttpRequest(req *http.Request, creds Creds) (*http.Response, error) {
func doApiRequestWithRedirects(req *http.Request, via []*http.Request, useCreds bool) (*http.Response, error) { func doApiRequestWithRedirects(req *http.Request, via []*http.Request, useCreds bool) (*http.Response, error) {
var creds Creds var creds Creds
if useCreds { if useCreds {
c, err := getCredsForAPI(req) c, err := getCredsForAPI(req, false)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -724,7 +752,7 @@ func setRequestAuthFromUrl(req *http.Request, u *url.URL) bool {
} }
func setRequestAuth(req *http.Request, user, pass string) { func setRequestAuth(req *http.Request, user, pass string) {
if(Config.NTLM()){ if(Config.NtlmAccess()){
//no-op. The NTLM manager will handle auth headers //no-op. The NTLM manager will handle auth headers
} else { } else {
if len(user) == 0 && len(pass) == 0 { if len(user) == 0 && len(pass) == 0 {
@ -758,10 +786,4 @@ func setErrorHeaderContext(err error, prefix string, head http.Header) {
ErrorSetContext(err, contextKey, head.Get(key)) ErrorSetContext(err, contextKey, head.Get(key))
} }
} }
} }
func ntlmHandshake(){
if !Config.NTLM(){
panic("NTLM is not enabled but an NTLM handshake was attempted")
}
}

@ -129,34 +129,6 @@ func (c *Configuration) ConcurrentTransfers() int {
return uploads return uploads
} }
func (c *Configuration) NTLM() bool {
if v, ok := c.GitConfig("lfs.ntlm"); ok {
if v == "true" || v == "" {
return true
}
// Any numeric value except 0 is considered true
if n, err := strconv.Atoi(v); err == nil && n != 0 {
return true
}
}
return false
}
func (c *Configuration) BatchTransfer() bool {
if v, ok := c.GitConfig("lfs.batch"); ok {
if v == "true" || v == "" {
return true
}
// Any numeric value except 0 is considered true
if n, err := strconv.Atoi(v); err == nil && n != 0 {
return true
}
}
return false
}
func (c *Configuration) BatchTransfer() bool { func (c *Configuration) BatchTransfer() bool {
value, ok := c.GitConfig("lfs.batch") value, ok := c.GitConfig("lfs.batch")
if !ok || len(value) == 0 { if !ok || len(value) == 0 {
@ -171,12 +143,16 @@ func (c *Configuration) BatchTransfer() bool {
return useBatch return useBatch
} }
func (c *Configuration) NtlmAccess() bool {
return c.Access() == "ntlm"
}
// PrivateAccess will retrieve the access value and return true if // PrivateAccess will retrieve the access value and return true if
// the value is set to private. When a repo is marked as having private // the value is set to private. When a repo is marked as having private
// access, the http requests for the batch api will fetch the credentials // access, the http requests for the batch api will fetch the credentials
// before running, otherwise the request will run without credentials. // before running, otherwise the request will run without credentials.
func (c *Configuration) PrivateAccess() bool { func (c *Configuration) PrivateAccess() bool {
return c.Access() != "none" return c.Access() != "none" && c.Access() != "ntlm"
} }
// Access returns the access auth type. // Access returns the access auth type.

@ -7,6 +7,7 @@ import (
"net/url" "net/url"
"os/exec" "os/exec"
"strings" "strings"
"github.com/github/git-lfs/vendor/_nuts/github.com/rubyist/tracerx"
) )
// getCreds gets the credentials for the given request's URL, and sets its // getCreds gets the credentials for the given request's URL, and sets its
@ -14,6 +15,7 @@ import (
// getCredsForAPI(), but skips checking the LFS url or git remote. // getCredsForAPI(), but skips checking the LFS url or git remote.
func getCreds(req *http.Request) (Creds, error) { func getCreds(req *http.Request) (Creds, error) {
if skipCredsCheck(req) { if skipCredsCheck(req) {
tracerx.Printf("getCreds: skipping")
return nil, nil return nil, nil
} }
@ -30,8 +32,9 @@ func getCreds(req *http.Request) (Creds, error) {
// This prefers the Git remote URL for checking credentials so that users only // This prefers the Git remote URL for checking credentials so that users only
// have to enter their passwords once for Git and Git LFS. It uses the same // have to enter their passwords once for Git and Git LFS. It uses the same
// URL path that Git does, in case 'useHttpPath' is enabled in the Git config. // URL path that Git does, in case 'useHttpPath' is enabled in the Git config.
func getCredsForAPI(req *http.Request) (Creds, error) { func getCredsForAPI(req *http.Request, skip bool) (Creds, error) {
if skipCredsCheck(req) {
if skip && skipCredsCheck(req) {
return nil, nil return nil, nil
} }
@ -47,6 +50,10 @@ func getCredsForAPI(req *http.Request) (Creds, error) {
return fillCredentials(req, credsUrl) return fillCredentials(req, credsUrl)
} }
func getCredsForNTLM(req *http.Request) (Creds, error){
return getCredsForAPI(req, false);
}
func getCredURLForAPI(req *http.Request) (*url.URL, error) { func getCredURLForAPI(req *http.Request) (*url.URL, error) {
apiUrl, err := Config.ObjectUrl("") apiUrl, err := Config.ObjectUrl("")
if err != nil { if err != nil {
@ -60,7 +67,7 @@ func getCredURLForAPI(req *http.Request) (*url.URL, error) {
return req.URL, nil return req.URL, nil
} }
if setRequestAuthFromUrl(req, apiUrl) { if !Config.NtlmAccess() && setRequestAuthFromUrl(req, apiUrl) {
return nil, nil return nil, nil
} }
@ -75,7 +82,7 @@ func getCredURLForAPI(req *http.Request) (*url.URL, error) {
if gitRemoteUrl.Scheme == apiUrl.Scheme && if gitRemoteUrl.Scheme == apiUrl.Scheme &&
gitRemoteUrl.Host == apiUrl.Host { gitRemoteUrl.Host == apiUrl.Host {
if setRequestAuthFromUrl(req, gitRemoteUrl) { if !Config.NtlmAccess() && setRequestAuthFromUrl(req, gitRemoteUrl) {
return nil, nil return nil, nil
} }
@ -83,7 +90,6 @@ func getCredURLForAPI(req *http.Request) (*url.URL, error) {
} }
} }
} }
return credsUrl, nil return credsUrl, nil
} }
@ -105,7 +111,7 @@ func fillCredentials(req *http.Request, u *url.URL) (Creds, error) {
creds, err := execCreds(input, "fill") creds, err := execCreds(input, "fill")
if creds != nil && err == nil { if creds != nil && err == nil && !Config.NtlmAccess() {
setRequestAuth(req, creds["username"], creds["password"]) setRequestAuth(req, creds["username"], creds["password"])
} }

@ -1,72 +1,72 @@
package lfs package lfs
import ( import (
"io"
//"bufio"
//"net"
"net/http"
//"encoding/xml"
"encoding/base64"
"bytes" "bytes"
"encoding/base64"
"github.com/ThomsonReutersEikon/go-ntlm/ntlm"
"io"
"io/ioutil" "io/ioutil"
"fmt" "net/http"
//"os" // "strings"
"github.com/ThomsonReutersEikon/go-ntlm/ntlm" "github.com/github/git-lfs/vendor/_nuts/github.com/rubyist/tracerx"
) )
func (c *Configuration) NTLMSession(creds Creds) ntlm.ClientSession {
type myReader struct {
*bytes.Buffer
}
// So that it implements the io.ReadCloser interface
func (m myReader) Close() error { return nil }
func (c *Configuration) NTLMSession() ntlm.ClientSession {
if c.ntlmSession != nil { if c.ntlmSession != nil {
return c.ntlmSession return c.ntlmSession
} }
tracerx.Printf("creds---------------------")
for key, val := range creds{
tracerx.Printf("%s:%s", key, val)
}
tracerx.Printf("-------------------------creds")
var session, _ = ntlm.CreateClientSession(ntlm.Version2, ntlm.ConnectionOrientedMode) var session, _ = ntlm.CreateClientSession(ntlm.Version2, ntlm.ConnectionOrientedMode)
session.SetUserInfo("user","pass","domain") session.SetUserInfo(creds["username"], creds["password"], "NORTHAMERICA")
c.ntlmSession = session c.ntlmSession = session
return session return session
} }
func DoNTLMRequest(request *http.Request, retry bool) (*http.Response, error) { func DoNTLMRequest(request *http.Request, retry bool) (*http.Response, error) {
if !Config.NTLM() { tracerx.Printf("ENTER DoNTLMRequest")
return nil, Error(fmt.Errorf("NTLM is not enabled")) defer tracerx.Printf("LEAVE DoNTLMRequest")
}
handReq := cloneRequest(request) handReq := cloneRequest(request)
res, nil := InitHandShake(handReq) res, nil := InitHandShake(handReq)
//If the status is 401 then we need to re-authenticate, otherwise it was successful //If the status is 401 then we need to re-authenticate, otherwise it was successful
if res.StatusCode == 401 { if res.StatusCode == 401 {
creds, _ := getCredsForNTLM(request)
negotiateReq := cloneRequest(request) negotiateReq := cloneRequest(request)
challengeMessage := Negotiate(negotiateReq, getNegotiateMessage()) challengeMessage := negotiate(negotiateReq, getNegotiateMessage())
challengeReq := cloneRequest(request) challengeReq := cloneRequest(request)
res, nil := Challenge(challengeReq, challengeMessage) res, _ := challenge(challengeReq, challengeMessage, creds)
//If the status is 401 then we need to re-authenticate //If the status is 401 then we need to re-authenticate
if res.StatusCode == 401 && retry == true { if res.StatusCode == 401 && retry == true {
return DoNTLMRequest(challengeReq, false) return DoNTLMRequest(challengeReq, false)
} }
return res, nil saveCredentials(creds, res)
return res, nil
} }
return res, nil return res, nil
} }
func InitHandShake(request *http.Request) (*http.Response, error){ func InitHandShake(request *http.Request) (*http.Response, error){
tracerx.Printf("ENTER InitHandShake")
defer tracerx.Printf("LEAVE InitHandShake")
var response, err = Config.HttpClient().Do(request) var response, err = Config.HttpClient().Do(request)
if err != nil { if err != nil {
@ -76,7 +76,10 @@ func InitHandShake(request *http.Request) (*http.Response, error){
return response, nil return response, nil
} }
func Negotiate(request *http.Request, message string) []byte{ func negotiate(request *http.Request, message string) []byte{
tracerx.Printf("ENTER negotiate")
defer tracerx.Printf("LEAVE negotiate")
request.Header.Add("Authorization", message) request.Header.Add("Authorization", message)
var response, err = Config.HttpClient().Do(request) var response, err = Config.HttpClient().Do(request)
@ -85,18 +88,21 @@ func Negotiate(request *http.Request, message string) []byte{
panic(err.Error()) panic(err.Error())
} }
ret := ParseChallengeMessage(response) ret := parseChallengeResponse(response)
//Always close negotiate to keep the connection alive //Always close negotiate to keep the connection alive
//We never return the response from negotiate so we //We never return the response from negotiate so we
//can't trust decodeApiResponse to decode it //can't trust decodeApiResponse to close it
io.Copy(ioutil.Discard, response.Body) io.Copy(ioutil.Discard, response.Body)
response.Body.Close() response.Body.Close()
return ret; return ret;
} }
func Challenge(request *http.Request, challengeBytes []byte) (*http.Response, error){ func challenge(request *http.Request, challengeBytes []byte, creds Creds) (*http.Response, error){
tracerx.Printf("ENTER challenge")
defer tracerx.Printf("LEAVE challenge")
challenge, err := ntlm.ParseChallengeMessage(challengeBytes) challenge, err := ntlm.ParseChallengeMessage(challengeBytes)
@ -104,14 +110,14 @@ func Challenge(request *http.Request, challengeBytes []byte) (*http.Response, er
return nil, Error(err) return nil, Error(err)
} }
Config.NTLMSession().ProcessChallengeMessage(challenge) Config.NTLMSession(creds).ProcessChallengeMessage(challenge)
authenticate, err := Config.NTLMSession().GenerateAuthenticateMessage() authenticate, err := Config.NTLMSession(creds).GenerateAuthenticateMessage()
if err != nil { if err != nil {
return nil, Error(err) return nil, Error(err)
} }
authenticateMessage := ConcatS("NTLM ", base64.StdEncoding.EncodeToString(authenticate.Bytes())) authenticateMessage := concatS("NTLM ", base64.StdEncoding.EncodeToString(authenticate.Bytes()))
request.Header.Add("Authorization", authenticateMessage) request.Header.Add("Authorization", authenticateMessage)
response, err := Config.HttpClient().Do(request) response, err := Config.HttpClient().Do(request)
@ -119,7 +125,10 @@ func Challenge(request *http.Request, challengeBytes []byte) (*http.Response, er
return response, nil return response, nil
} }
func ParseChallengeMessage(response *http.Response) []byte{ func parseChallengeResponse(response *http.Response) []byte{
tracerx.Printf("ENTER parseChallengeResponse")
defer tracerx.Printf("LEAVE parseChallengeResponse")
if headers, ok := response.Header["Www-Authenticate"]; ok{ if headers, ok := response.Header["Www-Authenticate"]; ok{
@ -138,6 +147,9 @@ func ParseChallengeMessage(response *http.Response) []byte{
func cloneRequest(request *http.Request) *http.Request { func cloneRequest(request *http.Request) *http.Request {
tracerx.Printf("ENTER cloneRequest")
defer tracerx.Printf("LEAVE cloneRequest")
var rdr1, rdr2 myReader var rdr1, rdr2 myReader
var clonedReq *http.Request var clonedReq *http.Request
@ -172,7 +184,7 @@ func getNegotiateMessage() string{
return "NTLM TlRMTVNTUAABAAAAB7IIogwADAAzAAAACwALACgAAAAKAAAoAAAAD1dJTExISS1NQUlOTk9SVEhBTUVSSUNB" return "NTLM TlRMTVNTUAABAAAAB7IIogwADAAzAAAACwALACgAAAAKAAAoAAAAD1dJTExISS1NQUlOTk9SVEhBTUVSSUNB"
} }
func ConcatS(ar ...string) string { func concatS(ar ...string) string {
var buffer bytes.Buffer var buffer bytes.Buffer
@ -183,6 +195,13 @@ func ConcatS(ar ...string) string {
return buffer.String() return buffer.String()
} }
func Concat(ar ...[]byte) []byte { func concat(ar ...[]byte) []byte {
return bytes.Join(ar, nil) return bytes.Join(ar, nil)
} }
type myReader struct {
*bytes.Buffer
}
// So that myReader implements the io.ReadCloser interface
func (m myReader) Close() error { return nil }

@ -130,7 +130,7 @@ func (q *TransferQueue) Watch() chan string {
func (q *TransferQueue) individualApiRoutine(apiWaiter chan interface{}) { func (q *TransferQueue) individualApiRoutine(apiWaiter chan interface{}) {
for t := range q.apic { for t := range q.apic {
obj, err := t.Check() obj, err := t.Check()
if err != nil {` if err != nil {
tracerx.Printf("tq-willhi: t.Check() failed %s", t.Check) tracerx.Printf("tq-willhi: t.Check() failed %s", t.Check)
if q.canRetry(err) { if q.canRetry(err) {