engine/guard_test.go
Rama main · réplica de lectura del repositorio original.
package engine
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"io"
"math/big"
"net"
"net/http"
"os"
"path/filepath"
"testing"
"time"
)
// writeTestCert writes a self-signed certificate and key for 127.0.0.1
func writeTestCert(t *testing.T) (certFile, keyFile string) {
t.Helper()
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
t.Fatal(err)
}
template := &x509.Certificate{
SerialNumber: big.NewInt(1),
Subject: pkix.Name{CommonName: "localhost"},
NotBefore: time.Now().Add(-time.Hour),
NotAfter: time.Now().Add(time.Hour),
IPAddresses: []net.IP{net.ParseIP("127.0.0.1")},
DNSNames: []string{"localhost"},
KeyUsage: x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
}
der, err := x509.CreateCertificate(rand.Reader, template, template, &key.PublicKey, key)
if err != nil {
t.Fatal(err)
}
keyDER, err := x509.MarshalECPrivateKey(key)
if err != nil {
t.Fatal(err)
}
dir := t.TempDir()
certFile = filepath.Join(dir, "cert.pem")
keyFile = filepath.Join(dir, "key.pem")
if err := os.WriteFile(certFile, pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der}), 0o600); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(keyFile, pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: keyDER}), 0o600); err != nil {
t.Fatal(err)
}
return certFile, keyFile
}
// newGuardTestConfig returns a Config where /admin is protected, together
// with a mux that answers every request with "served"
func newGuardTestConfig() (*Config, *http.ServeMux) {
ac := &Config{perm: &prefixPerm{rejectPrefixes: []string{"/admin", hmrUpdatePrefix + "private/"}}}
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, _ *http.Request) {
w.Write([]byte("served"))
})
mux.HandleFunc(hmrUpdatePrefix, func(w http.ResponseWriter, _ *http.Request) {
w.Write([]byte("served"))
})
return ac, mux
}
// checkGuarded checks that protected paths are denied and that a public path
// is served, by the server at baseURL. If wantProto is not 0, the responses
// must use that major HTTP version.
func checkGuarded(t *testing.T, client *http.Client, baseURL string, wantProto int) {
t.Helper()
for path, want := range map[string]int{
"/public": http.StatusOK,
"/admin/x": http.StatusForbidden,
"/%2fadmin/x": http.StatusForbidden,
"//admin/x": http.StatusForbidden,
"/ADMIN/x": http.StatusForbidden,
"/public/../admin/x": http.StatusForbidden,
hmrUpdatePrefix + "private/x": http.StatusForbidden,
} {
req, err := http.NewRequest(http.MethodGet, baseURL+path, nil)
if err != nil {
t.Fatal(err)
}
resp, err := client.Do(req)
if err != nil {
t.Errorf("GET %s: %v", path, err)
continue
}
body, _ := io.ReadAll(resp.Body)
resp.Body.Close()
if wantProto != 0 && resp.ProtoMajor != wantProto {
t.Errorf("GET %s: served over HTTP/%d, want HTTP/%d", path, resp.ProtoMajor, wantProto)
}
if want == http.StatusOK {
if resp.StatusCode != http.StatusOK || string(body) != "served" {
t.Errorf("GET %s: got %d %q, want 200 \"served\"", path, resp.StatusCode, body)
}
} else if string(body) == "served" {
t.Errorf("GET %s: the protected handler was reached (status %d)", path, resp.StatusCode)
}
}
}
// TestServersAreGuarded starts the servers that NewGracefulServer creates and
// checks that they all go through guardHandler.
func TestServersAreGuarded(t *testing.T) {
certFile, keyFile := writeTestCert(t)
insecureTLS := &tls.Config{InsecureSkipVerify: true}
for _, tc := range []struct {
name string
https bool
http2 bool
wantProto int
}{
{"HTTP", false, false, 1},
{"HTTPS", true, false, 1},
{"HTTP/2", true, true, 2},
} {
t.Run(tc.name, func(t *testing.T) {
ac, mux := newGuardTestConfig()
addr := findFreePort(t)
gs := ac.NewGracefulServer(mux, tc.http2, addr)
t.Cleanup(func() { gs.Server.Close() })
go func() {
if tc.https {
gs.ListenAndServeTLS(certFile, keyFile)
} else {
gs.ListenAndServe()
}
}()
waitForPort(t, addr)
transport := &http.Transport{TLSClientConfig: insecureTLS, ForceAttemptHTTP2: tc.http2}
if !tc.http2 {
transport.TLSNextProto = map[string]func(string, *tls.Conn) http.RoundTripper{}
}
defer transport.CloseIdleConnections()
scheme := "http"
if tc.https {
scheme = "https"
}
checkGuarded(t, &http.Client{Transport: transport, Timeout: 5 * time.Second}, scheme+"://"+addr, tc.wantProto)
})
}
}