package main import ( "net/http" "net/url" "log" "os" "strconv" "crypto/hmac" "crypto/sha256" "encoding/base64" "io" "github.com/gorilla/mux" "github.com/BurntSushi/toml" ) type tomlConfig struct { Port int `toml:"port"` Secret string `toml:"secret"` } func proxyRequest(w http.ResponseWriter, r *http.Request, secret []byte) { vars := mux.Vars(r) requestUrl, err := url.QueryUnescape(vars["url"]) if err != nil { log.Printf("Failed to url decode url: %s", err) return } macRaw, err := url.QueryUnescape(vars["hmac"]) if err != nil { log.Printf("Failed to url decode HMAC: %s", err) return } mac := hmac.New(sha256.New, secret) mac.Write([]byte(requestUrl)) expectedMac := mac.Sum(nil) receivedMac, err := base64.StdEncoding.DecodeString(macRaw) if err != nil { log.Printf("Failed to decode HMAC: %s", err) return } if !hmac.Equal(expectedMac, receivedMac) { log.Printf("invalid-hmac:%s", r.RemoteAddr) log.Printf("URL: '%s'", requestUrl) http.Error(w, "Invalid HMAC", http.StatusUnauthorized) return } client := &http.Client{} resp, err := client.Get(requestUrl) defer resp.Body.Close() if err != nil { log.Fatalf("Failed to perform GET: %s", err) } w.WriteHeader(resp.StatusCode) io.Copy(w, resp.Body) } func makeProxyRequestHandler(secret []byte) http.HandlerFunc { fn := func(w http.ResponseWriter, r *http.Request) { proxyRequest(w, r, secret) } return http.HandlerFunc(fn) } func exists(path string) bool { // Returns true if path exists. false otherwise _, err := os.Stat(path) return !os.IsNotExist(err) } func main() { config := tomlConfig { Port: 8080, } switch { case exists("./config.toml"): _, err := toml.DecodeFile("./config.toml", &config) if err != nil { log.Fatalf("Failed to read ./config.toml: %s", err) } case exists("/etc/miniproxy/config.toml"): _, err := toml.DecodeFile("/etc/miniproxy/config.toml", &config) if err != nil { log.Fatalf("Failed to read /etc/miniproxy/config.toml: %s", err) } } log.Printf("Running on :%d\n", config.Port) r := mux.NewRouter() r.UseEncodedPath() r.HandleFunc("/proxy/{hmac}/{url:.+}", makeProxyRequestHandler([]byte(config.Secret))) log.Fatal(http.ListenAndServe(":" + strconv.Itoa(config.Port), r)) }