Skip to content

Commit

Permalink
More tests for h2c and http post (#150)
Browse files Browse the repository at this point in the history
### Description

Add tests for h2c (http2 cleartext) backends, and tests for HTTP POST.

### Type of change

* [ ] New feature
* [x] Feature improvement
* [ ] Bug fix
* [ ] Documentation
* [ ] Cleanup / refactoring
* [ ] Other (please explain)

### How is this change tested ?

* [x] Unit tests
* [ ] Manual tests (explain)
* [ ] Tests are not needed
  • Loading branch information
rthellend authored Oct 29, 2024
1 parent d01837e commit 88f537e
Show file tree
Hide file tree
Showing 3 changed files with 287 additions and 13 deletions.
18 changes: 17 additions & 1 deletion proxy/backend-http.go
Original file line number Diff line number Diff line change
Expand Up @@ -331,9 +331,25 @@ func (be *Backend) reverseProxy() http.Handler {
req.Header.Del(k)
}
}
// A value of -1 for ContentLength indicates that the size of
// request's body is unknown or that the client did not specify
// it.
//
// The http2.Transport code encodes the request differently
// when the content length is unknown, i.e. it doesn't set the
// END_STREAM flag after the headers are sent, even for GET
// requests. This is flagged as a protocol violation by some
// HTTP servers, lighttpd in particular.
//
// To improve compatibility, we explicitly set the value to 0
// the HTTP methods don't expect a body and the client didn't
// provide a content length.
if req.ContentLength < 0 && req.Method != http.MethodPost && req.Method != http.MethodPut && req.Method != http.MethodPatch {
req.ContentLength = 0
req.Header.Del("Content-Length")
}
if req.ContentLength == 0 && req.Body != nil {
req.Body.Close()
req.Body = nil
}
reverseProxy.ServeHTTP(w, req.WithContext(ctx))
})
Expand Down
60 changes: 49 additions & 11 deletions proxy/proxy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import (
"net/url"
"os"
"path/filepath"
"strconv"
"strings"
"sync"
"testing"
Expand All @@ -49,6 +50,8 @@ import (
"github.com/c2FmZQ/tpm"
"github.com/google/go-tpm-tools/simulator"
"github.com/pires/go-proxyproto"
"golang.org/x/net/http2"
"golang.org/x/net/http2/h2c"

"github.com/c2FmZQ/tlsproxy/certmanager"
"github.com/c2FmZQ/tlsproxy/proxy/internal/ocspcache"
Expand Down Expand Up @@ -113,6 +116,7 @@ func TestProxyBackends(t *testing.T) {
be13 := newProxyProtocolServer(t, ctx, "backend12", nil)

trueValue := true
h2Value := "h2"

cfg := &Config{
MaxOpen: 100,
Expand Down Expand Up @@ -237,6 +241,17 @@ func TestProxyBackends(t *testing.T) {
},
SanitizePath: new(bool), // false
},
// HTTP H2C
{
ServerNames: []string{
"h2c.example.com",
},
Addresses: []string{
be8.String(),
},
Mode: "HTTP",
BackendProto: &h2Value,
},
// HTTPS
{
ServerNames: []string{
Expand Down Expand Up @@ -377,6 +392,7 @@ func TestProxyBackends(t *testing.T) {
{desc: "Hit backend8 header", host: "http.example.com", want: "[backend8] /?header=x-test\nx-test=foo\n", http: "/?header=x-test"},
{desc: "Hit backend8 header /foo", host: "http.example.com", want: "[backend9] /foo/?header=x-test\nx-test=bar\n", http: "/foo/?header=x-test"},
{desc: "Hit backend8 header /bar", host: "http.example.com", want: "[backend9] /bar/?header=x-test\nx-test=foo\n", http: "/bar/?header=x-test"},
{desc: "Hit backend8 h2c", host: "h2c.example.com", want: "[backend8] /", http: "/"},
{desc: "Hit backend9", host: "https.example.com", want: "[backend9] /", http: "/", certName: "client.example.com"},
{desc: "Hit backend9 /abc/../xyz/", host: "https.example.com", want: "[backend9] /xyz/", http: "/abc/../xyz/", certName: "client.example.com"},
{desc: "Hit backend10", host: "tls-pki.example.com", want: "Hello from backend10\n", certName: "client.example.com"},
Expand Down Expand Up @@ -1200,6 +1216,10 @@ func proxyProtoGet(name, addr, msg string, rootCA *certmanager.CertManager) (str
}

func httpGet(name, addr, path string, rootCA *certmanager.CertManager, clientCerts []tls.Certificate) (string, string, error) {
return httpOp(name, addr, path, "GET", nil, rootCA, clientCerts)
}

func httpOp(name, addr, path, method string, body io.ReadCloser, rootCA *certmanager.CertManager, clientCerts []tls.Certificate) (string, string, error) {
var localAddr string
var mu sync.Mutex
name = idnaToASCII(name)
Expand Down Expand Up @@ -1229,7 +1249,7 @@ func httpGet(name, addr, path string, rootCA *certmanager.CertManager, clientCer
if host == "" {
host = addr
}
req, err := http.NewRequest(http.MethodGet, "https://"+host+path, nil)
req, err := http.NewRequest(method, "https://"+host+path, body)
if err != nil {
return "", "", err
}
Expand Down Expand Up @@ -1262,16 +1282,34 @@ func newHTTPServer(t *testing.T, ctx context.Context, name string, ca *certmanag
t.Fatalf("[%s] Listen: %v", name, err)
}
s := &http.Server{
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if v := r.Header.Get(xFCCHeader); v != "" {
t.Logf("[%s] %s: %s", name, xFCCHeader, v)
}
fmt.Fprintf(w, "[%s] %s\n", name, r.RequestURI)
r.ParseForm()
if h := r.Form.Get("header"); h != "" {
fmt.Fprintf(w, "%s=%s\n", h, r.Header.Get(h))
}
}),
Handler: h2c.NewHandler(
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Body != nil {
defer r.Body.Close()
}
t.Logf("[%s] ContentLength=%d", name, r.ContentLength)
if v := r.Header.Get(xFCCHeader); v != "" {
t.Logf("[%s] %s: %s", name, xFCCHeader, v)
}
if v := r.Header.Get("Content-Length"); v != "" {
if l, err := strconv.Atoi(v); err != nil || l < 0 {
t.Errorf("[%s] Content-Length: %q", name, v)
}
}
switch r.Method {
case "GET":
fmt.Fprintf(w, "[%s] %s\n", name, r.RequestURI)
case "POST":
b, _ := io.ReadAll(r.Body)
fmt.Fprintf(w, "[%s] POST %s %s\n", name, r.RequestURI, b)
default:
fmt.Fprintf(w, "[%s] %s %s\n", name, r.Method, r.RequestURI)
}
r.ParseForm()
if h := r.Form.Get("header"); h != "" {
fmt.Fprintf(w, "%s=%s\n", h, r.Header.Get(h))
}
}), &http2.Server{}),
ErrorLog: log.New(os.Stderr, "["+name+"] ", 0),
}
go s.Serve(l)
Expand Down
Loading

0 comments on commit 88f537e

Please sign in to comment.