Skip to content

net/httputil: Lost user credentials in reverse proxy requests. #51022

Closed
@vkuznet

Description

@vkuznet

What version of Go are you using (go version)?

$ go version
go version go1.17.6 darwin/amd64

Does this issue reproduce with the latest release?

yes

What operating system and processor architecture are you using (go env)?

go env Output
$ go env
GO111MODULE=""
GOARCH="amd64"
GOBIN=""
GOCACHE="/Users/vk/Library/Caches/go-build"
GOENV="/Users/vk/Library/Application Support/go/env"
GOEXE=""
GOEXPERIMENT=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="darwin"
GOINSECURE=""
GOMODCACHE="/Users/vk/Work/Languages/Go/gopath/pkg/mod"
GONOPROXY=""
GONOSUMDB="github.com/galeone/tensorflow"
GOOS="darwin"
GOPATH="/Users/vk/Work/Languages/Go/gopath"
GOPRIVATE=""
GOPROXY="https://proxy.golang.org,direct"
GOROOT="/opt/local/lib/go"
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/opt/local/lib/go/pkg/tool/darwin_amd64"
GOVCS=""
GOVERSION="go1.17.6"
GCCGO="gccgo"
AR="ar"
CC="/usr/bin/clang"
CXX="clang++"
CGO_ENABLED="1"
GOMOD="/Users/vk/Work/Languages/Go/gopath/src/github.com/vkuznet/wh-server/go.mod"
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -arch x86_64 -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/var/folders/vp/p89gqpvd4f93zzqpz4g5jnvm0000gp/T/go-build1005891228=/tmp/go-build -gno-record-gcc-switches -fno-common"

What did you do?

Hi,
I'm trying to understand how to properly write proxy server which will properly handle user credentials in HTTP requests. For instance, let's say I have a CouchDB with admin:admin credentials, i.e. it should be accessed via http://admin:admin@localhost:5984 URL. We can easily write simple program to access it via default HTTP client. My question is how to do the same using HTTP reverse proxy.

Below you can find a fully working code which uses both HTTP client and HTTP reverse proxy. In both cases I pass around the same HTTP request, but results are completely different. For this demo, I setup CouchDB with admin username and admin password, I used stdout writer which implements HTTP response writer, and I omit errors for simplicity:

package main

import (
	"log"
	"net/http"
	"net/http/httputil"
	"net/url"
)

// StdoutWriter provides the same functionality as http.ResponseWriter
// and print results to stdout.
type StdoutWriter string

// Header implements Header() API of http.ResponseWriter interface
func (s StdoutWriter) Header() http.Header {
	return http.Header{}
}

// Write implements Write API of http.ResponseWriter interface
func (s StdoutWriter) Write(b []byte) (int, error) {
	v := string(b)
	log.Println(v)
	return len(v), nil
}

// WriteHeader implements WriteHeader API of http.ResponseWriter interface
func (s StdoutWriter) WriteHeader(statusCode int) {
	log.Println("statusCode", statusCode)
}

func main() {
	surl := "http://admin:admin@localhost:5984/_all_dbs"
	rurl, _ := url.Parse(surl)
	log.Println("url", rurl, rurl.Host, rurl.Scheme, rurl.User)

	r, _ := http.NewRequest("GET", surl, nil)
	w := StdoutWriter("")

	// make HTTP call via HTTP client
	client := http.Client{}
	resp, _ := client.Do(r)
	log.Println("HTTP response", resp.Status)

	// do the same via proxy
	log.Println("proxy request with", rurl)
	proxy := httputil.NewSingleHostReverseProxy(rurl)
	proxy.ServeHTTP(w, r)
}

When you'll run this program (assuming that you have your local CouchDB running) you'll get the following output:

2022/02/04 16:01:19 url http://admin:admin@localhost:5984/_all_dbs localhost:5984 http admin:admin
2022/02/04 16:01:19 HTTP response 200 OK
2022/02/04 16:01:19 proxy request with http://admin:admin@localhost:5984/_all_dbs
2022/02/04 16:01:19 statusCode 401
2022/02/04 16:01:19 {"error":"unauthorized","reason":"You are not a server admin."}

As you can see the http client works as expected and provides 200 OK, while proxy fails because it does not pass properly user credentials.

What did you expect to see?

The proxy server should keep user credentials when passing HTTP request.

What did you see instead?

I see that reverse proxy does not pass user info within HTTP request and can't access requested resource.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions