-
Notifications
You must be signed in to change notification settings - Fork 56
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
go/network: refactored and updated connector networking feature
Move connector networking entirely into this repo, from the legacy data-plane-gatweay repo, and significantly retool it along the way to: * Improve latency and throughput of HTTP reverse-proxy cases, by allowing the reverse proxy to use multiple pooled connections built atop network proxy RPCs with reasonable idle timeouts. This improves concurrency as many HTTP/2 requests can be in flight at once, and improves latency to the end user by ammortizing connections to reduce aggregate TCP and TLS startup time. * Improve user-facing error experience around misconfigurations, by often assuming an HTTP protocol and yielding a more informative error. * Overhauling metrics that we collect. * Updating the authorization flow, laying groundwork for the UI to use the /authorize/user/task API (but not requiring it just yet).
- Loading branch information
1 parent
e912244
commit 7b659c6
Showing
9 changed files
with
972 additions
and
50 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
package network | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
"net/http" | ||
"net/url" | ||
|
||
pb "go.gazette.dev/core/broker/protocol" | ||
"google.golang.org/grpc/metadata" | ||
) | ||
|
||
// verifyAuthorization ensures the request has an authorization which | ||
// is valid for capability NETWORK_PROXY to `taskName`. | ||
func verifyAuthorization(req *http.Request, verifier pb.Verifier, taskName string) error { | ||
var bearer = req.Header.Get("authorization") | ||
if bearer != "" { | ||
// Pass. | ||
} else if cookie, err := req.Cookie(AuthCookieName); err == nil { | ||
bearer = fmt.Sprintf("Bearer %s", cookie.Value) | ||
} else { | ||
return errors.New("missing authorization") | ||
} | ||
|
||
var _, cancel, claims, err = verifier.Verify( | ||
metadata.NewIncomingContext( | ||
req.Context(), | ||
metadata.Pairs("authorization", bearer), | ||
), | ||
0, // TODO(johnny): Should be pf.Capability_NETWORK_PROXY. | ||
) | ||
if err != nil { | ||
return err | ||
} | ||
cancel() // We don't use the returned context. | ||
|
||
/* TODO(johnny): Inspect claims once UI is updated to use /authorize/user/task API. | ||
if !claims.Selector.Matches(pb.MustLabelSet( | ||
labels.TaskName, taskName, | ||
)) { | ||
return fmt.Errorf("invalid authorization for task %s (%s)", taskName, bearer) | ||
} | ||
*/ | ||
_ = claims | ||
|
||
return nil | ||
} | ||
|
||
// startAuthRedirect redirect an interactive user to the dashboard, which will | ||
// obtain a user task authorization and redirect back to us with it. | ||
func startAuthRedirect(w http.ResponseWriter, req *http.Request, err error, dashboard *url.URL, taskName string) { | ||
var query = make(url.Values) | ||
query.Add("orig_url", "https://"+req.Host+req.URL.Path) | ||
query.Add("task", taskName) | ||
query.Add("prefix", taskName) | ||
query.Add("err", err.Error()) // Informational. | ||
|
||
var target = dashboard.JoinPath("/data-plane-auth-req") | ||
target.RawQuery = query.Encode() | ||
|
||
http.Redirect(w, req, target.String(), http.StatusTemporaryRedirect) | ||
} | ||
|
||
// completeAuthRedirect handles path "/auth-redirect" as part of a redirect chain | ||
// back from the dashboard. It expects a token parameter, which is set as a cookie, | ||
// and an original URL which it in-turn redirects to. | ||
func completeAuthRedirect(w http.ResponseWriter, req *http.Request) { | ||
var params = req.URL.Query() | ||
|
||
var token = params.Get("token") | ||
if token == "" { | ||
http.Error(w, "URL is missing required `token` parameter", http.StatusBadRequest) | ||
return | ||
} | ||
var origUrl = params.Get("orig_url") | ||
if origUrl == "" { | ||
http.Error(w, "URL is missing required `orig_url` parameter", http.StatusBadRequest) | ||
return | ||
} | ||
|
||
var cookie = &http.Cookie{ | ||
Name: AuthCookieName, | ||
Value: token, | ||
Secure: true, | ||
HttpOnly: true, | ||
Path: "/", | ||
} | ||
http.SetCookie(w, cookie) | ||
|
||
http.Redirect(w, req, origUrl, http.StatusTemporaryRedirect) | ||
} | ||
|
||
func scrubProxyRequest(req *http.Request, public bool) { | ||
if _, ok := req.Header["User-Agent"]; !ok { | ||
req.Header.Set("User-Agent", "") // Omit auto-added User-Agent. | ||
} | ||
|
||
if public { | ||
return // All done. | ||
} | ||
|
||
// Scrub authentication token(s) from the request. | ||
req.Header.Del("Authorization") | ||
|
||
// There's no `DeleteCookie` function, so we parse them, delete them all, and | ||
// add them back in while filtering out the flow_auth cookie. | ||
var cookies = req.Cookies() | ||
req.Header.Del("Cookie") | ||
|
||
for _, cookie := range cookies { | ||
if cookie.Name != AuthCookieName { | ||
req.AddCookie(cookie) | ||
} | ||
} | ||
} | ||
|
||
// AuthCookieName is the name of the cookie that we use for passing the JWT for interactive logins. | ||
// It's name begins with '__Host-' in order to opt in to some additional security restrictions. | ||
// See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#cookie_prefixes | ||
const AuthCookieName = "__Host-flow_auth" |
Oops, something went wrong.