mirror of
https://github.com/hashicorp/vault.git
synced 2025-08-07 07:07:05 +02:00
Reorganize request handling code so that we don't touch storage until we have the stateLock. (#11835)
This commit is contained in:
parent
2168d91efb
commit
7679223da6
@ -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
|
// 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.
|
// return false from the second return value if it the prefix doesn't exist.
|
||||||
func stripPrefix(prefix, path string) (string, bool) {
|
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 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,
|
// 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.
|
// 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
|
// Attach the header value if we have it
|
||||||
token, fromAuthzHeader := getTokenFromReq(r)
|
token, fromAuthzHeader := getTokenFromReq(r)
|
||||||
if token != "" {
|
if token != "" {
|
||||||
@ -987,29 +968,7 @@ func requestAuth(core *vault.Core, r *http.Request, req *logical.Request) (*logi
|
|||||||
req.ClientTokenSource = logical.ClientTokenFromAuthzHeader
|
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 {
|
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} {
|
for _, r := range []*http.Request{rWithVault, rWithAuthorization} {
|
||||||
req := logical.TestRequest(t, logical.ReadOperation, "test/path")
|
req := logical.TestRequest(t, logical.ReadOperation, "test/path")
|
||||||
r = r.WithContext(rootCtx)
|
r = r.WithContext(rootCtx)
|
||||||
req, err = requestAuth(core, r, req)
|
requestAuth(r, req)
|
||||||
|
err = core.PopulateTokenEntry(rootCtx, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
}
|
}
|
||||||
@ -675,25 +676,14 @@ func TestHandler_requestAuth(t *testing.T) {
|
|||||||
}
|
}
|
||||||
req := logical.TestRequest(t, logical.ReadOperation, "test/path")
|
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 {
|
if err != nil {
|
||||||
t.Fatalf("expected no error, got %s", err)
|
t.Fatalf("expected no error, got %s", err)
|
||||||
}
|
}
|
||||||
if req.ClientToken != "" {
|
if req.ClientToken != "" {
|
||||||
t.Fatalf("client token should not be filled, got %s", 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) {
|
func TestHandler_getTokenFromReq(t *testing.T) {
|
||||||
|
13
http/help.go
13
http/help.go
@ -1,10 +1,8 @@
|
|||||||
package http
|
package http
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/hashicorp/errwrap"
|
|
||||||
"github.com/hashicorp/vault/helper/namespace"
|
"github.com/hashicorp/vault/helper/namespace"
|
||||||
"github.com/hashicorp/vault/sdk/logical"
|
"github.com/hashicorp/vault/sdk/logical"
|
||||||
"github.com/hashicorp/vault/vault"
|
"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/"):])
|
path := ns.TrimmedPath(r.URL.Path[len("/v1/"):])
|
||||||
|
|
||||||
req, err := requestAuth(core, r, &logical.Request{
|
req := &logical.Request{
|
||||||
Operation: logical.HelpOperation,
|
Operation: logical.HelpOperation,
|
||||||
Path: path,
|
Path: path,
|
||||||
Connection: getConnection(r),
|
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)
|
resp, err := core.HandleRequest(r.Context(), req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package http
|
package http
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net/http"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/hashicorp/vault/vault"
|
"github.com/hashicorp/vault/vault"
|
||||||
@ -12,7 +13,12 @@ func TestHelp(t *testing.T) {
|
|||||||
defer ln.Close()
|
defer ln.Close()
|
||||||
TestServerAuth(t, addr, token)
|
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{}
|
var actual map[string]interface{}
|
||||||
testResponseStatus(t, resp, 200)
|
testResponseStatus(t, resp, 200)
|
||||||
|
138
http/logical.go
138
http/logical.go
@ -12,7 +12,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/hashicorp/errwrap"
|
|
||||||
uuid "github.com/hashicorp/go-uuid"
|
uuid "github.com/hashicorp/go-uuid"
|
||||||
"github.com/hashicorp/vault/helper/namespace"
|
"github.com/hashicorp/vault/helper/namespace"
|
||||||
"github.com/hashicorp/vault/sdk/helper/consts"
|
"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
|
return nil, nil, status, err
|
||||||
}
|
}
|
||||||
|
|
||||||
rawRequired := r.Header.Values(VaultIndexHeaderName)
|
req.SetRequiredState(r.Header.Values(VaultIndexHeaderName))
|
||||||
if len(rawRequired) != 0 && core.MissingRequiredState(rawRequired) {
|
requestAuth(r, req)
|
||||||
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, err = requestWrapInfo(r, req)
|
req, err = requestWrapInfo(r, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -313,129 +302,6 @@ func handleLogicalInternal(core *vault.Core, injectDataIntoTopLevel bool, noForw
|
|||||||
return
|
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
|
// Make the internal request. We attach the connection info
|
||||||
// as well in case this is an authentication request that requires
|
// as well in case this is an authentication request that requires
|
||||||
// it. Vault core handles stripping this if we need to. This also
|
// it. Vault core handles stripping this if we need to. This also
|
||||||
|
@ -42,6 +42,11 @@ var (
|
|||||||
// unrecoverable error.
|
// unrecoverable error.
|
||||||
// e.g.: misconfigured or disconnected storage backend.
|
// e.g.: misconfigured or disconnected storage backend.
|
||||||
ErrUnrecoverable = errors.New("unrecoverable error")
|
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 {
|
type HTTPCodedError interface {
|
||||||
|
@ -205,6 +205,11 @@ type Request struct {
|
|||||||
// request that generated this logical.Request object.
|
// request that generated this logical.Request object.
|
||||||
ResponseWriter *HTTPResponseWriter `json:"-" sentinel:""`
|
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
|
// responseState is used internally to propagate the state that should appear
|
||||||
// in response headers; it's attached to the request rather than the response
|
// in response headers; it's attached to the request rather than the response
|
||||||
// because not all requests yields non-nil responses.
|
// because not all requests yields non-nil responses.
|
||||||
@ -273,6 +278,14 @@ func (r *Request) SetLastRemoteWAL(last uint64) {
|
|||||||
r.lastRemoteWAL = last
|
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 {
|
func (r *Request) ResponseState() *WALState {
|
||||||
return r.responseState
|
return r.responseState
|
||||||
}
|
}
|
||||||
|
@ -102,6 +102,8 @@ func RespondErrorCommon(req *Request, resp *Response, err error) (int, error) {
|
|||||||
statusCode = http.StatusBadRequest
|
statusCode = http.StatusBadRequest
|
||||||
case errwrap.Contains(err, ErrPermissionDenied.Error()):
|
case errwrap.Contains(err, ErrPermissionDenied.Error()):
|
||||||
statusCode = http.StatusForbidden
|
statusCode = http.StatusForbidden
|
||||||
|
case errwrap.Contains(err, consts.ErrInvalidWrappingToken.Error()):
|
||||||
|
statusCode = http.StatusBadRequest
|
||||||
case errwrap.Contains(err, ErrUnsupportedOperation.Error()):
|
case errwrap.Contains(err, ErrUnsupportedOperation.Error()):
|
||||||
statusCode = http.StatusMethodNotAllowed
|
statusCode = http.StatusMethodNotAllowed
|
||||||
case errwrap.Contains(err, ErrUnsupportedPath.Error()):
|
case errwrap.Contains(err, ErrUnsupportedPath.Error()):
|
||||||
@ -114,6 +116,8 @@ func RespondErrorCommon(req *Request, resp *Response, err error) (int, error) {
|
|||||||
statusCode = http.StatusTooManyRequests
|
statusCode = http.StatusTooManyRequests
|
||||||
case errwrap.Contains(err, ErrLeaseCountQuotaExceeded.Error()):
|
case errwrap.Contains(err, ErrLeaseCountQuotaExceeded.Error()):
|
||||||
statusCode = http.StatusTooManyRequests
|
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 {
|
if req == nil {
|
||||||
retErr = multierror.Append(retErr, errors.New("nil request to seal"))
|
return errors.New("nil request to seal")
|
||||||
return retErr
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Since there is no token store in standby nodes, sealing cannot be done.
|
// 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.
|
// same thing.
|
||||||
if c.standby {
|
if c.standby {
|
||||||
c.logger.Error("vault cannot seal when in standby mode; please restart instead")
|
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 errors.New("vault cannot seal when in standby mode; please restart instead")
|
||||||
return retErr
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
acl, te, entity, identityPolicies, err := c.fetchACLTokenEntryAndEntity(ctx, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
retErr = multierror.Append(retErr, err)
|
return err
|
||||||
return retErr
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Audit-log the request before going any further
|
// 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 {
|
if err := c.auditBroker.LogRequest(ctx, logInput, c.auditedHeaders); err != nil {
|
||||||
c.logger.Error("failed to audit request", "request_path", req.Path, "error", err)
|
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 errors.New("failed to audit request, cannot continue")
|
||||||
return retErr
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if entity != nil && entity.Disabled {
|
if entity != nil && entity.Disabled {
|
||||||
c.logger.Warn("permission denied as the entity on the token is disabled")
|
c.logger.Warn("permission denied as the entity on the token is disabled")
|
||||||
retErr = multierror.Append(retErr, logical.ErrPermissionDenied)
|
return logical.ErrPermissionDenied
|
||||||
return retErr
|
|
||||||
}
|
}
|
||||||
if te != nil && te.EntityID != "" && entity == nil {
|
if te != nil && te.EntityID != "" && entity == nil {
|
||||||
c.logger.Warn("permission denied as the entity on the token is invalid")
|
c.logger.Warn("permission denied as the entity on the token is invalid")
|
||||||
retErr = multierror.Append(retErr, logical.ErrPermissionDenied)
|
return logical.ErrPermissionDenied
|
||||||
return retErr
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attempt to use the token (decrement num_uses)
|
// 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)
|
te, err = c.tokenStore.UseToken(ctx, te)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.logger.Error("failed to use token", "error", err)
|
c.logger.Error("failed to use token", "error", err)
|
||||||
retErr = multierror.Append(retErr, ErrInternalError)
|
return ErrInternalError
|
||||||
return retErr
|
|
||||||
}
|
}
|
||||||
if te == nil {
|
if te == nil {
|
||||||
// Token is no longer valid
|
// Token is no longer valid
|
||||||
retErr = multierror.Append(retErr, logical.ErrPermissionDenied)
|
return logical.ErrPermissionDenied
|
||||||
return retErr
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -177,6 +177,6 @@ func (c *Core) AllowForwardingViaHeader() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Core) MissingRequiredState(raw []string) bool {
|
func (c *Core) MissingRequiredState(raw []string, perfStandby bool) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
40
vault/ha.go
40
vault/ha.go
@ -11,17 +11,17 @@ import (
|
|||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"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/certutil"
|
||||||
"github.com/hashicorp/vault/sdk/helper/consts"
|
"github.com/hashicorp/vault/sdk/helper/consts"
|
||||||
"github.com/hashicorp/vault/sdk/helper/jsonutil"
|
"github.com/hashicorp/vault/sdk/helper/jsonutil"
|
||||||
"github.com/hashicorp/vault/sdk/logical"
|
"github.com/hashicorp/vault/sdk/logical"
|
||||||
"github.com/hashicorp/vault/sdk/physical"
|
"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/hashicorp/vault/vault/seal"
|
||||||
"github.com/oklog/run"
|
"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())
|
defer metrics.MeasureSince([]string{"core", "step_down"}, time.Now())
|
||||||
|
|
||||||
if req == nil {
|
if req == nil {
|
||||||
retErr = multierror.Append(retErr, errors.New("nil request to step-down"))
|
return errors.New("nil request to step-down")
|
||||||
return retErr
|
|
||||||
}
|
}
|
||||||
|
|
||||||
c.stateLock.RLock()
|
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)
|
acl, te, entity, identityPolicies, err := c.fetchACLTokenEntryAndEntity(ctx, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
retErr = multierror.Append(retErr, err)
|
return err
|
||||||
return retErr
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Audit-log the request before going any further
|
// 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 {
|
if err := c.auditBroker.LogRequest(ctx, logInput, c.auditedHeaders); err != nil {
|
||||||
c.logger.Error("failed to audit request", "request_path", req.Path, "error", err)
|
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 errors.New("failed to audit request, cannot continue")
|
||||||
return retErr
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if entity != nil && entity.Disabled {
|
if entity != nil && entity.Disabled {
|
||||||
c.logger.Warn("permission denied as the entity on the token is disabled")
|
c.logger.Warn("permission denied as the entity on the token is disabled")
|
||||||
retErr = multierror.Append(retErr, logical.ErrPermissionDenied)
|
return logical.ErrPermissionDenied
|
||||||
return retErr
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if te != nil && te.EntityID != "" && entity == nil {
|
if te != nil && te.EntityID != "" && entity == nil {
|
||||||
c.logger.Warn("permission denied as the entity on the token is invalid")
|
c.logger.Warn("permission denied as the entity on the token is invalid")
|
||||||
retErr = multierror.Append(retErr, logical.ErrPermissionDenied)
|
return logical.ErrPermissionDenied
|
||||||
return retErr
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attempt to use the token (decrement num_uses)
|
// 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)
|
te, err = c.tokenStore.UseToken(ctx, te)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.logger.Error("failed to use token", "error", err)
|
c.logger.Error("failed to use token", "error", err)
|
||||||
retErr = multierror.Append(retErr, ErrInternalError)
|
return ErrInternalError
|
||||||
return retErr
|
|
||||||
}
|
}
|
||||||
if te == nil {
|
if te == nil {
|
||||||
// Token has been revoked
|
// Token has been revoked
|
||||||
retErr = multierror.Append(retErr, logical.ErrPermissionDenied)
|
return logical.ErrPermissionDenied
|
||||||
return retErr
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -421,16 +421,15 @@ func (c *Core) switchedLockHandleRequest(httpCtx context.Context, req *logical.R
|
|||||||
cancel()
|
cancel()
|
||||||
return nil, fmt.Errorf("could not parse namespace from http context: %w", err)
|
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)
|
req.SetTokenEntry(nil)
|
||||||
cancel()
|
cancel()
|
||||||
return resp, err
|
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
|
// Allowing writing to a path ending in / makes it extremely difficult to
|
||||||
// understand user intent for the filesystem-like backends (kv,
|
// understand user intent for the filesystem-like backends (kv,
|
||||||
// cubbyhole) -- did they want a key named foo/ or did they want to write
|
// 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()
|
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 != "" {
|
if !hasNamespaces(c) && ns.Path != "" {
|
||||||
return nil, logical.CodedError(403, "namespaces feature not enabled")
|
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
|
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
|
// 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
|
// populated in the logical request along with the client token. The accessor
|
||||||
// of the client token can get audit logged.
|
// 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) {
|
func (c *Core) LookupToken(ctx context.Context, token string) (*logical.TokenEntry, error) {
|
||||||
if c.Sealed() {
|
if c.Sealed() {
|
||||||
return nil, consts.ErrSealed
|
return nil, consts.ErrSealed
|
||||||
}
|
}
|
||||||
|
|
||||||
c.stateLock.RLock()
|
|
||||||
defer c.stateLock.RUnlock()
|
|
||||||
|
|
||||||
if c.standby && !c.perfStandby {
|
if c.standby && !c.perfStandby {
|
||||||
return nil, consts.ErrStandby
|
return nil, consts.ErrStandby
|
||||||
}
|
}
|
||||||
|
@ -334,7 +334,7 @@ DONELISTHANDLING:
|
|||||||
// validateWrappingToken checks whether a token is a wrapping token. The passed
|
// validateWrappingToken checks whether a token is a wrapping token. The passed
|
||||||
// in logical request will be updated if the wrapping token was provided within
|
// in logical request will be updated if the wrapping token was provided within
|
||||||
// a JWT token.
|
// 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 {
|
if req == nil {
|
||||||
return false, fmt.Errorf("invalid request")
|
return false, fmt.Errorf("invalid request")
|
||||||
}
|
}
|
||||||
@ -343,9 +343,6 @@ func (c *Core) ValidateWrappingToken(ctx context.Context, req *logical.Request)
|
|||||||
return false, consts.ErrSealed
|
return false, consts.ErrSealed
|
||||||
}
|
}
|
||||||
|
|
||||||
c.stateLock.RLock()
|
|
||||||
defer c.stateLock.RUnlock()
|
|
||||||
|
|
||||||
if c.standby && !c.perfStandby {
|
if c.standby && !c.perfStandby {
|
||||||
return false, consts.ErrStandby
|
return false, consts.ErrStandby
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user