ACL error improvements: incomplete bootstrapping and non-existent token (#16105)

* add bootstrapping detail for acl errors

* error detail improvements

* update acl bootstrapping test coverage

* update namespace errors

* update test coverage

* add changelog

* update message for unbootstrapped error

* consolidate error message code and update changelog

* logout message change
This commit is contained in:
skpratt 2023-02-08 17:49:44 -06:00 committed by GitHub
parent ad7b71dbd7
commit 6f0b226b0d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 230 additions and 74 deletions

11
.changelog/16105.txt Normal file
View File

@ -0,0 +1,11 @@
```release-note:breaking-change
acl errors: Delete and get requests now return descriptive errors when the specified resource cannot be found. Other ACL request errors provide more information about when a resource is missing. Add error for when the ACL system has not been bootstrapped.
1. Delete Token/Policy/AuthMethod/Role/BindingRule endpoints now return 404 when the resource cannot be found.
- New error formats: "Requested * does not exist: ACL not found", "* not found in namespace $NAMESPACE: ACL not found"
3. Read Token/Policy/Role endpoints now return 404 when the resource cannot be found.
- New error format: "Cannot find * to delete"
4. Logout now returns a 401 error when the supplied token cannot be found
- New error format: "Supplied token does not exist"
5. Token Self endpoint now returns 404 when the token cannot be found.
- New error format: "Supplied token does not exist"
```

View File

@ -140,3 +140,10 @@ func extractAccessorID(authz interface{}) string {
} }
return accessor return accessor
} }
func ACLResourceNotExistError(resourceType string, entMeta EnterpriseMeta) error {
if ns := entMeta.NamespaceOrEmpty(); ns != "" {
return fmt.Errorf("Requested %s not found in namespace %s: %w", resourceType, ns, ErrNotFound)
}
return fmt.Errorf("Requested %s does not exist: %w", resourceType, ErrNotFound)
}

View File

