diff options
| author | Matthew Holt <mholt@users.noreply.github.com> | 2020-04-07 08:31:52 -0600 | 
|---|---|---|
| committer | Matthew Holt <mholt@users.noreply.github.com> | 2020-04-07 11:39:14 -0600 | 
| commit | 2c1b66315620fda3311f9bdffd0867de1c71dc9e (patch) | |
| tree | 83d2c38e8acbf5fef972f654c0ef70b0d8065781 /modules/caddyhttp/reverseproxy/ntlm.go | |
| parent | 657f0cab17c1597f4f78f15d0b720aa6f9d7fbcb (diff) | |
reverseproxy: Remove NTLM transport; refactor and improve docs
Diffstat (limited to 'modules/caddyhttp/reverseproxy/ntlm.go')
| -rw-r--r-- | modules/caddyhttp/reverseproxy/ntlm.go | 244 | 
1 files changed, 0 insertions, 244 deletions
| diff --git a/modules/caddyhttp/reverseproxy/ntlm.go b/modules/caddyhttp/reverseproxy/ntlm.go deleted file mode 100644 index 270135a..0000000 --- a/modules/caddyhttp/reverseproxy/ntlm.go +++ /dev/null @@ -1,244 +0,0 @@ -// Copyright 2015 Matthew Holt and The Caddy 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. - -package reverseproxy - -import ( -	"context" -	"fmt" -	"net" -	"net/http" -	"strings" -	"sync" - -	"github.com/caddyserver/caddy/v2" -	"github.com/caddyserver/caddy/v2/modules/caddyhttp" -) - -func init() { -	caddy.RegisterModule(NTLMTransport{}) -} - -// NTLMTransport proxies HTTP with NTLM authentication. -// It basically wraps HTTPTransport so that it is compatible with -// NTLM's HTTP-hostile requirements. Specifically, it will use -// HTTPTransport's single, default *http.Transport for all requests -// (unless the client's connection is already mapped to a different -// transport) until a request comes in with an Authorization header -// that has "NTLM" or "Negotiate"; when that happens, NTLMTransport -// maps the client's connection (by its address, req.RemoteAddr) -// to a new transport that is used only by that downstream conn. -// When the upstream connection is closed, the mapping is deleted. -// This preserves NTLM authentication contexts by ensuring that -// client connections use the same upstream connection. It does -// hurt performance a bit, but that's NTLM for you. -// -// This transport also forces HTTP/1.1 and Keep-Alives in order -// for NTLM to succeed. -// -// It is basically the same thing as -// [nginx's paid ntlm directive](https://nginx.org/en/docs/http/ngx_http_upstream_module.html#ntlm) -// (but is free in Caddy!). -type NTLMTransport struct { -	*HTTPTransport - -	transports   map[string]*http.Transport -	transportsMu *sync.RWMutex -} - -// CaddyModule returns the Caddy module information. -func (NTLMTransport) CaddyModule() caddy.ModuleInfo { -	return caddy.ModuleInfo{ -		ID: "http.reverse_proxy.transport.http_ntlm", -		New: func() caddy.Module { -			m := new(NTLMTransport) -			m.HTTPTransport = new(HTTPTransport) -			return m -		}, -	} -} - -// Provision sets up the transport module. -func (n *NTLMTransport) Provision(ctx caddy.Context) error { -	n.transports = make(map[string]*http.Transport) -	n.transportsMu = new(sync.RWMutex) - -	if n.HTTPTransport == nil { -		n.HTTPTransport = new(HTTPTransport) -	} - -	// NTLM requires HTTP/1.1 -	n.HTTPTransport.Versions = []string{"1.1"} - -	// NLTM requires keep-alive -	if n.HTTPTransport.KeepAlive != nil { -		enabled := true -		n.HTTPTransport.KeepAlive.Enabled = &enabled -	} - -	// set up the underlying transport, since we -	// rely on it for the heavy lifting -	err := n.HTTPTransport.Provision(ctx) -	if err != nil { -		return err -	} - -	return nil -} - -// RoundTrip implements http.RoundTripper. It basically wraps -// the underlying HTTPTransport.Transport in a way that preserves -// NTLM context by mapping transports/connections. Note that this -// method does not call n.HTTPTransport.RoundTrip (our own method), -// but the underlying n.HTTPTransport.Transport.RoundTrip (standard -// library's method). -func (n *NTLMTransport) RoundTrip(req *http.Request) (*http.Response, error) { -	n.HTTPTransport.setScheme(req) - -	// when the upstream connection is closed, make sure -	// we close the downstream connection with the client -	// when this request is done; we only do this if -	// using a bound transport -	closeDownstreamIfClosedUpstream := func() { -		n.transportsMu.Lock() -		if _, ok := n.transports[req.RemoteAddr]; !ok { -			req.Close = true -		} -		n.transportsMu.Unlock() -	} - -	// first, see if this downstream connection is -	// already bound to a particular transport -	// (transports are abstractions over connections -	// to our upstream, and NTLM auth requires -	// preserving authentication state for separate -	// connections over multiple roundtrips, sigh) -	n.transportsMu.Lock() -	transport, ok := n.transports[req.RemoteAddr] -	if ok { -		n.transportsMu.Unlock() -		defer closeDownstreamIfClosedUpstream() -		return transport.RoundTrip(req) -	} - -	// otherwise, start by assuming we will use -	// the default transport that carries all -	// normal/non-NTLM-authenticated requests -	transport = n.HTTPTransport.Transport - -	// but if this request begins the NTLM authentication -	// process, we need to pin it to a specific transport -	if requestHasAuth(req) { -		var err error -		transport, err = n.newTransport() -		if err != nil { -			return nil, fmt.Errorf("making new transport for %s: %v", req.RemoteAddr, err) -		} -		n.transports[req.RemoteAddr] = transport -		defer closeDownstreamIfClosedUpstream() -	} -	n.transportsMu.Unlock() - -	// finally, do the roundtrip with the transport we selected -	return transport.RoundTrip(req) -} - -// newTransport makes an NTLM-compatible transport. -func (n *NTLMTransport) newTransport() (*http.Transport, error) { -	// start with a regular HTTP transport -	transport, err := n.HTTPTransport.newTransport() -	if err != nil { -		return nil, err -	} - -	// we need to wrap upstream connections so we can -	// clean up in two ways when that connection is -	// closed: 1) destroy the transport that housed -	// this connection, and 2) use that as a signal -	// to close the connection to the downstream. -	wrappedDialContext := transport.DialContext - -	transport.DialContext = func(ctx context.Context, network, address string) (net.Conn, error) { -		conn2, err := wrappedDialContext(ctx, network, address) -		if err != nil { -			return nil, err -		} -		req := ctx.Value(caddyhttp.OriginalRequestCtxKey).(http.Request) -		conn := &unbinderConn{Conn: conn2, ntlm: n, clientAddr: req.RemoteAddr} -		return conn, nil -	} - -	return transport, nil -} - -// Cleanup implements caddy.CleanerUpper and closes any idle connections. -func (n *NTLMTransport) Cleanup() error { -	if err := n.HTTPTransport.Cleanup(); err != nil { -		return err -	} - -	n.transportsMu.Lock() -	for _, t := range n.transports { -		t.CloseIdleConnections() -	} -	n.transports = make(map[string]*http.Transport) -	n.transportsMu.Unlock() - -	return nil -} - -// deleteTransportsForClient deletes (unmaps) transports that are -// associated with clientAddr (a req.RemoteAddr value). -func (n *NTLMTransport) deleteTransportsForClient(clientAddr string) { -	n.transportsMu.Lock() -	for key := range n.transports { -		if key == clientAddr { -			delete(n.transports, key) -		} -	} -	n.transportsMu.Unlock() -} - -// requestHasAuth returns true if req has an Authorization -// header with values "NTLM" or "Negotiate". -func requestHasAuth(req *http.Request) bool { -	for _, val := range req.Header["Authorization"] { -		if strings.HasPrefix(val, "NTLM") || -			strings.HasPrefix(val, "Negotiate") { -			return true -		} -	} -	return false -} - -// unbinderConn is used to wrap upstream connections -// so that we know when they are closed and can clean -// up after that. -type unbinderConn struct { -	net.Conn -	clientAddr string -	ntlm       *NTLMTransport -} - -func (uc *unbinderConn) Close() error { -	uc.ntlm.deleteTransportsForClient(uc.clientAddr) -	return uc.Conn.Close() -} - -// Interface guards -var ( -	_ caddy.Provisioner  = (*NTLMTransport)(nil) -	_ http.RoundTripper  = (*NTLMTransport)(nil) -	_ caddy.CleanerUpper = (*NTLMTransport)(nil) -) | 
