net/tshttpproxy: add start of Kerberos Negotiate auth to proxies on Windows

For now only used by a new cmd/tailscale debug --get-url
subcommand. Not yet wired up to the places making HTTP requests.

Updates tailscale/corp#583
This commit is contained in:
Brad Fitzpatrick 2020-08-25 13:45:15 -07:00
parent dd2c61a519
commit f915ab6552
5 changed files with 89 additions and 1 deletions

View File

@ -6,14 +6,20 @@
import ( import (
"context" "context"
"crypto/tls"
"encoding/json" "encoding/json"
"errors" "errors"
"flag" "flag"
"fmt"
"log" "log"
"net/http"
"net/http/httptrace"
"net/url"
"os" "os"
"github.com/peterbourgon/ff/v2/ffcli" "github.com/peterbourgon/ff/v2/ffcli"
"tailscale.com/net/interfaces" "tailscale.com/net/interfaces"
"tailscale.com/net/tshttpproxy"
"tailscale.com/wgengine/monitor" "tailscale.com/wgengine/monitor"
) )
@ -22,13 +28,15 @@
Exec: runDebug, Exec: runDebug,
FlagSet: (func() *flag.FlagSet { FlagSet: (func() *flag.FlagSet {
fs := flag.NewFlagSet("debug", flag.ExitOnError) fs := flag.NewFlagSet("debug", flag.ExitOnError)
fs.BoolVar(&debugArgs.monitor, "monitor", false, "") fs.BoolVar(&debugArgs.monitor, "monitor", false, "If true, run link monitor forever. Precludes all other options.")
fs.StringVar(&debugArgs.getURL, "get-url", "", "If non-empty, fetch provided URL.")
return fs return fs
})(), })(),
} }
var debugArgs struct { var debugArgs struct {
monitor bool monitor bool
getURL string
} }
func runDebug(ctx context.Context, args []string) error { func runDebug(ctx context.Context, args []string) error {
@ -38,6 +46,9 @@ func runDebug(ctx context.Context, args []string) error {
if debugArgs.monitor { if debugArgs.monitor {
return runMonitor(ctx) return runMonitor(ctx)
} }
if debugArgs.getURL != "" {
return getURL(ctx, debugArgs.getURL)
}
return errors.New("only --monitor is available at the moment") return errors.New("only --monitor is available at the moment")
} }
@ -64,3 +75,46 @@ func runMonitor(ctx context.Context) error {
log.Printf("Started link change monitor; waiting...") log.Printf("Started link change monitor; waiting...")
select {} select {}
} }
func getURL(ctx context.Context, urlStr string) error {
if urlStr == "login" {
urlStr = "https://login.tailscale.com"
}
log.SetOutput(os.Stdout)
ctx = httptrace.WithClientTrace(ctx, &httptrace.ClientTrace{
GetConn: func(hostPort string) { log.Printf("GetConn(%q)", hostPort) },
GotConn: func(info httptrace.GotConnInfo) { log.Printf("GotConn: %+v", info) },
DNSStart: func(info httptrace.DNSStartInfo) { log.Printf("DNSStart: %+v", info) },
DNSDone: func(info httptrace.DNSDoneInfo) { log.Printf("DNSDoneInfo: %+v", info) },
TLSHandshakeStart: func() { log.Printf("TLSHandshakeStart") },
TLSHandshakeDone: func(cs tls.ConnectionState, err error) { log.Printf("TLSHandshakeDone: %+v, %v", cs, err) },
WroteRequest: func(info httptrace.WroteRequestInfo) { log.Printf("WroteRequest: %+v", info) },
})
req, err := http.NewRequestWithContext(ctx, "GET", urlStr, nil)
if err != nil {
return fmt.Errorf("http.NewRequestWithContext: %v", err)
}
proxyURL, err := tshttpproxy.ProxyFromEnvironment(req)
if err != nil {
return fmt.Errorf("tshttpproxy.ProxyFromEnvironment: %v", err)
}
log.Printf("proxy: %v", proxyURL)
tr := &http.Transport{
Proxy: func(*http.Request) (*url.URL, error) { return proxyURL, nil },
ProxyConnectHeader: http.Header{},
DisableKeepAlives: true,
}
if proxyURL != nil {
auth, err := tshttpproxy.GetAuthHeader(proxyURL)
log.Printf("tshttpproxy.GetAuthHeader(%v) = %q, %v", proxyURL, auth, err)
if err == nil && auth != "" {
tr.ProxyConnectHeader.Set("Authorization", auth)
}
}
res, err := tr.RoundTrip(req)
if err != nil {
return fmt.Errorf("Transport.RoundTrip: %v", err)
}
defer res.Body.Close()
return res.Write(os.Stdout)
}