@ -162,12 +162,15 @@ func (s *HTTPHandlers) ACLPolicyRead(resp http.ResponseWriter, req *http.Request
var out structs.ACLPolicyResponse var out structs.ACLPolicyResponse
defer setMeta(resp, &out.QueryMeta) defer setMeta(resp, &out.QueryMeta)
if err := s.agent.RPC(req.Context(), "ACL.PolicyRead", &args, &out); err != nil { if err := s.agent.RPC(req.Context(), "ACL.PolicyRead", &args, &out); err != nil {
// should return permission denied error if missing permissions
return nil, err return nil, err
} }
if out.Policy == nil { if out.Policy == nil {
// TODO(rb): should this return a normal 404? // if no error was returned above, the policy does not exist
return nil, acl.ErrNotFound resp.WriteHeader(http.StatusNotFound)
msg := acl.ACLResourceNotExistError("policy", args.EnterpriseMeta)
return nil, HTTPError{StatusCode: http.StatusNotFound, Reason: msg.Error()}
} }
return out.Policy, nil return out.Policy, nil
@ -247,6 +250,10 @@ func (s *HTTPHandlers) ACLPolicyDelete(resp http.ResponseWriter, req *http.Reque
var ignored string var ignored string
if err := s.agent.RPC(req.Context(), "ACL.PolicyDelete", args, &ignored); err != nil { if err := s.agent.RPC(req.Context(), "ACL.PolicyDelete", args, &ignored); err != nil {
if strings.Contains(err.Error(), acl.ErrNotFound.Error()) {
resp.WriteHeader(http.StatusNotFound)
return nil, HTTPError{StatusCode: http.StatusNotFound, Reason: "Cannot find policy to delete"}
}
return nil, err return nil, err
} }
@ -346,11 +353,14 @@ func (s *HTTPHandlers) ACLTokenSelf(resp http.ResponseWriter, req *http.Request)
var out structs.ACLTokenResponse var out structs.ACLTokenResponse
defer setMeta(resp, &out.QueryMeta) defer setMeta(resp, &out.QueryMeta)
if err := s.agent.RPC(req.Context(), "ACL.TokenRead", &args, &out); err != nil { if err := s.agent.RPC(req.Context(), "ACL.TokenRead", &args, &out); err != nil {
// should return permission denied error if missing permissions
return nil, err return nil, err
} }
if out.Token == nil { if out.Token == nil {
return nil, acl.ErrNotFound // if no error was returned above, the token does not exist
resp.WriteHeader(http.StatusNotFound)
return nil, HTTPError{StatusCode: http.StatusNotFound, Reason: "Supplied token does not exist"}
} }
return out.Token, nil return out.Token, nil
@ -393,7 +403,10 @@ func (s *HTTPHandlers) ACLTokenGet(resp http.ResponseWriter, req *http.Request,
} }
if out.Token == nil { if out.Token == nil {
return nil, acl.ErrNotFound // if no error was returned above, the token does not exist
resp.WriteHeader(http.StatusNotFound)
msg := acl.ACLResourceNotExistError("token", args.EnterpriseMeta)
return nil, HTTPError{StatusCode: http.StatusNotFound, Reason: msg.Error()}
} }
if args.Expanded { if args.Expanded {
@ -449,6 +462,10 @@ func (s *HTTPHandlers) ACLTokenDelete(resp http.ResponseWriter, req *http.Reques
var ignored string var ignored string
if err := s.agent.RPC(req.Context(), "ACL.TokenDelete", args, &ignored); err != nil { if err := s.agent.RPC(req.Context(), "ACL.TokenDelete", args, &ignored); err != nil {
if strings.Contains(err.Error(), acl.ErrNotFound.Error()) {
resp.WriteHeader(http.StatusNotFound)
return nil, HTTPError{StatusCode: http.StatusNotFound, Reason: "Cannot find token to delete"}
}
return nil, err return nil, err
} }
return true, nil return true, nil
@ -582,12 +599,15 @@ func (s *HTTPHandlers) ACLRoleRead(resp http.ResponseWriter, req *http.Request,
var out structs.ACLRoleResponse var out structs.ACLRoleResponse
defer setMeta(resp, &out.QueryMeta) defer setMeta(resp, &out.QueryMeta)
if err := s.agent.RPC(req.Context(), "ACL.RoleRead", &args, &out); err != nil { if err := s.agent.RPC(req.Context(), "ACL.RoleRead", &args, &out); err != nil {
// should return permission denied error if missing permissions
return nil, err return nil, err
} }
if out.Role == nil { if out.Role == nil {
// if not permission denied error is returned above, role does not exist
resp.WriteHeader(http.StatusNotFound) resp.WriteHeader(http.StatusNotFound)
return nil, nil msg := acl.ACLResourceNotExistError("role", args.EnterpriseMeta)
return nil, HTTPError{StatusCode: http.StatusNotFound, Reason: msg.Error()}
} }
return out.Role, nil return out.Role, nil
@ -640,6 +660,10 @@ func (s *HTTPHandlers) ACLRoleDelete(resp http.ResponseWriter, req *http.Request
var ignored string var ignored string
if err := s.agent.RPC(req.Context(), "ACL.RoleDelete", args, &ignored); err != nil { if err := s.agent.RPC(req.Context(), "ACL.RoleDelete", args, &ignored); err != nil {
if strings.Contains(err.Error(), acl.ErrNotFound.Error()) {
resp.WriteHeader(http.StatusNotFound)
return nil, HTTPError{StatusCode: http.StatusNotFound, Reason: "Cannot find role to delete"}
}
return nil, err return nil, err
} }
@ -729,12 +753,15 @@ func (s *HTTPHandlers) ACLBindingRuleRead(resp http.ResponseWriter, req *http.Re
var out structs.ACLBindingRuleResponse var out structs.ACLBindingRuleResponse
defer setMeta(resp, &out.QueryMeta) defer setMeta(resp, &out.QueryMeta)
if err := s.agent.RPC(req.Context(), "ACL.BindingRuleRead", &args, &out); err != nil { if err := s.agent.RPC(req.Context(), "ACL.BindingRuleRead", &args, &out); err != nil {
// should return permission denied error if missing permissions
return nil, err return nil, err
} }
if out.BindingRule == nil { if out.BindingRule == nil {
// if no error was returned above, the binding rule does not exist
resp.WriteHeader(http.StatusNotFound) resp.WriteHeader(http.StatusNotFound)
return nil, nil msg := acl.ACLResourceNotExistError("binding rule", args.EnterpriseMeta)
return nil, HTTPError{StatusCode: http.StatusNotFound, Reason: msg.Error()}
} }
return out.BindingRule, nil return out.BindingRule, nil
@ -786,6 +813,10 @@ func (s *HTTPHandlers) ACLBindingRuleDelete(resp http.ResponseWriter, req *http.
var ignored bool var ignored bool
if err := s.agent.RPC(req.Context(), "ACL.BindingRuleDelete", args, &ignored); err != nil { if err := s.agent.RPC(req.Context(), "ACL.BindingRuleDelete", args, &ignored); err != nil {
if strings.Contains(err.Error(), acl.ErrNotFound.Error()) {
resp.WriteHeader(http.StatusNotFound)
return nil, HTTPError{StatusCode: http.StatusNotFound, Reason: "Cannot find binding rule to delete"}
}
return nil, err return nil, err
} }
@ -871,12 +902,15 @@ func (s *HTTPHandlers) ACLAuthMethodRead(resp http.ResponseWriter, req *http.Req
var out structs.ACLAuthMethodResponse var out structs.ACLAuthMethodResponse
defer setMeta(resp, &out.QueryMeta) defer setMeta(resp, &out.QueryMeta)
if err := s.agent.RPC(req.Context(), "ACL.AuthMethodRead", &args, &out); err != nil { if err := s.agent.RPC(req.Context(), "ACL.AuthMethodRead", &args, &out); err != nil {
// should return permission denied if missing permissions
return nil, err return nil, err
} }
if out.AuthMethod == nil { if out.AuthMethod == nil {
// if no error was returned above, the auth method does not exist
resp.WriteHeader(http.StatusNotFound) resp.WriteHeader(http.StatusNotFound)
return nil, nil msg := acl.ACLResourceNotExistError("auth method", args.EnterpriseMeta)
return nil, HTTPError{StatusCode: http.StatusNotFound, Reason: msg.Error()}
} }
fixupAuthMethodConfig(out.AuthMethod) fixupAuthMethodConfig(out.AuthMethod)
@ -932,6 +966,10 @@ func (s *HTTPHandlers) ACLAuthMethodDelete(resp http.ResponseWriter, req *http.R
var ignored bool var ignored bool
if err := s.agent.RPC(req.Context(), "ACL.AuthMethodDelete", args, &ignored); err != nil { if err := s.agent.RPC(req.Context(), "ACL.AuthMethodDelete", args, &ignored); err != nil {
if strings.Contains(err.Error(), acl.ErrNotFound.Error()) {
resp.WriteHeader(http.StatusNotFound)
return nil, HTTPError{StatusCode: http.StatusNotFound, Reason: "Cannot find auth method to delete"}
}
return nil, err return nil, err
} }
@ -976,7 +1014,7 @@ func (s *HTTPHandlers) ACLLogout(resp http.ResponseWriter, req *http.Request) (i
s.parseToken(req, &args.Token) s.parseToken(req, &args.Token)
if args.Token == "" { if args.Token == "" {
return nil, acl.ErrNotFound return nil, HTTPError{StatusCode: http.StatusUnauthorized, Reason: "Supplied token does not exist"}
} }
var ignored bool var ignored bool

View File

@ -1845,7 +1845,7 @@ func TestACL_LoginProcedure_HTTP(t *testing.T) {
resp := httptest.NewRecorder() resp := httptest.NewRecorder()
_, err := a.srv.ACLTokenCRUD(resp, req) _, err := a.srv.ACLTokenCRUD(resp, req)
require.Error(t, err) require.Error(t, err)
require.True(t, acl.IsErrNotFound(err), err.Error()) require.ErrorContains(t, err, acl.ErrNotFound.Error())
}) })
}) })
} }
@ -2010,7 +2010,7 @@ func TestACLEndpoint_LoginLogout_jwt(t *testing.T) {
// make the request // make the request
_, err = a.srv.ACLTokenCRUD(resp, req) _, err = a.srv.ACLTokenCRUD(resp, req)
require.Error(t, err) require.Error(t, err)
require.Equal(t, acl.ErrNotFound, err) require.ErrorContains(t, err, acl.ErrNotFound.Error())
}) })
}) })
} }

