From e2c39c9ae658ae7b8e210c4b02beb4bcbefae8a9 Mon Sep 17 00:00:00 2001 From: Steven Tan Date: Sun, 3 Apr 2022 10:10:26 +0000 Subject: [PATCH 1/3] Attempting to rewrite the HTTP response content to some success via CURL but not via npm CLI --- src/tools/proxy.go | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/src/tools/proxy.go b/src/tools/proxy.go index f5f9aeb..4281860 100644 --- a/src/tools/proxy.go +++ b/src/tools/proxy.go @@ -1,10 +1,13 @@ package tools import ( + "fmt" + "io/ioutil" "log" "net/http" "net/http/httputil" "net/url" + "strconv" "strings" ) @@ -30,8 +33,40 @@ func ProxyResponseHandler() func(*http.Response) error { return func(r *http.Response) error { log.Printf("RES: %s \"%s\" %d \"%s\" \"%s\"", r.Request.RemoteAddr, r.Request.Method, r.StatusCode, r.Request.RequestURI, r.Request.UserAgent()) + contentType := r.Header.Get("Content-Type") + log.Printf("%s", contentType) + + // Do some quick fixes to the HTTP response for NPM install requests + // TODO: Get this actually working, it looks like the JSON responses provide the correct URLs via CURL, but not when using npm against it. + if strings.HasPrefix(r.Request.UserAgent(), "npm") { + if !strings.Contains(contentType, "application/json") { + return nil + } + + // replace any instances of the CodeArtifact URL with the local URL + oldContentResponse, _ := ioutil.ReadAll(r.Body) + oldContentResponseStr := string(oldContentResponse) + + u, _ := url.Parse(CodeArtifactAuthInfo.Url) + hostname := u.Host + ":443" + + resolvedHostname := strings.Replace(CodeArtifactAuthInfo.Url, u.Host, hostname, -1) + newUrl := fmt.Sprintf("http://%s/", "localhost") + log.Printf("URI conversion %s -> %s", resolvedHostname, newUrl) + + newResponseContent := strings.Replace(oldContentResponseStr, resolvedHostname, newUrl, -1) + newResponseContent = strings.Replace(newResponseContent, CodeArtifactAuthInfo.Url, newUrl, -1) + + log.Print(newResponseContent) + + r.Body = ioutil.NopCloser(strings.NewReader(newResponseContent)) + r.ContentLength = int64(len(newResponseContent)) + r.Header.Set("Content-Length", strconv.Itoa(len(newResponseContent))) + } + return nil } + } // ProxyInit initialises the CodeArtifact proxy and starts the HTTP listener From 9ee2350e3c0787c4b0ef2dcaae8a14d97dc671e4 Mon Sep 17 00:00:00 2001 From: Steven Tan Date: Sun, 3 Apr 2022 10:11:26 +0000 Subject: [PATCH 2/3] Removed debug statement for replacing URL --- src/tools/proxy.go | 1 - 1 file changed, 1 deletion(-) diff --git a/src/tools/proxy.go b/src/tools/proxy.go index 4281860..ebbf361 100644 --- a/src/tools/proxy.go +++ b/src/tools/proxy.go @@ -52,7 +52,6 @@ func ProxyResponseHandler() func(*http.Response) error { resolvedHostname := strings.Replace(CodeArtifactAuthInfo.Url, u.Host, hostname, -1) newUrl := fmt.Sprintf("http://%s/", "localhost") - log.Printf("URI conversion %s -> %s", resolvedHostname, newUrl) newResponseContent := strings.Replace(oldContentResponseStr, resolvedHostname, newUrl, -1) newResponseContent = strings.Replace(newResponseContent, CodeArtifactAuthInfo.Url, newUrl, -1) From 1d19f9c03d5dbd5a6b96812d7b8495d665550570 Mon Sep 17 00:00:00 2001 From: Steven Tan Date: Tue, 5 Apr 2022 18:13:22 +0000 Subject: [PATCH 3/3] Fixed NPM support --- src/tools/proxy.go | 57 ++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 48 insertions(+), 9 deletions(-) diff --git a/src/tools/proxy.go b/src/tools/proxy.go index ebbf361..6320c45 100644 --- a/src/tools/proxy.go +++ b/src/tools/proxy.go @@ -1,7 +1,9 @@ package tools import ( + "compress/gzip" "fmt" + "io" "io/ioutil" "log" "net/http" @@ -11,9 +13,22 @@ import ( "strings" ) +var originalUrlResolver = make(map[string]*url.URL) + // ProxyRequestHandler intercepts requests to CodeArtifact and add the Authorization header + correct Host header func ProxyRequestHandler(p *httputil.ReverseProxy) func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, r *http.Request) { + // Store the original host header for each request + originalUrlResolver[r.RemoteAddr] = r.URL + originalUrlResolver[r.RemoteAddr].Host = r.Host + originalUrlResolver[r.RemoteAddr].Scheme = r.URL.Scheme + + if r.Header.Get("X-Forwarded-Proto") == "https" { + originalUrlResolver[r.RemoteAddr].Scheme = "https" + } else { + originalUrlResolver[r.RemoteAddr].Scheme = "http" + } + // Override the Host header with the CodeArtifact Host u, _ := url.Parse(CodeArtifactAuthInfo.Url) r.Host = u.Host @@ -31,33 +46,57 @@ func ProxyRequestHandler(p *httputil.ReverseProxy) func(http.ResponseWriter, *ht func ProxyResponseHandler() func(*http.Response) error { return func(r *http.Response) error { + log.Printf("Received response from %s", r.Request.URL.String()) log.Printf("RES: %s \"%s\" %d \"%s\" \"%s\"", r.Request.RemoteAddr, r.Request.Method, r.StatusCode, r.Request.RequestURI, r.Request.UserAgent()) contentType := r.Header.Get("Content-Type") - log.Printf("%s", contentType) + + originalUrl := originalUrlResolver[r.Request.RemoteAddr] + delete(originalUrlResolver, r.Request.RemoteAddr) + + u, _ := url.Parse(CodeArtifactAuthInfo.Url) + hostname := u.Host + ":443" + + // Rewrite the 301 to point from CodeArtifact URL to the proxy instead.. + if r.StatusCode == 301 || r.StatusCode == 302 { + location, _ := r.Location() + + location.Host = originalUrl.Host + location.Scheme = originalUrl.Scheme + location.Path = strings.Replace(location.Path, u.Path, "", 1) + + r.Header.Set("Location", location.String()) + } // Do some quick fixes to the HTTP response for NPM install requests // TODO: Get this actually working, it looks like the JSON responses provide the correct URLs via CURL, but not when using npm against it. if strings.HasPrefix(r.Request.UserAgent(), "npm") { - if !strings.Contains(contentType, "application/json") { + + // Respond to only requests that respond with JSON + // There might eventually be additional headers i don't know about? + if !strings.Contains(contentType, "application/json") && !strings.Contains(contentType, "application/vnd.npm.install-v1+json") { return nil } + var body io.ReadCloser + + if r.Header.Get("Content-Encoding") == "gzip" { + body, _ = gzip.NewReader(r.Body) + r.Header.Del("Content-Encoding") + } else { + body = r.Body + } + // replace any instances of the CodeArtifact URL with the local URL - oldContentResponse, _ := ioutil.ReadAll(r.Body) + oldContentResponse, _ := ioutil.ReadAll(body) oldContentResponseStr := string(oldContentResponse) - u, _ := url.Parse(CodeArtifactAuthInfo.Url) - hostname := u.Host + ":443" - resolvedHostname := strings.Replace(CodeArtifactAuthInfo.Url, u.Host, hostname, -1) - newUrl := fmt.Sprintf("http://%s/", "localhost") + newUrl := fmt.Sprintf("%s://%s/", originalUrl.Scheme, originalUrl.Host) newResponseContent := strings.Replace(oldContentResponseStr, resolvedHostname, newUrl, -1) newResponseContent = strings.Replace(newResponseContent, CodeArtifactAuthInfo.Url, newUrl, -1) - log.Print(newResponseContent) - r.Body = ioutil.NopCloser(strings.NewReader(newResponseContent)) r.ContentLength = int64(len(newResponseContent)) r.Header.Set("Content-Length", strconv.Itoa(len(newResponseContent)))