sdfhomeproxy/server.go

161 lines
5.0 KiB
Go

package main
import (
"context"
"crypto/tls"
"encoding/base64"
"fmt"
"golang.org/x/crypto/acme/autocert"
"log"
"net"
"net/http"
"net/http/httputil"
"net/mail"
"os"
"strconv"
"strings"
"time"
)
var (
targetHost = os.Getenv("SDF_PROXY_TARGET_HOST")
targetScheme = os.Getenv("SDF_PROXY_TARGET_SCHEME")
myIp string
myPtrAddr string
allowedHosts = strings.Split(os.Getenv("SDF_PROXY_ALLOWED_HOSTS"), ",")
tlsLetsEncryptEmail = os.Getenv("SDF_PROXY_LETS_ENCRYPT_EMAIL")
tlsCertManager = autocert.Manager{
Prompt: autocert.AcceptTOS,
Cache: autocert.DirCache("certs"),
Email: tlsLetsEncryptEmail,
HostPolicy: autocert.HostWhitelist(allowedHosts...),
}
)
func init() {
setMyIpAndPtrAddr(&myIp, &myPtrAddr)
if len(myPtrAddr) > 0 {
tlsCertManager.HostPolicy = autocert.HostWhitelist(append(allowedHosts, myPtrAddr)...)
}
}
// setMyIpAndPtrAddr attempts to set myIp and myPtrAddr
func setMyIpAndPtrAddr(ip, addr *string) {
resolver := &net.Resolver{
PreferGo: true,
Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
d := net.Dialer{
Timeout: time.Duration(10) * time.Second,
}
return d.DialContext(ctx, network, "ns1.google.com:53")
},
}
ips, errTxtLookup := resolver.LookupTXT(context.Background(), "o-o.myaddr.l.google.com")
if errTxtLookup != nil {
log.Printf("%v\n", errTxtLookup)
return
}
if len(ips) > 0 {
*ip = ips[len(ips)-1]
addrs, errAddrLookup := net.LookupAddr(*ip)
if errAddrLookup != nil {
log.Printf("%v\n", errAddrLookup)
return
}
if len(addrs) > 0 {
*addr = strings.TrimSuffix(addrs[len(addrs)-1], ".")
}
}
}
type httpReverseProxy struct {
http.Server
serveTls bool
}
func newHttpReverseProxy(addr string, serveTls bool) *httpReverseProxy {
server := new(httpReverseProxy)
server.Addr = addr
server.serveTls = serveTls
if server.serveTls {
server.TLSConfig = &tls.Config{
GetCertificate: tlsCertManager.GetCertificate,
MinVersion: tls.VersionTLS12,
}
}
proxy := new(httputil.ReverseProxy)
proxy.Transport = &http.Transport{}
proxy.Director = func(req *http.Request) {
req.Header.Set("X-Forwarded-For", req.RemoteAddr)
req.Header.Set("X-Forwarded-Host", req.Host)
req.Host = targetHost
req.URL.Host = targetHost
req.URL.Scheme = targetScheme
fmt.Print("Handling request: ")
fmt.Println(req.Header)
}
mux := http.NewServeMux()
// Hostnames for which the reverse proxy should direct requests to the target host
for _, host := range allowedHosts {
mux.Handle(host+"/", proxy)
mux.Handle(host+"/.well-known/acme-challenge/", tlsCertManager.HTTPHandler(nil))
}
// First hostname in the PTR record of the IP address which is being used by the server to connect to the internet
// This is an easter egg and will only work if the server is also reachable by the IP address from the internet
if len(myPtrAddr) > 0 {
mux.HandleFunc(myPtrAddr+"/", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Header().Set("Content-Type", "text/html")
content, _ := base64.StdEncoding.DecodeString("PCFkb2N0eXBlIGh0bWw+CjxodG1sIGxhbmc9ZW4+CjxoZWFkPgo8bWV0YSBjaGFyc2V0PXV0Zi04Pgo8dGl0bGU+8J+Qh/CfpZo8L3RpdGxlPgo8c3R5bGU+CmJvZHkge2JhY2tncm91bmQtY29sb3I6ICM3MDc0NzA7fQpodG1sIHtjdXJzb3I6IHVybChkYXRhOmltYWdlL3BuZztiYXNlNjQsaVZCT1J3MEtHZ29BQUFBTlNVaEVVZ0FBQUJBQUFBQVFDQVlBQUFBZjgvOWhBQUFBQVhOU1IwSUFyczRjNlFBQUFBWmlTMGRFQVA4QS93RC9vTDJua3dBQUFBbHdTRmx6QUFBTEV3QUFDeE1CQUpxY0dBQUFBQWQwU1UxRkIrQUtIUUFKRDNNdWdCa0FBQUI1U1VSQlZEakxwVk5CRHNBZ0NDdUdRLy8vV2c0bTdLS0dtRzBxa25DQlVBb0ZzV3FPQzFNQW9ESlZiTlZRc3AydEdxZ1V6UlEyMWpKR09PMGFZd1dYZGdSQUpXYlZqaG5NSUtrUklraDZCeDNrK2c3S1c2SnJ2U1ZubThVQmVGeE9qRWZmVm9GS21abThIZExvOVBXVnF6eXNtcTllK2kvL0FQUW9aZmJneUlxQkFBQUFBRWxGVGtTdVFtQ0MpLCBhdXRvO30KPC9zdHlsZT4K")
if _, err := w.Write(content); err != nil {
fmt.Println(err)
return
}
})
mux.Handle(myPtrAddr+"/.well-known/acme-challenge/", tlsCertManager.HTTPHandler(nil))
}
// All other hostnames, which are being ignored
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusGatewayTimeout)
})
server.Handler = mux
return server
}
// listenAndServe will call ListenAndServe or ListenAndServeTLS on httpReverseProxy to handle requests on incoming connections
func (t *httpReverseProxy) listenAndServe() {
var err error
fmt.Printf("Listening on %s\n", t.Addr)
if t.serveTls {
err = t.ListenAndServeTLS("", "")
} else {
err = t.ListenAndServe()
}
if err != nil {
log.Fatalf("listenAndServe, Addr: %s, serveTls: %s, error: %v", t.Addr, strconv.FormatBool(t.serveTls), err)
}
}
func main() {
if _, err := mail.ParseAddress(tlsLetsEncryptEmail); err != nil {
log.Fatalf("Invalid Let's Encrypt account email: %v\n", err)
}
fmt.Print("Allowed hosts: ")
fmt.Println(allowedHosts)
fmt.Print("PTR record (if any): ")
fmt.Println(myPtrAddr)
httpProxy := newHttpReverseProxy(":http", false)
httpsProxy := newHttpReverseProxy(":https", true)
go httpProxy.listenAndServe()
httpsProxy.listenAndServe()
}