View File

@ -314,19 +314,22 @@ func (a *ACL) TokenRead(args *structs.ACLTokenGetRequest, reply *structs.ACLToke
// no extra validation is needed here. If you have the secret ID you can read it. // no extra validation is needed here. If you have the secret ID you can read it.
} }
if token != nil && token.IsExpired(time.Now()) {
token = nil
}
if err != nil { if err != nil {
return err return err
} }
if token != nil && token.IsExpired(time.Now()) {
return fmt.Errorf("token has expired: %w", acl.ErrNotFound)
} else if token == nil {
// token does not exist
if ns := args.EnterpriseMeta.NamespaceOrEmpty(); ns != "" {
return fmt.Errorf("token not found in namespace %s: %w", ns, acl.ErrNotFound)
}
return fmt.Errorf("token does not exist: %w", acl.ErrNotFound)
}
reply.Index, reply.Token = index, token reply.Index, reply.Token = index, token
reply.SourceDatacenter = args.Datacenter reply.SourceDatacenter = args.Datacenter
if token == nil {
return errNotFound
}
if args.Expanded { if args.Expanded {
info, err := a.lookupExpandedTokenInfo(ws, state, token) info, err := a.lookupExpandedTokenInfo(ws, state, token)
@ -458,8 +461,13 @@ func (a *ACL) TokenClone(args *structs.ACLTokenSetRequest, reply *structs.ACLTok
_, token, err := a.srv.fsm.State().ACLTokenGetByAccessor(nil, args.ACLToken.AccessorID, &args.ACLToken.EnterpriseMeta) _, token, err := a.srv.fsm.State().ACLTokenGetByAccessor(nil, args.ACLToken.AccessorID, &args.ACLToken.EnterpriseMeta)
if err != nil { if err != nil {
return err return err
} else if token == nil || token.IsExpired(time.Now()) { } else if token == nil {
return acl.ErrNotFound if ns := args.ACLToken.EnterpriseMeta.NamespaceOrEmpty(); ns != "" {
return fmt.Errorf("token not found in namespace %s: %w", ns, acl.ErrNotFound)
}
return fmt.Errorf("token does not exist: %w", acl.ErrNotFound)
} else if token.IsExpired(time.Now()) {
return fmt.Errorf("token is expired: %w", acl.ErrNotFound)
} else if !a.srv.InPrimaryDatacenter() && !token.Local { } else if !a.srv.InPrimaryDatacenter() && !token.Local {
// global token writes must be forwarded to the primary DC // global token writes must be forwarded to the primary DC
args.Datacenter = a.srv.config.PrimaryDatacenter args.Datacenter = a.srv.config.PrimaryDatacenter
@ -597,8 +605,11 @@ func (a *ACL) TokenDelete(args *structs.ACLTokenDeleteRequest, reply *string) er
args.Datacenter = a.srv.config.PrimaryDatacenter args.Datacenter = a.srv.config.PrimaryDatacenter
return a.srv.forwardDC("ACL.TokenDelete", a.srv.config.PrimaryDatacenter, args, reply) return a.srv.forwardDC("ACL.TokenDelete", a.srv.config.PrimaryDatacenter, args, reply)
} else { } else {
// in Primary Datacenter but the token does not exist - return early as there is nothing to do. // in Primary Datacenter but the token does not exist - return early indicating it wasn't found.
return nil if ns := args.EnterpriseMeta.NamespaceOrEmpty(); ns != "" {
return fmt.Errorf("token not found in namespace %s: %w", ns, acl.ErrNotFound)
}
return fmt.Errorf("token does not exist: %w", acl.ErrNotFound)
} }
req := &structs.ACLTokenBatchDeleteRequest{ req := &structs.ACLTokenBatchDeleteRequest{
@ -979,7 +990,10 @@ func (a *ACL) PolicyDelete(args *structs.ACLPolicyDeleteRequest, reply *string)
} }
if policy == nil { if policy == nil {
return nil if ns := args.EnterpriseMeta.NamespaceOrEmpty(); ns != "" {
return fmt.Errorf("policy not found in namespace %s: %w", ns, acl.ErrNotFound)
}
return fmt.Errorf("policy does not exist: %w", acl.ErrNotFound)
} }
if policy.ID == structs.ACLPolicyGlobalManagementID { if policy.ID == structs.ACLPolicyGlobalManagementID {
@ -1393,7 +1407,10 @@ func (a *ACL) RoleDelete(args *structs.ACLRoleDeleteRequest, reply *string) erro
} }
if role == nil { if role == nil {
return nil if ns := args.EnterpriseMeta.NamespaceOrEmpty(); ns != "" {
return fmt.Errorf("role not found in namespace %s: %w", ns, acl.ErrNotFound)
}
return fmt.Errorf("role does not exist: %w", acl.ErrNotFound)
} }
req := structs.ACLRoleBatchDeleteRequest{ req := structs.ACLRoleBatchDeleteRequest{
@ -1706,11 +1723,15 @@ func (a *ACL) BindingRuleDelete(args *structs.ACLBindingRuleDeleteRequest, reply
} }
_, rule, err := a.srv.fsm.State().ACLBindingRuleGetByID(nil, args.BindingRuleID, &args.EnterpriseMeta) _, rule, err := a.srv.fsm.State().ACLBindingRuleGetByID(nil, args.BindingRuleID, &args.EnterpriseMeta)
switch { if err != nil {
case err != nil:
return err return err
case rule == nil: }
return nil
if rule == nil {
if ns := args.EnterpriseMeta.NamespaceOrEmpty(); ns != "" {
return fmt.Errorf("binding rule not found in namespace %s: %w", ns, acl.ErrNotFound)
}
return fmt.Errorf("binding rule does not exist: %w", acl.ErrNotFound)
} }
req := structs.ACLBindingRuleBatchDeleteRequest{ req := structs.ACLBindingRuleBatchDeleteRequest{
@ -1955,7 +1976,10 @@ func (a *ACL) AuthMethodDelete(args *structs.ACLAuthMethodDeleteRequest, reply *
} }
if method == nil { if method == nil {
return nil if ns := args.EnterpriseMeta.NamespaceOrEmpty(); ns != "" {
return fmt.Errorf("auth method not found in namespace %s: %w", ns, acl.ErrNotFound)
}
return fmt.Errorf("auth method does not exist: %w", acl.ErrNotFound)
} }
if err := a.srv.enterpriseAuthMethodTypeValidation(method.Type); err != nil { if err := a.srv.enterpriseAuthMethodTypeValidation(method.Type); err != nil {
@ -2082,7 +2106,7 @@ func (a *ACL) Logout(args *structs.ACLLogoutRequest, reply *bool) error {
} }
if args.Token == "" { if args.Token == "" {
return acl.ErrNotFound return fmt.Errorf("no valid token ID provided: %w", acl.ErrNotFound)
} }
if done, err := a.srv.ForwardRPC("ACL.Logout", args, reply); done { if done, err := a.srv.ForwardRPC("ACL.Logout", args, reply); done {

View File

@ -214,13 +214,16 @@ func TestACLEndpoint_TokenRead(t *testing.T) {
resp := structs.ACLTokenResponse{} resp := structs.ACLTokenResponse{}
retry.Run(t, func(r *retry.R) { retry.Run(t, func(r *retry.R) {
require.NoError(r, aclEp.TokenRead(&req, &resp)) time.Sleep(200 * time.Millisecond)
err := aclEp.TokenRead(&req, &resp)
require.Error(r, err)
require.ErrorContains(t, err, "ACL not found")
require.Nil(r, resp.Token) require.Nil(r, resp.Token)
}) })
}) })
}) })
t.Run("nil when token does not exist", func(t *testing.T) { t.Run("error when token does not exist", func(t *testing.T) {
fakeID, err := uuid.GenerateUUID() fakeID, err := uuid.GenerateUUID()
require.NoError(t, err) require.NoError(t, err)
@ -235,7 +238,8 @@ func TestACLEndpoint_TokenRead(t *testing.T) {
err = aclEp.TokenRead(&req, &resp) err = aclEp.TokenRead(&req, &resp)
require.Nil(t, resp.Token) require.Nil(t, resp.Token)
require.NoError(t, err) require.Error(t, err)
require.ErrorContains(t, err, acl.ErrNotFound.Error())
}) })
t.Run("validates ID format", func(t *testing.T) { t.Run("validates ID format", func(t *testing.T) {
@ -507,7 +511,7 @@ func TestACLEndpoint_TokenClone(t *testing.T) {
err = endpoint.TokenClone(&req, &t2) err = endpoint.TokenClone(&req, &t2)
require.Error(t, err) require.Error(t, err)
require.Equal(t, acl.ErrNotFound, err) require.ErrorContains(t, err, "token is expired")
}) })
} }
@ -1647,7 +1651,8 @@ func TestACLEndpoint_TokenDelete(t *testing.T) {
// Make sure the token is gone // Make sure the token is gone
tokenResp, err = retrieveTestToken(codec, TestDefaultInitialManagementToken, "dc1", testToken.AccessorID) tokenResp, err = retrieveTestToken(codec, TestDefaultInitialManagementToken, "dc1", testToken.AccessorID)
require.NoError(t, err) require.Error(t, err)
require.ErrorContains(t, err, acl.ErrNotFound.Error())
require.Nil(t, tokenResp.Token) require.Nil(t, tokenResp.Token)
}) })
@ -1662,7 +1667,8 @@ func TestACLEndpoint_TokenDelete(t *testing.T) {
// Make sure the token is not listable (filtered due to expiry) // Make sure the token is not listable (filtered due to expiry)
tokenResp, err := retrieveTestToken(codec, TestDefaultInitialManagementToken, "dc1", expiringToken.AccessorID) tokenResp, err := retrieveTestToken(codec, TestDefaultInitialManagementToken, "dc1", expiringToken.AccessorID)
require.NoError(t, err) require.Error(t, err)
require.ErrorContains(t, err, "token has expired")
require.Nil(t, tokenResp.Token) require.Nil(t, tokenResp.Token)
// Now try to delete it (this should work). // Now try to delete it (this should work).
@ -1679,7 +1685,8 @@ func TestACLEndpoint_TokenDelete(t *testing.T) {
// Make sure the token is still gone (this time it's actually gone) // Make sure the token is still gone (this time it's actually gone)
tokenResp, err = retrieveTestToken(codec, TestDefaultInitialManagementToken, "dc1", expiringToken.AccessorID) tokenResp, err = retrieveTestToken(codec, TestDefaultInitialManagementToken, "dc1", expiringToken.AccessorID)
require.NoError(t, err) require.Error(t, err)
require.ErrorContains(t, err, acl.ErrNotFound.Error())
require.Nil(t, tokenResp.Token) require.Nil(t, tokenResp.Token)
}) })
@ -1697,8 +1704,9 @@ func TestACLEndpoint_TokenDelete(t *testing.T) {
// Make sure the token is gone // Make sure the token is gone
tokenResp, err := retrieveTestToken(codec, TestDefaultInitialManagementToken, "dc1", existingToken.AccessorID) tokenResp, err := retrieveTestToken(codec, TestDefaultInitialManagementToken, "dc1", existingToken.AccessorID)
require.Error(t, err)
require.ErrorContains(t, err, acl.ErrNotFound.Error())
require.Nil(t, tokenResp.Token) require.Nil(t, tokenResp.Token)
require.NoError(t, err)
}) })
t.Run("can't delete itself", func(t *testing.T) { t.Run("can't delete itself", func(t *testing.T) {
@ -1739,12 +1747,14 @@ func TestACLEndpoint_TokenDelete(t *testing.T) {
var resp string var resp string
err = acl1.TokenDelete(&req, &resp) err = acl1.TokenDelete(&req, &resp)
require.NoError(t, err) require.Error(t, err)
require.ErrorIs(t, err, acl.ErrNotFound)
// token should be nil // token should be nil
tokenResp, err := retrieveTestToken(codec, TestDefaultInitialManagementToken, "dc1", existingToken.AccessorID) tokenResp, err := retrieveTestToken(codec, TestDefaultInitialManagementToken, "dc1", existingToken.AccessorID)
require.Nil(t, tokenResp.Token) require.Nil(t, tokenResp.Token)
require.NoError(t, err) require.Error(t, err)
require.ErrorContains(t, err, acl.ErrNotFound.Error())
}) })
t.Run("don't segfault when attempting to delete non existent token in secondary dc", func(t *testing.T) { t.Run("don't segfault when attempting to delete non existent token in secondary dc", func(t *testing.T) {
@ -1760,12 +1770,14 @@ func TestACLEndpoint_TokenDelete(t *testing.T) {
var resp string var resp string
err = acl2.TokenDelete(&req, &resp) err = acl2.TokenDelete(&req, &resp)
require.NoError(t, err) require.Error(t, err)
require.ErrorContains(t, err, acl.ErrNotFound.Error())
// token should be nil // token should be nil
tokenResp, err := retrieveTestToken(codec2, TestDefaultInitialManagementToken, "dc1", existingToken.AccessorID) tokenResp, err := retrieveTestToken(codec2, TestDefaultInitialManagementToken, "dc1", existingToken.AccessorID)
require.Nil(t, tokenResp.Token) require.Nil(t, tokenResp.Token)
require.NoError(t, err) require.Error(t, err)
require.ErrorContains(t, err, acl.ErrNotFound.Error())
}) })
} }
@ -3307,7 +3319,8 @@ func TestACLEndpoint_AuthMethodDelete(t *testing.T) {
var ignored bool var ignored bool
err = aclEp.AuthMethodDelete(&req, &ignored) err = aclEp.AuthMethodDelete(&req, &ignored)
require.NoError(t, err) require.Error(t, err)
require.ErrorContains(t, err, acl.ErrNotFound.Error())
}) })
} }
@ -3413,7 +3426,8 @@ func TestACLEndpoint_AuthMethodDelete_RuleAndTokenCascade(t *testing.T) {
} }
for _, id := range []string{i1_t1.AccessorID, i1_t2.AccessorID} { for _, id := range []string{i1_t1.AccessorID, i1_t2.AccessorID} {
tokResp, err := retrieveTestToken(codec, TestDefaultInitialManagementToken, "dc1", id) tokResp, err := retrieveTestToken(codec, TestDefaultInitialManagementToken, "dc1", id)
require.NoError(t, err) require.Error(t, err)
require.ErrorContains(t, err, acl.ErrNotFound.Error())
require.Nil(t, tokResp.Token) require.Nil(t, tokResp.Token)
} }
@ -3774,7 +3788,8 @@ func TestACLEndpoint_BindingRuleDelete(t *testing.T) {
var ignored bool var ignored bool
err = aclEp.BindingRuleDelete(&req, &ignored) err = aclEp.BindingRuleDelete(&req, &ignored)
require.NoError(t, err) require.Error(t, err)
require.ErrorContains(t, err, acl.ErrNotFound.Error())
}) })
} }
@ -4197,7 +4212,8 @@ func TestACLEndpoint_SecureIntroEndpoints_OnlyCreateLocalData(t *testing.T) {
require.Equal(t, "web2", resp2.Token.ServiceIdentities[0].ServiceName) require.Equal(t, "web2", resp2.Token.ServiceIdentities[0].ServiceName)
// absent in dc1 // absent in dc1
resp2, err = retrieveTestToken(codec1, TestDefaultInitialManagementToken, "dc1", remoteToken.AccessorID) resp2, err = retrieveTestToken(codec1, TestDefaultInitialManagementToken, "dc1", remoteToken.AccessorID)
require.NoError(t, err) require.Error(t, err)
require.ErrorContains(t, err, acl.ErrNotFound.Error())
require.Nil(t, resp2.Token) require.Nil(t, resp2.Token)
}) })
@ -4256,7 +4272,8 @@ func TestACLEndpoint_SecureIntroEndpoints_OnlyCreateLocalData(t *testing.T) {
require.Equal(t, "web1", resp2.Token.ServiceIdentities[0].ServiceName) require.Equal(t, "web1", resp2.Token.ServiceIdentities[0].ServiceName)
// absent in dc2 // absent in dc2
resp2, err = retrieveTestToken(codec2, TestDefaultInitialManagementToken, "dc2", primaryToken.AccessorID) resp2, err = retrieveTestToken(codec2, TestDefaultInitialManagementToken, "dc2", primaryToken.AccessorID)
require.NoError(t, err) require.Error(t, err)
require.ErrorContains(t, err, acl.ErrNotFound.Error())
require.Nil(t, resp2.Token) require.Nil(t, resp2.Token)
}) })
@ -4274,11 +4291,13 @@ func TestACLEndpoint_SecureIntroEndpoints_OnlyCreateLocalData(t *testing.T) {
// absent in dc2 // absent in dc2
resp2, err := retrieveTestToken(codec2, TestDefaultInitialManagementToken, "dc2", remoteToken.AccessorID) resp2, err := retrieveTestToken(codec2, TestDefaultInitialManagementToken, "dc2", remoteToken.AccessorID)
require.NoError(t, err) require.Error(t, err)
require.ErrorContains(t, err, acl.ErrNotFound.Error())
require.Nil(t, resp2.Token) require.Nil(t, resp2.Token)
// absent in dc1 // absent in dc1
resp2, err = retrieveTestToken(codec1, TestDefaultInitialManagementToken, "dc1", remoteToken.AccessorID) resp2, err = retrieveTestToken(codec1, TestDefaultInitialManagementToken, "dc1", remoteToken.AccessorID)
require.NoError(t, err) require.Error(t, err)
require.ErrorContains(t, err, acl.ErrNotFound.Error())
require.Nil(t, resp2.Token) require.Nil(t, resp2.Token)
}) })
@ -4299,7 +4318,8 @@ func TestACLEndpoint_SecureIntroEndpoints_OnlyCreateLocalData(t *testing.T) {
require.Equal(t, "web1", resp2.Token.ServiceIdentities[0].ServiceName) require.Equal(t, "web1", resp2.Token.ServiceIdentities[0].ServiceName)
// absent in dc2 // absent in dc2
resp2, err = retrieveTestToken(codec2, TestDefaultInitialManagementToken, "dc2", primaryToken.AccessorID) resp2, err = retrieveTestToken(codec2, TestDefaultInitialManagementToken, "dc2", primaryToken.AccessorID)
require.NoError(t, err) require.Error(t, err)
require.ErrorContains(t, err, acl.ErrNotFound.Error())
require.Nil(t, resp2.Token) require.Nil(t, resp2.Token)
}) })
@ -5458,11 +5478,7 @@ func retrieveTestToken(codec rpc.ClientCodec, initialManagementToken string, dat
err := msgpackrpc.CallWithCodec(codec, "ACL.TokenRead", &arg, &out) err := msgpackrpc.CallWithCodec(codec, "ACL.TokenRead", &arg, &out)
if err != nil { return &out, err
return nil, err
}
return &out, nil
} }
func deleteTestToken(codec rpc.ClientCodec, initialManagementToken string, datacenter string, tokenAccessor string) error { func deleteTestToken(codec rpc.ClientCodec, initialManagementToken string, datacenter string, tokenAccessor string) error {

View File

@ -754,8 +754,8 @@ func TestACLReplication_TokensRedacted(t *testing.T) {
QueryOptions: structs.QueryOptions{Token: aclfilter.RedactedToken}, QueryOptions: structs.QueryOptions{Token: aclfilter.RedactedToken},
} }
err := s2.RPC(context.Background(), "ACL.TokenRead", &req, &tokenResp) err := s2.RPC(context.Background(), "ACL.TokenRead", &req, &tokenResp)
// its not an error for the secret to not be found. require.Error(r, err)
require.NoError(r, err) require.ErrorContains(r, err, "token does not exist")
require.Nil(r, tokenResp.Token) require.Nil(r, tokenResp.Token)
var status structs.ACLReplicationStatus var status structs.ACLReplicationStatus

View File

@ -142,7 +142,6 @@ func (s *Server) ResolveIdentityFromToken(token string) (bool, structs.ACLIdenti
if !s.InPrimaryDatacenter() && !s.config.ACLTokenReplication { if !s.InPrimaryDatacenter() && !s.config.ACLTokenReplication {
return false, nil, nil return false, nil, nil
} }
index, aclToken, err := s.fsm.State().ACLTokenGetBySecret(nil, token, nil) index, aclToken, err := s.fsm.State().ACLTokenGetBySecret(nil, token, nil)
if err != nil { if err != nil {
return true, nil, err return true, nil, err
@ -150,7 +149,12 @@ func (s *Server) ResolveIdentityFromToken(token string) (bool, structs.ACLIdenti
return true, aclToken, nil return true, aclToken, nil
} }
return s.InPrimaryDatacenter() || index > 0, nil, acl.ErrNotFound defaultErr := acl.ErrNotFound
canBootstrap, _, _ := s.fsm.State().CanBootstrapACLToken()
if canBootstrap {
defaultErr = fmt.Errorf("ACL system must be bootstrapped before making any requests that require authorization: %w", defaultErr)
}
return s.InPrimaryDatacenter() || index > 0, nil, defaultErr
} }
func (s *serverACLResolverBackend) ResolvePolicyFromID(policyID string) (bool, *structs.ACLPolicy, error) { func (s *serverACLResolverBackend) ResolvePolicyFromID(policyID string) (bool, *structs.ACLPolicy, error) {

View File

@ -289,6 +289,28 @@ func prepTokenPoliciesInPartition(t *testing.T, acl *ACL, partition string) (pol
return return
} }
func TestAPI_ACLBootstrap(t *testing.T) {
t.Parallel()
c, s := makeNonBootstrappedACLClient(t)
defer s.Stop()
acl := c.ACL()
s.WaitForLeader(t)
// not bootstrapped
_, _, err := acl.TokenList(nil)
require.EqualError(t, err, "Unexpected response code: 403 (ACL system must be bootstrapped before making any requests that require authorization: ACL not found)")
// bootstrap
mgmtTok, _, err := acl.Bootstrap()
require.NoError(t, err)
// bootstrapped
acl.c.config.Token = mgmtTok.SecretID
toks, _, err := acl.TokenList(nil)
require.NoError(t, err)
// management and anonymous should be only tokens
require.Len(t, toks, 2)
}
func TestAPI_ACLToken_CreateReadDelete(t *testing.T) { func TestAPI_ACLToken_CreateReadDelete(t *testing.T) {
t.Parallel() t.Parallel()
c, s := makeACLClient(t) c, s := makeACLClient(t)

View File

@ -50,6 +50,19 @@ func makeACLClient(t *testing.T) (*Client, *testutil.TestServer) {
}) })
} }
func makeNonBootstrappedACLClient(t *testing.T) (*Client, *testutil.TestServer) {
return makeClientWithConfig(t,
func(clientConfig *Config) {
clientConfig.Token = "root"
},
func(serverConfig *testutil.TestServerConfig) {
serverConfig.PrimaryDatacenter = "dc1"
serverConfig.ACL.Enabled = true
serverConfig.ACL.DefaultPolicy = "deny"
serverConfig.Bootstrap = true
})
}
func makeClientWithCA(t *testing.T) (*Client, *testutil.TestServer) { func makeClientWithCA(t *testing.T) (*Client, *testutil.TestServer) {
return makeClientWithConfig(t, return makeClientWithConfig(t,
func(c *Config) { func(c *Config) {

View File

@ -163,7 +163,7 @@ func GetBindingRuleIDFromPartial(client *api.Client, partialID string) (string,
} }
if ruleID == "" { if ruleID == "" {
return "", fmt.Errorf("No such rule ID with prefix: %s", partialID) return "", fmt.Errorf("no such rule ID with prefix: %s: %w", partialID, acl.ErrNotFound)
} }
return ruleID, nil return ruleID, nil

View File

@ -5,13 +5,14 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/hashicorp/consul/agent"
"github.com/hashicorp/consul/api"
"github.com/hashicorp/consul/testrpc"
"github.com/hashicorp/go-uuid" "github.com/hashicorp/go-uuid"
"github.com/mitchellh/cli" "github.com/mitchellh/cli"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/hashicorp/consul/agent"
"github.com/hashicorp/consul/api"
"github.com/hashicorp/consul/testrpc"
// activate testing auth method // activate testing auth method
_ "github.com/hashicorp/consul/agent/consul/authmethod/testauth" _ "github.com/hashicorp/consul/agent/consul/authmethod/testauth"
) )
@ -70,12 +71,11 @@ func TestAuthMethodDeleteCommand(t *testing.T) {
} }
code := cmd.Run(args) code := cmd.Run(args)
require.Equal(t, code, 0) require.Equal(t, 1, code)
require.Empty(t, ui.ErrorWriter.String()) require.Contains(t, ui.ErrorWriter.String(), "404 (Cannot find auth method to delete)")
output := ui.OutputWriter.String() output := ui.OutputWriter.String()
require.Contains(t, output, fmt.Sprintf("deleted successfully")) require.Empty(t, output)
require.Contains(t, output, "notfound")
}) })
createAuthMethod := func(t *testing.T) string { createAuthMethod := func(t *testing.T) string {

View File

@ -5,11 +5,12 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/mitchellh/cli"
"github.com/stretchr/testify/require"
"github.com/hashicorp/consul/agent" "github.com/hashicorp/consul/agent"
"github.com/hashicorp/consul/api" "github.com/hashicorp/consul/api"
"github.com/hashicorp/consul/testrpc" "github.com/hashicorp/consul/testrpc"
"github.com/mitchellh/cli"
"github.com/stretchr/testify/require"
// activate testing auth method // activate testing auth method
_ "github.com/hashicorp/consul/agent/consul/authmethod/testauth" _ "github.com/hashicorp/consul/agent/consul/authmethod/testauth"
@ -180,4 +181,22 @@ func TestBindingRuleDeleteCommand(t *testing.T) {
require.Equal(t, code, 1) require.Equal(t, code, 1)
require.Contains(t, ui.ErrorWriter.String(), "Error determining binding rule ID") require.Contains(t, ui.ErrorWriter.String(), "Error determining binding rule ID")
}) })
t.Run("delete notfound", func(t *testing.T) {
ui := cli.NewMockUi()
cmd := New(ui)
args := []string{
"-http-addr=" + a.HTTPAddr(),
"-token=root",
"-id=notfound",
}
code := cmd.Run(args)
require.Equal(t, 1, code)
require.Contains(t, ui.ErrorWriter.String(), "Error determining binding rule ID: no such rule ID with prefix: notfound")
output := ui.OutputWriter.String()
require.Empty(t, output)
})
} }

View File

@ -5,11 +5,12 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/mitchellh/cli"
"github.com/stretchr/testify/assert"
"github.com/hashicorp/consul/agent" "github.com/hashicorp/consul/agent"
"github.com/hashicorp/consul/api" "github.com/hashicorp/consul/api"
"github.com/hashicorp/consul/testrpc" "github.com/hashicorp/consul/testrpc"
"github.com/mitchellh/cli"
"github.com/stretchr/testify/assert"
) )
func TestPolicyDeleteCommand_noTabs(t *testing.T) { func TestPolicyDeleteCommand_noTabs(t *testing.T) {
@ -69,5 +70,5 @@ func TestPolicyDeleteCommand(t *testing.T) {
policy.ID, policy.ID,
&api.QueryOptions{Token: "root"}, &api.QueryOptions{Token: "root"},
) )
assert.EqualError(t, err, "Unexpected response code: 403 (ACL not found)") assert.EqualError(t, err, "Unexpected response code: 404 (Requested policy does not exist: ACL not found)")
} }

View File

@ -70,5 +70,6 @@ func TestTokenDeleteCommand(t *testing.T) {
token.AccessorID, token.AccessorID,
&api.QueryOptions{Token: "root"}, &api.QueryOptions{Token: "root"},
) )
assert.EqualError(t, err, "Unexpected response code: 403 (ACL not found)") assert.ErrorContains(t, err, "Unexpected response code: 403")
assert.ErrorContains(t, err, "ACL not found")
} }

View File

@ -55,7 +55,7 @@ func TestLogoutCommand(t *testing.T) {
code := cmd.Run(args) code := cmd.Run(args)
require.Equal(t, code, 1, "err: %s", ui.ErrorWriter.String()) require.Equal(t, code, 1, "err: %s", ui.ErrorWriter.String())
require.Contains(t, ui.ErrorWriter.String(), "403 (ACL not found)") require.Contains(t, ui.ErrorWriter.String(), "401 (Supplied token does not exist)")
}) })
t.Run("logout of deleted token", func(t *testing.T) { t.Run("logout of deleted token", func(t *testing.T) {
@ -185,7 +185,7 @@ func TestLogoutCommand_k8s(t *testing.T) {
code := cmd.Run(args) code := cmd.Run(args)
require.Equal(t, code, 1, "err: %s", ui.ErrorWriter.String()) require.Equal(t, code, 1, "err: %s", ui.ErrorWriter.String())
require.Contains(t, ui.ErrorWriter.String(), "403 (ACL not found)") require.Contains(t, ui.ErrorWriter.String(), "401 (Supplied token does not exist)")
}) })
t.Run("logout of deleted token", func(t *testing.T) { t.Run("logout of deleted token", func(t *testing.T) {