proxy-test-server.go 2.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. // The proxy-test-server command is a simple HTTP proxy server for testing
  4. // Tailscale's client proxy functionality.
  5. package main
  6. import (
  7. "crypto/tls"
  8. "flag"
  9. "fmt"
  10. "log"
  11. "net"
  12. "net/http"
  13. "os"
  14. "strings"
  15. "golang.org/x/crypto/acme/autocert"
  16. "tailscale.com/net/connectproxy"
  17. "tailscale.com/tempfork/acme"
  18. )
  19. var (
  20. listen = flag.String("listen", ":8080", "Address to listen on for HTTPS proxy requests")
  21. hostname = flag.String("hostname", "localhost", "Hostname for the proxy server")
  22. tailscaleOnly = flag.Bool("tailscale-only", true, "Restrict proxy to Tailscale targets only")
  23. extraAllowedHosts = flag.String("allow-hosts", "", "Comma-separated list of allowed target hosts to additionally allow if --tailscale-only is true")
  24. )
  25. func main() {
  26. flag.Parse()
  27. am := &autocert.Manager{
  28. HostPolicy: autocert.HostWhitelist(*hostname),
  29. Prompt: autocert.AcceptTOS,
  30. Cache: autocert.DirCache(os.ExpandEnv("$HOME/.cache/autocert/proxy-test-server")),
  31. }
  32. var allowTarget func(hostPort string) error
  33. if *tailscaleOnly {
  34. allowTarget = func(hostPort string) error {
  35. host, port, err := net.SplitHostPort(hostPort)
  36. if err != nil {
  37. return fmt.Errorf("invalid target %q: %v", hostPort, err)
  38. }
  39. if port != "443" {
  40. return fmt.Errorf("target %q must use port 443", hostPort)
  41. }
  42. for allowed := range strings.SplitSeq(*extraAllowedHosts, ",") {
  43. if host == allowed {
  44. return nil // explicitly allowed target
  45. }
  46. }
  47. if !strings.HasSuffix(host, ".tailscale.com") {
  48. return fmt.Errorf("target %q is not a Tailscale host", hostPort)
  49. }
  50. return nil // valid Tailscale target
  51. }
  52. }
  53. go func() {
  54. if err := http.ListenAndServe(":http", am.HTTPHandler(nil)); err != nil {
  55. log.Fatalf("autocert HTTP server failed: %v", err)
  56. }
  57. }()
  58. hs := &http.Server{
  59. Addr: *listen,
  60. Handler: &connectproxy.Handler{
  61. Check: allowTarget,
  62. Logf: log.Printf,
  63. },
  64. TLSConfig: &tls.Config{
  65. GetCertificate: am.GetCertificate,
  66. NextProtos: []string{
  67. "http/1.1", // enable HTTP/2
  68. acme.ALPNProto, // enable tls-alpn ACME challenges
  69. },
  70. },
  71. }
  72. log.Printf("Starting proxy-test-server on %s (hostname: %q)\n", *listen, *hostname)
  73. log.Fatal(hs.ListenAndServeTLS("", "")) // cert and key are provided by autocert
  74. }