1
go.mod
View File

@ -3,6 +3,7 @@ module tailscale.com
go 1.14 go 1.14
require ( require (
github.com/alexbrainman/sspi v0.0.0-20180613141037-e580b900e9f5
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 // indirect github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 // indirect
github.com/apenwarr/fixconsole v0.0.0-20191012055117-5a9f6489cc29 github.com/apenwarr/fixconsole v0.0.0-20191012055117-5a9f6489cc29
github.com/coreos/go-iptables v0.4.5 github.com/coreos/go-iptables v0.4.5

2
go.sum
View File

@ -9,6 +9,8 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafo
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d h1:UQZhZ2O0vMHr2cI+DC1Mbh0TJxzA3RcLoMsFw+aXw7E= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d h1:UQZhZ2O0vMHr2cI+DC1Mbh0TJxzA3RcLoMsFw+aXw7E=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/alexbrainman/sspi v0.0.0-20180613141037-e580b900e9f5 h1:P5U+E4x5OkVEKQDklVPmzs71WM56RTTRqV4OrDC//Y4=
github.com/alexbrainman/sspi v0.0.0-20180613141037-e580b900e9f5/go.mod h1:976q2ETgjT2snVCf2ZaBnyBbVoPERGjUz+0sofzEfro=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
github.com/apenwarr/fixconsole v0.0.0-20191012055117-5a9f6489cc29 h1:muXWUcay7DDy1/hEQWrYlBy+g0EuwT70sBHg65SeUc4= github.com/apenwarr/fixconsole v0.0.0-20191012055117-5a9f6489cc29 h1:muXWUcay7DDy1/hEQWrYlBy+g0EuwT70sBHg65SeUc4=

View File

@ -31,3 +31,13 @@ func ProxyFromEnvironment(req *http.Request) (*url.URL, error) {
return nil, err return nil, err
} }
var sysAuthHeader func(*url.URL) (string, error)
// GetAuthHeader returns the Authorization header value to send to proxy u.
func GetAuthHeader(u *url.URL) (string, error) {
if sysAuthHeader != nil {
return sysAuthHeader(u)
}
return "", nil
}

View File

@ -5,6 +5,8 @@
package tshttpproxy package tshttpproxy
import ( import (
"encoding/base64"
"fmt"
"log" "log"
"net/http" "net/http"
"net/url" "net/url"
@ -12,6 +14,7 @@
"syscall" "syscall"
"unsafe" "unsafe"
"github.com/alexbrainman/sspi/negotiate"
"golang.org/x/sys/windows" "golang.org/x/sys/windows"
) )
@ -24,6 +27,7 @@
func init() { func init() {
sysProxyFromEnv = proxyFromWinHTTP sysProxyFromEnv = proxyFromWinHTTP
sysAuthHeader = sysAuthHeaderWindows
} }
func proxyFromWinHTTP(req *http.Request) (*url.URL, error) { func proxyFromWinHTTP(req *http.Request) (*url.URL, error) {
@ -144,3 +148,20 @@ func (hi winHTTPInternet) GetProxyForURL(urlStr string) (string, error) {
} }
return "", err return "", err
} }
func sysAuthHeaderWindows(u *url.URL) (string, error) {
spn := "HTTP/" + u.Hostname()
creds, err := negotiate.AcquireCurrentUserCredentials()
if err != nil {
return "", fmt.Errorf("negotiate.AcquireCurrentUserCredentials: %w", err)
}
defer creds.Release()
secCtx, token, err := negotiate.NewClientContext(creds, spn)
if err != nil {
return "", fmt.Errorf("negotiate.NewClientContext: %w", err)
}
defer secCtx.Release()
return "Negotiate " + base64.StdEncoding.EncodeToString(token), nil
}