diff --git a/cmd/http-gw/server_test.go b/cmd/http-gw/server_test.go new file mode 100644 index 0000000..a937366 --- /dev/null +++ b/cmd/http-gw/server_test.go @@ -0,0 +1,119 @@ +package main + +import ( + "context" + "crypto/rand" + "crypto/rsa" + "crypto/tls" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "fmt" + "math/big" + "net" + "net/http" + "os" + "path" + "testing" + "time" + + "github.com/stretchr/testify/require" + "golang.org/x/net/http2" +) + +const ( + expHeaderKey = "Foo" + expHeaderValue = "Bar" +) + +func TestHTTP2TLS(t *testing.T) { + ctx := context.Background() + certPath, keyPath := prepareTestCerts(t) + + srv := &http.Server{ + Handler: http.HandlerFunc(testHandler), + } + + tlsListener, err := newServer(ctx, ServerInfo{ + Address: ":0", + TLS: ServerTLSInfo{ + Enabled: true, + CertFile: certPath, + KeyFile: keyPath, + }, + }) + require.NoError(t, err) + port := tlsListener.Listener().Addr().(*net.TCPAddr).Port + addr := fmt.Sprintf("https://localhost:%d", port) + + go func() { + _ = srv.Serve(tlsListener.Listener()) + }() + + // Server is running, now send HTTP/2 request + + tlsClientConfig := &tls.Config{ + InsecureSkipVerify: true, + } + + cliHTTP1 := http.Client{Transport: &http.Transport{TLSClientConfig: tlsClientConfig}} + cliHTTP2 := http.Client{Transport: &http2.Transport{TLSClientConfig: tlsClientConfig}} + + req, err := http.NewRequest("GET", addr, nil) + require.NoError(t, err) + req.Header[expHeaderKey] = []string{expHeaderValue} + + resp, err := cliHTTP1.Do(req) + require.NoError(t, err) + require.Equal(t, http.StatusOK, resp.StatusCode) + + resp, err = cliHTTP2.Do(req) + require.NoError(t, err) + require.Equal(t, http.StatusOK, resp.StatusCode) +} + +func testHandler(resp http.ResponseWriter, req *http.Request) { + hdr, ok := req.Header[expHeaderKey] + if !ok || len(hdr) != 1 || hdr[0] != expHeaderValue { + resp.WriteHeader(http.StatusBadRequest) + } else { + resp.WriteHeader(http.StatusOK) + } +} + +func prepareTestCerts(t *testing.T) (certPath, keyPath string) { + privateKey, err := rsa.GenerateKey(rand.Reader, 2048) + require.NoError(t, err) + + template := x509.Certificate{ + SerialNumber: big.NewInt(1), + Subject: pkix.Name{CommonName: "localhost"}, + NotBefore: time.Now(), + NotAfter: time.Now().Add(time.Hour * 24 * 365), + KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, + BasicConstraintsValid: true, + } + + derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &privateKey.PublicKey, privateKey) + require.NoError(t, err) + + dir := t.TempDir() + certPath = path.Join(dir, "cert.pem") + keyPath = path.Join(dir, "key.pem") + + certFile, err := os.Create(certPath) + require.NoError(t, err) + defer certFile.Close() + + keyFile, err := os.Create(keyPath) + require.NoError(t, err) + defer keyFile.Close() + + err = pem.Encode(certFile, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) + require.NoError(t, err) + + err = pem.Encode(keyFile, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(privateKey)}) + require.NoError(t, err) + + return certPath, keyPath +} diff --git a/go.mod b/go.mod index 9388eae..dd2896c 100644 --- a/go.mod +++ b/go.mod @@ -22,6 +22,7 @@ require ( go.opentelemetry.io/otel/trace v1.16.0 go.uber.org/zap v1.24.0 golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc + golang.org/x/net v0.10.0 google.golang.org/grpc v1.55.0 ) @@ -103,7 +104,6 @@ require ( go.uber.org/atomic v1.10.0 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/crypto v0.9.0 // indirect - golang.org/x/net v0.10.0 // indirect golang.org/x/sync v0.2.0 // indirect golang.org/x/sys v0.8.0 // indirect golang.org/x/term v0.8.0 // indirect