mirror of
				https://github.com/optim-enterprises-bv/vault.git
				synced 2025-10-30 18:17:55 +00:00 
			
		
		
		
	VAULT-28255: Fix namespaced redirects (#27660)
* handle namespaced events redirects * full test: * changelog * lint
This commit is contained in:
		
							
								
								
									
										3
									
								
								changelog/27660.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								changelog/27660.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -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. | ||||
| ``` | ||||
| @@ -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$`) | ||||
| ) | ||||
| @@ -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{} | ||||
| @@ -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 { | ||||
| @@ -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 | ||||
| @@ -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 | ||||
| 	} | ||||
|  | ||||
| @@ -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 | ||||
| 	} | ||||
|  | ||||
| @@ -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 | ||||
| 	} | ||||
|  | ||||
| @@ -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()) { | ||||
| @@ -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 { | ||||
| @@ -1131,8 +1145,13 @@ 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 { | ||||
| @@ -1140,6 +1159,11 @@ func respondStandby(core *vault.Core, w http.ResponseWriter, reqURL *url.URL) { | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	originalPath, ok := logical.ContextOriginalRequestPathValue(ctx) | ||||
| 	if ok { | ||||
| 		finalURL.Path = originalPath | ||||
| 	} | ||||
|  | ||||
| 	// Ensure there is a scheme, default to https | ||||
| 	if finalURL.Scheme == "" { | ||||
| 		finalURL.Scheme = "https" | ||||
| @@ -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/"):]) | ||||
| } | ||||
|   | ||||
| @@ -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, | ||||
|   | ||||
| @@ -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 | ||||
| @@ -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) | ||||
|   | ||||
| @@ -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 | ||||
| 		} | ||||
|  | ||||
| @@ -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 | ||||
| 		} | ||||
|  | ||||
| @@ -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 | ||||
| 		} | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 miagilepner
					miagilepner