diff --git a/lib/httplib/httpheaders.go b/lib/httplib/httpheaders.go index 1fae246120433..b5f6ff9cae464 100644 --- a/lib/httplib/httpheaders.go +++ b/lib/httplib/httpheaders.go @@ -224,35 +224,6 @@ func SetIndexContentSecurityPolicy(h http.Header, cfg proto.Features, urlPath st h.Set("Content-Security-Policy", cspString) } -// DELETE IN 17.0: Kept for legacy app access. -var appLaunchCSPStringCache *cspCache = newCSPCache() - -// DELETE IN 17.0: Kept for legacy app access. -func getAppLaunchContentSecurityPolicyString(applicationURL string) string { - if cspString, ok := appLaunchCSPStringCache.get(applicationURL); ok { - return cspString - } - - cspString := getContentSecurityPolicyString( - defaultContentSecurityPolicy, - defaultFontSrc, - cspMap{ - "connect-src": {"'self'", applicationURL}, - }, - ) - appLaunchCSPStringCache.set(applicationURL, cspString) - - return cspString -} - -// DELETE IN 17.0: Kept for legacy app access. -// -// SetAppLaunchContentSecurityPolicy sets the Content-Security-Policy header for /web/launch -func SetAppLaunchContentSecurityPolicy(h http.Header, applicationURL string) { - cspString := getAppLaunchContentSecurityPolicyString(applicationURL) - h.Set("Content-Security-Policy", cspString) -} - var redirectCSPStringCache *cspCache = newCSPCache() func getRedirectPageContentSecurityPolicyString(scriptSrc string) string { diff --git a/lib/httplib/httplib_test.go b/lib/httplib/httplib_test.go index 7511fd652cd55..4de4a114461b2 100644 --- a/lib/httplib/httplib_test.go +++ b/lib/httplib/httplib_test.go @@ -376,32 +376,6 @@ func TestSetIndexContentSecurityPolicy(t *testing.T) { } } -func TestSetAppLaunchContentSecurityPolicy(t *testing.T) { - t.Parallel() - - applicationURL := "https://example.com" - - expectedCspVals := map[string]string{ - "default-src": "'self'", - "base-uri": "'self'", - "form-action": "'self'", - "frame-ancestors": "'none'", - "object-src": "'none'", - "style-src": "'self' 'unsafe-inline'", - "img-src": "'self' data: blob:", - "font-src": "'self' data:", - "connect-src": fmt.Sprintf("'self' %s", applicationURL), - } - - h := make(http.Header) - SetAppLaunchContentSecurityPolicy(h, applicationURL) - actualCsp := h.Get("Content-Security-Policy") - for k, v := range expectedCspVals { - expectedCspSubString := fmt.Sprintf("%s %s;", k, v) - require.Contains(t, actualCsp, expectedCspSubString) - } -} - func TestSetRedirectPageContentSecurityPolicy(t *testing.T) { t.Parallel() diff --git a/lib/web/apiserver.go b/lib/web/apiserver.go index e1ca68e849adc..9ca2ca3e39c7f 100644 --- a/lib/web/apiserver.go +++ b/lib/web/apiserver.go @@ -577,43 +577,7 @@ func NewHandler(cfg Config, opts ...HandlerOption) (*APIHandler, error) { session.XCSRF = csrfToken httplib.SetNoCacheHeaders(w.Header()) - - // DELETE IN 17.0: Delete the first if block. Keep the else case. - // Kept for backwards compatibility. - // - // This case only adds an additional CSP `content-src` value of the - // app URL which allows requesting to the app domain required by - // the legacy app access. - if strings.HasPrefix(r.URL.Path, "/web/launch/") { - // legacy app access needs to make a CORS fetch request, - // so we only set the default CSP on that page - parts := strings.Split(r.URL.Path, "/") - - if len(parts[3]) == 0 { - h.log.Warn("Application domain is missing from web/launch URL") - http.Error(w, "missing application domain", http.StatusBadRequest) - return - } - - // grab the FQDN from the URL to allow in the connect-src CSP - applicationOrigin := "https://" + parts[3] - - // Parse to validate the application domain extracted is in a valid format. - // An invalid domain is: - // - having spaces - // - having escape characters eg: %20 - // - invalid port after host eg: :unsafe-inline - _, err := url.Parse(applicationOrigin) - if err != nil { - h.log.WithError(err).Warn("Failed to parse application domain extracted from web/launch.") - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - - httplib.SetAppLaunchContentSecurityPolicy(w.Header(), applicationOrigin+":*") - } else { - httplib.SetIndexContentSecurityPolicy(w.Header(), cfg.ClusterFeatures, r.URL.Path) - } + httplib.SetIndexContentSecurityPolicy(w.Header(), cfg.ClusterFeatures, r.URL.Path) if err := indexPage.Execute(w, session); err != nil { h.log.WithError(err).Error("Failed to execute index page template.") diff --git a/lib/web/apiserver_test.go b/lib/web/apiserver_test.go index 502ee17b1f4e7..2e566d651cd88 100644 --- a/lib/web/apiserver_test.go +++ b/lib/web/apiserver_test.go @@ -6164,79 +6164,6 @@ func TestListConnectionsDiagnostic(t *testing.T) { require.Equal(t, "some details", receivedConnectionDiagnostic.Traces[0].Details) } -func TestWebLauncherURL(t *testing.T) { - t.Parallel() - env := newWebPack(t, 1) - proxy := env.proxies[0] - pack := proxy.authPack(t, "foo@example.com", nil /* roles */) - - testcases := []struct { - desc string - appURL string - statusCode int - }{ - { - desc: "valid semicolon", - appURL: "dumper.localhost;testing", - statusCode: http.StatusOK, - }, - { - desc: "valid with query (question mark)", - appURL: "dumper.localhost?foo", - statusCode: http.StatusOK, - }, - { - desc: "valid with multipath", - appURL: "dumper.localhost/foo/bar/", - statusCode: http.StatusOK, - }, - { - desc: "valid with multipath with query", - appURL: "dumper.localhost/foo/bar?foo=bar", - statusCode: http.StatusOK, - }, - { - desc: "invalid: empty application url", - appURL: "/", - statusCode: http.StatusBadRequest, - }, - { - desc: "invalid use of semicolon, invalid port", - appURL: "dumper.localhost;script-src:something", - statusCode: http.StatusBadRequest, - }, - { - desc: "invalid use of escape characters (space)", - appURL: "dumper.localhost;%20script-src%20unsafe-inline%20;", - statusCode: http.StatusBadRequest, - }, - { - desc: "invalid use of escape characters (double encoded space)", - appURL: "dumper.localhost;%2520script-src%2520unsafe-inline%2520;", - statusCode: http.StatusBadRequest, - }, - { - desc: "invalid use of spaces", - appURL: "dumper.localhost; script-src * unsafe-inline;", - statusCode: http.StatusBadRequest, - }, - } - - for _, tc := range testcases { - t.Run(tc.desc, func(t *testing.T) { - url, err := url.JoinPath(proxy.webURL.String(), "web", "launch", tc.appURL) - require.NoError(t, err) - - resp, err := pack.clt.HTTPClient().Get(url) - require.NoError(t, err) - defer resp.Body.Close() - - require.Equal(t, tc.statusCode, resp.StatusCode) - }) - } - -} - func TestDiagnoseSSHConnection(t *testing.T) { ctx := context.Background() diff --git a/lib/web/app/auth.go b/lib/web/app/auth.go index bda92d4083dcd..e6b3e18150eb1 100644 --- a/lib/web/app/auth.go +++ b/lib/web/app/auth.go @@ -198,75 +198,6 @@ func (h *Handler) completeAppAuthExchange(w http.ResponseWriter, r *http.Request return nil } -// DELETE IN 17.0: kept for backwards compat. -// -// handleAuth handles authentication for an app -// When a `POST` request comes in from a trusted proxy address, it'll set the value from the -// `X-Cookie-Value` header to the `__Host-grv_app_session` cookie. -func (h *Handler) handleAuth(w http.ResponseWriter, r *http.Request, p httprouter.Params) error { - httplib.SetNoCacheHeaders(w.Header()) - - cookieValue := r.Header.Get("X-Cookie-Value") - if cookieValue == "" { - h.log.Warn("Request failed: missing X-Cookie-Value header") - h.emitErrorEventAndDeleteAppSession(r, emitErrorEventFields{ - err: "missing X-Cookie-Value header", - }) - return trace.AccessDenied("access denied") - } - - subjectCookieValue := r.Header.Get("X-Subject-Cookie-Value") - if subjectCookieValue == "" { - h.log.Warn("Request failed: X-Subject-Cookie-Value") - h.emitErrorEventAndDeleteAppSession(r, emitErrorEventFields{ - err: "missing X-Subject-Cookie-Value header", - sessionID: cookieValue, - }) - return trace.AccessDenied("access denied") - } - - // Validate that the caller is asking for a session that exists. - ws, err := h.c.AccessPoint.GetAppSession(r.Context(), types.GetAppSessionRequest{ - SessionID: cookieValue, - }) - if err != nil { - h.log.WithError(err).Warn("Request failed: unable to get app session") - return trace.AccessDenied("access denied") - } - - if err := checkSubjectToken(subjectCookieValue, ws); err != nil { - h.log.Warnf("Request failed: %v.", err) - - h.emitErrorEventAndDeleteAppSession(r, emitErrorEventFields{ - sessionID: cookieValue, - err: err.Error(), - loginName: ws.GetUser(), - }) - - return trace.AccessDenied("access denied") - } - - http.SetCookie(w, &http.Cookie{ - Name: CookieName, - Value: cookieValue, - Path: "/", - HttpOnly: true, - Secure: true, - SameSite: http.SameSiteNoneMode, - }) - - http.SetCookie(w, &http.Cookie{ - Name: SubjectCookieName, - Value: subjectCookieValue, - Path: "/", - HttpOnly: true, - Secure: true, - SameSite: http.SameSiteNoneMode, - }) - - return nil -} - func checkSubjectToken(subjectCookieValue string, ws types.WebSession) error { if subjectCookieValue == "" { return trace.AccessDenied("subject session token is not set") diff --git a/lib/web/app/handler.go b/lib/web/app/handler.go index 2d7e687a1b3be..46efecbeacd08 100644 --- a/lib/web/app/handler.go +++ b/lib/web/app/handler.go @@ -138,13 +138,7 @@ func NewHandler(ctx context.Context, c *HandlerConfig) (*Handler, error) { h.router = httprouter.New() h.router.UseRawPath = true h.router.GET("/x-teleport-auth", makeRouterHandler(h.startAppAuthExchange)) - // DELETE IN 17.0 - // Kept for legacy app access. - h.router.OPTIONS("/x-teleport-auth", makeRouterHandler(h.withCustomCORS(nil))) - // DELETE IN 17.0 - // when deleting, replace with the commented handler below: - // h.router.POST("/x-teleport-auth", makeRouterHandler(h.completeAppAuthExchange)) - h.router.POST("/x-teleport-auth", makeRouterHandler(h.withCustomCORS(h.handleAuth))) + h.router.POST("/x-teleport-auth", makeRouterHandler(h.completeAppAuthExchange)) h.router.GET("/teleport-logout", h.withRouterAuth(h.handleLogout)) h.router.NotFound = h.withAuth(h.handleHttp) diff --git a/lib/web/app/handler_test.go b/lib/web/app/handler_test.go index a6daf2195d612..8e1c087ba303d 100644 --- a/lib/web/app/handler_test.go +++ b/lib/web/app/handler_test.go @@ -135,7 +135,7 @@ func TestAuthPOST(t *testing.T) { }, Status: apievents.Status{ Success: false, - Error: "Failed app access authentication: missing required fields in JSON request body", + Error: "Failed app access authentication: state token was not in the expected format", }, }), } @@ -160,11 +160,12 @@ func TestAuthPOST(t *testing.T) { Code: events.AuthAttemptFailureCode, }, UserMetadata: apievents.UserMetadata{ - User: "unknown", + User: "unknown", + Login: "testuser", }, Status: apievents.Status{ Success: false, - Error: "Failed app access authentication: missing required fields in JSON request body", + Error: "Failed app access authentication: subject session token is not set", }, }), } @@ -225,7 +226,7 @@ func TestAuthPOST(t *testing.T) { sessionError: test.sessionError, appSession: appSession, } - p := setup(t, fakeClock, authClient, nil, nil) + p := setup(t, fakeClock, authClient, nil) reqBody := test.makeRequestBody(appSession) req, err := json.Marshal(reqBody) @@ -243,263 +244,6 @@ func TestAuthPOST(t *testing.T) { } } -// DELETE IN 17.0 -func TestAuthPOST_Legacy(t *testing.T) { - const ( - cookieValue = "5588e2be54a2834b4f152c56bafcd789f53b15477129d2ab4044e9a3c1bf0f3b" // random value we set in the header and expect to get back as a cookie - ) - - fakeClock := clockwork.NewFakeClock() - clusterName := "test-cluster" - publicAddr := "proxy.goteleport.com:443" - - // Generate CA TLS key and cert with the cluster and application DNS. - key, cert, err := tlsca.GenerateSelfSignedCA( - pkix.Name{CommonName: clusterName}, - []string{publicAddr, apiutils.EncodeClusterName(clusterName)}, - defaults.CATTL, - ) - require.NoError(t, err) - - appSession := createAppSession(t, fakeClock, key, cert, clusterName, publicAddr) - - tests := []struct { - desc string - headers map[string]string - sessionError error - outStatusCode int - eventChecks []eventCheckFn - proxyAddrs []utils.NetAddr - cookieValue string - subjectCookieValue string - }{ - { - desc: "success", - headers: map[string]string{ - "Origin": "https://proxy.goteleport.com", - "X-Cookie-Value": cookieValue, - "X-Subject-Cookie-Value": appSession.GetBearerToken(), - }, - outStatusCode: http.StatusOK, - eventChecks: []eventCheckFn{hasAuditEventCount(0)}, - proxyAddrs: []utils.NetAddr{ - *utils.MustParseAddr(publicAddr), - }, - cookieValue: cookieValue, - subjectCookieValue: appSession.GetBearerToken(), - }, - { - desc: "success - proxy addr with custom port", - headers: map[string]string{ - "Origin": "https://proxy.goteleport.com:3080", - "X-Cookie-Value": cookieValue, - "X-Subject-Cookie-Value": appSession.GetBearerToken(), - }, - outStatusCode: http.StatusOK, - eventChecks: []eventCheckFn{hasAuditEventCount(0)}, - proxyAddrs: []utils.NetAddr{ - *utils.MustParseAddr("proxy.goteleport.com:3080"), - }, - cookieValue: cookieValue, - subjectCookieValue: appSession.GetBearerToken(), - }, - { - desc: "missing subject session token in request", - headers: map[string]string{ - "Origin": "https://proxy.goteleport.com", - "X-Cookie-Value": cookieValue, - }, - outStatusCode: http.StatusForbidden, - eventChecks: []eventCheckFn{ - hasAuditEventCount(1), - hasAuditEvent(0, &apievents.AuthAttempt{ - Metadata: apievents.Metadata{ - Type: events.AuthAttemptEvent, - Code: events.AuthAttemptFailureCode, - }, - UserMetadata: apievents.UserMetadata{ - User: "unknown", - }, - Status: apievents.Status{ - Success: false, - Error: "Failed app access authentication: missing X-Subject-Cookie-Value header", - }, - }), - }, - proxyAddrs: []utils.NetAddr{ - *utils.MustParseAddr(publicAddr), - }, - }, - { - desc: "missing subject session token in request", - headers: map[string]string{ - "Origin": "https://proxy.goteleport.com", - "X-Subject-Cookie-Value": "foobar", - }, - outStatusCode: http.StatusForbidden, - eventChecks: []eventCheckFn{ - hasAuditEventCount(1), - hasAuditEvent(0, &apievents.AuthAttempt{ - Metadata: apievents.Metadata{ - Type: events.AuthAttemptEvent, - Code: events.AuthAttemptFailureCode, - }, - UserMetadata: apievents.UserMetadata{ - User: "unknown", - }, - Status: apievents.Status{ - Success: false, - Error: "Failed app access authentication: missing X-Cookie-Value header", - }, - }), - }, - proxyAddrs: []utils.NetAddr{ - *utils.MustParseAddr(publicAddr), - }, - }, - { - desc: "subject session token in request does not match", - headers: map[string]string{ - "Origin": "https://proxy.goteleport.com", - "X-Cookie-Value": cookieValue, - "X-Subject-Cookie-Value": "foobar", - }, - outStatusCode: http.StatusForbidden, - eventChecks: []eventCheckFn{ - hasAuditEventCount(1), - hasAuditEvent(0, &apievents.AuthAttempt{ - Metadata: apievents.Metadata{ - Type: events.AuthAttemptEvent, - Code: events.AuthAttemptFailureCode, - }, - UserMetadata: apievents.UserMetadata{ - Login: appSession.GetUser(), - User: "unknown", - }, - Status: apievents.Status{ - Success: false, - Error: "Failed app access authentication: subject session token does not match", - }, - }), - }, - proxyAddrs: []utils.NetAddr{ - *utils.MustParseAddr(publicAddr), - }, - }, - { - desc: "invalid session", - headers: map[string]string{ - "Origin": "https://proxy.goteleport.com", - "X-Cookie-Value": "foobar", - "X-Subject-Cookie-Value": appSession.GetBearerToken(), - }, - sessionError: trace.NotFound("invalid session"), - outStatusCode: http.StatusForbidden, - eventChecks: []eventCheckFn{hasAuditEventCount(0)}, - proxyAddrs: []utils.NetAddr{ - *utils.MustParseAddr(publicAddr), - }, - }, - { - desc: "incorrect origin", - headers: map[string]string{ - "Origin": "https://incorrect.origin.com", - "X-Cookie-Value": "foobar", - "X-Subject-Cookie-Value": appSession.GetBearerToken(), - }, - outStatusCode: http.StatusForbidden, - eventChecks: []eventCheckFn{hasAuditEventCount(0)}, - proxyAddrs: []utils.NetAddr{ - *utils.MustParseAddr(publicAddr), - }, - }, - { - desc: "incorrect origin port", - headers: map[string]string{ - "Origin": "https://proxy.goteleport.com:3080", - "X-Cookie-Value": "foobar", - "X-Subject-Cookie-Value": appSession.GetBearerToken(), - }, - outStatusCode: http.StatusForbidden, - eventChecks: []eventCheckFn{hasAuditEventCount(0)}, - proxyAddrs: []utils.NetAddr{ - *utils.MustParseAddr(publicAddr), - }, - }, - } - - for _, test := range tests { - test := test - t.Run(test.desc, func(t *testing.T) { - t.Parallel() - - authClient := &mockAuthClient{ - sessionError: test.sessionError, - appSession: appSession, - } - - p := setup(t, fakeClock, authClient, nil, test.proxyAddrs) - - res := p.makeRequestWithHeaders(t, "/x-teleport-auth", test.headers) - - require.NoError(t, res.Body.Close()) - require.Equal(t, test.outStatusCode, res.StatusCode) - - var cookieValue string - var subjectCookieValue string - for _, cookie := range res.Cookies() { - if cookie.Name == CookieName { - cookieValue = cookie.Value - } - - if cookie.Name == SubjectCookieName { - subjectCookieValue = cookie.Value - } - } - - require.Equal(t, subjectCookieValue, test.subjectCookieValue) - require.Equal(t, cookieValue, test.cookieValue) - - for _, check := range test.eventChecks { - check(t, authClient.emittedEvents) - } - }) - } -} - -// DELETE IN 17.0 -func (p *testServer) makeRequestWithHeaders(t *testing.T, endpoint string, headers map[string]string) *http.Response { - u := url.URL{ - Scheme: p.serverURL.Scheme, - Host: p.serverURL.Host, - Path: endpoint, - } - req, err := http.NewRequest(http.MethodPost, u.String(), nil) - require.NoError(t, err) - - for key, value := range headers { - req.Header.Add(key, value) - } - - // Issue request. - client := &http.Client{ - Transport: &http.Transport{ - TLSClientConfig: &tls.Config{ - InsecureSkipVerify: true, - }, - }, - CheckRedirect: func(req *http.Request, via []*http.Request) error { - return http.ErrUseLastResponse - }, - } - - resp, err := client.Do(req) - require.NoError(t, err) - require.NoError(t, resp.Body.Close()) - - return resp -} - func TestHasName(t *testing.T) { for _, test := range []struct { desc string @@ -610,7 +354,7 @@ func TestMatchApplicationServers(t *testing.T) { server.Close() }) - p := setup(t, fakeClock, authClient, tunnel, nil) + p := setup(t, fakeClock, authClient, tunnel) status, content := p.makeRequest(t, "GET", "/", []byte{}, []http.Cookie{ { Name: CookieName, @@ -734,14 +478,13 @@ type testServer struct { serverURL *url.URL } -func setup(t *testing.T, clock clockwork.FakeClock, authClient auth.ClientI, proxyClient reversetunnelclient.Tunnel, proxyPublicAddrs []utils.NetAddr) *testServer { +func setup(t *testing.T, clock clockwork.FakeClock, authClient auth.ClientI, proxyClient reversetunnelclient.Tunnel) *testServer { appHandler, err := NewHandler(context.Background(), &HandlerConfig{ Clock: clock, AuthClient: authClient, AccessPoint: authClient, ProxyClient: proxyClient, CipherSuites: utils.DefaultCipherSuites(), - ProxyPublicAddrs: proxyPublicAddrs, IntegrationAppHandler: &mockIntegrationAppHandler{}, }) require.NoError(t, err) diff --git a/lib/web/app/middleware.go b/lib/web/app/middleware.go index 6735ae1d198a7..eff818212ab6b 100644 --- a/lib/web/app/middleware.go +++ b/lib/web/app/middleware.go @@ -19,17 +19,11 @@ package app import ( - "bytes" - "encoding/json" - "io" "net/http" - "net/url" - "strconv" "github.com/gravitational/trace" "github.com/julienschmidt/httprouter" - "github.com/gravitational/teleport" "github.com/gravitational/teleport/lib/utils" ) @@ -97,87 +91,6 @@ func (h *Handler) redirectToLauncher(w http.ResponseWriter, r *http.Request, p l return nil } -// DELETE IN 17.0 along with blocks of code that uses it. -// Kept for legacy app access. -func (h *Handler) withCustomCORS(handle routerFunc) routerFunc { - return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) error { - - // There can be two types of POST app launcher request. - // 1): legacy app access - // 2): new app access - // Legacy app access will send a POST request with an empty body. - if r.Method == http.MethodPost && r.Body != http.NoBody { - body, err := utils.ReadAtMost(r.Body, teleport.MaxHTTPRequestSize) - if err != nil { - return trace.Wrap(err) - } - - var req fragmentRequest - if err := json.Unmarshal(body, &req); err != nil { - h.log.Warn("Failed to decode JSON from request body") - return trace.AccessDenied("access denied") - } - // Replace the body with a new reader, allows re-reading the body. - // (the handler `completeAppAuthExchange` will also read the body) - r.Body = io.NopCloser(bytes.NewBuffer(body)) - - if req.CookieValue != "" && req.StateValue != "" && req.SubjectCookieValue != "" { - return h.completeAppAuthExchange(w, r, p) - } - - h.log.Warn("Missing fields from parsed JSON request body") - h.emitErrorEventAndDeleteAppSession(r, emitErrorEventFields{ - sessionID: req.CookieValue, - err: "missing required fields in JSON request body", - }) - return trace.AccessDenied("access denied") - } - - // Allow minimal CORS from only the proxy origin - // This allows for requests from the proxy to `POST` to `/x-teleport-auth` and only - // permits the headers `X-Cookie-Value` and `X-Subject-Cookie-Value`. - // This is for the web UI to post a request to the application to get the proper app session - // cookie set on the right application subdomain. - w.Header().Set("Access-Control-Allow-Methods", "POST") - w.Header().Set("Access-Control-Allow-Credentials", "true") - w.Header().Set("Access-Control-Allow-Headers", "X-Cookie-Value, X-Subject-Cookie-Value") - - // Validate that the origin for the request matches any of the public proxy addresses. - // This is instead of protecting via CORS headers, as that only supports a single domain. - originValue := r.Header.Get("Origin") - origin, err := url.Parse(originValue) - if err != nil { - return trace.BadParameter("malformed Origin header: %v", err) - } - - var match bool - originPort := origin.Port() - if originPort == "" { - originPort = "443" - } - - for _, addr := range h.c.ProxyPublicAddrs { - if strconv.Itoa(addr.Port(0)) == originPort && addr.Host() == origin.Hostname() { - match = true - break - } - } - - if !match { - return trace.AccessDenied("port or hostname did not match") - } - - // As we've already checked the origin matches a public proxy address, we can allow requests from that origin - // We do this dynamically as this header can only contain one value - w.Header().Set("Access-Control-Allow-Origin", originValue) - if handle != nil { - return handle(w, r, p) - } - - return nil - } -} - // makeRouterHandler creates a httprouter.Handle. func makeRouterHandler(handler routerFunc) httprouter.Handle { return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {