diff controllers/externalwfs.go @ 408:ac23905e64b1

Improve WFS proxy a lot. It now generates signed re-writings.
author Sascha L. Teichmann <sascha.teichmann@intevation.de>
date Wed, 15 Aug 2018 15:55:41 +0200
parents 15369b41be74
children
line wrap: on
line diff
--- a/controllers/externalwfs.go	Wed Aug 15 15:14:47 2018 +0200
+++ b/controllers/externalwfs.go	Wed Aug 15 15:55:41 2018 +0200
@@ -3,104 +3,110 @@
 import (
 	"compress/flate"
 	"compress/gzip"
+	"crypto/hmac"
+	"crypto/sha256"
+	"encoding/base64"
 	"encoding/xml"
 	"io"
 	"io/ioutil"
 	"log"
 	"net/http"
 	"net/url"
+	"regexp"
 	"strings"
 	"time"
 
+	"gemma.intevation.de/gemma/config"
 	"github.com/gorilla/mux"
 	"golang.org/x/net/html/charset"
-
-	"gemma.intevation.de/gemma/config"
 )
 
-// roundTripFunc is a helper type to make externalWFSDirector a http.RoundTripper.
-type roundTripFunc func(*http.Request) (*http.Response, error)
-
-func (rtf roundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) {
-	return rtf(req)
+// proxyBlackList is a set of URLs that should not be rewritten by the proxy.
+var proxyBlackList = map[string]struct{}{
+	"http://www.w3.org/2001/XMLSchema-instance": struct{}{},
+	"http://www.w3.org/1999/xlink":              struct{}{},
+	"http://www.w3.org/2001/XMLSchema":          struct{}{},
+	"http://www.w3.org/XML/1998/namespace":      struct{}{},
+	"http://www.opengis.net/wfs/2.0":            struct{}{},
+	"http://www.opengis.net/ows/1.1":            struct{}{},
+	"http://www.opengis.net/gml/3.2":            struct{}{},
+	"http://www.opengis.net/fes/2.0":            struct{}{},
+	"http://schemas.opengis.net/gml":            struct{}{},
 }
 
-func externalWFSDirector(req *http.Request) {
+func findEntry(entry string) (string, bool) {
+	external := config.ExternalWFSs()
+	if external == nil || len(external) == 0 {
+		return "", false
+	}
+	alias, found := external[entry]
+	if !found {
+		return "", false
+	}
+	data, ok := alias.(map[string]interface{})
+	if !ok {
+		return "", false
+	}
+	urlS, found := data["url"]
+	if !found {
+		return "", false
+	}
+	url, ok := urlS.(string)
+	return url, ok
+}
+
+func proxyDirector(req *http.Request) {
+
+	log.Printf("proxyDirector: %s\n", req.RequestURI)
 
 	abort := func(format string, args ...interface{}) {
 		log.Printf(format, args...)
 		panic(http.ErrAbortHandler)
 	}
 
-	external := config.ExternalWFSs()
-	if external == nil || len(external) == 0 {
-		abort("No external WFS proxy config found\n")
-	}
 	vars := mux.Vars(req)
-	wfs := vars["wfs"]
-	rest := vars["rest"]
+
+	var s string
 
-	log.Printf("rest: %s\n", rest)
+	if entry, found := vars["entry"]; found {
+		if s, found = findEntry(entry); !found {
+			abort("Cannot find entry '%s'\n", entry)
+		}
+	} else {
+		expectedMAC, err := base64.URLEncoding.DecodeString(vars["hash"])
+		if err != nil {
+			abort("Cannot base64 decode hash: %v\n", err)
+		}
+		url, err := base64.URLEncoding.DecodeString(vars["url"])
+		if err != nil {
+			abort("Cannot base64 decode url: %v\n", err)
+		}
 
-	alias, found := external[wfs]
-	if !found {
-		abort("No config found for %s\n", wfs)
-	}
-	data, ok := alias.(map[string]interface{})
-	if !ok {
-		abort("error: badly configured external WFS %s\n", wfs)
+		mac := hmac.New(sha256.New, config.ProxyKey())
+		mac.Write(url)
+		messageMAC := mac.Sum(nil)
+
+		s = string(url)
+
+		if !hmac.Equal(messageMAC, expectedMAC) {
+			abort("HMAC of URL %s failed.\n", s)
+		}
 	}
 
-	urlS, found := data["url"]
-	if !found {
-		abort("error: missing url for external WFS %s\n", wfs)
-	}
-
-	prefix, ok := urlS.(string)
-	if !ok {
-		abort("error: badly configured url for external WFS %s\n", wfs)
-	}
-
-	https := useHTTPS(req)
-
-	log.Printf("%v\n", prefix)
-	nURL := prefix + "/" + rest + "?" + req.URL.RawQuery
-	log.Printf("%v\n", nURL)
+	nURL := s + "?" + req.URL.RawQuery
+	//log.Printf("%v\n", nURL)
 
 	u, err := url.Parse(nURL)
 	if err != nil {
 		abort("Invalid url: %v\n", err)
 	}
 	req.URL = u
-	req.Header.Set("X-Gemma-From", prefix)
-	to := https + "://" + req.Host + "/api/externalwfs/" + wfs
-	req.Header.Set("X-Gemma-To", to)
 
 	req.Host = u.Host
-
+	//req.Header.Del("If-None-Match")
 	//log.Printf("headers: %v\n", req.Header)
 }
 
-func externalWFSTransport(req *http.Request) (*http.Response, error) {
-
-	from := req.Header.Get("X-Gemma-From")
-	to := req.Header.Get("X-Gemma-To")
-	req.Header.Del("X-Gemma-From")
-	req.Header.Del("X-Gemma-To")
-
-	// To prevent some caching effects.
-	req.Header.Del("If-None-Match")
-
-	resp, err := http.DefaultTransport.RoundTrip(req)
-	if err != nil {
-		return nil, err
-	}
-	resp.Header.Set("X-Gemma-From", from)
-	resp.Header.Set("X-Gemma-To", to)
-
-	return resp, err
-}
-
 type nopCloser struct {
 	io.Writer
 }
@@ -145,19 +151,12 @@
 	}
 }
 
-func externalWFSModifyResponse(resp *http.Response) error {
-
-	from := resp.Header.Get("X-Gemma-From")
-	to := resp.Header.Get("X-Gemma-To")
-	resp.Header.Del("X-Gemma-From")
-	resp.Header.Del("X-Gemma-To")
+func proxyModifyResponse(resp *http.Response) error {
 
 	if !isXML(resp.Header) {
 		return nil
 	}
 
-	log.Printf("rewrite from %s to %s\n", from, to)
-
 	pr, pw := io.Pipe()
 
 	var (
@@ -185,7 +184,7 @@
 			force.Close()
 			log.Printf("rewrite took %s\n", time.Since(start))
 		}()
-		if err := rewrite(w, r, from, to); err != nil {
+		if err := rewrite(w, r); err != nil {
 			log.Printf("rewrite failed: %v\n", err)
 			return
 		}
@@ -215,17 +214,35 @@
 	return false
 }
 
-func rewrite(w io.Writer, r io.Reader, from, to string) error {
+var replaceRe = regexp.MustCompile(`\b(https?://[^\s\?]*)`)
+
+func replace(s string) string {
+
+	proxyKey := config.ProxyKey()
+	proxyPrefix := config.ProxyPrefix() + "/api/proxy/"
+
+	return replaceRe.ReplaceAllStringFunc(s, func(s string) string {
+		if _, found := proxyBlackList[s]; found {
+			return s
+		}
+		mac := hmac.New(sha256.New, proxyKey)
+		b := []byte(s)
+		mac.Write(b)
+		expectedMAC := mac.Sum(nil)
+
+		hash := base64.URLEncoding.EncodeToString(expectedMAC)
+		enc := base64.URLEncoding.EncodeToString(b)
+		return proxyPrefix + hash + "/" + enc
+	})
+}
+
+func rewrite(w io.Writer, r io.Reader) error {
 
 	decoder := xml.NewDecoder(r)
 	decoder.CharsetReader = charset.NewReaderLabel
 
 	encoder := xml.NewEncoder(w)
 
-	replace := func(s string) string {
-		return strings.Replace(s, from, to, -1)
-	}
-
 	var n nsdef
 
 tokens: