Add websocket browser_redirect_url config option

If an HTTP(S) request is received without the Upgrade: header, then if this new
option is set then we'll assume it's a browser and issue a redirect.

There's no flexibility based upon the passed in URL; this should help with
redirecting people to dashboards or other tooling.
This commit is contained in:
Phil Pennock
2020-10-23 20:44:34 -04:00
parent 773ac7fbd1
commit 8f0b61870a
2 changed files with 31 additions and 1 deletions

View File

@@ -263,6 +263,12 @@ type WebsocketOpts struct {
// The host:port to advertise to websocket clients in the cluster.
Advertise string
// If someone visits the NATS server with a browser, they will send a GET
// without the Upgrade header.
// For convenience, you can supply a URL to be used as an HTTP redirect,
// instead of erroring out.
BrowserRedirectURL string
// If no user name is provided when a client connects, will default to the
// matching user from the global list of users in `Options.Users`.
NoAuthUser string
@@ -3453,6 +3459,8 @@ func parseWebsocket(v interface{}, o *Options, errors *[]error, warnings *[]erro
o.Websocket.JWTCookie = mv.(string)
case "no_auth_user":
o.Websocket.NoAuthUser = mv.(string)
case "browser_redirect_url":
o.Websocket.BrowserRedirectURL = mv.(string)
default:
if !tk.IsUsedVariable() {
err := &unknownConfigFieldErr{

View File

@@ -529,7 +529,13 @@ func (s *Server) wsUpgrade(w http.ResponseWriter, r *http.Request) (*wsUpgradeRe
}
// Point 3.
if !wsHeaderContains(r.Header, "Upgrade", "websocket") {
return nil, wsReturnHTTPError(w, http.StatusBadRequest, "invalid value for header 'Upgrade'")
_, haveUpgrade := r.Header["Upgrade"]
if s.opts.Websocket.BrowserRedirectURL != "" && !haveUpgrade {
// We still have to return an error, to prevent the connection upgrade.
return nil, wsHTTPRedirectAndReturnError(w, s.opts.Websocket.BrowserRedirectURL)
} else {
return nil, wsReturnHTTPError(w, http.StatusBadRequest, "invalid value for header 'Upgrade'")
}
}
// Point 4.
if !wsHeaderContains(r.Header, "Connection", "Upgrade") {
@@ -645,6 +651,15 @@ func wsReturnHTTPError(w http.ResponseWriter, status int, reason string) error {
return err
}
// Send a redirect to a browser and return an error
func wsHTTPRedirectAndReturnError(w http.ResponseWriter, redirectURL string) error {
err := fmt.Errorf("not a websocket request, issued redirect")
w.Header().Set("Sec-Websocket-Version", "13")
w.Header().Set("Location", redirectURL)
http.Error(w, http.StatusText(http.StatusFound), http.StatusFound)
return err
}
// If the server is configured to accept any origin, then this function returns
// `nil` without checking if the Origin is present and valid.
// Otherwise, this will check that the Origin matches the same origine or
@@ -761,6 +776,13 @@ func validateWebsocketOptions(o *Options) error {
return fmt.Errorf("trusted operators or trusted keys configuration is required for JWT authentication via cookie %q", wo.JWTCookie)
}
}
// Ensure that we're not given random junk which might have us do
// non-sensical things later.
if wo.BrowserRedirectURL != "" {
if _, err := url.Parse(wo.BrowserRedirectURL); err != nil {
return fmt.Errorf("unable to parse get redirect URL: %v", err)
}
}
return nil
}