Mercurial > gemma
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: