diff --git a/README.md b/README.md index 4e86154..9c6c139 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,14 @@ localhost.direct works immediately without configuration, functioning just like Download or clone the .key and .crt files, then deploy them to your local web server to set up an SSL-enabled local development environment. +### Temporarily forward HTTPS requests to your local HTTP service +``` +go get ./... +go build +# Forward requests from https://.localhost.direct:3000 to http://192.168.9.113:3000 +./localhost_direct -p 3000 -rh 192.168.9.113 -rp 3000 +``` + ## Limitation: **get.localhost.direct** is reserved and it is the only subdomain that you cannot use. diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..6417805 --- /dev/null +++ b/go.mod @@ -0,0 +1,7 @@ +module localhost_direct + +go 1.21.6 + +require github.com/yeka/zip v0.0.0-20231116150916-03d6312748a9 + +require golang.org/x/crypto v0.26.0 // indirect diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..198ce86 --- /dev/null +++ b/go.sum @@ -0,0 +1,4 @@ +github.com/yeka/zip v0.0.0-20231116150916-03d6312748a9 h1:K8gF0eekWPEX+57l30ixxzGhHH/qscI3JCnuhbN6V4M= +github.com/yeka/zip v0.0.0-20231116150916-03d6312748a9/go.mod h1:9BnoKCcgJ/+SLhfAXj15352hTOuVmG5Gzo8xNRINfqI= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= diff --git a/main.go b/main.go new file mode 100644 index 0000000..9f0fa56 --- /dev/null +++ b/main.go @@ -0,0 +1,128 @@ +package main + +import ( + "bytes" + "crypto/tls" + "flag" + "fmt" + "io" + "net" + "net/http" + "net/http/httputil" + "net/url" + "os" + "path/filepath" + + "github.com/yeka/zip" +) + +func main() { + port := flag.Int("p", 443, "Local HTTPS server port") + remoteHost := flag.String("rh", "127.0.0.1", "Remote HTTP server host") + remotePort := flag.Int("rp", 0, "Remote HTTP server port") + + // Custom usage message + flag.Usage = func() { + fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0]) + flag.PrintDefaults() + fmt.Fprintf(os.Stderr, "\nAccess the server using https://a.localhost.direct:\n") + } + flag.Parse() + + if !isValidIP(*remoteHost) { + fmt.Printf("Error: Invalid IP address for remote host: %s\n", *remoteHost) + flag.Usage() + os.Exit(1) + } + + if *remotePort == 0 { + *remotePort = *port + } + + // Download and extract SSL certificate + certFile, keyFile, err := downloadAndExtractCert() + if err != nil { + fmt.Printf("Error downloading certificate: %v\n", err) + os.Exit(1) + } + defer os.Remove(certFile) + defer os.Remove(keyFile) + + // Set up reverse proxy + target, _ := url.Parse(fmt.Sprintf("http://%s:%d", *remoteHost, *remotePort)) + proxy := httputil.NewSingleHostReverseProxy(target) + + // Configure TLS + cert, err := tls.LoadX509KeyPair(certFile, keyFile) + if err != nil { + fmt.Printf("Error loading certificate: %v\n", err) + os.Exit(1) + } + + tlsConfig := &tls.Config{Certificates: []tls.Certificate{cert}} + server := &http.Server{ + Addr: fmt.Sprintf(":%d", *port), + TLSConfig: tlsConfig, + Handler: proxy, + } + + fmt.Printf("Starting HTTPS server on port %d, forwarding to %s:%d\n", *port, *remoteHost, *remotePort) + err = server.ListenAndServeTLS("", "") + if err != nil { + fmt.Printf("Server error: %v\n", err) + } +} + +func isValidIP(ip string) bool { + return net.ParseIP(ip) != nil +} + +func downloadAndExtractCert() (string, string, error) { + resp, err := http.Get("https://aka.re/localhost") + if err != nil { + return "", "", err + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + return "", "", err + } + + zipReader, err := zip.NewReader(bytes.NewReader(body), int64(len(body))) + if err != nil { + return "", "", err + } + + var certFile, keyFile string + for _, file := range zipReader.File { + if file.IsEncrypted() { + file.SetPassword("localhost") + } + + outFile, err := os.CreateTemp("", filepath.Base(file.Name)) + if err != nil { + return "", "", err + } + defer outFile.Close() + + fileReader, err := file.Open() + if err != nil { + return "", "", err + } + defer fileReader.Close() + + _, err = io.Copy(outFile, fileReader) + if err != nil { + return "", "", err + } + + if filepath.Ext(file.Name) == ".crt" { + certFile = outFile.Name() + } else if filepath.Ext(file.Name) == ".key" { + keyFile = outFile.Name() + } + } + + return certFile, keyFile, nil +}