Ctrl K
ComunidadGuías…

engine/guard_test.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
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)
		})
	}
}