Skip to content

Commit

Permalink
VAULT-28255: Fix namespaced redirects (#27660)
Browse files Browse the repository at this point in the history
* handle namespaced events redirects

* full test:

* changelog

* lint
  • Loading branch information
miagilepner authored Jul 3, 2024
1 parent fc19a9c commit 9e299c2
Show file tree
Hide file tree
Showing 5 changed files with 55 additions and 22 deletions.
3 changes: 3 additions & 0 deletions changelog/27660.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:bug
core (enterprise): Fix HTTP redirects in namespaces to use the correct path and (in the case of event subscriptions) the correct URI scheme.
```
59 changes: 44 additions & 15 deletions http/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ var (
"/v1/sys/wrapping/wrap",
}
websocketRawPaths = []string{
"/v1/sys/events/subscribe",
"sys/events/subscribe",
}
oidcProtectedPathRegex = regexp.MustCompile(`^identity/oidc/provider/\w(([\w-.]+)?\w)?/userinfo$`)
)
Expand All @@ -128,9 +128,7 @@ func init() {
"!sys/storage/raft/snapshot-auto/config",
})
websocketPaths.AddPaths(websocketRawPaths)
for _, path := range websocketRawPaths {
alwaysRedirectPaths.AddPaths([]string{strings.TrimPrefix(path, "/v1/")})
}
alwaysRedirectPaths.AddPaths(websocketRawPaths)
}

type HandlerAnchor struct{}
Expand Down Expand Up @@ -434,7 +432,7 @@ func wrapGenericHandler(core *vault.Core, h http.Handler, props *vault.HandlerPr
} else if standby && !perfStandby {
// Standby nodes, not performance standbys, don't start plugins
// so registration can not happen, instead redirect to active
respondStandby(core, w, r.URL)
respondStandby(core, w, r)
cancelFunc()
return
} else {
Expand Down Expand Up @@ -909,7 +907,7 @@ func handleRequestForwarding(core *vault.Core, handler http.Handler) http.Handle
respondError(w, http.StatusBadRequest, err)
return
}
path := ns.TrimmedPath(r.URL.Path[len("/v1/"):])
path := trimPath(ns, r.URL.Path)
if !perfStandbyAlwaysForwardPaths.HasPath(path) && !alwaysRedirectPaths.HasPath(path) {
handler.ServeHTTP(w, r)
return
Expand Down Expand Up @@ -946,14 +944,14 @@ func handleRequestForwarding(core *vault.Core, handler http.Handler) http.Handle

func forwardRequest(core *vault.Core, w http.ResponseWriter, r *http.Request) {
if r.Header.Get(vault.IntNoForwardingHeaderName) != "" {
respondStandby(core, w, r.URL)
respondStandby(core, w, r)
return
}

if r.Header.Get(NoRequestForwardingHeaderName) != "" {
// Forwarding explicitly disabled, fall back to previous behavior
core.Logger().Debug("handleRequestForwarding: forwarding disabled by client request")
respondStandby(core, w, r.URL)
respondStandby(core, w, r)
return
}

Expand All @@ -962,10 +960,25 @@ func forwardRequest(core *vault.Core, w http.ResponseWriter, r *http.Request) {
respondError(w, http.StatusBadRequest, err)
return
}
path := ns.TrimmedPath(r.URL.Path[len("/v1/"):])
if alwaysRedirectPaths.HasPath(path) {
path := trimPath(ns, r.URL.Path)
redirect := alwaysRedirectPaths.HasPath(path)
// websocket paths are special, because they can contain a namespace
// in front of them. This isn't an issue on perf standbys where the
// namespace manager will know all the namespaces, so we will have
// already extracted it from the path. But regular standbys don't have
// knowledge of the namespaces, so we need
// to add an extra check
if !redirect && !core.PerfStandby() {
for _, websocketPath := range websocketRawPaths {
if strings.Contains(path, websocketPath) {
redirect = true
break
}
}
}
if redirect {
core.Logger().Trace("cannot forward request (path included in always redirect paths), falling back to redirection to standby")
respondStandby(core, w, r.URL)
respondStandby(core, w, r)
return
}

Expand All @@ -981,7 +994,7 @@ func forwardRequest(core *vault.Core, w http.ResponseWriter, r *http.Request) {
}

// Fall back to redirection
respondStandby(core, w, r.URL)
respondStandby(core, w, r)
return
}

Expand Down Expand Up @@ -1045,7 +1058,7 @@ func request(core *vault.Core, w http.ResponseWriter, rawReq *http.Request, r *l
return resp, false, false
}
if errwrap.Contains(err, consts.ErrStandby.Error()) {
respondStandby(core, w, rawReq.URL)
respondStandby(core, w, rawReq)
return resp, false, false
}
if err != nil && errwrap.Contains(err, logical.ErrPerfStandbyPleaseForward.Error()) {
Expand Down Expand Up @@ -1094,7 +1107,8 @@ func request(core *vault.Core, w http.ResponseWriter, rawReq *http.Request, r *l
}

// respondStandby is used to trigger a redirect in the case that this Vault is currently a hot standby
func respondStandby(core *vault.Core, w http.ResponseWriter, reqURL *url.URL) {
func respondStandby(core *vault.Core, w http.ResponseWriter, r *http.Request) {
reqURL := r.URL
// Request the leader address
_, redirectAddr, _, err := core.Leader()
if err != nil {
Expand Down Expand Up @@ -1131,15 +1145,25 @@ func respondStandby(core *vault.Core, w http.ResponseWriter, reqURL *url.URL) {
RawQuery: reqURL.RawQuery,
}

ctx := r.Context()
ns, err := namespace.FromContext(ctx)
if err != nil {
respondError(w, http.StatusBadRequest, err)
}
// WebSockets schemas are ws or wss
if websocketPaths.HasPath(reqURL.Path) {
if websocketPaths.HasPath(trimPath(ns, reqURL.Path)) {
if finalURL.Scheme == "http" {
finalURL.Scheme = "ws"
} else {
finalURL.Scheme = "wss"
}
}

originalPath, ok := logical.ContextOriginalRequestPathValue(ctx)
if ok {
finalURL.Path = originalPath
}

// Ensure there is a scheme, default to https
if finalURL.Scheme == "" {
finalURL.Scheme = "https"
Expand Down Expand Up @@ -1391,3 +1415,8 @@ func respondOIDCPermissionDenied(w http.ResponseWriter) {
enc := json.NewEncoder(w)
enc.Encode(oidcResponse)
}

// trimPath removes the /v1/ prefix and the namespace from the path
func trimPath(ns *namespace.Namespace, path string) string {
return ns.TrimmedPath(path[len("/v1/"):])
}
2 changes: 1 addition & 1 deletion http/help.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ func handleHelp(core *vault.Core, w http.ResponseWriter, r *http.Request) {
respondError(w, http.StatusNotFound, errors.New("Missing /v1/ prefix in path. Use vault path-help command to retrieve API help for paths"))
return
}
path := ns.TrimmedPath(r.URL.Path[len("/v1/"):])
path := trimPath(ns, r.URL.Path)

req := &logical.Request{
Operation: logical.HelpOperation,
Expand Down
7 changes: 4 additions & 3 deletions http/logical.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,7 @@ func buildLogicalRequestNoAuth(perfStandby bool, ra *vault.RouterAccess, w http.
if err != nil {
return nil, nil, http.StatusBadRequest, nil
}
path := ns.TrimmedPath(r.URL.Path[len("/v1/"):])

path := trimPath(ns, r.URL.Path)
var data map[string]interface{}
var origBody io.ReadCloser
var passHTTPReq bool
Expand Down Expand Up @@ -361,11 +360,13 @@ func handleLogicalInternal(core *vault.Core, injectDataIntoTopLevel bool, noForw
respondError(w, http.StatusInternalServerError, err)
return
}
trimmedPath := trimPath(ns, r.URL.Path)

nsPath := ns.Path
if ns.ID == namespace.RootNamespaceID {
nsPath = ""
}
if strings.HasPrefix(r.URL.Path, fmt.Sprintf("/v1/%ssys/events/subscribe/", nsPath)) {
if websocketPaths.HasPath(trimmedPath) {
handler := entHandleEventsSubscribe(core, req)
if handler != nil {
handler.ServeHTTP(w, r)
Expand Down
6 changes: 3 additions & 3 deletions http/sys_rekey.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ func handleSysRekeyInit(core *vault.Core, recovery bool) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
standby, _ := core.Standby()
if standby {
respondStandby(core, w, r.URL)
respondStandby(core, w, r)
return
}

Expand Down Expand Up @@ -155,7 +155,7 @@ func handleSysRekeyUpdate(core *vault.Core, recovery bool) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
standby, _ := core.Standby()
if standby {
respondStandby(core, w, r.URL)
respondStandby(core, w, r)
return
}

Expand Down Expand Up @@ -228,7 +228,7 @@ func handleSysRekeyVerify(core *vault.Core, recovery bool) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
standby, _ := core.Standby()
if standby {
respondStandby(core, w, r.URL)
respondStandby(core, w, r)
return
}

Expand Down

0 comments on commit 9e299c2

Please sign in to comment.