mirror of
				https://github.com/optim-enterprises-bv/vault.git
				synced 2025-10-31 10:37:56 +00:00 
			
		
		
		
	Reorganize request handling code so that we don't touch storage until we have the stateLock. (#11835)
This commit is contained in:
		| @@ -449,25 +449,6 @@ func WrapForwardedForHandler(h http.Handler, l *configutil.Listener) http.Handle | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| // A lookup on a token that is about to expire returns nil, which means by the | ||||
| // time we can validate a wrapping token lookup will return nil since it will | ||||
| // be revoked after the call. So we have to do the validation here. | ||||
| func wrappingVerificationFunc(ctx context.Context, core *vault.Core, req *logical.Request) error { | ||||
| 	if req == nil { | ||||
| 		return fmt.Errorf("invalid request") | ||||
| 	} | ||||
|  | ||||
| 	valid, err := core.ValidateWrappingToken(ctx, req) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("error validating wrapping token: %w", err) | ||||
| 	} | ||||
| 	if !valid { | ||||
| 		return consts.ErrInvalidWrappingToken | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // stripPrefix is a helper to strip a prefix from the path. It will | ||||
| // return false from the second return value if it the prefix doesn't exist. | ||||
| func stripPrefix(prefix, path string) (string, bool) { | ||||
| @@ -731,7 +712,7 @@ func forwardBasedOnHeaders(core *vault.Core, r *http.Request) (bool, error) { | ||||
| 		return false, nil | ||||
| 	} | ||||
|  | ||||
| 	return core.MissingRequiredState(r.Header.Values(VaultIndexHeaderName)), nil | ||||
| 	return core.MissingRequiredState(r.Header.Values(VaultIndexHeaderName), core.PerfStandby()), nil | ||||
| } | ||||
|  | ||||
| // handleRequestForwarding determines whether to forward a request or not, | ||||
| @@ -977,7 +958,7 @@ func getTokenFromReq(r *http.Request) (string, bool) { | ||||
| } | ||||
|  | ||||
| // requestAuth adds the token to the logical.Request if it exists. | ||||
| func requestAuth(core *vault.Core, r *http.Request, req *logical.Request) (*logical.Request, error) { | ||||
| func requestAuth(r *http.Request, req *logical.Request) { | ||||
| 	// Attach the header value if we have it | ||||
| 	token, fromAuthzHeader := getTokenFromReq(r) | ||||
| 	if token != "" { | ||||
| @@ -987,29 +968,7 @@ func requestAuth(core *vault.Core, r *http.Request, req *logical.Request) (*logi | ||||
| 			req.ClientTokenSource = logical.ClientTokenFromAuthzHeader | ||||
| 		} | ||||
|  | ||||
| 		// Also attach the accessor if we have it. This doesn't fail if it | ||||
| 		// doesn't exist because the request may be to an unauthenticated | ||||
| 		// endpoint/login endpoint where a bad current token doesn't matter, or | ||||
| 		// a token from a Vault version pre-accessors. We ignore errors for | ||||
| 		// JWTs. | ||||
| 		te, err := core.LookupToken(r.Context(), token) | ||||
| 		if err != nil { | ||||
| 			dotCount := strings.Count(token, ".") | ||||
| 			// If we have two dots but the second char is a dot it's a vault | ||||
| 			// token of the form s.SOMETHING.nsid, not a JWT | ||||
| 			if dotCount != 2 || | ||||
| 				dotCount == 2 && token[1] == '.' { | ||||
| 				return req, err | ||||
| 	} | ||||
| 		} | ||||
| 		if err == nil && te != nil { | ||||
| 			req.ClientTokenAccessor = te.Accessor | ||||
| 			req.ClientTokenRemainingUses = te.NumUses | ||||
| 			req.SetTokenEntry(te) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return req, nil | ||||
| } | ||||
|  | ||||
| func requestPolicyOverride(r *http.Request, req *logical.Request) error { | ||||
|   | ||||
| @@ -650,7 +650,8 @@ func TestHandler_requestAuth(t *testing.T) { | ||||
| 	for _, r := range []*http.Request{rWithVault, rWithAuthorization} { | ||||
| 		req := logical.TestRequest(t, logical.ReadOperation, "test/path") | ||||
| 		r = r.WithContext(rootCtx) | ||||
| 		req, err = requestAuth(core, r, req) | ||||
| 		requestAuth(r, req) | ||||
| 		err = core.PopulateTokenEntry(rootCtx, req) | ||||
| 		if err != nil { | ||||
| 			t.Fatalf("err: %s", err) | ||||
| 		} | ||||
| @@ -675,25 +676,14 @@ func TestHandler_requestAuth(t *testing.T) { | ||||
| 	} | ||||
| 	req := logical.TestRequest(t, logical.ReadOperation, "test/path") | ||||
|  | ||||
| 	req, err = requestAuth(core, rNothing, req) | ||||
| 	requestAuth(rNothing, req) | ||||
| 	err = core.PopulateTokenEntry(rootCtx, req) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("expected no error, got %s", err) | ||||
| 	} | ||||
| 	if req.ClientToken != "" { | ||||
| 		t.Fatalf("client token should not be filled, got %s", req.ClientToken) | ||||
| 	} | ||||
|  | ||||
| 	rFragmentedHeader, err := http.NewRequest("GET", "v1/test/path", nil) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("err: %s", err) | ||||
| 	} | ||||
| 	rFragmentedHeader.Header.Set("Authorization", "Bearer something somewhat") | ||||
| 	req = logical.TestRequest(t, logical.ReadOperation, "test/path") | ||||
|  | ||||
| 	_, err = requestAuth(core, rFragmentedHeader, req) | ||||
| 	if err == nil { | ||||
| 		t.Fatalf("expected an error, got none") | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestHandler_getTokenFromReq(t *testing.T) { | ||||
|   | ||||
							
								
								
									
										13
									
								
								http/help.go
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								http/help.go
									
									
									
									
									
								
							| @@ -1,10 +1,8 @@ | ||||
| package http | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"net/http" | ||||
|  | ||||
| 	"github.com/hashicorp/errwrap" | ||||
| 	"github.com/hashicorp/vault/helper/namespace" | ||||
| 	"github.com/hashicorp/vault/sdk/logical" | ||||
| 	"github.com/hashicorp/vault/vault" | ||||
| @@ -35,19 +33,12 @@ func handleHelp(core *vault.Core, w http.ResponseWriter, r *http.Request) { | ||||
| 	} | ||||
| 	path := ns.TrimmedPath(r.URL.Path[len("/v1/"):]) | ||||
|  | ||||
| 	req, err := requestAuth(core, r, &logical.Request{ | ||||
| 	req := &logical.Request{ | ||||
| 		Operation:  logical.HelpOperation, | ||||
| 		Path:       path, | ||||
| 		Connection: getConnection(r), | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| 		if errwrap.Contains(err, logical.ErrPermissionDenied.Error()) { | ||||
| 			respondError(w, http.StatusForbidden, nil) | ||||
| 			return | ||||
| 		} | ||||
| 		respondError(w, http.StatusBadRequest, fmt.Errorf("error performing token check: %w", err)) | ||||
| 		return | ||||
| 	} | ||||
| 	requestAuth(r, req) | ||||
|  | ||||
| 	resp, err := core.HandleRequest(r.Context(), req) | ||||
| 	if err != nil { | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| package http | ||||
|  | ||||
| import ( | ||||
| 	"net/http" | ||||
| 	"testing" | ||||
|  | ||||
| 	"github.com/hashicorp/vault/vault" | ||||
| @@ -12,7 +13,12 @@ func TestHelp(t *testing.T) { | ||||
| 	defer ln.Close() | ||||
| 	TestServerAuth(t, addr, token) | ||||
|  | ||||
| 	resp := testHttpGet(t, token, addr+"/v1/sys/mounts?help=1") | ||||
| 	resp := testHttpGet(t, "", addr+"/v1/sys/mounts?help=1") | ||||
| 	if resp.StatusCode != http.StatusBadRequest { | ||||
| 		t.Fatal("expected bad request with no token") | ||||
| 	} | ||||
|  | ||||
| 	resp = testHttpGet(t, token, addr+"/v1/sys/mounts?help=1") | ||||
|  | ||||
| 	var actual map[string]interface{} | ||||
| 	testResponseStatus(t, resp, 200) | ||||
|   | ||||
							
								
								
									
										138
									
								
								http/logical.go
									
									
									
									
									
								
							
							
						
						
									
										138
									
								
								http/logical.go
									
									
									
									
									
								
							| @@ -12,7 +12,6 @@ import ( | ||||
| 	"strings" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/hashicorp/errwrap" | ||||
| 	uuid "github.com/hashicorp/go-uuid" | ||||
| 	"github.com/hashicorp/vault/helper/namespace" | ||||
| 	"github.com/hashicorp/vault/sdk/helper/consts" | ||||
| @@ -220,18 +219,8 @@ func buildLogicalRequest(core *vault.Core, w http.ResponseWriter, r *http.Reques | ||||
| 		return nil, nil, status, err | ||||
| 	} | ||||
|  | ||||
| 	rawRequired := r.Header.Values(VaultIndexHeaderName) | ||||
| 	if len(rawRequired) != 0 && core.MissingRequiredState(rawRequired) { | ||||
| 		return nil, nil, http.StatusPreconditionFailed, fmt.Errorf("required index state not present") | ||||
| 	} | ||||
|  | ||||
| 	req, err = requestAuth(core, r, req) | ||||
| 	if err != nil { | ||||
| 		if errwrap.Contains(err, logical.ErrPermissionDenied.Error()) { | ||||
| 			return nil, nil, http.StatusForbidden, nil | ||||
| 		} | ||||
| 		return nil, nil, http.StatusBadRequest, fmt.Errorf("error performing token check: %w", err) | ||||
| 	} | ||||
| 	req.SetRequiredState(r.Header.Values(VaultIndexHeaderName)) | ||||
| 	requestAuth(r, req) | ||||
|  | ||||
| 	req, err = requestWrapInfo(r, req) | ||||
| 	if err != nil { | ||||
| @@ -313,129 +302,6 @@ func handleLogicalInternal(core *vault.Core, injectDataIntoTopLevel bool, noForw | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		// Always forward requests that are using a limited use count token. | ||||
| 		if core.PerfStandby() && req.ClientTokenRemainingUses > 0 { | ||||
| 			// Prevent forwarding on local-only requests. | ||||
| 			if noForward { | ||||
| 				respondError(w, http.StatusBadRequest, vault.ErrCannotForwardLocalOnly) | ||||
| 				return | ||||
| 			} | ||||
| 			if origBody != nil { | ||||
| 				r.Body = origBody | ||||
| 			} | ||||
| 			forwardRequest(core, w, r) | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		// req.Path will be relative by this point. The prefix check is first | ||||
| 		// to fail faster if we're not in this situation since it's a hot path | ||||
| 		switch { | ||||
| 		case strings.HasPrefix(req.Path, "sys/wrapping/"), strings.HasPrefix(req.Path, "auth/token/"): | ||||
| 			// Get the token ns info; if we match the paths below we want to | ||||
| 			// swap in the token context (but keep the relative path) | ||||
| 			te := req.TokenEntry() | ||||
| 			newCtx := r.Context() | ||||
| 			if te != nil { | ||||
| 				ns, err := vault.NamespaceByID(newCtx, te.NamespaceID, core) | ||||
| 				if err != nil { | ||||
| 					core.Logger().Warn("error looking up namespace from the token's namespace ID", "error", err) | ||||
| 					respondError(w, http.StatusInternalServerError, err) | ||||
| 					return | ||||
| 				} | ||||
| 				if ns != nil { | ||||
| 					newCtx = namespace.ContextWithNamespace(newCtx, ns) | ||||
| 				} | ||||
| 			} | ||||
| 			switch req.Path { | ||||
| 			// Route the token wrapping request to its respective sys NS | ||||
| 			case "sys/wrapping/lookup", "sys/wrapping/rewrap", "sys/wrapping/unwrap": | ||||
| 				r = r.WithContext(newCtx) | ||||
| 				if err := wrappingVerificationFunc(r.Context(), core, req); err != nil { | ||||
| 					if errwrap.Contains(err, logical.ErrPermissionDenied.Error()) { | ||||
| 						respondError(w, http.StatusForbidden, err) | ||||
| 					} else { | ||||
| 						respondError(w, http.StatusBadRequest, err) | ||||
| 					} | ||||
| 					return | ||||
| 				} | ||||
|  | ||||
| 			// The -self paths have no meaning outside of the token NS, so | ||||
| 			// requests for these paths always go to the token NS | ||||
| 			case "auth/token/lookup-self", "auth/token/renew-self", "auth/token/revoke-self": | ||||
| 				r = r.WithContext(newCtx) | ||||
|  | ||||
| 			// For the following operations, we can set the proper namespace context | ||||
| 			// using the token's embedded nsID if a relative path was provided. Since | ||||
| 			// this is done at the HTTP layer, the operation will still be gated by | ||||
| 			// ACLs. | ||||
| 			case "auth/token/lookup", "auth/token/renew", "auth/token/revoke", "auth/token/revoke-orphan": | ||||
| 				token, ok := req.Data["token"] | ||||
| 				// If the token is not present (e.g. a bad request), break out and let the backend | ||||
| 				// handle the error | ||||
| 				if !ok { | ||||
| 					// If this is a token lookup request and if the token is not | ||||
| 					// explicitly provided, it will use the client token so we simply set | ||||
| 					// the context to the client token's context. | ||||
| 					if req.Path == "auth/token/lookup" { | ||||
| 						r = r.WithContext(newCtx) | ||||
| 					} | ||||
| 					break | ||||
| 				} | ||||
| 				_, nsID := namespace.SplitIDFromString(token.(string)) | ||||
| 				if nsID != "" { | ||||
| 					ns, err := vault.NamespaceByID(newCtx, nsID, core) | ||||
| 					if err != nil { | ||||
| 						core.Logger().Warn("error looking up namespace from the token's namespace ID", "error", err) | ||||
| 						respondError(w, http.StatusInternalServerError, err) | ||||
| 						return | ||||
| 					} | ||||
| 					if ns != nil { | ||||
| 						newCtx = namespace.ContextWithNamespace(newCtx, ns) | ||||
| 						r = r.WithContext(newCtx) | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 		// The following relative sys/leases/ paths handles re-routing requests | ||||
| 		// to the proper namespace using the lease ID on applicable paths. | ||||
| 		case strings.HasPrefix(req.Path, "sys/leases/"): | ||||
| 			switch req.Path { | ||||
| 			// For the following operations, we can set the proper namespace context | ||||
| 			// using the lease's embedded nsID if a relative path was provided. Since | ||||
| 			// this is done at the HTTP layer, the operation will still be gated by | ||||
| 			// ACLs. | ||||
| 			case "sys/leases/lookup", "sys/leases/renew", "sys/leases/revoke", "sys/leases/revoke-force": | ||||
| 				leaseID, ok := req.Data["lease_id"] | ||||
| 				// If lease ID is not present, break out and let the backend handle the error | ||||
| 				if !ok { | ||||
| 					break | ||||
| 				} | ||||
| 				_, nsID := namespace.SplitIDFromString(leaseID.(string)) | ||||
| 				if nsID != "" { | ||||
| 					newCtx := r.Context() | ||||
| 					ns, err := vault.NamespaceByID(newCtx, nsID, core) | ||||
| 					if err != nil { | ||||
| 						core.Logger().Warn("error looking up namespace from the lease's namespace ID", "error", err) | ||||
| 						respondError(w, http.StatusInternalServerError, err) | ||||
| 						return | ||||
| 					} | ||||
| 					if ns != nil { | ||||
| 						newCtx = namespace.ContextWithNamespace(newCtx, ns) | ||||
| 						r = r.WithContext(newCtx) | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 		// Prevent any metrics requests to be forwarded from a standby node. | ||||
| 		// Instead, we return an error since we cannot be sure if we have an | ||||
| 		// active token store to validate the provided token. | ||||
| 		case strings.HasPrefix(req.Path, "sys/metrics"): | ||||
| 			if isStandby, _ := core.Standby(); isStandby { | ||||
| 				respondError(w, http.StatusBadRequest, vault.ErrCannotForwardLocalOnly) | ||||
| 				return | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		// Make the internal request. We attach the connection info | ||||
| 		// as well in case this is an authentication request that requires | ||||
| 		// it. Vault core handles stripping this if we need to. This also | ||||
|   | ||||
| @@ -42,6 +42,11 @@ var ( | ||||
| 	// unrecoverable error. | ||||
| 	// e.g.: misconfigured or disconnected storage backend. | ||||
| 	ErrUnrecoverable = errors.New("unrecoverable error") | ||||
|  | ||||
| 	// ErrMissingRequiredState is returned when a request can't be satisfied | ||||
| 	// with the data in the local node's storage, based on the provided | ||||
| 	// X-Vault-Index request header. | ||||
| 	ErrMissingRequiredState = errors.New("required index state not present") | ||||
| ) | ||||
|  | ||||
| type HTTPCodedError interface { | ||||
|   | ||||
| @@ -205,6 +205,11 @@ type Request struct { | ||||
| 	// request that generated this logical.Request object. | ||||
| 	ResponseWriter *HTTPResponseWriter `json:"-" sentinel:""` | ||||
|  | ||||
| 	// requiredState is used internally to propagate the X-Vault-Index request | ||||
| 	// header to later levels of request processing that operate only on | ||||
| 	// logical.Request. | ||||
| 	requiredState []string | ||||
|  | ||||
| 	// responseState is used internally to propagate the state that should appear | ||||
| 	// in response headers; it's attached to the request rather than the response | ||||
| 	// because not all requests yields non-nil responses. | ||||
| @@ -273,6 +278,14 @@ func (r *Request) SetLastRemoteWAL(last uint64) { | ||||
| 	r.lastRemoteWAL = last | ||||
| } | ||||
|  | ||||
| func (r *Request) RequiredState() []string { | ||||
| 	return r.requiredState | ||||
| } | ||||
|  | ||||
| func (r *Request) SetRequiredState(state []string) { | ||||
| 	r.requiredState = state | ||||
| } | ||||
|  | ||||
| func (r *Request) ResponseState() *WALState { | ||||
| 	return r.responseState | ||||
| } | ||||
|   | ||||
| @@ -102,6 +102,8 @@ func RespondErrorCommon(req *Request, resp *Response, err error) (int, error) { | ||||
| 			statusCode = http.StatusBadRequest | ||||
| 		case errwrap.Contains(err, ErrPermissionDenied.Error()): | ||||
| 			statusCode = http.StatusForbidden | ||||
| 		case errwrap.Contains(err, consts.ErrInvalidWrappingToken.Error()): | ||||
| 			statusCode = http.StatusBadRequest | ||||
| 		case errwrap.Contains(err, ErrUnsupportedOperation.Error()): | ||||
| 			statusCode = http.StatusMethodNotAllowed | ||||
| 		case errwrap.Contains(err, ErrUnsupportedPath.Error()): | ||||
| @@ -114,6 +116,8 @@ func RespondErrorCommon(req *Request, resp *Response, err error) (int, error) { | ||||
| 			statusCode = http.StatusTooManyRequests | ||||
| 		case errwrap.Contains(err, ErrLeaseCountQuotaExceeded.Error()): | ||||
| 			statusCode = http.StatusTooManyRequests | ||||
| 		case errwrap.Contains(err, ErrMissingRequiredState.Error()): | ||||
| 			statusCode = http.StatusPreconditionFailed | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
|   | ||||
| @@ -1673,8 +1673,7 @@ func (c *Core) sealInitCommon(ctx context.Context, req *logical.Request) (retErr | ||||
| 	}() | ||||
|  | ||||
| 	if req == nil { | ||||
| 		retErr = multierror.Append(retErr, errors.New("nil request to seal")) | ||||
| 		return retErr | ||||
| 		return errors.New("nil request to seal") | ||||
| 	} | ||||
|  | ||||
| 	// Since there is no token store in standby nodes, sealing cannot be done. | ||||
| @@ -1684,14 +1683,19 @@ func (c *Core) sealInitCommon(ctx context.Context, req *logical.Request) (retErr | ||||
| 	// same thing. | ||||
| 	if c.standby { | ||||
| 		c.logger.Error("vault cannot seal when in standby mode; please restart instead") | ||||
| 		retErr = multierror.Append(retErr, errors.New("vault cannot seal when in standby mode; please restart instead")) | ||||
| 		return retErr | ||||
| 		return errors.New("vault cannot seal when in standby mode; please restart instead") | ||||
| 	} | ||||
|  | ||||
| 	err := c.PopulateTokenEntry(ctx, req) | ||||
| 	if err != nil { | ||||
| 		if errwrap.Contains(err, logical.ErrPermissionDenied.Error()) { | ||||
| 			return logical.ErrPermissionDenied | ||||
| 		} | ||||
| 		return logical.ErrInvalidRequest | ||||
| 	} | ||||
| 	acl, te, entity, identityPolicies, err := c.fetchACLTokenEntryAndEntity(ctx, req) | ||||
| 	if err != nil { | ||||
| 		retErr = multierror.Append(retErr, err) | ||||
| 		return retErr | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	// Audit-log the request before going any further | ||||
| @@ -1717,19 +1721,16 @@ func (c *Core) sealInitCommon(ctx context.Context, req *logical.Request) (retErr | ||||
| 	} | ||||
| 	if err := c.auditBroker.LogRequest(ctx, logInput, c.auditedHeaders); err != nil { | ||||
| 		c.logger.Error("failed to audit request", "request_path", req.Path, "error", err) | ||||
| 		retErr = multierror.Append(retErr, errors.New("failed to audit request, cannot continue")) | ||||
| 		return retErr | ||||
| 		return errors.New("failed to audit request, cannot continue") | ||||
| 	} | ||||
|  | ||||
| 	if entity != nil && entity.Disabled { | ||||
| 		c.logger.Warn("permission denied as the entity on the token is disabled") | ||||
| 		retErr = multierror.Append(retErr, logical.ErrPermissionDenied) | ||||
| 		return retErr | ||||
| 		return logical.ErrPermissionDenied | ||||
| 	} | ||||
| 	if te != nil && te.EntityID != "" && entity == nil { | ||||
| 		c.logger.Warn("permission denied as the entity on the token is invalid") | ||||
| 		retErr = multierror.Append(retErr, logical.ErrPermissionDenied) | ||||
| 		return retErr | ||||
| 		return logical.ErrPermissionDenied | ||||
| 	} | ||||
|  | ||||
| 	// Attempt to use the token (decrement num_uses) | ||||
| @@ -1738,13 +1739,11 @@ func (c *Core) sealInitCommon(ctx context.Context, req *logical.Request) (retErr | ||||
| 		te, err = c.tokenStore.UseToken(ctx, te) | ||||
| 		if err != nil { | ||||
| 			c.logger.Error("failed to use token", "error", err) | ||||
| 			retErr = multierror.Append(retErr, ErrInternalError) | ||||
| 			return retErr | ||||
| 			return ErrInternalError | ||||
| 		} | ||||
| 		if te == nil { | ||||
| 			// Token is no longer valid | ||||
| 			retErr = multierror.Append(retErr, logical.ErrPermissionDenied) | ||||
| 			return retErr | ||||
| 			return logical.ErrPermissionDenied | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
|   | ||||
| @@ -177,6 +177,6 @@ func (c *Core) AllowForwardingViaHeader() bool { | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| func (c *Core) MissingRequiredState(raw []string) bool { | ||||
| func (c *Core) MissingRequiredState(raw []string, perfStandby bool) bool { | ||||
| 	return false | ||||
| } | ||||
|   | ||||
							
								
								
									
										40
									
								
								vault/ha.go
									
									
									
									
									
								
							
							
						
						
									
										40
									
								
								vault/ha.go
									
									
									
									
									
								
							| @@ -11,17 +11,17 @@ import ( | ||||
| 	"sync/atomic" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/armon/go-metrics" | ||||
| 	"github.com/hashicorp/errwrap" | ||||
| 	aeadwrapper "github.com/hashicorp/go-kms-wrapping/wrappers/aead" | ||||
| 	"github.com/hashicorp/go-multierror" | ||||
| 	"github.com/hashicorp/go-uuid" | ||||
| 	"github.com/hashicorp/vault/helper/namespace" | ||||
| 	"github.com/hashicorp/vault/sdk/helper/certutil" | ||||
| 	"github.com/hashicorp/vault/sdk/helper/consts" | ||||
| 	"github.com/hashicorp/vault/sdk/helper/jsonutil" | ||||
| 	"github.com/hashicorp/vault/sdk/logical" | ||||
| 	"github.com/hashicorp/vault/sdk/physical" | ||||
|  | ||||
| 	"github.com/armon/go-metrics" | ||||
| 	aeadwrapper "github.com/hashicorp/go-kms-wrapping/wrappers/aead" | ||||
| 	"github.com/hashicorp/go-multierror" | ||||
| 	"github.com/hashicorp/go-uuid" | ||||
| 	"github.com/hashicorp/vault/helper/namespace" | ||||
| 	"github.com/hashicorp/vault/vault/seal" | ||||
| 	"github.com/oklog/run" | ||||
| ) | ||||
| @@ -218,8 +218,7 @@ func (c *Core) StepDown(httpCtx context.Context, req *logical.Request) (retErr e | ||||
| 	defer metrics.MeasureSince([]string{"core", "step_down"}, time.Now()) | ||||
|  | ||||
| 	if req == nil { | ||||
| 		retErr = multierror.Append(retErr, errors.New("nil request to step-down")) | ||||
| 		return retErr | ||||
| 		return errors.New("nil request to step-down") | ||||
| 	} | ||||
|  | ||||
| 	c.stateLock.RLock() | ||||
| @@ -243,10 +242,16 @@ func (c *Core) StepDown(httpCtx context.Context, req *logical.Request) (retErr e | ||||
| 		} | ||||
| 	}() | ||||
|  | ||||
| 	err := c.PopulateTokenEntry(ctx, req) | ||||
| 	if err != nil { | ||||
| 		if errwrap.Contains(err, logical.ErrPermissionDenied.Error()) { | ||||
| 			return logical.ErrPermissionDenied | ||||
| 		} | ||||
| 		return logical.ErrInvalidRequest | ||||
| 	} | ||||
| 	acl, te, entity, identityPolicies, err := c.fetchACLTokenEntryAndEntity(ctx, req) | ||||
| 	if err != nil { | ||||
| 		retErr = multierror.Append(retErr, err) | ||||
| 		return retErr | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	// Audit-log the request before going any further | ||||
| @@ -272,20 +277,17 @@ func (c *Core) StepDown(httpCtx context.Context, req *logical.Request) (retErr e | ||||
| 	} | ||||
| 	if err := c.auditBroker.LogRequest(ctx, logInput, c.auditedHeaders); err != nil { | ||||
| 		c.logger.Error("failed to audit request", "request_path", req.Path, "error", err) | ||||
| 		retErr = multierror.Append(retErr, errors.New("failed to audit request, cannot continue")) | ||||
| 		return retErr | ||||
| 		return errors.New("failed to audit request, cannot continue") | ||||
| 	} | ||||
|  | ||||
| 	if entity != nil && entity.Disabled { | ||||
| 		c.logger.Warn("permission denied as the entity on the token is disabled") | ||||
| 		retErr = multierror.Append(retErr, logical.ErrPermissionDenied) | ||||
| 		return retErr | ||||
| 		return logical.ErrPermissionDenied | ||||
| 	} | ||||
|  | ||||
| 	if te != nil && te.EntityID != "" && entity == nil { | ||||
| 		c.logger.Warn("permission denied as the entity on the token is invalid") | ||||
| 		retErr = multierror.Append(retErr, logical.ErrPermissionDenied) | ||||
| 		return retErr | ||||
| 		return logical.ErrPermissionDenied | ||||
| 	} | ||||
|  | ||||
| 	// Attempt to use the token (decrement num_uses) | ||||
| @@ -293,13 +295,11 @@ func (c *Core) StepDown(httpCtx context.Context, req *logical.Request) (retErr e | ||||
| 		te, err = c.tokenStore.UseToken(ctx, te) | ||||
| 		if err != nil { | ||||
| 			c.logger.Error("failed to use token", "error", err) | ||||
| 			retErr = multierror.Append(retErr, ErrInternalError) | ||||
| 			return retErr | ||||
| 			return ErrInternalError | ||||
| 		} | ||||
| 		if te == nil { | ||||
| 			// Token has been revoked | ||||
| 			retErr = multierror.Append(retErr, logical.ErrPermissionDenied) | ||||
| 			return retErr | ||||
| 			return logical.ErrPermissionDenied | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
|   | ||||
| @@ -421,16 +421,15 @@ func (c *Core) switchedLockHandleRequest(httpCtx context.Context, req *logical.R | ||||
| 		cancel() | ||||
| 		return nil, fmt.Errorf("could not parse namespace from http context: %w", err) | ||||
| 	} | ||||
| 	ctx = namespace.ContextWithNamespace(ctx, ns) | ||||
|  | ||||
| 	resp, err = c.handleCancelableRequest(ctx, ns, req) | ||||
| 	resp, err = c.handleCancelableRequest(namespace.ContextWithNamespace(ctx, ns), req) | ||||
|  | ||||
| 	req.SetTokenEntry(nil) | ||||
| 	cancel() | ||||
| 	return resp, err | ||||
| } | ||||
|  | ||||
| func (c *Core) handleCancelableRequest(ctx context.Context, ns *namespace.Namespace, req *logical.Request) (resp *logical.Response, err error) { | ||||
| func (c *Core) handleCancelableRequest(ctx context.Context, req *logical.Request) (resp *logical.Response, err error) { | ||||
| 	// Allowing writing to a path ending in / makes it extremely difficult to | ||||
| 	// understand user intent for the filesystem-like backends (kv, | ||||
| 	// cubbyhole) -- did they want a key named foo/ or did they want to write | ||||
| @@ -453,6 +452,133 @@ func (c *Core) handleCancelableRequest(ctx context.Context, ns *namespace.Namesp | ||||
| 		defer waitGroup.Done() | ||||
| 	} | ||||
|  | ||||
| 	if c.MissingRequiredState(req.RequiredState(), c.perfStandby) { | ||||
| 		return nil, logical.ErrMissingRequiredState | ||||
| 	} | ||||
|  | ||||
| 	err = c.PopulateTokenEntry(ctx, req) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	// Always forward requests that are using a limited use count token. | ||||
| 	if c.perfStandby && req.ClientTokenRemainingUses > 0 { | ||||
| 		// Prevent forwarding on local-only requests. | ||||
| 		return nil, logical.ErrPerfStandbyPleaseForward | ||||
| 	} | ||||
|  | ||||
| 	ns, err := namespace.FromContext(ctx) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("could not parse namespace from http context: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	// req.Path will be relative by this point. The prefix check is first | ||||
| 	// to fail faster if we're not in this situation since it's a hot path | ||||
| 	switch { | ||||
| 	case strings.HasPrefix(req.Path, "sys/wrapping/"), strings.HasPrefix(req.Path, "auth/token/"): | ||||
| 		// Get the token ns info; if we match the paths below we want to | ||||
| 		// swap in the token context (but keep the relative path) | ||||
| 		te := req.TokenEntry() | ||||
| 		newCtx := ctx | ||||
| 		if te != nil { | ||||
| 			ns, err := NamespaceByID(ctx, te.NamespaceID, c) | ||||
| 			if err != nil { | ||||
| 				c.Logger().Warn("error looking up namespace from the token's namespace ID", "error", err) | ||||
| 				return nil, err | ||||
| 			} | ||||
| 			if ns != nil { | ||||
| 				newCtx = namespace.ContextWithNamespace(ctx, ns) | ||||
| 			} | ||||
| 		} | ||||
| 		switch req.Path { | ||||
| 		// Route the token wrapping request to its respective sys NS | ||||
| 		case "sys/wrapping/lookup", "sys/wrapping/rewrap", "sys/wrapping/unwrap": | ||||
| 			ctx = newCtx | ||||
| 			// A lookup on a token that is about to expire returns nil, which means by the | ||||
| 			// time we can validate a wrapping token lookup will return nil since it will | ||||
| 			// be revoked after the call. So we have to do the validation here. | ||||
| 			valid, err := c.validateWrappingToken(ctx, req) | ||||
| 			if err != nil { | ||||
| 				return nil, fmt.Errorf("error validating wrapping token: %w", err) | ||||
| 			} | ||||
| 			if !valid { | ||||
| 				return nil, consts.ErrInvalidWrappingToken | ||||
| 			} | ||||
|  | ||||
| 		// The -self paths have no meaning outside of the token NS, so | ||||
| 		// requests for these paths always go to the token NS | ||||
| 		case "auth/token/lookup-self", "auth/token/renew-self", "auth/token/revoke-self": | ||||
| 			ctx = newCtx | ||||
|  | ||||
| 		// For the following operations, we can set the proper namespace context | ||||
| 		// using the token's embedded nsID if a relative path was provided. | ||||
| 		// The operation will still be gated by ACLs, which are checked later. | ||||
| 		case "auth/token/lookup", "auth/token/renew", "auth/token/revoke", "auth/token/revoke-orphan": | ||||
| 			token, ok := req.Data["token"] | ||||
| 			// If the token is not present (e.g. a bad request), break out and let the backend | ||||
| 			// handle the error | ||||
| 			if !ok { | ||||
| 				// If this is a token lookup request and if the token is not | ||||
| 				// explicitly provided, it will use the client token so we simply set | ||||
| 				// the context to the client token's context. | ||||
| 				if req.Path == "auth/token/lookup" { | ||||
| 					ctx = newCtx | ||||
| 				} | ||||
| 				break | ||||
| 			} | ||||
| 			_, nsID := namespace.SplitIDFromString(token.(string)) | ||||
| 			if nsID != "" { | ||||
| 				ns, err := NamespaceByID(ctx, nsID, c) | ||||
| 				if err != nil { | ||||
| 					c.Logger().Warn("error looking up namespace from the token's namespace ID", "error", err) | ||||
| 					return nil, err | ||||
| 				} | ||||
| 				if ns != nil { | ||||
| 					ctx = namespace.ContextWithNamespace(ctx, ns) | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 	// The following relative sys/leases/ paths handles re-routing requests | ||||
| 	// to the proper namespace using the lease ID on applicable paths. | ||||
| 	case strings.HasPrefix(req.Path, "sys/leases/"): | ||||
| 		switch req.Path { | ||||
| 		// For the following operations, we can set the proper namespace context | ||||
| 		// using the lease's embedded nsID if a relative path was provided. | ||||
| 		// The operation will still be gated by ACLs, which are checked later. | ||||
| 		case "sys/leases/lookup", "sys/leases/renew", "sys/leases/revoke", "sys/leases/revoke-force": | ||||
| 			leaseID, ok := req.Data["lease_id"] | ||||
| 			// If lease ID is not present, break out and let the backend handle the error | ||||
| 			if !ok { | ||||
| 				break | ||||
| 			} | ||||
| 			_, nsID := namespace.SplitIDFromString(leaseID.(string)) | ||||
| 			if nsID != "" { | ||||
| 				ns, err := NamespaceByID(ctx, nsID, c) | ||||
| 				if err != nil { | ||||
| 					c.Logger().Warn("error looking up namespace from the lease's namespace ID", "error", err) | ||||
| 					return nil, err | ||||
| 				} | ||||
| 				if ns != nil { | ||||
| 					ctx = namespace.ContextWithNamespace(ctx, ns) | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 	// Prevent any metrics requests to be forwarded from a standby node. | ||||
| 	// Instead, we return an error since we cannot be sure if we have an | ||||
| 	// active token store to validate the provided token. | ||||
| 	case strings.HasPrefix(req.Path, "sys/metrics"): | ||||
| 		if c.standby && !c.perfStandby { | ||||
| 			return nil, ErrCannotForwardLocalOnly | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	ns, err = namespace.FromContext(ctx) | ||||
| 	if err != nil { | ||||
| 		return nil, errwrap.Wrapf("could not parse namespace from http context: {{err}}", err) | ||||
| 	} | ||||
|  | ||||
| 	if !hasNamespaces(c) && ns.Path != "" { | ||||
| 		return nil, logical.CodedError(403, "namespaces feature not enabled") | ||||
| 	} | ||||
| @@ -1368,3 +1494,35 @@ func (c *Core) RegisterAuth(ctx context.Context, tokenTTL time.Duration, path st | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // PopulateTokenEntry looks up req.ClientToken in the token store and uses | ||||
| // it to set other fields in req.  Does nothing if ClientToken is empty | ||||
| // or a JWT token, or for service tokens that don't exist in the token store. | ||||
| // Should be called with read stateLock held. | ||||
| func (c *Core) PopulateTokenEntry(ctx context.Context, req *logical.Request) error { | ||||
| 	if req.ClientToken == "" { | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	// Also attach the accessor if we have it. This doesn't fail if it | ||||
| 	// doesn't exist because the request may be to an unauthenticated | ||||
| 	// endpoint/login endpoint where a bad current token doesn't matter, or | ||||
| 	// a token from a Vault version pre-accessors. We ignore errors for | ||||
| 	// JWTs. | ||||
| 	token := req.ClientToken | ||||
| 	te, err := c.LookupToken(ctx, token) | ||||
| 	if err != nil { | ||||
| 		dotCount := strings.Count(token, ".") | ||||
| 		// If we have two dots but the second char is a dot it's a vault | ||||
| 		// token of the form s.SOMETHING.nsid, not a JWT | ||||
| 		if dotCount != 2 || (dotCount == 2 && token[1] == '.') { | ||||
| 			return fmt.Errorf("error performing token check: %w", err) | ||||
| 		} | ||||
| 	} | ||||
| 	if err == nil && te != nil { | ||||
| 		req.ClientTokenAccessor = te.Accessor | ||||
| 		req.ClientTokenRemainingUses = te.NumUses | ||||
| 		req.SetTokenEntry(te) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|   | ||||
| @@ -452,14 +452,13 @@ func (ts *TokenStore) paths() []*framework.Path { | ||||
| // is particularly useful to fetch the accessor of the client token and get it | ||||
| // populated in the logical request along with the client token. The accessor | ||||
| // of the client token can get audit logged. | ||||
| // | ||||
| // Should be called with read stateLock held. | ||||
| func (c *Core) LookupToken(ctx context.Context, token string) (*logical.TokenEntry, error) { | ||||
| 	if c.Sealed() { | ||||
| 		return nil, consts.ErrSealed | ||||
| 	} | ||||
|  | ||||
| 	c.stateLock.RLock() | ||||
| 	defer c.stateLock.RUnlock() | ||||
|  | ||||
| 	if c.standby && !c.perfStandby { | ||||
| 		return nil, consts.ErrStandby | ||||
| 	} | ||||
|   | ||||
| @@ -334,7 +334,7 @@ DONELISTHANDLING: | ||||
| // validateWrappingToken checks whether a token is a wrapping token. The passed | ||||
| // in logical request will be updated if the wrapping token was provided within | ||||
| // a JWT token. | ||||
| func (c *Core) ValidateWrappingToken(ctx context.Context, req *logical.Request) (valid bool, err error) { | ||||
| func (c *Core) validateWrappingToken(ctx context.Context, req *logical.Request) (valid bool, err error) { | ||||
| 	if req == nil { | ||||
| 		return false, fmt.Errorf("invalid request") | ||||
| 	} | ||||
| @@ -343,9 +343,6 @@ func (c *Core) ValidateWrappingToken(ctx context.Context, req *logical.Request) | ||||
| 		return false, consts.ErrSealed | ||||
| 	} | ||||
|  | ||||
| 	c.stateLock.RLock() | ||||
| 	defer c.stateLock.RUnlock() | ||||
|  | ||||
| 	if c.standby && !c.perfStandby { | ||||
| 		return false, consts.ErrStandby | ||||
| 	} | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Nick Cabatoff
					Nick Cabatoff