From d3881dd754ebf27c2aed5153750489d7793d596e Mon Sep 17 00:00:00 2001 From: Matt Keeler Date: Tue, 16 Jun 2020 12:54:27 -0400 Subject: [PATCH] ACL Node Identities (#7970) A Node Identity is very similar to a service identity. Its main targeted use is to allow creating tokens for use by Consul agents that will grant the necessary permissions for all the typical agent operations (node registration, coordinate updates, anti-entropy). Half of this commit is for golden file based tests of the acl token and role cli output. Another big updates was to refactor many of the tests in agent/consul/acl_endpoint_test.go to use the same style of tests and the same helpers. Besides being less boiler plate in the tests it also uses a common way of starting a test server with ACLs that should operate without any warnings regarding deprecated non-uuid master tokens etc. --- agent/acl_endpoint_test.go | 28 + agent/consul/acl.go | 55 +- agent/consul/acl_authmethod.go | 37 +- agent/consul/acl_endpoint.go | 53 +- agent/consul/acl_endpoint_test.go | 1981 +++++++---------- agent/consul/acl_test.go | 102 +- agent/consul/intention_endpoint_test.go | 27 +- agent/consul/leader.go | 1 + agent/consul/leader_test.go | 4 +- agent/consul/server_test.go | 10 +- agent/consul/state/acl.go | 20 +- agent/structs/acl.go | 97 + agent/structs/acl_legacy.go | 1 + agent/structs/acl_oss.go | 42 + api/acl.go | 10 + command/acl/acl_helpers.go | 17 + command/acl/role/create/role_create.go | 15 +- command/acl/role/create/role_create_test.go | 75 +- command/acl/role/formatter.go | 13 + command/acl/role/formatter_test.go | 195 ++ .../testdata/FormatRole/basic.json.golden | 8 + .../FormatRole/basic.pretty-meta.golden | 6 + .../testdata/FormatRole/basic.pretty.golden | 3 + .../testdata/FormatRole/complex.json.golden | 33 + .../FormatRole/complex.pretty-meta.golden | 14 + .../testdata/FormatRole/complex.pretty.golden | 11 + .../testdata/FormatRoleList/basic.json.golden | 10 + .../FormatRoleList/basic.pretty-meta.golden | 6 + .../FormatRoleList/basic.pretty.golden | 3 + .../FormatRoleList/complex.json.golden | 35 + .../FormatRoleList/complex.pretty-meta.golden | 14 + .../FormatRoleList/complex.pretty.golden | 11 + command/acl/role/update/role_update.go | 25 + command/acl/role/update/role_update_test.go | 76 +- command/acl/token/create/token_create.go | 15 +- command/acl/token/create/token_create_test.go | 90 +- command/acl/token/formatter.go | 16 + command/acl/token/formatter_test.go | 251 +++ .../testdata/FormatToken/basic.json.golden | 10 + .../FormatToken/basic.pretty-meta.golden | 8 + .../testdata/FormatToken/basic.pretty.golden | 5 + .../testdata/FormatToken/complex.json.golden | 47 + .../FormatToken/complex.pretty-meta.golden | 21 + .../FormatToken/complex.pretty.golden | 18 + .../testdata/FormatToken/legacy.json.golden | 10 + .../FormatToken/legacy.pretty-meta.golden | 10 + .../testdata/FormatToken/legacy.pretty.golden | 7 + .../FormatTokenList/basic.json.golden | 12 + .../FormatTokenList/basic.pretty-meta.golden | 8 + .../FormatTokenList/basic.pretty.golden | 5 + .../FormatTokenList/complex.json.golden | 49 + .../complex.pretty-meta.golden | 21 + .../FormatTokenList/complex.pretty.golden | 18 + .../FormatTokenList/legacy.json.golden | 12 + .../FormatTokenList/legacy.pretty-meta.golden | 8 + .../FormatTokenList/legacy.pretty.golden | 5 + command/acl/token/update/token_update.go | 31 + command/acl/token/update/token_update_test.go | 159 +- website/pages/api-docs/acl/binding-rules.mdx | 23 + website/pages/api-docs/acl/roles.mdx | 58 + website/pages/api-docs/acl/tokens.mdx | 24 + website/pages/docs/acl/acl-system.mdx | 39 + 62 files changed, 2595 insertions(+), 1423 deletions(-) create mode 100644 command/acl/role/formatter_test.go create mode 100644 command/acl/role/testdata/FormatRole/basic.json.golden create mode 100644 command/acl/role/testdata/FormatRole/basic.pretty-meta.golden create mode 100644 command/acl/role/testdata/FormatRole/basic.pretty.golden create mode 100644 command/acl/role/testdata/FormatRole/complex.json.golden create mode 100644 command/acl/role/testdata/FormatRole/complex.pretty-meta.golden create mode 100644 command/acl/role/testdata/FormatRole/complex.pretty.golden create mode 100644 command/acl/role/testdata/FormatRoleList/basic.json.golden create mode 100644 command/acl/role/testdata/FormatRoleList/basic.pretty-meta.golden create mode 100644 command/acl/role/testdata/FormatRoleList/basic.pretty.golden create mode 100644 command/acl/role/testdata/FormatRoleList/complex.json.golden create mode 100644 command/acl/role/testdata/FormatRoleList/complex.pretty-meta.golden create mode 100644 command/acl/role/testdata/FormatRoleList/complex.pretty.golden create mode 100644 command/acl/token/formatter_test.go create mode 100644 command/acl/token/testdata/FormatToken/basic.json.golden create mode 100644 command/acl/token/testdata/FormatToken/basic.pretty-meta.golden create mode 100644 command/acl/token/testdata/FormatToken/basic.pretty.golden create mode 100644 command/acl/token/testdata/FormatToken/complex.json.golden create mode 100644 command/acl/token/testdata/FormatToken/complex.pretty-meta.golden create mode 100644 command/acl/token/testdata/FormatToken/complex.pretty.golden create mode 100644 command/acl/token/testdata/FormatToken/legacy.json.golden create mode 100644 command/acl/token/testdata/FormatToken/legacy.pretty-meta.golden create mode 100644 command/acl/token/testdata/FormatToken/legacy.pretty.golden create mode 100644 command/acl/token/testdata/FormatTokenList/basic.json.golden create mode 100644 command/acl/token/testdata/FormatTokenList/basic.pretty-meta.golden create mode 100644 command/acl/token/testdata/FormatTokenList/basic.pretty.golden create mode 100644 command/acl/token/testdata/FormatTokenList/complex.json.golden create mode 100644 command/acl/token/testdata/FormatTokenList/complex.pretty-meta.golden create mode 100644 command/acl/token/testdata/FormatTokenList/complex.pretty.golden create mode 100644 command/acl/token/testdata/FormatTokenList/legacy.json.golden create mode 100644 command/acl/token/testdata/FormatTokenList/legacy.pretty-meta.golden create mode 100644 command/acl/token/testdata/FormatTokenList/legacy.pretty.golden diff --git a/agent/acl_endpoint_test.go b/agent/acl_endpoint_test.go index 0727772072..1835444fee 100644 --- a/agent/acl_endpoint_test.go +++ b/agent/acl_endpoint_test.go @@ -408,6 +408,12 @@ func TestACL_HTTP(t *testing.T) { Name: policyMap[idMap["policy-read-all-nodes"]].Name, }, }, + NodeIdentities: []*structs.ACLNodeIdentity{ + &structs.ACLNodeIdentity{ + NodeName: "web-node", + Datacenter: "foo", + }, + }, } req, _ := http.NewRequest("PUT", "/v1/acl/role?token=root", jsonBody(roleInput)) @@ -423,6 +429,7 @@ func TestACL_HTTP(t *testing.T) { require.Equal(t, roleInput.Name, role.Name) require.Equal(t, roleInput.Description, role.Description) require.Equal(t, roleInput.Policies, role.Policies) + require.Equal(t, roleInput.NodeIdentities, role.NodeIdentities) require.True(t, role.CreateIndex > 0) require.Equal(t, role.CreateIndex, role.ModifyIndex) require.NotNil(t, role.Hash) @@ -502,6 +509,12 @@ func TestACL_HTTP(t *testing.T) { ServiceName: "web-indexer", }, }, + NodeIdentities: []*structs.ACLNodeIdentity{ + &structs.ACLNodeIdentity{ + NodeName: "web-node", + Datacenter: "foo", + }, + }, } req, _ := http.NewRequest("PUT", "/v1/acl/role/"+idMap["role-test"]+"?token=root", jsonBody(roleInput)) @@ -518,6 +531,7 @@ func TestACL_HTTP(t *testing.T) { require.Equal(t, roleInput.Description, role.Description) require.Equal(t, roleInput.Policies, role.Policies) require.Equal(t, roleInput.ServiceIdentities, role.ServiceIdentities) + require.Equal(t, roleInput.NodeIdentities, role.NodeIdentities) require.True(t, role.CreateIndex > 0) require.True(t, role.CreateIndex < role.ModifyIndex) require.NotNil(t, role.Hash) @@ -623,6 +637,12 @@ func TestACL_HTTP(t *testing.T) { Name: policyMap[idMap["policy-read-all-nodes"]].Name, }, }, + NodeIdentities: []*structs.ACLNodeIdentity{ + &structs.ACLNodeIdentity{ + NodeName: "foo", + Datacenter: "bar", + }, + }, } req, _ := http.NewRequest("PUT", "/v1/acl/token?token=root", jsonBody(tokenInput)) @@ -638,6 +658,7 @@ func TestACL_HTTP(t *testing.T) { require.Len(t, token.SecretID, 36) require.Equal(t, tokenInput.Description, token.Description) require.Equal(t, tokenInput.Policies, token.Policies) + require.Equal(t, tokenInput.NodeIdentities, token.NodeIdentities) require.True(t, token.CreateIndex > 0) require.Equal(t, token.CreateIndex, token.ModifyIndex) require.NotNil(t, token.Hash) @@ -741,6 +762,12 @@ func TestACL_HTTP(t *testing.T) { Name: policyMap[idMap["policy-read-all-nodes"]].Name, }, }, + NodeIdentities: []*structs.ACLNodeIdentity{ + &structs.ACLNodeIdentity{ + NodeName: "foo", + Datacenter: "bar", + }, + }, } req, _ := http.NewRequest("PUT", "/v1/acl/token/"+originalToken.AccessorID+"?token=root", jsonBody(tokenInput)) @@ -754,6 +781,7 @@ func TestACL_HTTP(t *testing.T) { require.Equal(t, originalToken.SecretID, token.SecretID) require.Equal(t, tokenInput.Description, token.Description) require.Equal(t, tokenInput.Policies, token.Policies) + require.Equal(t, tokenInput.NodeIdentities, token.NodeIdentities) require.True(t, token.CreateIndex > 0) require.True(t, token.CreateIndex < token.ModifyIndex) require.NotNil(t, token.Hash) diff --git a/agent/consul/acl.go b/agent/consul/acl.go index 4c6c7d00dd..ba260743cf 100644 --- a/agent/consul/acl.go +++ b/agent/consul/acl.go @@ -99,6 +99,10 @@ func (id *missingIdentity) ServiceIdentityList() []*structs.ACLServiceIdentity { return nil } +func (id *missingIdentity) NodeIdentityList() []*structs.ACLNodeIdentity { + return nil +} + func (id *missingIdentity) IsExpired(asOf time.Time) bool { return false } @@ -648,8 +652,9 @@ func (r *ACLResolver) resolvePoliciesForIdentity(identity structs.ACLIdentity) ( policyIDs := identity.PolicyIDs() roleIDs := identity.RoleIDs() serviceIdentities := identity.ServiceIdentityList() + nodeIdentities := identity.NodeIdentityList() - if len(policyIDs) == 0 && len(serviceIdentities) == 0 && len(roleIDs) == 0 { + if len(policyIDs) == 0 && len(serviceIdentities) == 0 && len(roleIDs) == 0 && len(nodeIdentities) == 0 { policy := identity.EmbeddedPolicy() if policy != nil { return []*structs.ACLPolicy{policy}, nil @@ -671,14 +676,17 @@ func (r *ACLResolver) resolvePoliciesForIdentity(identity structs.ACLIdentity) ( policyIDs = append(policyIDs, link.ID) } serviceIdentities = append(serviceIdentities, role.ServiceIdentities...) + nodeIdentities = append(nodeIdentities, role.NodeIdentityList()...) } // Now deduplicate any policies or service identities that occur more than once. policyIDs = dedupeStringSlice(policyIDs) serviceIdentities = dedupeServiceIdentities(serviceIdentities) + nodeIdentities = dedupeNodeIdentities(nodeIdentities) // Generate synthetic policies for all service identities in effect. syntheticPolicies := r.synthesizePoliciesForServiceIdentities(serviceIdentities, identity.EnterpriseMetadata()) + syntheticPolicies = append(syntheticPolicies, r.synthesizePoliciesForNodeIdentities(nodeIdentities)...) // For the new ACLs policy replication is mandatory for correct operation on servers. Therefore // we only attempt to resolve policies locally @@ -705,6 +713,19 @@ func (r *ACLResolver) synthesizePoliciesForServiceIdentities(serviceIdentities [ return syntheticPolicies } +func (r *ACLResolver) synthesizePoliciesForNodeIdentities(nodeIdentities []*structs.ACLNodeIdentity) []*structs.ACLPolicy { + if len(nodeIdentities) == 0 { + return nil + } + + syntheticPolicies := make([]*structs.ACLPolicy, 0, len(nodeIdentities)) + for _, n := range nodeIdentities { + syntheticPolicies = append(syntheticPolicies, n.SyntheticPolicy()) + } + + return syntheticPolicies +} + func dedupeServiceIdentities(in []*structs.ACLServiceIdentity) []*structs.ACLServiceIdentity { // From: https://github.com/golang/go/wiki/SliceTricks#in-place-deduplicate-comparable @@ -739,6 +760,38 @@ func dedupeServiceIdentities(in []*structs.ACLServiceIdentity) []*structs.ACLSer return in[:j+1] } +func dedupeNodeIdentities(in []*structs.ACLNodeIdentity) []*structs.ACLNodeIdentity { + // From: https://github.com/golang/go/wiki/SliceTricks#in-place-deduplicate-comparable + + if len(in) <= 1 { + return in + } + + sort.Slice(in, func(i, j int) bool { + if in[i].NodeName < in[j].NodeName { + return true + } + + return in[i].Datacenter < in[j].Datacenter + }) + + j := 0 + for i := 1; i < len(in); i++ { + if in[j].NodeName == in[i].NodeName && in[j].Datacenter == in[i].Datacenter { + continue + } + j++ + in[j] = in[i] + } + + // Discard the skipped items. + for i := j + 1; i < len(in); i++ { + in[i] = nil + } + + return in[:j+1] +} + func mergeStringSlice(a, b []string) []string { out := make([]string, 0, len(a)+len(b)) out = append(out, a...) diff --git a/agent/consul/acl_authmethod.go b/agent/consul/acl_authmethod.go index bcdedfc5d5..2e973c6a12 100644 --- a/agent/consul/acl_authmethod.go +++ b/agent/consul/acl_authmethod.go @@ -36,6 +36,12 @@ func (s *Server) loadAuthMethodValidator(idx uint64, method *structs.ACLAuthMeth return v, nil } +type aclBindings struct { + roles []structs.ACLTokenRoleLink + serviceIdentities []*structs.ACLServiceIdentity + nodeIdentities []*structs.ACLNodeIdentity +} + // evaluateRoleBindings evaluates all current binding rules associated with the // given auth method against the verified data returned from the authentication // process. @@ -46,13 +52,13 @@ func (s *Server) evaluateRoleBindings( verifiedIdentity *authmethod.Identity, methodMeta *structs.EnterpriseMeta, targetMeta *structs.EnterpriseMeta, -) ([]*structs.ACLServiceIdentity, []structs.ACLTokenRoleLink, error) { +) (*aclBindings, error) { // Only fetch rules that are relevant for this method. _, rules, err := s.fsm.State().ACLBindingRuleList(nil, validator.Name(), methodMeta) if err != nil { - return nil, nil, err + return nil, err } else if len(rules) == 0 { - return nil, nil, nil + return nil, nil } // Find all binding rules that match the provided fields. @@ -63,36 +69,39 @@ func (s *Server) evaluateRoleBindings( } } if len(matchingRules) == 0 { - return nil, nil, nil + return nil, nil } // For all matching rules compute the attributes of a token. - var ( - roleLinks []structs.ACLTokenRoleLink - serviceIdentities []*structs.ACLServiceIdentity - ) + var bindings aclBindings for _, rule := range matchingRules { bindName, valid, err := computeBindingRuleBindName(rule.BindType, rule.BindName, verifiedIdentity.ProjectedVars) if err != nil { - return nil, nil, fmt.Errorf("cannot compute %q bind name for bind target: %v", rule.BindType, err) + return nil, fmt.Errorf("cannot compute %q bind name for bind target: %v", rule.BindType, err) } else if !valid { - return nil, nil, fmt.Errorf("computed %q bind name for bind target is invalid: %q", rule.BindType, bindName) + return nil, fmt.Errorf("computed %q bind name for bind target is invalid: %q", rule.BindType, bindName) } switch rule.BindType { case structs.BindingRuleBindTypeService: - serviceIdentities = append(serviceIdentities, &structs.ACLServiceIdentity{ + bindings.serviceIdentities = append(bindings.serviceIdentities, &structs.ACLServiceIdentity{ ServiceName: bindName, }) + case structs.BindingRuleBindTypeNode: + bindings.nodeIdentities = append(bindings.nodeIdentities, &structs.ACLNodeIdentity{ + NodeName: bindName, + Datacenter: s.config.Datacenter, + }) + case structs.BindingRuleBindTypeRole: _, role, err := s.fsm.State().ACLRoleGetByName(nil, bindName, targetMeta) if err != nil { - return nil, nil, err + return nil, err } if role != nil { - roleLinks = append(roleLinks, structs.ACLTokenRoleLink{ + bindings.roles = append(bindings.roles, structs.ACLTokenRoleLink{ ID: role.ID, }) } @@ -102,7 +111,7 @@ func (s *Server) evaluateRoleBindings( } } - return serviceIdentities, roleLinks, nil + return &bindings, nil } // doesSelectorMatch checks that a single selector matches the provided vars. diff --git a/agent/consul/acl_endpoint.go b/agent/consul/acl_endpoint.go index 835e7bf442..8dc5d3c71a 100644 --- a/agent/consul/acl_endpoint.go +++ b/agent/consul/acl_endpoint.go @@ -34,6 +34,8 @@ var ( validPolicyName = regexp.MustCompile(`^[A-Za-z0-9\-_]{1,128}$`) validServiceIdentityName = regexp.MustCompile(`^[a-z0-9]([a-z0-9\-_]*[a-z0-9])?$`) serviceIdentityNameMaxLength = 256 + validNodeIdentityName = regexp.MustCompile(`^[a-z0-9]([a-z0-9\-_]*[a-z0-9])?$`) + nodeIdentityNameMaxLength = 256 validRoleName = regexp.MustCompile(`^[A-Za-z0-9\-_]{1,256}$`) validAuthMethod = regexp.MustCompile(`^[A-Za-z0-9\-_]{1,128}$`) ) @@ -319,6 +321,7 @@ func (a *ACL) TokenClone(args *structs.ACLTokenSetRequest, reply *structs.ACLTok Policies: token.Policies, Roles: token.Roles, ServiceIdentities: token.ServiceIdentities, + NodeIdentities: token.NodeIdentities, Local: token.Local, Description: token.Description, ExpirationTime: token.ExpirationTime, @@ -615,6 +618,19 @@ func (a *ACL) tokenSetInternal(args *structs.ACLTokenSetRequest, reply *structs. } token.ServiceIdentities = dedupeServiceIdentities(token.ServiceIdentities) + for _, nodeid := range token.NodeIdentities { + if nodeid.NodeName == "" { + return fmt.Errorf("Node identity is missing the node name field on this token") + } + if nodeid.Datacenter == "" { + return fmt.Errorf("Node identity is missing the datacenter field on this token") + } + if !isValidNodeIdentityName(nodeid.NodeName) { + return fmt.Errorf("Node identity has an invalid name. Only alphanumeric characters, '-' and '_' are allowed") + } + } + token.NodeIdentities = dedupeNodeIdentities(token.NodeIdentities) + if token.Rules != "" { return fmt.Errorf("Rules cannot be specified for this token") } @@ -700,7 +716,8 @@ func computeBindingRuleBindName(bindType, bindName string, projectedVars map[str switch bindType { case structs.BindingRuleBindTypeService: valid = isValidServiceIdentityName(bindName) - + case structs.BindingRuleBindTypeNode: + valid = isValidNodeIdentityName(bindName) case structs.BindingRuleBindTypeRole: valid = validRoleName.MatchString(bindName) @@ -722,6 +739,17 @@ func isValidServiceIdentityName(name string) bool { return validServiceIdentityName.MatchString(name) } +// isValidNodeIdentityName returns true if the provided name can be used as +// an ACLNodeIdentity NodeName. This is more restrictive than standard +// catalog registration, which basically takes the view that "everything is +// valid". +func isValidNodeIdentityName(name string) bool { + if len(name) < 1 || len(name) > nodeIdentityNameMaxLength { + return false + } + return validNodeIdentityName.MatchString(name) +} + func (a *ACL) TokenDelete(args *structs.ACLTokenDeleteRequest, reply *string) error { if err := a.aclPreCheck(); err != nil { return err @@ -1572,6 +1600,19 @@ func (a *ACL) RoleSet(args *structs.ACLRoleSetRequest, reply *structs.ACLRole) e } role.ServiceIdentities = dedupeServiceIdentities(role.ServiceIdentities) + for _, nodeid := range role.NodeIdentities { + if nodeid.NodeName == "" { + return fmt.Errorf("Node identity is missing the node name field on this role") + } + if nodeid.Datacenter == "" { + return fmt.Errorf("Node identity is missing the datacenter field on this role") + } + if !isValidNodeIdentityName(nodeid.NodeName) { + return fmt.Errorf("Node identity has an invalid name. Only alphanumeric characters, '-' and '_' are allowed") + } + } + role.NodeIdentities = dedupeNodeIdentities(role.NodeIdentities) + // calculate the hash for this role role.SetHash(true) @@ -1892,6 +1933,7 @@ func (a *ACL) BindingRuleSet(args *structs.ACLBindingRuleSetRequest, reply *stru switch rule.BindType { case structs.BindingRuleBindTypeService: + case structs.BindingRuleBindTypeNode: case structs.BindingRuleBindTypeRole: default: return fmt.Errorf("Invalid Binding Rule: unknown BindType %q", rule.BindType) @@ -2365,14 +2407,14 @@ func (a *ACL) tokenSetFromAuthMethod( } // 3. send map through role bindings - serviceIdentities, roleLinks, err := a.srv.evaluateRoleBindings(validator, verifiedIdentity, entMeta, targetMeta) + bindings, err := a.srv.evaluateRoleBindings(validator, verifiedIdentity, entMeta, targetMeta) if err != nil { return err } // We try to prevent the creation of a useless token without taking a trip // through the state store if we can. - if len(serviceIdentities) == 0 && len(roleLinks) == 0 { + if bindings == nil || (len(bindings.serviceIdentities) == 0 && len(bindings.nodeIdentities) == 0 && len(bindings.roles) == 0) { return acl.ErrPermissionDenied } @@ -2392,8 +2434,9 @@ func (a *ACL) tokenSetFromAuthMethod( Description: description, Local: true, AuthMethod: method.Name, - ServiceIdentities: serviceIdentities, - Roles: roleLinks, + ServiceIdentities: bindings.serviceIdentities, + NodeIdentities: bindings.nodeIdentities, + Roles: bindings.roles, ExpirationTTL: method.MaxTokenTTL, EnterpriseMeta: *targetMeta, } diff --git a/agent/consul/acl_endpoint_test.go b/agent/consul/acl_endpoint_test.go index 38be7f89bc..04834f7761 100644 --- a/agent/consul/acl_endpoint_test.go +++ b/agent/consul/acl_endpoint_test.go @@ -6,7 +6,6 @@ import ( "net/rpc" "os" "path/filepath" - "reflect" "strings" "testing" "time" @@ -15,13 +14,10 @@ import ( "github.com/hashicorp/consul/agent/consul/authmethod/kubeauth" "github.com/hashicorp/consul/agent/consul/authmethod/testauth" "github.com/hashicorp/consul/agent/structs" - tokenStore "github.com/hashicorp/consul/agent/token" "github.com/hashicorp/consul/internal/go-sso/oidcauth/oidcauthtest" - "github.com/hashicorp/consul/lib/stringslice" "github.com/hashicorp/consul/sdk/freeport" "github.com/hashicorp/consul/sdk/testutil" "github.com/hashicorp/consul/sdk/testutil/retry" - "github.com/hashicorp/consul/testrpc" uuid "github.com/hashicorp/go-uuid" msgpackrpc "github.com/hashicorp/net-rpc-msgpackrpc" "github.com/stretchr/testify/require" @@ -30,17 +26,14 @@ import ( func TestACLEndpoint_Bootstrap(t *testing.T) { t.Parallel() - dir1, s1 := testServerWithConfig(t, func(c *Config) { + _, srv, codec := testACLServerWithConfig(t, func(c *Config) { c.Build = "0.8.0" // Too low for auto init of bootstrap. c.ACLDatacenter = "dc1" c.ACLsEnabled = true - }) - defer os.RemoveAll(dir1) - defer s1.Shutdown() - codec := rpcClient(t, s1) - defer codec.Close() - - testrpc.WaitForLeader(t, s1.RPC, "dc1") + // remove the default as we want to bootstrap + c.ACLMasterToken = "" + }, false) + waitForLeaderEstablishment(t, srv) // Expect an error initially since ACL bootstrap is not initialized. arg := structs.DCSpecificRequest{ @@ -50,36 +43,26 @@ func TestACLEndpoint_Bootstrap(t *testing.T) { // We can only do some high // level checks on the ACL since we don't have control over the UUID or // Raft indexes at this level. - if err := msgpackrpc.CallWithCodec(codec, "ACL.Bootstrap", &arg, &out); err != nil { - t.Fatalf("err: %v", err) - } - if len(out.ID) != len("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx") || - !strings.HasPrefix(out.Name, "Bootstrap Token") || - out.Type != structs.ACLTokenTypeManagement || - out.CreateIndex == 0 || out.ModifyIndex == 0 { - t.Fatalf("bad: %#v", out) - } + err := msgpackrpc.CallWithCodec(codec, "ACL.Bootstrap", &arg, &out) + require.NoError(t, err) + require.Len(t, out.ID, len("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx")) + require.True(t, strings.HasPrefix(out.Name, "Bootstrap Token")) + require.Equal(t, structs.ACLTokenTypeManagement, out.Type) + require.NotEqual(t, uint64(0), out.CreateIndex) + require.NotEqual(t, uint64(0), out.ModifyIndex) // Finally, make sure that another attempt is rejected. - err := msgpackrpc.CallWithCodec(codec, "ACL.Bootstrap", &arg, &out) - if err.Error() != structs.ACLBootstrapNotAllowedErr.Error() { - t.Fatalf("err: %v", err) - } + err = msgpackrpc.CallWithCodec(codec, "ACL.Bootstrap", &arg, &out) + testutil.RequireErrorContains(t, err, structs.ACLBootstrapNotAllowedErr.Error()) } func TestACLEndpoint_BootstrapTokens(t *testing.T) { t.Parallel() - dir1, s1 := testServerWithConfig(t, func(c *Config) { - c.ACLDatacenter = "dc1" - c.ACLsEnabled = true - c.ACLsEnabled = true - }) - defer os.RemoveAll(dir1) - defer s1.Shutdown() - codec := rpcClient(t, s1) - defer codec.Close() - - testrpc.WaitForLeader(t, s1.RPC, "dc1") + dir, srv, codec := testACLServerWithConfig(t, func(c *Config) { + // remove this as we are bootstrapping + c.ACLMasterToken = "" + }, false) + waitForLeaderEstablishment(t, srv) // Expect an error initially since ACL bootstrap is not initialized. arg := structs.DCSpecificRequest{ @@ -101,9 +84,9 @@ func TestACLEndpoint_BootstrapTokens(t *testing.T) { require.Error(t, err) require.True(t, strings.HasPrefix(err.Error(), structs.ACLBootstrapNotAllowedErr.Error())) - _, resetIdx, err := s1.fsm.State().CanBootstrapACLToken() + _, resetIdx, err := srv.fsm.State().CanBootstrapACLToken() - resetPath := filepath.Join(dir1, "acl-bootstrap-reset") + resetPath := filepath.Join(dir, "acl-bootstrap-reset") require.NoError(t, ioutil.WriteFile(resetPath, []byte(fmt.Sprintf("%d", resetIdx)), 0600)) oldID := out.AccessorID @@ -119,17 +102,8 @@ func TestACLEndpoint_BootstrapTokens(t *testing.T) { func TestACLEndpoint_Apply(t *testing.T) { t.Parallel() - dir1, s1 := testServerWithConfig(t, func(c *Config) { - c.ACLDatacenter = "dc1" - c.ACLsEnabled = true - c.ACLMasterToken = "root" - }) - defer os.RemoveAll(dir1) - defer s1.Shutdown() - codec := rpcClient(t, s1) - defer codec.Close() - - testrpc.WaitForLeader(t, s1.RPC, "dc1") + _, srv, codec := testACLServerWithConfig(t, nil, false) + waitForLeaderEstablishment(t, srv) arg := structs.ACLRequest{ Datacenter: "dc1", @@ -138,140 +112,88 @@ func TestACLEndpoint_Apply(t *testing.T) { Name: "User token", Type: structs.ACLTokenTypeClient, }, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } var out string - if err := msgpackrpc.CallWithCodec(codec, "ACL.Apply", &arg, &out); err != nil { - t.Fatalf("err: %v", err) - } + err := msgpackrpc.CallWithCodec(codec, "ACL.Apply", &arg, &out) + require.NoError(t, err) id := out // Verify - state := s1.fsm.State() + state := srv.fsm.State() _, s, err := state.ACLTokenGetBySecret(nil, out, nil) - if err != nil { - t.Fatalf("err: %v", err) - } - if s == nil { - t.Fatalf("should not be nil") - } - if s.SecretID != out { - t.Fatalf("bad: %v", s) - } - if s.Description != "User token" { - t.Fatalf("bad: %v", s) - } + require.NoError(t, err) + require.NotNil(t, s) + require.Equal(t, out, s.SecretID) + require.Equal(t, "User token", s.Description) // Do a delete arg.Op = structs.ACLDelete arg.ACL.ID = out - if err := msgpackrpc.CallWithCodec(codec, "ACL.Apply", &arg, &out); err != nil { - t.Fatalf("err: %v", err) - } + err = msgpackrpc.CallWithCodec(codec, "ACL.Apply", &arg, &out) + require.NoError(t, err) // Verify _, s, err = state.ACLTokenGetBySecret(nil, id, nil) - if err != nil { - t.Fatalf("err: %v", err) - } - if s != nil { - t.Fatalf("bad: %v", s) - } + require.NoError(t, err) + require.Nil(t, s) } func TestACLEndpoint_Update_PurgeCache(t *testing.T) { t.Parallel() - dir1, s1 := testServerWithConfig(t, func(c *Config) { - c.ACLDatacenter = "dc1" - c.ACLsEnabled = true - c.ACLMasterToken = "root" - }) - defer os.RemoveAll(dir1) - defer s1.Shutdown() - codec := rpcClient(t, s1) - defer codec.Close() - - testrpc.WaitForLeader(t, s1.RPC, "dc1") + _, srv, codec := testACLServerWithConfig(t, nil, false) + waitForLeaderEstablishment(t, srv) arg := structs.ACLRequest{ Datacenter: "dc1", Op: structs.ACLSet, ACL: structs.ACL{ - Name: "User token", - Type: structs.ACLTokenTypeClient, + Name: "User token", + Type: structs.ACLTokenTypeClient, + Rules: `key "" { policy = "read"}`, }, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } var out string - if err := msgpackrpc.CallWithCodec(codec, "ACL.Apply", &arg, &out); err != nil { - t.Fatalf("err: %v", err) - } + err := msgpackrpc.CallWithCodec(codec, "ACL.Apply", &arg, &out) + require.NoError(t, err) id := out // Resolve - acl1, err := s1.ResolveToken(id) - if err != nil { - t.Fatalf("err: %v", err) - } - if acl1 == nil { - t.Fatalf("should not be nil") - } - if acl1.KeyRead("foo", nil) != acl.Allow { - t.Fatalf("should be allowed") - } + acl1, err := srv.ResolveToken(id) + require.NoError(t, err) + require.NotNil(t, acl1) + require.Equal(t, acl.Allow, acl1.KeyRead("foo", nil)) // Do an update arg.ACL.ID = out arg.ACL.Rules = `{"key": {"": {"policy": "deny"}}}` - if err := msgpackrpc.CallWithCodec(codec, "ACL.Apply", &arg, &out); err != nil { - t.Fatalf("err: %v", err) - } + err = msgpackrpc.CallWithCodec(codec, "ACL.Apply", &arg, &out) + require.NoError(t, err) // Resolve again - acl2, err := s1.ResolveToken(id) - if err != nil { - t.Fatalf("err: %v", err) - } - if acl2 == nil { - t.Fatalf("should not be nil") - } - if acl2 == acl1 { - t.Fatalf("should not be cached") - } - if acl2.KeyRead("foo", nil) == acl.Allow { - t.Fatalf("should not be allowed") - } + acl2, err := srv.ResolveToken(id) + require.NoError(t, err) + require.NotNil(t, acl2) + require.NotSame(t, acl2, acl1) + require.NotEqual(t, acl.Allow, acl2.KeyRead("foo", nil)) // Do a delete arg.Op = structs.ACLDelete arg.ACL.Rules = "" - if err := msgpackrpc.CallWithCodec(codec, "ACL.Apply", &arg, &out); err != nil { - t.Fatalf("err: %v", err) - } + err = msgpackrpc.CallWithCodec(codec, "ACL.Apply", &arg, &out) + require.NoError(t, err) // Resolve again - acl3, err := s1.ResolveToken(id) - if !acl.IsErrNotFound(err) { - t.Fatalf("err: %v", err) - } - if acl3 != nil { - t.Fatalf("should be nil") - } + acl3, err := srv.ResolveToken(id) + require.True(t, acl.IsErrNotFound(err), "Error %v is not acl.ErrNotFound", err) + require.Nil(t, acl3) } func TestACLEndpoint_Apply_CustomID(t *testing.T) { t.Parallel() - dir1, s1 := testServerWithConfig(t, func(c *Config) { - c.ACLDatacenter = "dc1" - c.ACLsEnabled = true - c.ACLMasterToken = "root" - }) - defer os.RemoveAll(dir1) - defer s1.Shutdown() - codec := rpcClient(t, s1) - defer codec.Close() - - testrpc.WaitForLeader(t, s1.RPC, "dc1") + _, srv, codec := testACLServerWithConfig(t, nil, false) + waitForLeaderEstablishment(t, srv) arg := structs.ACLRequest{ Datacenter: "dc1", @@ -281,45 +203,26 @@ func TestACLEndpoint_Apply_CustomID(t *testing.T) { Name: "User token", Type: structs.ACLTokenTypeClient, }, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } var out string - if err := msgpackrpc.CallWithCodec(codec, "ACL.Apply", &arg, &out); err != nil { - t.Fatalf("err: %v", err) - } - if out != "foobarbaz" { - t.Fatalf("bad token ID: %s", out) - } + err := msgpackrpc.CallWithCodec(codec, "ACL.Apply", &arg, &out) + require.NoError(t, err) + require.Equal(t, "foobarbaz", out) // Verify - state := s1.fsm.State() + state := srv.fsm.State() _, s, err := state.ACLTokenGetBySecret(nil, out, nil) - if err != nil { - t.Fatalf("err: %v", err) - } - if s == nil { - t.Fatalf("should not be nil") - } - if s.SecretID != out { - t.Fatalf("bad: %v", s) - } - if s.Description != "User token" { - t.Fatalf("bad: %v", s) - } + require.NoError(t, err) + require.NotNil(t, s) + require.Equal(t, out, s.SecretID) + require.Equal(t, "User token", s.Description) } func TestACLEndpoint_Apply_Denied(t *testing.T) { t.Parallel() - dir1, s1 := testServerWithConfig(t, func(c *Config) { - c.ACLDatacenter = "dc1" - c.ACLsEnabled = true - }) - defer os.RemoveAll(dir1) - defer s1.Shutdown() - codec := rpcClient(t, s1) - defer codec.Close() - - testrpc.WaitForLeader(t, s1.RPC, "dc1") + _, srv, codec := testACLServerWithConfig(t, nil, false) + waitForLeaderEstablishment(t, srv) arg := structs.ACLRequest{ Datacenter: "dc1", @@ -331,24 +234,13 @@ func TestACLEndpoint_Apply_Denied(t *testing.T) { } var out string err := msgpackrpc.CallWithCodec(codec, "ACL.Apply", &arg, &out) - if !acl.IsErrPermissionDenied(err) { - t.Fatalf("err: %v", err) - } + require.True(t, acl.IsErrPermissionDenied(err), "Err %v is not acl.PermissionDenied", err) } func TestACLEndpoint_Apply_DeleteAnon(t *testing.T) { t.Parallel() - dir1, s1 := testServerWithConfig(t, func(c *Config) { - c.ACLDatacenter = "dc1" - c.ACLsEnabled = true - c.ACLMasterToken = "root" - }) - defer os.RemoveAll(dir1) - defer s1.Shutdown() - codec := rpcClient(t, s1) - defer codec.Close() - - testrpc.WaitForLeader(t, s1.RPC, "dc1") + _, srv, codec := testACLServerWithConfig(t, nil, false) + waitForLeaderEstablishment(t, srv) arg := structs.ACLRequest{ Datacenter: "dc1", @@ -358,28 +250,17 @@ func TestACLEndpoint_Apply_DeleteAnon(t *testing.T) { Name: "User token", Type: structs.ACLTokenTypeClient, }, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } var out string err := msgpackrpc.CallWithCodec(codec, "ACL.Apply", &arg, &out) - if err == nil || !strings.Contains(err.Error(), "delete anonymous") { - t.Fatalf("err: %v", err) - } + testutil.RequireErrorContains(t, err, "delete anonymous") } func TestACLEndpoint_Apply_RootChange(t *testing.T) { t.Parallel() - dir1, s1 := testServerWithConfig(t, func(c *Config) { - c.ACLDatacenter = "dc1" - c.ACLsEnabled = true - c.ACLMasterToken = "root" - }) - defer os.RemoveAll(dir1) - defer s1.Shutdown() - codec := rpcClient(t, s1) - defer codec.Close() - - testrpc.WaitForLeader(t, s1.RPC, "dc1") + _, srv, codec := testACLServerWithConfig(t, nil, false) + waitForLeaderEstablishment(t, srv) arg := structs.ACLRequest{ Datacenter: "dc1", @@ -389,28 +270,17 @@ func TestACLEndpoint_Apply_RootChange(t *testing.T) { Name: "User token", Type: structs.ACLTokenTypeClient, }, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } var out string err := msgpackrpc.CallWithCodec(codec, "ACL.Apply", &arg, &out) - if err == nil || !strings.Contains(err.Error(), "root ACL") { - t.Fatalf("err: %v", err) - } + testutil.RequireErrorContains(t, err, "root ACL") } func TestACLEndpoint_Get(t *testing.T) { t.Parallel() - dir1, s1 := testServerWithConfig(t, func(c *Config) { - c.ACLDatacenter = "dc1" - c.ACLsEnabled = true - c.ACLMasterToken = "root" - }) - defer os.RemoveAll(dir1) - defer s1.Shutdown() - codec := rpcClient(t, s1) - defer codec.Close() - - testrpc.WaitForLeader(t, s1.RPC, "dc1") + _, srv, codec := testACLServerWithConfig(t, nil, false) + waitForLeaderEstablishment(t, srv) arg := structs.ACLRequest{ Datacenter: "dc1", @@ -419,48 +289,28 @@ func TestACLEndpoint_Get(t *testing.T) { Name: "User token", Type: structs.ACLTokenTypeClient, }, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } var out string - if err := msgpackrpc.CallWithCodec(codec, "ACL.Apply", &arg, &out); err != nil { - t.Fatalf("err: %v", err) - } + err := msgpackrpc.CallWithCodec(codec, "ACL.Apply", &arg, &out) + require.NoError(t, err) getR := structs.ACLSpecificRequest{ Datacenter: "dc1", ACL: out, } var acls structs.IndexedACLs - if err := msgpackrpc.CallWithCodec(codec, "ACL.Get", &getR, &acls); err != nil { - t.Fatalf("err: %v", err) - } - - if acls.Index == 0 { - t.Fatalf("Bad: %v", acls) - } - if len(acls.ACLs) != 1 { - t.Fatalf("Bad: %v", acls) - } - s := acls.ACLs[0] - if s.ID != out { - t.Fatalf("bad: %v", s) - } + err = msgpackrpc.CallWithCodec(codec, "ACL.Get", &getR, &acls) + require.NoError(t, err) + require.NotEqual(t, uint64(0), acls.Index) + require.Len(t, acls.ACLs, 1) + require.Equal(t, out, acls.ACLs[0].ID) } func TestACLEndpoint_GetPolicy(t *testing.T) { t.Parallel() - dir1, s1 := testServerWithConfig(t, func(c *Config) { - c.ACLDatacenter = "dc1" - c.ACLsEnabled = true - c.ACLMasterToken = "root" - c.ACLDefaultPolicy = "deny" - }) - defer os.RemoveAll(dir1) - defer s1.Shutdown() - codec := rpcClient(t, s1) - defer codec.Close() - - testrpc.WaitForLeader(t, s1.RPC, "dc1") + _, srv, codec := testACLServerWithConfig(t, nil, false) + waitForLeaderEstablishment(t, srv) arg := structs.ACLRequest{ Datacenter: "dc1", @@ -469,10 +319,11 @@ func TestACLEndpoint_GetPolicy(t *testing.T) { Name: "User token", Type: structs.ACLTokenTypeClient, }, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } var out string - require.NoError(t, msgpackrpc.CallWithCodec(codec, "ACL.Apply", &arg, &out)) + err := msgpackrpc.CallWithCodec(codec, "ACL.Apply", &arg, &out) + require.NoError(t, err) getR := structs.ACLPolicyResolveLegacyRequest{ Datacenter: "dc1", @@ -481,9 +332,10 @@ func TestACLEndpoint_GetPolicy(t *testing.T) { var acls structs.ACLPolicyResolveLegacyResponse retry.Run(t, func(r *retry.R) { - require.NoError(r, msgpackrpc.CallWithCodec(codec, "ACL.GetPolicy", &getR, &acls)) + err := msgpackrpc.CallWithCodec(codec, "ACL.GetPolicy", &getR, &acls) + + require.NoError(r, err) require.NotNil(t, acls.Policy) - require.Equal(t, "deny", acls.Parent) require.Equal(t, 30*time.Second, acls.TTL) }) @@ -498,20 +350,16 @@ func TestACLEndpoint_GetPolicy(t *testing.T) { func TestACLEndpoint_GetPolicy_Management(t *testing.T) { t.Parallel() - dir1, s1 := testServerWithConfig(t, testServerACLConfig(nil)) - defer os.RemoveAll(dir1) - defer s1.Shutdown() - codec := rpcClient(t, s1) - defer codec.Close() + _, srv, codec := testACLServerWithConfig(t, nil, false) // wait for leader election and leader establishment to finish. // after this the global management policy, master token and // anonymous token will have been injected into the state store // and we will be ready to resolve the master token - waitForLeaderEstablishment(t, s1) + waitForLeaderEstablishment(t, srv) req := structs.ACLPolicyResolveLegacyRequest{ - Datacenter: s1.config.Datacenter, + Datacenter: srv.config.Datacenter, ACL: TestDefaultMasterToken, } @@ -522,19 +370,10 @@ func TestACLEndpoint_GetPolicy_Management(t *testing.T) { func TestACLEndpoint_List(t *testing.T) { t.Parallel() - dir1, s1 := testServerWithConfig(t, func(c *Config) { - c.ACLDatacenter = "dc1" - c.ACLsEnabled = true - c.ACLMasterToken = "root" - }) - defer os.RemoveAll(dir1) - defer s1.Shutdown() - codec := rpcClient(t, s1) - defer codec.Close() + _, srv, codec := testACLServerWithConfig(t, nil, false) + waitForLeaderEstablishment(t, srv) + var expectedIDs []string - testrpc.WaitForLeader(t, s1.RPC, "dc1") - - ids := []string{} for i := 0; i < 5; i++ { arg := structs.ACLRequest{ Datacenter: "dc1", @@ -543,85 +382,62 @@ func TestACLEndpoint_List(t *testing.T) { Name: "User token", Type: structs.ACLTokenTypeClient, }, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } var out string - if err := msgpackrpc.CallWithCodec(codec, "ACL.Apply", &arg, &out); err != nil { - t.Fatalf("err: %v", err) - } - ids = append(ids, out) + err := msgpackrpc.CallWithCodec(codec, "ACL.Apply", &arg, &out) + require.NoError(t, err) + expectedIDs = append(expectedIDs, out) } getR := structs.DCSpecificRequest{ Datacenter: "dc1", - QueryOptions: structs.QueryOptions{Token: "root"}, + QueryOptions: structs.QueryOptions{Token: TestDefaultMasterToken}, } var acls structs.IndexedACLs - if err := msgpackrpc.CallWithCodec(codec, "ACL.List", &getR, &acls); err != nil { - t.Fatalf("err: %v", err) - } - - if acls.Index == 0 { - t.Fatalf("Bad: %v", acls) - } + err := msgpackrpc.CallWithCodec(codec, "ACL.List", &getR, &acls) + require.NoError(t, err) + require.NotEqual(t, uint64(0), acls.Index) // 5 + master - if len(acls.ACLs) != 6 { - t.Fatalf("Bad: %v", acls.ACLs) - } + require.Len(t, acls.ACLs, 6) + var actualIDs []string for i := 0; i < len(acls.ACLs); i++ { s := acls.ACLs[i] - if s.ID == anonymousToken || s.ID == "root" { + if s.ID == anonymousToken || s.ID == TestDefaultMasterToken { continue } - if !stringslice.Contains(ids, s.ID) { - t.Fatalf("bad: %v", s) - } - if s.Name != "User token" { - t.Fatalf("bad: %v", s) - } + + require.Equal(t, "User token", s.Name) + + actualIDs = append(actualIDs, s.ID) } + + require.ElementsMatch(t, expectedIDs, actualIDs) } func TestACLEndpoint_List_Denied(t *testing.T) { t.Parallel() - dir1, s1 := testServerWithConfig(t, func(c *Config) { - c.ACLDatacenter = "dc1" - c.ACLsEnabled = true - }) - defer os.RemoveAll(dir1) - defer s1.Shutdown() - codec := rpcClient(t, s1) - defer codec.Close() - - testrpc.WaitForLeader(t, s1.RPC, "dc1") + _, srv, codec := testACLServerWithConfig(t, nil, false) + waitForLeaderEstablishment(t, srv) getR := structs.DCSpecificRequest{ Datacenter: "dc1", } var acls structs.IndexedACLs err := msgpackrpc.CallWithCodec(codec, "ACL.List", &getR, &acls) - if !acl.IsErrPermissionDenied(err) { - t.Fatalf("err: %v", err) - } + require.True(t, acl.IsErrPermissionDenied(err), "Err %v is not an acl.ErrPermissionDenied", err) } func TestACLEndpoint_ReplicationStatus(t *testing.T) { t.Parallel() - dir1, s1 := testServerWithConfig(t, func(c *Config) { + _, srv, codec := testACLServerWithConfig(t, func(c *Config) { c.ACLDatacenter = "dc2" - c.ACLsEnabled = true c.ACLTokenReplication = true c.ACLReplicationRate = 100 c.ACLReplicationBurst = 100 - }) - s1.tokens.UpdateReplicationToken("secret", tokenStore.TokenSourceConfig) - defer os.RemoveAll(dir1) - defer s1.Shutdown() - codec := rpcClient(t, s1) - defer codec.Close() - - testrpc.WaitForLeader(t, s1.RPC, "dc1") + }, true) + waitForLeaderEstablishment(t, srv) getR := structs.DCSpecificRequest{ Datacenter: "dc1", @@ -630,43 +446,34 @@ func TestACLEndpoint_ReplicationStatus(t *testing.T) { retry.Run(t, func(r *retry.R) { var status structs.ACLReplicationStatus err := msgpackrpc.CallWithCodec(codec, "ACL.ReplicationStatus", &getR, &status) - if err != nil { - r.Fatalf("err: %v", err) - } - if !status.Enabled || !status.Running || status.SourceDatacenter != "dc2" { - r.Fatalf("bad: %#v", status) - } + require.NoError(t, err) + + require.True(t, status.Enabled) + require.True(t, status.Running) + require.Equal(t, "dc2", status.SourceDatacenter) }) } func TestACLEndpoint_TokenRead(t *testing.T) { t.Parallel() - - dir1, s1 := testServerWithConfig(t, func(c *Config) { - c.ACLDatacenter = "dc1" - c.ACLsEnabled = true - c.ACLMasterToken = "root" + _, srv, codec := testACLServerWithConfig(t, func(c *Config) { c.ACLTokenMinExpirationTTL = 10 * time.Millisecond c.ACLTokenMaxExpirationTTL = 5 * time.Second - }) - defer os.RemoveAll(dir1) - defer s1.Shutdown() - codec := rpcClient(t, s1) - defer codec.Close() + }, false) - testrpc.WaitForLeader(t, s1.RPC, "dc1") + waitForLeaderEstablishment(t, srv) - acl := ACL{srv: s1} + acl := ACL{srv: srv} t.Run("exists and matches what we created", func(t *testing.T) { - token, err := upsertTestToken(codec, "root", "dc1", nil) + token, err := upsertTestToken(codec, TestDefaultMasterToken, "dc1", nil) require.NoError(t, err) req := structs.ACLTokenGetRequest{ Datacenter: "dc1", TokenID: token.AccessorID, TokenIDType: structs.ACLTokenAccessor, - QueryOptions: structs.QueryOptions{Token: "root"}, + QueryOptions: structs.QueryOptions{Token: TestDefaultMasterToken}, } resp := structs.ACLTokenResponse{} @@ -674,14 +481,12 @@ func TestACLEndpoint_TokenRead(t *testing.T) { err = acl.TokenRead(&req, &resp) require.NoError(t, err) - if !reflect.DeepEqual(resp.Token, token) { - t.Fatalf("tokens are not equal: %v != %v", resp.Token, token) - } + require.Equal(t, token, resp.Token) }) t.Run("expired tokens are filtered", func(t *testing.T) { // insert a token that will expire - token, err := upsertTestToken(codec, "root", "dc1", func(t *structs.ACLToken) { + token, err := upsertTestToken(codec, TestDefaultMasterToken, "dc1", func(t *structs.ACLToken) { t.ExpirationTTL = 200 * time.Millisecond }) require.NoError(t, err) @@ -691,7 +496,7 @@ func TestACLEndpoint_TokenRead(t *testing.T) { Datacenter: "dc1", TokenID: token.AccessorID, TokenIDType: structs.ACLTokenAccessor, - QueryOptions: structs.QueryOptions{Token: "root"}, + QueryOptions: structs.QueryOptions{Token: TestDefaultMasterToken}, } resp := structs.ACLTokenResponse{} @@ -705,7 +510,7 @@ func TestACLEndpoint_TokenRead(t *testing.T) { Datacenter: "dc1", TokenID: token.AccessorID, TokenIDType: structs.ACLTokenAccessor, - QueryOptions: structs.QueryOptions{Token: "root"}, + QueryOptions: structs.QueryOptions{Token: TestDefaultMasterToken}, } resp := structs.ACLTokenResponse{} @@ -725,7 +530,7 @@ func TestACLEndpoint_TokenRead(t *testing.T) { Datacenter: "dc1", TokenID: fakeID, TokenIDType: structs.ACLTokenAccessor, - QueryOptions: structs.QueryOptions{Token: "root"}, + QueryOptions: structs.QueryOptions{Token: TestDefaultMasterToken}, } resp := structs.ACLTokenResponse{} @@ -740,7 +545,7 @@ func TestACLEndpoint_TokenRead(t *testing.T) { Datacenter: "dc1", TokenID: "definitely-really-certainly-not-a-uuid", TokenIDType: structs.ACLTokenAccessor, - QueryOptions: structs.QueryOptions{Token: "root"}, + QueryOptions: structs.QueryOptions{Token: TestDefaultMasterToken}, } resp := structs.ACLTokenResponse{} @@ -754,27 +559,19 @@ func TestACLEndpoint_TokenRead(t *testing.T) { func TestACLEndpoint_TokenClone(t *testing.T) { t.Parallel() - dir1, s1 := testServerWithConfig(t, func(c *Config) { - c.ACLDatacenter = "dc1" - c.ACLsEnabled = true - c.ACLMasterToken = "root" + _, srv, codec := testACLServerWithConfig(t, func(c *Config) { c.ACLTokenMinExpirationTTL = 10 * time.Millisecond c.ACLTokenMaxExpirationTTL = 5 * time.Second - }) - defer os.RemoveAll(dir1) - defer s1.Shutdown() - codec := rpcClient(t, s1) - defer codec.Close() + }, false) + waitForLeaderEstablishment(t, srv) - testrpc.WaitForLeader(t, s1.RPC, "dc1") - - p1, err := upsertTestPolicy(codec, "root", "dc1") + p1, err := upsertTestPolicy(codec, TestDefaultMasterToken, "dc1") require.NoError(t, err) - r1, err := upsertTestRole(codec, "root", "dc1") + r1, err := upsertTestRole(codec, TestDefaultMasterToken, "dc1") require.NoError(t, err) - t1, err := upsertTestToken(codec, "root", "dc1", func(t *structs.ACLToken) { + t1, err := upsertTestToken(codec, TestDefaultMasterToken, "dc1", func(t *structs.ACLToken) { t.Policies = []structs.ACLTokenPolicyLink{ {ID: p1.ID}, } @@ -782,18 +579,21 @@ func TestACLEndpoint_TokenClone(t *testing.T) { {ID: r1.ID}, } t.ServiceIdentities = []*structs.ACLServiceIdentity{ - &structs.ACLServiceIdentity{ServiceName: "web"}, + {ServiceName: "web"}, + } + t.NodeIdentities = []*structs.ACLNodeIdentity{ + {NodeName: "foo", Datacenter: "bar"}, } }) require.NoError(t, err) - endpoint := ACL{srv: s1} + endpoint := ACL{srv: srv} t.Run("normal", func(t *testing.T) { req := structs.ACLTokenSetRequest{ Datacenter: "dc1", ACLToken: structs.ACLToken{AccessorID: t1.AccessorID}, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } t2 := structs.ACLToken{} @@ -805,6 +605,7 @@ func TestACLEndpoint_TokenClone(t *testing.T) { require.Equal(t, t1.Policies, t2.Policies) require.Equal(t, t1.Roles, t2.Roles) require.Equal(t, t1.ServiceIdentities, t2.ServiceIdentities) + require.Equal(t, t1.NodeIdentities, t2.NodeIdentities) require.Equal(t, t1.Rules, t2.Rules) require.Equal(t, t1.Local, t2.Local) require.NotEqual(t, t1.AccessorID, t2.AccessorID) @@ -813,7 +614,7 @@ func TestACLEndpoint_TokenClone(t *testing.T) { t.Run("can't clone expired token", func(t *testing.T) { // insert a token that will expire - t1, err := upsertTestToken(codec, "root", "dc1", func(t *structs.ACLToken) { + t1, err := upsertTestToken(codec, TestDefaultMasterToken, "dc1", func(t *structs.ACLToken) { t.ExpirationTTL = 11 * time.Millisecond }) require.NoError(t, err) @@ -823,7 +624,7 @@ func TestACLEndpoint_TokenClone(t *testing.T) { req := structs.ACLTokenSetRequest{ Datacenter: "dc1", ACLToken: structs.ACLToken{AccessorID: t1.AccessorID}, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } t2 := structs.ACLToken{} @@ -837,21 +638,13 @@ func TestACLEndpoint_TokenClone(t *testing.T) { func TestACLEndpoint_TokenSet(t *testing.T) { t.Parallel() - dir1, s1 := testServerWithConfig(t, func(c *Config) { - c.ACLDatacenter = "dc1" - c.ACLsEnabled = true - c.ACLMasterToken = "root" + _, srv, codec := testACLServerWithConfig(t, func(c *Config) { c.ACLTokenMinExpirationTTL = 10 * time.Millisecond c.ACLTokenMaxExpirationTTL = 5 * time.Second - }) - defer os.RemoveAll(dir1) - defer s1.Shutdown() - codec := rpcClient(t, s1) - defer codec.Close() + }, false) + waitForLeaderEstablishment(t, srv) - testrpc.WaitForLeader(t, s1.RPC, "dc1") - - acl := ACL{srv: s1} + acl := ACL{srv: srv} var tokenID string @@ -862,8 +655,14 @@ func TestACLEndpoint_TokenSet(t *testing.T) { Description: "foobar", Policies: nil, Local: false, + NodeIdentities: []*structs.ACLNodeIdentity{ + { + NodeName: "foo", + Datacenter: "dc1", + }, + }, }, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } resp := structs.ACLToken{} @@ -872,7 +671,7 @@ func TestACLEndpoint_TokenSet(t *testing.T) { require.NoError(t, err) // Get the token directly to validate that it exists - tokenResp, err := retrieveTestToken(codec, "root", "dc1", resp.AccessorID) + tokenResp, err := retrieveTestToken(codec, TestDefaultMasterToken, "dc1", resp.AccessorID) require.NoError(t, err) token := tokenResp.Token @@ -880,6 +679,9 @@ func TestACLEndpoint_TokenSet(t *testing.T) { require.NotNil(t, token.AccessorID) require.Equal(t, token.Description, "foobar") require.Equal(t, token.AccessorID, resp.AccessorID) + require.Len(t, token.NodeIdentities, 1) + require.Equal(t, "foo", token.NodeIdentities[0].NodeName) + require.Equal(t, "dc1", token.NodeIdentities[0].Datacenter) tokenID = token.AccessorID }) @@ -891,7 +693,7 @@ func TestACLEndpoint_TokenSet(t *testing.T) { Description: "new-description", AccessorID: tokenID, }, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } resp := structs.ACLToken{} @@ -900,7 +702,7 @@ func TestACLEndpoint_TokenSet(t *testing.T) { require.NoError(t, err) // Get the token directly to validate that it exists - tokenResp, err := retrieveTestToken(codec, "root", "dc1", resp.AccessorID) + tokenResp, err := retrieveTestToken(codec, TestDefaultMasterToken, "dc1", resp.AccessorID) require.NoError(t, err) token := tokenResp.Token @@ -908,12 +710,13 @@ func TestACLEndpoint_TokenSet(t *testing.T) { require.NotNil(t, token.AccessorID) require.Equal(t, token.Description, "new-description") require.Equal(t, token.AccessorID, resp.AccessorID) + require.Empty(t, token.NodeIdentities) }) t.Run("Create it using Policies linked by id and name", func(t *testing.T) { - policy1, err := upsertTestPolicy(codec, "root", "dc1") + policy1, err := upsertTestPolicy(codec, TestDefaultMasterToken, "dc1") require.NoError(t, err) - policy2, err := upsertTestPolicy(codec, "root", "dc1") + policy2, err := upsertTestPolicy(codec, TestDefaultMasterToken, "dc1") require.NoError(t, err) req := structs.ACLTokenSetRequest{ @@ -921,16 +724,16 @@ func TestACLEndpoint_TokenSet(t *testing.T) { ACLToken: structs.ACLToken{ Description: "foobar", Policies: []structs.ACLTokenPolicyLink{ - structs.ACLTokenPolicyLink{ + { ID: policy1.ID, }, - structs.ACLTokenPolicyLink{ + { Name: policy2.Name, }, }, Local: false, }, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } resp := structs.ACLToken{} @@ -940,11 +743,11 @@ func TestACLEndpoint_TokenSet(t *testing.T) { // Delete both policies to ensure that we skip resolving ID->Name // in the returned data. - require.NoError(t, deleteTestPolicy(codec, "root", "dc1", policy1.ID)) - require.NoError(t, deleteTestPolicy(codec, "root", "dc1", policy2.ID)) + require.NoError(t, deleteTestPolicy(codec, TestDefaultMasterToken, "dc1", policy1.ID)) + require.NoError(t, deleteTestPolicy(codec, TestDefaultMasterToken, "dc1", policy2.ID)) // Get the token directly to validate that it exists - tokenResp, err := retrieveTestToken(codec, "root", "dc1", resp.AccessorID) + tokenResp, err := retrieveTestToken(codec, TestDefaultMasterToken, "dc1", resp.AccessorID) require.NoError(t, err) token := tokenResp.Token @@ -957,9 +760,9 @@ func TestACLEndpoint_TokenSet(t *testing.T) { }) t.Run("Create it using Roles linked by id and name", func(t *testing.T) { - role1, err := upsertTestRole(codec, "root", "dc1") + role1, err := upsertTestRole(codec, TestDefaultMasterToken, "dc1") require.NoError(t, err) - role2, err := upsertTestRole(codec, "root", "dc1") + role2, err := upsertTestRole(codec, TestDefaultMasterToken, "dc1") require.NoError(t, err) req := structs.ACLTokenSetRequest{ @@ -967,16 +770,16 @@ func TestACLEndpoint_TokenSet(t *testing.T) { ACLToken: structs.ACLToken{ Description: "foobar", Roles: []structs.ACLTokenRoleLink{ - structs.ACLTokenRoleLink{ + { ID: role1.ID, }, - structs.ACLTokenRoleLink{ + { Name: role2.Name, }, }, Local: false, }, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } resp := structs.ACLToken{} @@ -986,11 +789,11 @@ func TestACLEndpoint_TokenSet(t *testing.T) { // Delete both roles to ensure that we skip resolving ID->Name // in the returned data. - require.NoError(t, deleteTestRole(codec, "root", "dc1", role1.ID)) - require.NoError(t, deleteTestRole(codec, "root", "dc1", role2.ID)) + require.NoError(t, deleteTestRole(codec, TestDefaultMasterToken, "dc1", role1.ID)) + require.NoError(t, deleteTestRole(codec, TestDefaultMasterToken, "dc1", role2.ID)) // Get the token directly to validate that it exists - tokenResp, err := retrieveTestToken(codec, "root", "dc1", resp.AccessorID) + tokenResp, err := retrieveTestToken(codec, TestDefaultMasterToken, "dc1", resp.AccessorID) require.NoError(t, err) token := tokenResp.Token @@ -1009,7 +812,7 @@ func TestACLEndpoint_TokenSet(t *testing.T) { Description: "foobar", AuthMethod: "fakemethod", }, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } resp := structs.ACLToken{} @@ -1019,16 +822,16 @@ func TestACLEndpoint_TokenSet(t *testing.T) { }) t.Run("Update auth method linked token and try to change auth method", func(t *testing.T) { - acl := ACL{srv: s1} + acl := ACL{srv: srv} testSessionID := testauth.StartSession() defer testauth.ResetSession(testSessionID) testauth.InstallSessionToken(testSessionID, "fake-token", "default", "demo", "abc123") - method1, err := upsertTestAuthMethod(codec, "root", "dc1", testSessionID) + method1, err := upsertTestAuthMethod(codec, TestDefaultMasterToken, "dc1", testSessionID) require.NoError(t, err) - _, err = upsertTestBindingRule(codec, "root", "dc1", method1.Name, "", structs.BindingRuleBindTypeService, "demo") + _, err = upsertTestBindingRule(codec, TestDefaultMasterToken, "dc1", method1.Name, "", structs.BindingRuleBindTypeService, "demo") require.NoError(t, err) // create a token in one method @@ -1041,7 +844,7 @@ func TestACLEndpoint_TokenSet(t *testing.T) { Datacenter: "dc1", }, &methodToken)) - method2, err := upsertTestAuthMethod(codec, "root", "dc1", "") + method2, err := upsertTestAuthMethod(codec, TestDefaultMasterToken, "dc1", "") require.NoError(t, err) // try to update the token and change the method @@ -1054,7 +857,7 @@ func TestACLEndpoint_TokenSet(t *testing.T) { Description: "updated token", Local: true, }, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } resp := structs.ACLToken{} @@ -1064,16 +867,16 @@ func TestACLEndpoint_TokenSet(t *testing.T) { }) t.Run("Update auth method linked token and let the SecretID and AuthMethod be defaulted", func(t *testing.T) { - acl := ACL{srv: s1} + acl := ACL{srv: srv} testSessionID := testauth.StartSession() defer testauth.ResetSession(testSessionID) testauth.InstallSessionToken(testSessionID, "fake-token", "default", "demo", "abc123") - method, err := upsertTestAuthMethod(codec, "root", "dc1", testSessionID) + method, err := upsertTestAuthMethod(codec, TestDefaultMasterToken, "dc1", testSessionID) require.NoError(t, err) - _, err = upsertTestBindingRule(codec, "root", "dc1", method.Name, "", structs.BindingRuleBindTypeService, "demo") + _, err = upsertTestBindingRule(codec, TestDefaultMasterToken, "dc1", method.Name, "", structs.BindingRuleBindTypeService, "demo") require.NoError(t, err) methodToken := structs.ACLToken{} @@ -1094,7 +897,7 @@ func TestACLEndpoint_TokenSet(t *testing.T) { Description: "updated token", Local: true, }, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } resp := structs.ACLToken{} @@ -1102,7 +905,7 @@ func TestACLEndpoint_TokenSet(t *testing.T) { require.NoError(t, acl.TokenSet(&req, &resp)) // Get the token directly to validate that it exists - tokenResp, err := retrieveTestToken(codec, "root", "dc1", resp.AccessorID) + tokenResp, err := retrieveTestToken(codec, TestDefaultMasterToken, "dc1", resp.AccessorID) require.NoError(t, err) token := tokenResp.Token @@ -1122,10 +925,10 @@ func TestACLEndpoint_TokenSet(t *testing.T) { Policies: nil, Local: false, ServiceIdentities: []*structs.ACLServiceIdentity{ - &structs.ACLServiceIdentity{ServiceName: ""}, + {ServiceName: ""}, }, }, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } resp := structs.ACLToken{} @@ -1143,10 +946,10 @@ func TestACLEndpoint_TokenSet(t *testing.T) { Policies: nil, Local: false, ServiceIdentities: []*structs.ACLServiceIdentity{ - &structs.ACLServiceIdentity{ServiceName: long}, + {ServiceName: long}, }, }, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } resp := structs.ACLToken{} @@ -1189,10 +992,10 @@ func TestACLEndpoint_TokenSet(t *testing.T) { Policies: nil, Local: false, ServiceIdentities: []*structs.ACLServiceIdentity{ - &structs.ACLServiceIdentity{ServiceName: test.name}, + {ServiceName: test.name}, }, }, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } resp := structs.ACLToken{} @@ -1202,7 +1005,7 @@ func TestACLEndpoint_TokenSet(t *testing.T) { require.NoError(t, err) // Get the token directly to validate that it exists - tokenResp, err := retrieveTestToken(codec, "root", "dc1", resp.AccessorID) + tokenResp, err := retrieveTestToken(codec, TestDefaultMasterToken, "dc1", resp.AccessorID) require.NoError(t, err) token := tokenResp.Token require.NotNil(t, token) @@ -1221,11 +1024,11 @@ func TestACLEndpoint_TokenSet(t *testing.T) { Policies: nil, Local: false, ServiceIdentities: []*structs.ACLServiceIdentity{ - &structs.ACLServiceIdentity{ServiceName: "example"}, - &structs.ACLServiceIdentity{ServiceName: "example"}, + {ServiceName: "example"}, + {ServiceName: "example"}, }, }, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } resp := structs.ACLToken{} @@ -1234,7 +1037,7 @@ func TestACLEndpoint_TokenSet(t *testing.T) { require.NoError(t, err) // Get the token directly to validate that it exists - tokenResp, err := retrieveTestToken(codec, "root", "dc1", resp.AccessorID) + tokenResp, err := retrieveTestToken(codec, TestDefaultMasterToken, "dc1", resp.AccessorID) require.NoError(t, err) token := tokenResp.Token require.NotNil(t, token) @@ -1249,17 +1052,17 @@ func TestACLEndpoint_TokenSet(t *testing.T) { Policies: nil, Local: false, ServiceIdentities: []*structs.ACLServiceIdentity{ - &structs.ACLServiceIdentity{ + { ServiceName: "example", Datacenters: []string{"dc2", "dc3"}, }, - &structs.ACLServiceIdentity{ + { ServiceName: "example", Datacenters: []string{"dc1", "dc2"}, }, }, }, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } resp := structs.ACLToken{} @@ -1268,7 +1071,7 @@ func TestACLEndpoint_TokenSet(t *testing.T) { require.NoError(t, err) // Get the token directly to validate that it exists - tokenResp, err := retrieveTestToken(codec, "root", "dc1", resp.AccessorID) + tokenResp, err := retrieveTestToken(codec, TestDefaultMasterToken, "dc1", resp.AccessorID) require.NoError(t, err) token := tokenResp.Token require.NotNil(t, token) @@ -1286,10 +1089,10 @@ func TestACLEndpoint_TokenSet(t *testing.T) { Policies: nil, Local: true, ServiceIdentities: []*structs.ACLServiceIdentity{ - &structs.ACLServiceIdentity{ServiceName: "foo", Datacenters: []string{"dc2"}}, + {ServiceName: "foo", Datacenters: []string{"dc2"}}, }, }, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } resp := structs.ACLToken{} @@ -1317,7 +1120,7 @@ func TestACLEndpoint_TokenSet(t *testing.T) { Local: false, ExpirationTime: timePointer(time.Now().Add(test.offset)), }, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } resp := structs.ACLToken{} @@ -1339,7 +1142,7 @@ func TestACLEndpoint_TokenSet(t *testing.T) { Local: false, ExpirationTTL: test.offset, }, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } resp := structs.ACLToken{} @@ -1363,7 +1166,7 @@ func TestACLEndpoint_TokenSet(t *testing.T) { ExpirationTime: timePointer(time.Now().Add(4 * time.Second)), ExpirationTTL: 4 * time.Second, }, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } resp := structs.ACLToken{} @@ -1381,7 +1184,7 @@ func TestACLEndpoint_TokenSet(t *testing.T) { Local: false, ExpirationTTL: 4 * time.Second, }, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } resp := structs.ACLToken{} @@ -1390,7 +1193,7 @@ func TestACLEndpoint_TokenSet(t *testing.T) { require.NoError(t, err) // Get the token directly to validate that it exists - tokenResp, err := retrieveTestToken(codec, "root", "dc1", resp.AccessorID) + tokenResp, err := retrieveTestToken(codec, TestDefaultMasterToken, "dc1", resp.AccessorID) require.NoError(t, err) token := tokenResp.Token @@ -1416,7 +1219,7 @@ func TestACLEndpoint_TokenSet(t *testing.T) { Local: false, ExpirationTime: &expTime, }, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } resp := structs.ACLToken{} @@ -1425,7 +1228,7 @@ func TestACLEndpoint_TokenSet(t *testing.T) { require.NoError(t, err) // Get the token directly to validate that it exists - tokenResp, err := retrieveTestToken(codec, "root", "dc1", resp.AccessorID) + tokenResp, err := retrieveTestToken(codec, TestDefaultMasterToken, "dc1", resp.AccessorID) require.NoError(t, err) token := tokenResp.Token @@ -1448,7 +1251,7 @@ func TestACLEndpoint_TokenSet(t *testing.T) { AccessorID: tokenID, ExpirationTime: timePointer(expTime.Add(-1 * time.Second)), }, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } resp := structs.ACLToken{} @@ -1466,7 +1269,7 @@ func TestACLEndpoint_TokenSet(t *testing.T) { Description: "new-description-1", AccessorID: tokenID, }, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } resp := structs.ACLToken{} @@ -1475,7 +1278,7 @@ func TestACLEndpoint_TokenSet(t *testing.T) { require.NoError(t, err) // Get the token directly to validate that it exists - tokenResp, err := retrieveTestToken(codec, "root", "dc1", resp.AccessorID) + tokenResp, err := retrieveTestToken(codec, TestDefaultMasterToken, "dc1", resp.AccessorID) require.NoError(t, err) token := tokenResp.Token @@ -1494,7 +1297,7 @@ func TestACLEndpoint_TokenSet(t *testing.T) { AccessorID: tokenID, ExpirationTime: &expTime, }, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } resp := structs.ACLToken{} @@ -1503,7 +1306,7 @@ func TestACLEndpoint_TokenSet(t *testing.T) { require.NoError(t, err) // Get the token directly to validate that it exists - tokenResp, err := retrieveTestToken(codec, "root", "dc1", resp.AccessorID) + tokenResp, err := retrieveTestToken(codec, TestDefaultMasterToken, "dc1", resp.AccessorID) require.NoError(t, err) token := tokenResp.Token @@ -1516,7 +1319,7 @@ func TestACLEndpoint_TokenSet(t *testing.T) { t.Run("cannot update a token that is past its expiration time", func(t *testing.T) { // create a token that will expire - expiringToken, err := upsertTestToken(codec, "root", "dc1", func(token *structs.ACLToken) { + expiringToken, err := upsertTestToken(codec, TestDefaultMasterToken, "dc1", func(token *structs.ACLToken) { token.ExpirationTTL = 11 * time.Millisecond }) require.NoError(t, err) @@ -1530,7 +1333,7 @@ func TestACLEndpoint_TokenSet(t *testing.T) { AccessorID: expiringToken.AccessorID, ExpirationTTL: 4 * time.Second, }, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } resp := structs.ACLToken{} @@ -1538,24 +1341,72 @@ func TestACLEndpoint_TokenSet(t *testing.T) { err = acl.TokenSet(&req, &resp) testutil.RequireErrorContains(t, err, "Cannot find token") }) + + t.Run("invalid node identity - no name", func(t *testing.T) { + req := structs.ACLTokenSetRequest{ + Datacenter: "dc1", + ACLToken: structs.ACLToken{ + NodeIdentities: []*structs.ACLNodeIdentity{ + { + Datacenter: "dc1", + }, + }, + }, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, + } + + resp := structs.ACLToken{} + + err := acl.TokenSet(&req, &resp) + testutil.RequireErrorContains(t, err, "Node identity is missing the node name field on this token") + }) + + t.Run("invalid node identity - invalid name", func(t *testing.T) { + req := structs.ACLTokenSetRequest{ + Datacenter: "dc1", + ACLToken: structs.ACLToken{ + NodeIdentities: []*structs.ACLNodeIdentity{ + { + NodeName: "foo.bar", + Datacenter: "dc1", + }, + }, + }, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, + } + + resp := structs.ACLToken{} + + err := acl.TokenSet(&req, &resp) + testutil.RequireErrorContains(t, err, "Node identity has an invalid name.") + }) + t.Run("invalid node identity - no datacenter", func(t *testing.T) { + req := structs.ACLTokenSetRequest{ + Datacenter: "dc1", + ACLToken: structs.ACLToken{ + NodeIdentities: []*structs.ACLNodeIdentity{ + { + NodeName: "foo", + }, + }, + }, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, + } + + resp := structs.ACLToken{} + + err := acl.TokenSet(&req, &resp) + testutil.RequireErrorContains(t, err, "Node identity is missing the datacenter field on this token") + }) } func TestACLEndpoint_TokenSet_CustomID(t *testing.T) { t.Parallel() - dir1, s1 := testServerWithConfig(t, func(c *Config) { - c.ACLDatacenter = "dc1" - c.ACLsEnabled = true - c.ACLMasterToken = "root" - }) - defer os.RemoveAll(dir1) - defer s1.Shutdown() - codec := rpcClient(t, s1) - defer codec.Close() + _, srv, codec := testACLServerWithConfig(t, nil, false) + waitForLeaderEstablishment(t, srv) - testrpc.WaitForLeader(t, s1.RPC, "dc1") - - acl := ACL{srv: s1} + acl := ACL{srv: srv} // No Create Arg t.Run("no create arg", func(t *testing.T) { @@ -1568,7 +1419,7 @@ func TestACLEndpoint_TokenSet_CustomID(t *testing.T) { Policies: nil, Local: false, }, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } resp := structs.ACLToken{} @@ -1589,7 +1440,7 @@ func TestACLEndpoint_TokenSet_CustomID(t *testing.T) { Local: false, }, Create: true, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } resp := structs.ACLToken{} @@ -1598,7 +1449,7 @@ func TestACLEndpoint_TokenSet_CustomID(t *testing.T) { require.NoError(t, err) // Get the token directly to validate that it exists - tokenResp, err := retrieveTestToken(codec, "root", "dc1", resp.AccessorID) + tokenResp, err := retrieveTestToken(codec, TestDefaultMasterToken, "dc1", resp.AccessorID) require.NoError(t, err) token := tokenResp.Token @@ -1619,7 +1470,7 @@ func TestACLEndpoint_TokenSet_CustomID(t *testing.T) { Local: false, }, Create: true, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } resp := structs.ACLToken{} @@ -1639,7 +1490,7 @@ func TestACLEndpoint_TokenSet_CustomID(t *testing.T) { Local: false, }, Create: true, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } resp := structs.ACLToken{} @@ -1659,7 +1510,7 @@ func TestACLEndpoint_TokenSet_CustomID(t *testing.T) { Local: false, }, Create: true, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } resp := structs.ACLToken{} @@ -1679,7 +1530,7 @@ func TestACLEndpoint_TokenSet_CustomID(t *testing.T) { Local: false, }, Create: true, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } resp := structs.ACLToken{} @@ -1699,7 +1550,7 @@ func TestACLEndpoint_TokenSet_CustomID(t *testing.T) { Local: false, }, Create: true, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } resp := structs.ACLToken{} @@ -1719,7 +1570,7 @@ func TestACLEndpoint_TokenSet_CustomID(t *testing.T) { Local: false, }, Create: true, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } resp := structs.ACLToken{} @@ -1739,7 +1590,7 @@ func TestACLEndpoint_TokenSet_CustomID(t *testing.T) { Policies: nil, Local: false, }, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } resp := structs.ACLToken{} @@ -1760,7 +1611,7 @@ func TestACLEndpoint_TokenSet_CustomID(t *testing.T) { Local: false, }, Create: true, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } resp := structs.ACLToken{} @@ -1780,7 +1631,7 @@ func TestACLEndpoint_TokenSet_CustomID(t *testing.T) { Policies: nil, Local: false, }, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } resp := structs.ACLToken{} @@ -1801,7 +1652,7 @@ func TestACLEndpoint_TokenSet_CustomID(t *testing.T) { Local: false, }, Create: true, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } resp := structs.ACLToken{} @@ -1814,21 +1665,13 @@ func TestACLEndpoint_TokenSet_CustomID(t *testing.T) { func TestACLEndpoint_TokenSet_anon(t *testing.T) { t.Parallel() - dir1, s1 := testServerWithConfig(t, func(c *Config) { - c.ACLDatacenter = "dc1" - c.ACLsEnabled = true - c.ACLMasterToken = "root" - }) - defer os.RemoveAll(dir1) - defer s1.Shutdown() - codec := rpcClient(t, s1) - defer codec.Close() + _, srv, codec := testACLServerWithConfig(t, nil, false) + waitForLeaderEstablishment(t, srv) - testrpc.WaitForLeader(t, s1.RPC, "dc1") - policy, err := upsertTestPolicy(codec, "root", "dc1") + policy, err := upsertTestPolicy(codec, TestDefaultMasterToken, "dc1") require.NoError(t, err) - acl := ACL{srv: s1} + acl := ACL{srv: srv} // Assign the policies to a token tokenUpsertReq := structs.ACLTokenSetRequest{ @@ -1836,19 +1679,19 @@ func TestACLEndpoint_TokenSet_anon(t *testing.T) { ACLToken: structs.ACLToken{ AccessorID: structs.ACLTokenAnonymousID, Policies: []structs.ACLTokenPolicyLink{ - structs.ACLTokenPolicyLink{ + { ID: policy.ID, }, }, }, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } token := structs.ACLToken{} err = acl.TokenSet(&tokenUpsertReq, &token) require.NoError(t, err) require.NotEmpty(t, token.SecretID) - tokenResp, err := retrieveTestToken(codec, "root", "dc1", structs.ACLTokenAnonymousID) + tokenResp, err := retrieveTestToken(codec, TestDefaultMasterToken, "dc1", structs.ACLTokenAnonymousID) require.Equal(t, len(tokenResp.Token.Policies), 1) require.Equal(t, tokenResp.Token.Policies[0].ID, policy.ID) @@ -1857,38 +1700,21 @@ func TestACLEndpoint_TokenSet_anon(t *testing.T) { func TestACLEndpoint_TokenDelete(t *testing.T) { t.Parallel() - dir1, s1 := testServerWithConfig(t, func(c *Config) { - c.ACLDatacenter = "dc1" - c.ACLsEnabled = true - c.ACLMasterToken = "root" + _, s1, codec := testACLServerWithConfig(t, func(c *Config) { c.ACLTokenMinExpirationTTL = 10 * time.Millisecond c.ACLTokenMaxExpirationTTL = 5 * time.Second - }) - defer os.RemoveAll(dir1) - defer s1.Shutdown() - codec := rpcClient(t, s1) - defer codec.Close() + }, false) - testrpc.WaitForLeader(t, s1.RPC, "dc1") - - dir2, s2 := testServerWithConfig(t, func(c *Config) { - c.ACLDatacenter = "dc1" - c.ACLsEnabled = true + _, s2, codec2 := testACLServerWithConfig(t, func(c *Config) { c.Datacenter = "dc2" c.ACLTokenMinExpirationTTL = 10 * time.Millisecond c.ACLTokenMaxExpirationTTL = 5 * time.Second // token replication is required to test deleting non-local tokens in secondary dc c.ACLTokenReplication = true - }) - defer os.RemoveAll(dir2) - defer s2.Shutdown() - codec2 := rpcClient(t, s2) - defer codec2.Close() + }, true) - s2.tokens.UpdateReplicationToken("root", tokenStore.TokenSourceConfig) - - testrpc.WaitForLeader(t, s1.RPC, "dc1") - testrpc.WaitForLeader(t, s2.RPC, "dc2") + waitForLeaderEstablishment(t, s1) + waitForLeaderEstablishment(t, s2) // Try to join joinWAN(t, s2, s1) @@ -1902,18 +1728,18 @@ func TestACLEndpoint_TokenDelete(t *testing.T) { acl := ACL{srv: s1} acl2 := ACL{srv: s2} - existingToken, err := upsertTestToken(codec, "root", "dc1", nil) + existingToken, err := upsertTestToken(codec, TestDefaultMasterToken, "dc1", nil) require.NoError(t, err) t.Run("deletes a token that has an expiration time in the future", func(t *testing.T) { // create a token that will expire - testToken, err := upsertTestToken(codec, "root", "dc1", func(token *structs.ACLToken) { + testToken, err := upsertTestToken(codec, TestDefaultMasterToken, "dc1", func(token *structs.ACLToken) { token.ExpirationTTL = 4 * time.Second }) require.NoError(t, err) // Make sure the token is listable - tokenResp, err := retrieveTestToken(codec, "root", "dc1", testToken.AccessorID) + tokenResp, err := retrieveTestToken(codec, TestDefaultMasterToken, "dc1", testToken.AccessorID) require.NoError(t, err) require.NotNil(t, tokenResp.Token) @@ -1921,7 +1747,7 @@ func TestACLEndpoint_TokenDelete(t *testing.T) { req := structs.ACLTokenDeleteRequest{ Datacenter: "dc1", TokenID: testToken.AccessorID, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } var resp string @@ -1930,14 +1756,14 @@ func TestACLEndpoint_TokenDelete(t *testing.T) { require.NoError(t, err) // Make sure the token is gone - tokenResp, err = retrieveTestToken(codec, "root", "dc1", testToken.AccessorID) + tokenResp, err = retrieveTestToken(codec, TestDefaultMasterToken, "dc1", testToken.AccessorID) require.NoError(t, err) require.Nil(t, tokenResp.Token) }) t.Run("deletes a token that is past its expiration time", func(t *testing.T) { // create a token that will expire - expiringToken, err := upsertTestToken(codec, "root", "dc1", func(token *structs.ACLToken) { + expiringToken, err := upsertTestToken(codec, TestDefaultMasterToken, "dc1", func(token *structs.ACLToken) { token.ExpirationTTL = 11 * time.Millisecond }) require.NoError(t, err) @@ -1945,7 +1771,7 @@ func TestACLEndpoint_TokenDelete(t *testing.T) { time.Sleep(20 * time.Millisecond) // now 'expiringToken' is expired // Make sure the token is not listable (filtered due to expiry) - tokenResp, err := retrieveTestToken(codec, "root", "dc1", expiringToken.AccessorID) + tokenResp, err := retrieveTestToken(codec, TestDefaultMasterToken, "dc1", expiringToken.AccessorID) require.NoError(t, err) require.Nil(t, tokenResp.Token) @@ -1953,7 +1779,7 @@ func TestACLEndpoint_TokenDelete(t *testing.T) { req := structs.ACLTokenDeleteRequest{ Datacenter: "dc1", TokenID: expiringToken.AccessorID, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } var resp string @@ -1962,7 +1788,7 @@ func TestACLEndpoint_TokenDelete(t *testing.T) { require.NoError(t, err) // Make sure the token is still gone (this time it's actually gone) - tokenResp, err = retrieveTestToken(codec, "root", "dc1", expiringToken.AccessorID) + tokenResp, err = retrieveTestToken(codec, TestDefaultMasterToken, "dc1", expiringToken.AccessorID) require.NoError(t, err) require.Nil(t, tokenResp.Token) }) @@ -1971,7 +1797,7 @@ func TestACLEndpoint_TokenDelete(t *testing.T) { req := structs.ACLTokenDeleteRequest{ Datacenter: "dc1", TokenID: existingToken.AccessorID, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } var resp string @@ -1980,7 +1806,7 @@ func TestACLEndpoint_TokenDelete(t *testing.T) { require.NoError(t, err) // Make sure the token is gone - tokenResp, err := retrieveTestToken(codec, "root", "dc1", existingToken.AccessorID) + tokenResp, err := retrieveTestToken(codec, TestDefaultMasterToken, "dc1", existingToken.AccessorID) require.Nil(t, tokenResp.Token) require.NoError(t, err) }) @@ -1988,9 +1814,9 @@ func TestACLEndpoint_TokenDelete(t *testing.T) { t.Run("can't delete itself", func(t *testing.T) { readReq := structs.ACLTokenGetRequest{ Datacenter: "dc1", - TokenID: "root", + TokenID: TestDefaultMasterToken, TokenIDType: structs.ACLTokenSecret, - QueryOptions: structs.QueryOptions{Token: "root"}, + QueryOptions: structs.QueryOptions{Token: TestDefaultMasterToken}, } var out structs.ACLTokenResponse @@ -2002,7 +1828,7 @@ func TestACLEndpoint_TokenDelete(t *testing.T) { req := structs.ACLTokenDeleteRequest{ Datacenter: "dc1", TokenID: out.Token.AccessorID, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } var resp string @@ -2017,7 +1843,7 @@ func TestACLEndpoint_TokenDelete(t *testing.T) { req := structs.ACLTokenDeleteRequest{ Datacenter: "dc1", TokenID: fakeID, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } var resp string @@ -2026,7 +1852,7 @@ func TestACLEndpoint_TokenDelete(t *testing.T) { require.NoError(t, err) // token should be nil - tokenResp, err := retrieveTestToken(codec, "root", "dc1", existingToken.AccessorID) + tokenResp, err := retrieveTestToken(codec, TestDefaultMasterToken, "dc1", existingToken.AccessorID) require.Nil(t, tokenResp.Token) require.NoError(t, err) }) @@ -2038,7 +1864,7 @@ func TestACLEndpoint_TokenDelete(t *testing.T) { req := structs.ACLTokenDeleteRequest{ Datacenter: "dc2", TokenID: fakeID, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } var resp string @@ -2047,7 +1873,7 @@ func TestACLEndpoint_TokenDelete(t *testing.T) { require.NoError(t, err) // token should be nil - tokenResp, err := retrieveTestToken(codec2, "root", "dc1", existingToken.AccessorID) + tokenResp, err := retrieveTestToken(codec2, TestDefaultMasterToken, "dc1", existingToken.AccessorID) require.Nil(t, tokenResp.Token) require.NoError(t, err) }) @@ -2056,24 +1882,15 @@ func TestACLEndpoint_TokenDelete(t *testing.T) { func TestACLEndpoint_TokenDelete_anon(t *testing.T) { t.Parallel() - dir1, s1 := testServerWithConfig(t, func(c *Config) { - c.ACLDatacenter = "dc1" - c.ACLsEnabled = true - c.ACLMasterToken = "root" - }) - defer os.RemoveAll(dir1) - defer s1.Shutdown() - codec := rpcClient(t, s1) - defer codec.Close() + _, srv, codec := testACLServerWithConfig(t, nil, false) + waitForLeaderEstablishment(t, srv) - testrpc.WaitForLeader(t, s1.RPC, "dc1") - - acl := ACL{srv: s1} + acl := ACL{srv: srv} req := structs.ACLTokenDeleteRequest{ Datacenter: "dc1", TokenID: structs.ACLTokenAnonymousID, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } var resp string @@ -2082,37 +1899,28 @@ func TestACLEndpoint_TokenDelete_anon(t *testing.T) { require.EqualError(t, err, "Delete operation not permitted on the anonymous token") // Make sure the token is still there - tokenResp, err := retrieveTestToken(codec, "root", "dc1", structs.ACLTokenAnonymousID) + tokenResp, err := retrieveTestToken(codec, TestDefaultMasterToken, "dc1", structs.ACLTokenAnonymousID) require.NotNil(t, tokenResp.Token) } func TestACLEndpoint_TokenList(t *testing.T) { t.Parallel() - dir1, s1 := testServerWithConfig(t, func(c *Config) { - c.ACLDatacenter = "dc1" - c.ACLsEnabled = true - c.ACLMasterToken = "root" + _, srv, codec := testACLServerWithConfig(t, func(c *Config) { c.ACLTokenMinExpirationTTL = 10 * time.Millisecond c.ACLTokenMaxExpirationTTL = 5 * time.Second - }) - defer os.RemoveAll(dir1) - defer s1.Shutdown() - codec := rpcClient(t, s1) - defer codec.Close() + }, false) + waitForLeaderEstablishment(t, srv) - testrpc.WaitForLeader(t, s1.RPC, "dc1") - waitForLeaderEstablishment(t, s1) + acl := ACL{srv: srv} - acl := ACL{srv: s1} - - t1, err := upsertTestToken(codec, "root", "dc1", nil) + t1, err := upsertTestToken(codec, TestDefaultMasterToken, "dc1", nil) require.NoError(t, err) - t2, err := upsertTestToken(codec, "root", "dc1", nil) + t2, err := upsertTestToken(codec, TestDefaultMasterToken, "dc1", nil) require.NoError(t, err) - masterTokenAccessorID, err := retrieveTestTokenAccessorForSecret(codec, "root", "dc1", "root") + masterTokenAccessorID, err := retrieveTestTokenAccessorForSecret(codec, TestDefaultMasterToken, "dc1", TestDefaultMasterToken) require.NoError(t, err) t.Run("normal", func(t *testing.T) { @@ -2120,14 +1928,14 @@ func TestACLEndpoint_TokenList(t *testing.T) { // however previously inserting it outside of the subtest func resulted in this being // extra flakey due to there being more code that needed to run to setup the subtest // between when we inserted the token and when we performed the listing. - t3, err := upsertTestToken(codec, "root", "dc1", func(token *structs.ACLToken) { + t3, err := upsertTestToken(codec, TestDefaultMasterToken, "dc1", func(token *structs.ACLToken) { token.ExpirationTTL = 50 * time.Millisecond }) require.NoError(t, err) req := structs.ACLTokenListRequest{ Datacenter: "dc1", - QueryOptions: structs.QueryOptions{Token: "root"}, + QueryOptions: structs.QueryOptions{Token: TestDefaultMasterToken}, } resp := structs.ACLTokenListResponse{} @@ -2150,7 +1958,7 @@ func TestACLEndpoint_TokenList(t *testing.T) { t.Run("filter expired", func(t *testing.T) { req := structs.ACLTokenListRequest{ Datacenter: "dc1", - QueryOptions: structs.QueryOptions{Token: "root"}, + QueryOptions: structs.QueryOptions{Token: TestDefaultMasterToken}, } resp := structs.ACLTokenListResponse{} @@ -2171,29 +1979,21 @@ func TestACLEndpoint_TokenList(t *testing.T) { func TestACLEndpoint_TokenBatchRead(t *testing.T) { t.Parallel() - dir1, s1 := testServerWithConfig(t, func(c *Config) { - c.ACLDatacenter = "dc1" - c.ACLsEnabled = true - c.ACLMasterToken = "root" + _, srv, codec := testACLServerWithConfig(t, func(c *Config) { c.ACLTokenMinExpirationTTL = 10 * time.Millisecond c.ACLTokenMaxExpirationTTL = 5 * time.Second - }) - defer os.RemoveAll(dir1) - defer s1.Shutdown() - codec := rpcClient(t, s1) - defer codec.Close() + }, false) + waitForLeaderEstablishment(t, srv) - testrpc.WaitForLeader(t, s1.RPC, "dc1") + acl := ACL{srv: srv} - acl := ACL{srv: s1} - - t1, err := upsertTestToken(codec, "root", "dc1", nil) + t1, err := upsertTestToken(codec, TestDefaultMasterToken, "dc1", nil) require.NoError(t, err) - t2, err := upsertTestToken(codec, "root", "dc1", nil) + t2, err := upsertTestToken(codec, TestDefaultMasterToken, "dc1", nil) require.NoError(t, err) - t3, err := upsertTestToken(codec, "root", "dc1", func(token *structs.ACLToken) { + t3, err := upsertTestToken(codec, TestDefaultMasterToken, "dc1", func(token *structs.ACLToken) { token.ExpirationTTL = 4 * time.Second }) require.NoError(t, err) @@ -2204,7 +2004,7 @@ func TestACLEndpoint_TokenBatchRead(t *testing.T) { req := structs.ACLTokenBatchGetRequest{ Datacenter: "dc1", AccessorIDs: tokens, - QueryOptions: structs.QueryOptions{Token: "root"}, + QueryOptions: structs.QueryOptions{Token: TestDefaultMasterToken}, } resp := structs.ACLTokenBatchResponse{} @@ -2222,7 +2022,7 @@ func TestACLEndpoint_TokenBatchRead(t *testing.T) { req := structs.ACLTokenBatchGetRequest{ Datacenter: "dc1", AccessorIDs: tokens, - QueryOptions: structs.QueryOptions{Token: "root"}, + QueryOptions: structs.QueryOptions{Token: TestDefaultMasterToken}, } resp := structs.ACLTokenBatchResponse{} @@ -2235,110 +2035,69 @@ func TestACLEndpoint_TokenBatchRead(t *testing.T) { func TestACLEndpoint_PolicyRead(t *testing.T) { t.Parallel() - dir1, s1 := testServerWithConfig(t, func(c *Config) { - c.ACLDatacenter = "dc1" - c.ACLsEnabled = true - c.ACLMasterToken = "root" - }) - defer os.RemoveAll(dir1) - defer s1.Shutdown() - codec := rpcClient(t, s1) - defer codec.Close() + _, srv, codec := testACLServerWithConfig(t, nil, false) + waitForLeaderEstablishment(t, srv) - testrpc.WaitForLeader(t, s1.RPC, "dc1") + policy, err := upsertTestPolicy(codec, TestDefaultMasterToken, "dc1") + require.NoError(t, err) - policy, err := upsertTestPolicy(codec, "root", "dc1") - if err != nil { - t.Fatalf("err: %v", err) - } - - acl := ACL{srv: s1} + acl := ACL{srv: srv} req := structs.ACLPolicyGetRequest{ Datacenter: "dc1", PolicyID: policy.ID, - QueryOptions: structs.QueryOptions{Token: "root"}, + QueryOptions: structs.QueryOptions{Token: TestDefaultMasterToken}, } resp := structs.ACLPolicyResponse{} err = acl.PolicyRead(&req, &resp) - if err != nil { - t.Fatalf("err: %v", err) - } - - if !reflect.DeepEqual(resp.Policy, policy) { - t.Fatalf("tokens are not equal: %v != %v", resp.Policy, policy) - } + require.NoError(t, err) + require.Equal(t, policy, resp.Policy) } func TestACLEndpoint_PolicyReadByName(t *testing.T) { t.Parallel() - dir1, s1 := testServerWithConfig(t, func(c *Config) { - c.ACLDatacenter = "dc1" - c.ACLsEnabled = true - c.ACLMasterToken = "root" - }) - defer os.RemoveAll(dir1) - defer s1.Shutdown() - codec := rpcClient(t, s1) - defer codec.Close() + _, srv, codec := testACLServerWithConfig(t, nil, false) + waitForLeaderEstablishment(t, srv) - testrpc.WaitForLeader(t, s1.RPC, "dc1") + policy, err := upsertTestPolicy(codec, TestDefaultMasterToken, "dc1") + require.NoError(t, err) - policy, err := upsertTestPolicy(codec, "root", "dc1") - if err != nil { - t.Fatalf("err: %v", err) - } - - acl := ACL{srv: s1} + acl := ACL{srv: srv} req := structs.ACLPolicyGetRequest{ Datacenter: "dc1", PolicyName: policy.Name, - QueryOptions: structs.QueryOptions{Token: "root"}, + QueryOptions: structs.QueryOptions{Token: TestDefaultMasterToken}, } resp := structs.ACLPolicyResponse{} err = acl.PolicyRead(&req, &resp) - if err != nil { - t.Fatalf("err: %v", err) - } - - if !reflect.DeepEqual(resp.Policy, policy) { - t.Fatalf("tokens are not equal: %v != %v", resp.Policy, policy) - } + require.NoError(t, err) + require.Equal(t, policy, resp.Policy) } func TestACLEndpoint_PolicyBatchRead(t *testing.T) { t.Parallel() - dir1, s1 := testServerWithConfig(t, func(c *Config) { - c.ACLDatacenter = "dc1" - c.ACLsEnabled = true - c.ACLMasterToken = "root" - }) - defer os.RemoveAll(dir1) - defer s1.Shutdown() - codec := rpcClient(t, s1) - defer codec.Close() + _, srv, codec := testACLServerWithConfig(t, nil, false) + waitForLeaderEstablishment(t, srv) - testrpc.WaitForLeader(t, s1.RPC, "dc1") - - p1, err := upsertTestPolicy(codec, "root", "dc1") + p1, err := upsertTestPolicy(codec, TestDefaultMasterToken, "dc1") require.NoError(t, err) - p2, err := upsertTestPolicy(codec, "root", "dc1") + p2, err := upsertTestPolicy(codec, TestDefaultMasterToken, "dc1") require.NoError(t, err) - acl := ACL{srv: s1} + acl := ACL{srv: srv} policies := []string{p1.ID, p2.ID} req := structs.ACLPolicyBatchGetRequest{ Datacenter: "dc1", PolicyIDs: policies, - QueryOptions: structs.QueryOptions{Token: "root"}, + QueryOptions: structs.QueryOptions{Token: TestDefaultMasterToken}, } resp := structs.ACLPolicyBatchResponse{} @@ -2351,19 +2110,10 @@ func TestACLEndpoint_PolicyBatchRead(t *testing.T) { func TestACLEndpoint_PolicySet(t *testing.T) { t.Parallel() - dir1, s1 := testServerWithConfig(t, func(c *Config) { - c.ACLDatacenter = "dc1" - c.ACLsEnabled = true - c.ACLMasterToken = "root" - }) - defer os.RemoveAll(dir1) - defer s1.Shutdown() - codec := rpcClient(t, s1) - defer codec.Close() + _, srv, codec := testACLServerWithConfig(t, nil, false) + waitForLeaderEstablishment(t, srv) + acl := ACL{srv: srv} - testrpc.WaitForLeader(t, s1.RPC, "dc1") - - acl := ACL{srv: s1} var policyID string t.Run("Create it", func(t *testing.T) { @@ -2374,7 +2124,7 @@ func TestACLEndpoint_PolicySet(t *testing.T) { Name: "baz", Rules: "service \"\" { policy = \"read\" }", }, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } resp := structs.ACLPolicy{} @@ -2383,7 +2133,7 @@ func TestACLEndpoint_PolicySet(t *testing.T) { require.NotNil(t, resp.ID) // Get the policy directly to validate that it exists - policyResp, err := retrieveTestPolicy(codec, "root", "dc1", resp.ID) + policyResp, err := retrieveTestPolicy(codec, TestDefaultMasterToken, "dc1", resp.ID) require.NoError(t, err) policy := policyResp.Policy @@ -2403,7 +2153,7 @@ func TestACLEndpoint_PolicySet(t *testing.T) { Name: "baz", Rules: "service \"\" { policy = \"read\" }", }, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } resp := structs.ACLPolicy{} @@ -2420,7 +2170,7 @@ func TestACLEndpoint_PolicySet(t *testing.T) { Name: "bar", Rules: "service \"\" { policy = \"write\" }", }, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } resp := structs.ACLPolicy{} @@ -2429,7 +2179,7 @@ func TestACLEndpoint_PolicySet(t *testing.T) { require.NotNil(t, resp.ID) // Get the policy directly to validate that it exists - policyResp, err := retrieveTestPolicy(codec, "root", "dc1", resp.ID) + policyResp, err := retrieveTestPolicy(codec, TestDefaultMasterToken, "dc1", resp.ID) require.NoError(t, err) policy := policyResp.Policy @@ -2443,19 +2193,10 @@ func TestACLEndpoint_PolicySet(t *testing.T) { func TestACLEndpoint_PolicySet_CustomID(t *testing.T) { t.Parallel() - dir1, s1 := testServerWithConfig(t, func(c *Config) { - c.ACLDatacenter = "dc1" - c.ACLsEnabled = true - c.ACLMasterToken = "root" - }) - defer os.RemoveAll(dir1) - defer s1.Shutdown() - codec := rpcClient(t, s1) - defer codec.Close() + _, srv, _ := testACLServerWithConfig(t, nil, false) + waitForLeaderEstablishment(t, srv) - testrpc.WaitForLeader(t, s1.RPC, "dc1") - - acl := ACL{srv: s1} + acl := ACL{srv: srv} // Attempt to create policy with ID req := structs.ACLPolicySetRequest{ @@ -2466,7 +2207,7 @@ func TestACLEndpoint_PolicySet_CustomID(t *testing.T) { Name: "baz", Rules: "service \"\" { policy = \"read\" }", }, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } resp := structs.ACLPolicy{} @@ -2477,23 +2218,13 @@ func TestACLEndpoint_PolicySet_CustomID(t *testing.T) { func TestACLEndpoint_PolicySet_globalManagement(t *testing.T) { t.Parallel() - dir1, s1 := testServerWithConfig(t, func(c *Config) { - c.ACLDatacenter = "dc1" - c.ACLsEnabled = true - c.ACLMasterToken = "root" - }) - defer os.RemoveAll(dir1) - defer s1.Shutdown() - codec := rpcClient(t, s1) - defer codec.Close() + _, srv, codec := testACLServerWithConfig(t, nil, false) + waitForLeaderEstablishment(t, srv) - testrpc.WaitForLeader(t, s1.RPC, "dc1") - - acl := ACL{srv: s1} + acl := ACL{srv: srv} // Can't change the rules { - req := structs.ACLPolicySetRequest{ Datacenter: "dc1", Policy: structs.ACLPolicy{ @@ -2501,7 +2232,7 @@ func TestACLEndpoint_PolicySet_globalManagement(t *testing.T) { Name: "foobar", // This is required to get past validation Rules: "service \"\" { policy = \"write\" }", }, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } resp := structs.ACLPolicy{} @@ -2518,7 +2249,7 @@ func TestACLEndpoint_PolicySet_globalManagement(t *testing.T) { Name: "foobar", Rules: structs.ACLPolicyGlobalManagement, }, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } resp := structs.ACLPolicy{} @@ -2526,7 +2257,7 @@ func TestACLEndpoint_PolicySet_globalManagement(t *testing.T) { require.NoError(t, err) // Get the policy again - policyResp, err := retrieveTestPolicy(codec, "root", "dc1", structs.ACLPolicyGlobalManagementID) + policyResp, err := retrieveTestPolicy(codec, TestDefaultMasterToken, "dc1", structs.ACLPolicyGlobalManagementID) require.NoError(t, err) policy := policyResp.Policy @@ -2539,29 +2270,18 @@ func TestACLEndpoint_PolicySet_globalManagement(t *testing.T) { func TestACLEndpoint_PolicyDelete(t *testing.T) { t.Parallel() - dir1, s1 := testServerWithConfig(t, func(c *Config) { - c.ACLDatacenter = "dc1" - c.ACLsEnabled = true - c.ACLMasterToken = "root" - }) - defer os.RemoveAll(dir1) - defer s1.Shutdown() - codec := rpcClient(t, s1) - defer codec.Close() + _, srv, codec := testACLServerWithConfig(t, nil, false) + waitForLeaderEstablishment(t, srv) - testrpc.WaitForLeader(t, s1.RPC, "dc1") + existingPolicy, err := upsertTestPolicy(codec, TestDefaultMasterToken, "dc1") + require.NoError(t, err) - existingPolicy, err := upsertTestPolicy(codec, "root", "dc1") - if err != nil { - t.Fatalf("err: %v", err) - } - - acl := ACL{srv: s1} + acl := ACL{srv: srv} req := structs.ACLPolicyDeleteRequest{ Datacenter: "dc1", PolicyID: existingPolicy.ID, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } var resp string @@ -2570,31 +2290,21 @@ func TestACLEndpoint_PolicyDelete(t *testing.T) { require.NoError(t, err) // Make sure the policy is gone - tokenResp, err := retrieveTestPolicy(codec, "root", "dc1", existingPolicy.ID) + tokenResp, err := retrieveTestPolicy(codec, TestDefaultMasterToken, "dc1", existingPolicy.ID) require.Nil(t, tokenResp.Policy) } func TestACLEndpoint_PolicyDelete_globalManagement(t *testing.T) { t.Parallel() - dir1, s1 := testServerWithConfig(t, func(c *Config) { - c.ACLDatacenter = "dc1" - c.ACLsEnabled = true - c.ACLMasterToken = "root" - }) - defer os.RemoveAll(dir1) - defer s1.Shutdown() - codec := rpcClient(t, s1) - defer codec.Close() - - testrpc.WaitForLeader(t, s1.RPC, "dc1") - - acl := ACL{srv: s1} + _, srv, _ := testACLServerWithConfig(t, nil, false) + waitForLeaderEstablishment(t, srv) + acl := ACL{srv: srv} req := structs.ACLPolicyDeleteRequest{ Datacenter: "dc1", PolicyID: structs.ACLPolicyGlobalManagementID, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } var resp string @@ -2606,29 +2316,20 @@ func TestACLEndpoint_PolicyDelete_globalManagement(t *testing.T) { func TestACLEndpoint_PolicyList(t *testing.T) { t.Parallel() - dir1, s1 := testServerWithConfig(t, func(c *Config) { - c.ACLDatacenter = "dc1" - c.ACLsEnabled = true - c.ACLMasterToken = "root" - }) - defer os.RemoveAll(dir1) - defer s1.Shutdown() - codec := rpcClient(t, s1) - defer codec.Close() + _, srv, codec := testACLServerWithConfig(t, nil, false) + waitForLeaderEstablishment(t, srv) - testrpc.WaitForLeader(t, s1.RPC, "dc1") - - p1, err := upsertTestPolicy(codec, "root", "dc1") + p1, err := upsertTestPolicy(codec, TestDefaultMasterToken, "dc1") require.NoError(t, err) - p2, err := upsertTestPolicy(codec, "root", "dc1") + p2, err := upsertTestPolicy(codec, TestDefaultMasterToken, "dc1") require.NoError(t, err) - acl := ACL{srv: s1} + acl := ACL{srv: srv} req := structs.ACLPolicyListRequest{ Datacenter: "dc1", - QueryOptions: structs.QueryOptions{Token: "root"}, + QueryOptions: structs.QueryOptions{Token: TestDefaultMasterToken}, } resp := structs.ACLPolicyListResponse{} @@ -2647,25 +2348,16 @@ func TestACLEndpoint_PolicyList(t *testing.T) { func TestACLEndpoint_PolicyResolve(t *testing.T) { t.Parallel() - dir1, s1 := testServerWithConfig(t, func(c *Config) { - c.ACLDatacenter = "dc1" - c.ACLsEnabled = true - c.ACLMasterToken = "root" - }) - defer os.RemoveAll(dir1) - defer s1.Shutdown() - codec := rpcClient(t, s1) - defer codec.Close() + _, srv, codec := testACLServerWithConfig(t, nil, false) + waitForLeaderEstablishment(t, srv) - testrpc.WaitForLeader(t, s1.RPC, "dc1") - - p1, err := upsertTestPolicy(codec, "root", "dc1") + p1, err := upsertTestPolicy(codec, TestDefaultMasterToken, "dc1") require.NoError(t, err) - p2, err := upsertTestPolicy(codec, "root", "dc1") + p2, err := upsertTestPolicy(codec, TestDefaultMasterToken, "dc1") require.NoError(t, err) - acl := ACL{srv: s1} + acl := ACL{srv: srv} policies := []string{p1.ID, p2.ID} @@ -2674,15 +2366,15 @@ func TestACLEndpoint_PolicyResolve(t *testing.T) { Datacenter: "dc1", ACLToken: structs.ACLToken{ Policies: []structs.ACLTokenPolicyLink{ - structs.ACLTokenPolicyLink{ + { ID: p1.ID, }, - structs.ACLTokenPolicyLink{ + { ID: p2.ID, }, }, }, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } token := structs.ACLToken{} err = acl.TokenSet(&tokenUpsertReq, &token) @@ -2702,27 +2394,18 @@ func TestACLEndpoint_PolicyResolve(t *testing.T) { func TestACLEndpoint_RoleRead(t *testing.T) { t.Parallel() - dir1, s1 := testServerWithConfig(t, func(c *Config) { - c.ACLDatacenter = "dc1" - c.ACLsEnabled = true - c.ACLMasterToken = "root" - }) - defer os.RemoveAll(dir1) - defer s1.Shutdown() - codec := rpcClient(t, s1) - defer codec.Close() + _, srv, codec := testACLServerWithConfig(t, nil, false) + waitForLeaderEstablishment(t, srv) - testrpc.WaitForLeader(t, s1.RPC, "dc1") - - role, err := upsertTestRole(codec, "root", "dc1") + role, err := upsertTestRole(codec, TestDefaultMasterToken, "dc1") require.NoError(t, err) - acl := ACL{srv: s1} + acl := ACL{srv: srv} req := structs.ACLRoleGetRequest{ Datacenter: "dc1", RoleID: role.ID, - QueryOptions: structs.QueryOptions{Token: "root"}, + QueryOptions: structs.QueryOptions{Token: TestDefaultMasterToken}, } resp := structs.ACLRoleResponse{} @@ -2735,31 +2418,22 @@ func TestACLEndpoint_RoleRead(t *testing.T) { func TestACLEndpoint_RoleBatchRead(t *testing.T) { t.Parallel() - dir1, s1 := testServerWithConfig(t, func(c *Config) { - c.ACLDatacenter = "dc1" - c.ACLsEnabled = true - c.ACLMasterToken = "root" - }) - defer os.RemoveAll(dir1) - defer s1.Shutdown() - codec := rpcClient(t, s1) - defer codec.Close() + _, srv, codec := testACLServerWithConfig(t, nil, false) + waitForLeaderEstablishment(t, srv) - testrpc.WaitForLeader(t, s1.RPC, "dc1") - - r1, err := upsertTestRole(codec, "root", "dc1") + r1, err := upsertTestRole(codec, TestDefaultMasterToken, "dc1") require.NoError(t, err) - r2, err := upsertTestRole(codec, "root", "dc1") + r2, err := upsertTestRole(codec, TestDefaultMasterToken, "dc1") require.NoError(t, err) - acl := ACL{srv: s1} + acl := ACL{srv: srv} roles := []string{r1.ID, r2.ID} req := structs.ACLRoleBatchGetRequest{ Datacenter: "dc1", RoleIDs: roles, - QueryOptions: structs.QueryOptions{Token: "root"}, + QueryOptions: structs.QueryOptions{Token: TestDefaultMasterToken}, } resp := structs.ACLRoleBatchResponse{} @@ -2772,24 +2446,15 @@ func TestACLEndpoint_RoleBatchRead(t *testing.T) { func TestACLEndpoint_RoleSet(t *testing.T) { t.Parallel() - dir1, s1 := testServerWithConfig(t, func(c *Config) { - c.ACLDatacenter = "dc1" - c.ACLsEnabled = true - c.ACLMasterToken = "root" - }) - defer os.RemoveAll(dir1) - defer s1.Shutdown() - codec := rpcClient(t, s1) - defer codec.Close() + _, srv, codec := testACLServerWithConfig(t, nil, false) + waitForLeaderEstablishment(t, srv) - testrpc.WaitForLeader(t, s1.RPC, "dc1") - - acl := ACL{srv: s1} + acl := ACL{srv: srv} var roleID string - testPolicy1, err := upsertTestPolicy(codec, "root", "dc1") + testPolicy1, err := upsertTestPolicy(codec, TestDefaultMasterToken, "dc1") require.NoError(t, err) - testPolicy2, err := upsertTestPolicy(codec, "root", "dc1") + testPolicy2, err := upsertTestPolicy(codec, TestDefaultMasterToken, "dc1") require.NoError(t, err) t.Run("Create it", func(t *testing.T) { @@ -2799,12 +2464,18 @@ func TestACLEndpoint_RoleSet(t *testing.T) { Description: "foobar", Name: "baz", Policies: []structs.ACLRolePolicyLink{ - structs.ACLRolePolicyLink{ + { ID: testPolicy1.ID, }, }, + NodeIdentities: []*structs.ACLNodeIdentity{ + { + NodeName: "foo", + Datacenter: "bar", + }, + }, }, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } resp := structs.ACLRole{} @@ -2813,7 +2484,7 @@ func TestACLEndpoint_RoleSet(t *testing.T) { require.NotNil(t, resp.ID) // Get the role directly to validate that it exists - roleResp, err := retrieveTestRole(codec, "root", "dc1", resp.ID) + roleResp, err := retrieveTestRole(codec, TestDefaultMasterToken, "dc1", resp.ID) require.NoError(t, err) role := roleResp.Role @@ -2822,6 +2493,9 @@ func TestACLEndpoint_RoleSet(t *testing.T) { require.Equal(t, role.Name, "baz") require.Len(t, role.Policies, 1) require.Equal(t, testPolicy1.ID, role.Policies[0].ID) + require.Len(t, role.NodeIdentities, 1) + require.Equal(t, "foo", role.NodeIdentities[0].NodeName) + require.Equal(t, "bar", role.NodeIdentities[0].Datacenter) roleID = role.ID }) @@ -2834,12 +2508,12 @@ func TestACLEndpoint_RoleSet(t *testing.T) { Description: "bat", Name: "bar", Policies: []structs.ACLRolePolicyLink{ - structs.ACLRolePolicyLink{ + { ID: testPolicy2.ID, }, }, }, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } resp := structs.ACLRole{} @@ -2848,7 +2522,7 @@ func TestACLEndpoint_RoleSet(t *testing.T) { require.NotNil(t, resp.ID) // Get the role directly to validate that it exists - roleResp, err := retrieveTestRole(codec, "root", "dc1", resp.ID) + roleResp, err := retrieveTestRole(codec, TestDefaultMasterToken, "dc1", resp.ID) require.NoError(t, err) role := roleResp.Role @@ -2857,12 +2531,13 @@ func TestACLEndpoint_RoleSet(t *testing.T) { require.Equal(t, role.Name, "bar") require.Len(t, role.Policies, 1) require.Equal(t, testPolicy2.ID, role.Policies[0].ID) + require.Empty(t, role.NodeIdentities) }) t.Run("Create it using Policies linked by id and name", func(t *testing.T) { - policy1, err := upsertTestPolicy(codec, "root", "dc1") + policy1, err := upsertTestPolicy(codec, TestDefaultMasterToken, "dc1") require.NoError(t, err) - policy2, err := upsertTestPolicy(codec, "root", "dc1") + policy2, err := upsertTestPolicy(codec, TestDefaultMasterToken, "dc1") require.NoError(t, err) req := structs.ACLRoleSetRequest{ @@ -2871,15 +2546,15 @@ func TestACLEndpoint_RoleSet(t *testing.T) { Description: "foobar", Name: "baz", Policies: []structs.ACLRolePolicyLink{ - structs.ACLRolePolicyLink{ + { ID: policy1.ID, }, - structs.ACLRolePolicyLink{ + { Name: policy2.Name, }, }, }, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } resp := structs.ACLRole{} @@ -2889,11 +2564,11 @@ func TestACLEndpoint_RoleSet(t *testing.T) { // Delete both policies to ensure that we skip resolving ID->Name // in the returned data. - require.NoError(t, deleteTestPolicy(codec, "root", "dc1", policy1.ID)) - require.NoError(t, deleteTestPolicy(codec, "root", "dc1", policy2.ID)) + require.NoError(t, deleteTestPolicy(codec, TestDefaultMasterToken, "dc1", policy1.ID)) + require.NoError(t, deleteTestPolicy(codec, TestDefaultMasterToken, "dc1", policy2.ID)) // Get the role directly to validate that it exists - roleResp, err := retrieveTestRole(codec, "root", "dc1", resp.ID) + roleResp, err := retrieveTestRole(codec, TestDefaultMasterToken, "dc1", resp.ID) require.NoError(t, err) role := roleResp.Role @@ -2918,10 +2593,10 @@ func TestACLEndpoint_RoleSet(t *testing.T) { Description: "foobar", Name: roleNameGen(t), ServiceIdentities: []*structs.ACLServiceIdentity{ - &structs.ACLServiceIdentity{ServiceName: ""}, + {ServiceName: ""}, }, }, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } resp := structs.ACLRole{} @@ -2937,10 +2612,10 @@ func TestACLEndpoint_RoleSet(t *testing.T) { Description: "foobar", Name: roleNameGen(t), ServiceIdentities: []*structs.ACLServiceIdentity{ - &structs.ACLServiceIdentity{ServiceName: long}, + {ServiceName: long}, }, }, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } resp := structs.ACLRole{} @@ -2981,10 +2656,10 @@ func TestACLEndpoint_RoleSet(t *testing.T) { Description: "foobar", Name: roleNameGen(t), ServiceIdentities: []*structs.ACLServiceIdentity{ - &structs.ACLServiceIdentity{ServiceName: test.name}, + {ServiceName: test.name}, }, }, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } resp := structs.ACLRole{} @@ -2994,7 +2669,7 @@ func TestACLEndpoint_RoleSet(t *testing.T) { require.NoError(t, err) // Get the token directly to validate that it exists - roleResp, err := retrieveTestRole(codec, "root", "dc1", resp.ID) + roleResp, err := retrieveTestRole(codec, TestDefaultMasterToken, "dc1", resp.ID) require.NoError(t, err) role := roleResp.Role require.ElementsMatch(t, req.Role.ServiceIdentities, role.ServiceIdentities) @@ -3011,11 +2686,11 @@ func TestACLEndpoint_RoleSet(t *testing.T) { Description: "foobar", Name: roleNameGen(t), ServiceIdentities: []*structs.ACLServiceIdentity{ - &structs.ACLServiceIdentity{ServiceName: "example"}, - &structs.ACLServiceIdentity{ServiceName: "example"}, + {ServiceName: "example"}, + {ServiceName: "example"}, }, }, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } resp := structs.ACLRole{} @@ -3024,7 +2699,7 @@ func TestACLEndpoint_RoleSet(t *testing.T) { require.NoError(t, err) // Get the role directly to validate that it exists - roleResp, err := retrieveTestRole(codec, "root", "dc1", resp.ID) + roleResp, err := retrieveTestRole(codec, TestDefaultMasterToken, "dc1", resp.ID) require.NoError(t, err) role := roleResp.Role require.Len(t, role.ServiceIdentities, 1) @@ -3037,17 +2712,17 @@ func TestACLEndpoint_RoleSet(t *testing.T) { Description: "foobar", Name: roleNameGen(t), ServiceIdentities: []*structs.ACLServiceIdentity{ - &structs.ACLServiceIdentity{ + { ServiceName: "example", Datacenters: []string{"dc2", "dc3"}, }, - &structs.ACLServiceIdentity{ + { ServiceName: "example", Datacenters: []string{"dc1", "dc2"}, }, }, }, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } resp := structs.ACLRole{} @@ -3056,7 +2731,7 @@ func TestACLEndpoint_RoleSet(t *testing.T) { require.NoError(t, err) // Get the role directly to validate that it exists - roleResp, err := retrieveTestRole(codec, "root", "dc1", resp.ID) + roleResp, err := retrieveTestRole(codec, TestDefaultMasterToken, "dc1", resp.ID) require.NoError(t, err) role := roleResp.Role require.Len(t, role.ServiceIdentities, 1) @@ -3064,26 +2739,77 @@ func TestACLEndpoint_RoleSet(t *testing.T) { require.Equal(t, "example", svcid.ServiceName) require.ElementsMatch(t, []string{"dc1", "dc2", "dc3"}, svcid.Datacenters) }) + + t.Run("invalid node identity - no name", func(t *testing.T) { + req := structs.ACLRoleSetRequest{ + Datacenter: "dc1", + Role: structs.ACLRole{ + Name: roleNameGen(t), + NodeIdentities: []*structs.ACLNodeIdentity{ + { + Datacenter: "dc1", + }, + }, + }, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, + } + + resp := structs.ACLRole{} + + err := acl.RoleSet(&req, &resp) + testutil.RequireErrorContains(t, err, "Node identity is missing the node name field on this role") + }) + + t.Run("invalid node identity - invalid name", func(t *testing.T) { + req := structs.ACLRoleSetRequest{ + Datacenter: "dc1", + Role: structs.ACLRole{ + Name: roleNameGen(t), + NodeIdentities: []*structs.ACLNodeIdentity{ + { + NodeName: "foo.bar", + Datacenter: "dc1", + }, + }, + }, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, + } + + resp := structs.ACLRole{} + + err := acl.RoleSet(&req, &resp) + testutil.RequireErrorContains(t, err, "Node identity has an invalid name.") + }) + t.Run("invalid node identity - no datacenter", func(t *testing.T) { + req := structs.ACLRoleSetRequest{ + Datacenter: "dc1", + Role: structs.ACLRole{ + Name: roleNameGen(t), + NodeIdentities: []*structs.ACLNodeIdentity{ + { + NodeName: "foo", + }, + }, + }, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, + } + + resp := structs.ACLRole{} + + err := acl.RoleSet(&req, &resp) + testutil.RequireErrorContains(t, err, "Node identity is missing the datacenter field on this role") + }) } func TestACLEndpoint_RoleSet_names(t *testing.T) { t.Parallel() - dir1, s1 := testServerWithConfig(t, func(c *Config) { - c.ACLDatacenter = "dc1" - c.ACLsEnabled = true - c.ACLMasterToken = "root" - }) - defer os.RemoveAll(dir1) - defer s1.Shutdown() - codec := rpcClient(t, s1) - defer codec.Close() + _, srv, codec := testACLServerWithConfig(t, nil, false) + waitForLeaderEstablishment(t, srv) - testrpc.WaitForLeader(t, s1.RPC, "dc1") + acl := ACL{srv: srv} + testPolicy1, err := upsertTestPolicy(codec, TestDefaultMasterToken, "dc1") - acl := ACL{srv: s1} - - testPolicy1, err := upsertTestPolicy(codec, "root", "dc1") require.NoError(t, err) for _, test := range []struct { @@ -3121,7 +2847,7 @@ func TestACLEndpoint_RoleSet_names(t *testing.T) { t.Run(testName, func(t *testing.T) { // cleanup from a prior insertion that may have succeeded - require.NoError(t, deleteTestRoleByName(codec, "root", "dc1", test.name)) + require.NoError(t, deleteTestRoleByName(codec, TestDefaultMasterToken, "dc1", test.name)) req := structs.ACLRoleSetRequest{ Datacenter: "dc1", @@ -3129,12 +2855,12 @@ func TestACLEndpoint_RoleSet_names(t *testing.T) { Name: test.name, Description: "foobar", Policies: []structs.ACLRolePolicyLink{ - structs.ACLRolePolicyLink{ + { ID: testPolicy1.ID, }, }, }, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } resp := structs.ACLRole{} @@ -3142,7 +2868,7 @@ func TestACLEndpoint_RoleSet_names(t *testing.T) { if test.ok { require.NoError(t, err) - roleResp, err := retrieveTestRole(codec, "root", "dc1", resp.ID) + roleResp, err := retrieveTestRole(codec, TestDefaultMasterToken, "dc1", resp.ID) require.NoError(t, err) role := roleResp.Role require.Equal(t, test.name, role.Name) @@ -3156,27 +2882,18 @@ func TestACLEndpoint_RoleSet_names(t *testing.T) { func TestACLEndpoint_RoleDelete(t *testing.T) { t.Parallel() - dir1, s1 := testServerWithConfig(t, func(c *Config) { - c.ACLDatacenter = "dc1" - c.ACLsEnabled = true - c.ACLMasterToken = "root" - }) - defer os.RemoveAll(dir1) - defer s1.Shutdown() - codec := rpcClient(t, s1) - defer codec.Close() + _, srv, codec := testACLServerWithConfig(t, nil, false) + waitForLeaderEstablishment(t, srv) + existingRole, err := upsertTestRole(codec, TestDefaultMasterToken, "dc1") - testrpc.WaitForLeader(t, s1.RPC, "dc1") - - existingRole, err := upsertTestRole(codec, "root", "dc1") require.NoError(t, err) - acl := ACL{srv: s1} + acl := ACL{srv: srv} req := structs.ACLRoleDeleteRequest{ Datacenter: "dc1", RoleID: existingRole.ID, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } var resp string @@ -3185,36 +2902,27 @@ func TestACLEndpoint_RoleDelete(t *testing.T) { require.NoError(t, err) // Make sure the role is gone - roleResp, err := retrieveTestRole(codec, "root", "dc1", existingRole.ID) + roleResp, err := retrieveTestRole(codec, TestDefaultMasterToken, "dc1", existingRole.ID) require.Nil(t, roleResp.Role) } func TestACLEndpoint_RoleList(t *testing.T) { t.Parallel() - dir1, s1 := testServerWithConfig(t, func(c *Config) { - c.ACLDatacenter = "dc1" - c.ACLsEnabled = true - c.ACLMasterToken = "root" - }) - defer os.RemoveAll(dir1) - defer s1.Shutdown() - codec := rpcClient(t, s1) - defer codec.Close() + _, srv, codec := testACLServerWithConfig(t, nil, false) + waitForLeaderEstablishment(t, srv) - testrpc.WaitForLeader(t, s1.RPC, "dc1") - - r1, err := upsertTestRole(codec, "root", "dc1") + r1, err := upsertTestRole(codec, TestDefaultMasterToken, "dc1") require.NoError(t, err) - r2, err := upsertTestRole(codec, "root", "dc1") + r2, err := upsertTestRole(codec, TestDefaultMasterToken, "dc1") require.NoError(t, err) - acl := ACL{srv: s1} + acl := ACL{srv: srv} req := structs.ACLRoleListRequest{ Datacenter: "dc1", - QueryOptions: structs.QueryOptions{Token: "root"}, + QueryOptions: structs.QueryOptions{Token: TestDefaultMasterToken}, } resp := structs.ACLRoleListResponse{} @@ -3227,41 +2935,32 @@ func TestACLEndpoint_RoleList(t *testing.T) { func TestACLEndpoint_RoleResolve(t *testing.T) { t.Parallel() - dir1, s1 := testServerWithConfig(t, func(c *Config) { - c.ACLDatacenter = "dc1" - c.ACLsEnabled = true - c.ACLMasterToken = "root" - }) - defer os.RemoveAll(dir1) - defer s1.Shutdown() - codec := rpcClient(t, s1) - defer codec.Close() - - testrpc.WaitForLeader(t, s1.RPC, "dc1") + _, srv, codec := testACLServerWithConfig(t, nil, false) + waitForLeaderEstablishment(t, srv) t.Run("Normal", func(t *testing.T) { - r1, err := upsertTestRole(codec, "root", "dc1") + r1, err := upsertTestRole(codec, TestDefaultMasterToken, "dc1") require.NoError(t, err) - r2, err := upsertTestRole(codec, "root", "dc1") + r2, err := upsertTestRole(codec, TestDefaultMasterToken, "dc1") require.NoError(t, err) - acl := ACL{srv: s1} + acl := ACL{srv: srv} // Assign the roles to a token tokenUpsertReq := structs.ACLTokenSetRequest{ Datacenter: "dc1", ACLToken: structs.ACLToken{ Roles: []structs.ACLTokenRoleLink{ - structs.ACLTokenRoleLink{ + { ID: r1.ID, }, - structs.ACLTokenRoleLink{ + { ID: r2.ID, }, }, }, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } token := structs.ACLToken{} err = acl.TokenSet(&tokenUpsertReq, &token) @@ -3285,21 +2984,12 @@ func TestACLEndpoint_AuthMethodSet(t *testing.T) { tempDir, err := ioutil.TempDir("", "consul") require.NoError(t, err) - defer os.RemoveAll(tempDir) + t.Cleanup(func() { os.RemoveAll(tempDir) }) + _, srv, codec := testACLServerWithConfig(t, nil, false) - dir1, s1 := testServerWithConfig(t, func(c *Config) { - c.ACLDatacenter = "dc1" - c.ACLsEnabled = true - c.ACLMasterToken = "root" - }) - defer os.RemoveAll(dir1) - defer s1.Shutdown() - codec := rpcClient(t, s1) - defer codec.Close() + waitForLeaderEstablishment(t, srv) - testrpc.WaitForLeader(t, s1.RPC, "dc1") - - acl := ACL{srv: s1} + acl := ACL{srv: srv} newAuthMethod := func(name string) structs.ACLAuthMethod { return structs.ACLAuthMethod{ @@ -3315,7 +3005,7 @@ func TestACLEndpoint_AuthMethodSet(t *testing.T) { req := structs.ACLAuthMethodSetRequest{ Datacenter: "dc1", AuthMethod: reqMethod, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } resp := structs.ACLAuthMethod{} @@ -3323,7 +3013,7 @@ func TestACLEndpoint_AuthMethodSet(t *testing.T) { require.NoError(t, err) // Get the method directly to validate that it exists - methodResp, err := retrieveTestAuthMethod(codec, "root", "dc1", resp.Name) + methodResp, err := retrieveTestAuthMethod(codec, TestDefaultMasterToken, "dc1", resp.Name) require.NoError(t, err) method := methodResp.AuthMethod @@ -3339,7 +3029,7 @@ func TestACLEndpoint_AuthMethodSet(t *testing.T) { req := structs.ACLAuthMethodSetRequest{ Datacenter: "dc1", AuthMethod: reqMethod, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } resp := structs.ACLAuthMethod{} @@ -3356,7 +3046,7 @@ func TestACLEndpoint_AuthMethodSet(t *testing.T) { req := structs.ACLAuthMethodSetRequest{ Datacenter: "dc1", AuthMethod: reqMethod, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } resp := structs.ACLAuthMethod{} @@ -3364,7 +3054,7 @@ func TestACLEndpoint_AuthMethodSet(t *testing.T) { require.NoError(t, err) // Get the method directly to validate that it exists - methodResp, err := retrieveTestAuthMethod(codec, "root", "dc1", resp.Name) + methodResp, err := retrieveTestAuthMethod(codec, TestDefaultMasterToken, "dc1", resp.Name) require.NoError(t, err) method := methodResp.AuthMethod @@ -3382,7 +3072,7 @@ func TestACLEndpoint_AuthMethodSet(t *testing.T) { req := structs.ACLAuthMethodSetRequest{ Datacenter: "dc1", AuthMethod: reqMethod, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } resp := structs.ACLAuthMethod{} @@ -3390,7 +3080,7 @@ func TestACLEndpoint_AuthMethodSet(t *testing.T) { require.NoError(t, err) // Get the method directly to validate that it exists - methodResp, err := retrieveTestAuthMethod(codec, "root", "dc1", resp.Name) + methodResp, err := retrieveTestAuthMethod(codec, TestDefaultMasterToken, "dc1", resp.Name) require.NoError(t, err) method := methodResp.AuthMethod @@ -3404,7 +3094,7 @@ func TestACLEndpoint_AuthMethodSet(t *testing.T) { req := structs.ACLAuthMethodSetRequest{ Datacenter: "dc1", AuthMethod: newAuthMethod(""), - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } resp := structs.ACLAuthMethod{} @@ -3420,7 +3110,7 @@ func TestACLEndpoint_AuthMethodSet(t *testing.T) { Description: "invalid test", Type: "invalid", }, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } resp := structs.ACLAuthMethod{} @@ -3460,7 +3150,7 @@ func TestACLEndpoint_AuthMethodSet(t *testing.T) { req := structs.ACLAuthMethodSetRequest{ Datacenter: "dc1", AuthMethod: newAuthMethod(test.name), - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } resp := structs.ACLAuthMethod{} @@ -3470,7 +3160,7 @@ func TestACLEndpoint_AuthMethodSet(t *testing.T) { require.NoError(t, err) // Get the method directly to validate that it exists - methodResp, err := retrieveTestAuthMethod(codec, "root", "dc1", resp.Name) + methodResp, err := retrieveTestAuthMethod(codec, TestDefaultMasterToken, "dc1", resp.Name) require.NoError(t, err) method := methodResp.AuthMethod @@ -3489,7 +3179,7 @@ func TestACLEndpoint_AuthMethodSet(t *testing.T) { req := structs.ACLAuthMethodSetRequest{ Datacenter: "dc1", AuthMethod: reqMethod, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } resp := structs.ACLAuthMethod{} @@ -3497,7 +3187,7 @@ func TestACLEndpoint_AuthMethodSet(t *testing.T) { require.NoError(t, err) // Get the method directly to validate that it exists - methodResp, err := retrieveTestAuthMethod(codec, "root", "dc1", resp.Name) + methodResp, err := retrieveTestAuthMethod(codec, TestDefaultMasterToken, "dc1", resp.Name) require.NoError(t, err) method := methodResp.AuthMethod @@ -3516,7 +3206,7 @@ func TestACLEndpoint_AuthMethodSet(t *testing.T) { req := structs.ACLAuthMethodSetRequest{ Datacenter: "dc1", AuthMethod: reqMethod, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } resp := structs.ACLAuthMethod{} @@ -3524,7 +3214,7 @@ func TestACLEndpoint_AuthMethodSet(t *testing.T) { require.NoError(t, err) // Get the method directly to validate that it exists - methodResp, err := retrieveTestAuthMethod(codec, "root", "dc1", resp.Name) + methodResp, err := retrieveTestAuthMethod(codec, TestDefaultMasterToken, "dc1", resp.Name) require.NoError(t, err) method := methodResp.AuthMethod @@ -3542,7 +3232,7 @@ func TestACLEndpoint_AuthMethodSet(t *testing.T) { req := structs.ACLAuthMethodSetRequest{ Datacenter: "dc1", AuthMethod: reqMethod, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } resp := structs.ACLAuthMethod{} @@ -3557,7 +3247,7 @@ func TestACLEndpoint_AuthMethodSet(t *testing.T) { req := structs.ACLAuthMethodSetRequest{ Datacenter: "dc1", AuthMethod: reqMethod, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } resp := structs.ACLAuthMethod{} @@ -3569,31 +3259,22 @@ func TestACLEndpoint_AuthMethodSet(t *testing.T) { func TestACLEndpoint_AuthMethodDelete(t *testing.T) { t.Parallel() - dir1, s1 := testServerWithConfig(t, func(c *Config) { - c.ACLDatacenter = "dc1" - c.ACLsEnabled = true - c.ACLMasterToken = "root" - }) - defer os.RemoveAll(dir1) - defer s1.Shutdown() - codec := rpcClient(t, s1) - defer codec.Close() - - testrpc.WaitForLeader(t, s1.RPC, "dc1") + _, srv, codec := testACLServerWithConfig(t, nil, false) + waitForLeaderEstablishment(t, srv) testSessionID := testauth.StartSession() defer testauth.ResetSession(testSessionID) - existingMethod, err := upsertTestAuthMethod(codec, "root", "dc1", testSessionID) + existingMethod, err := upsertTestAuthMethod(codec, TestDefaultMasterToken, "dc1", testSessionID) require.NoError(t, err) - acl := ACL{srv: s1} + acl := ACL{srv: srv} t.Run("normal", func(t *testing.T) { req := structs.ACLAuthMethodDeleteRequest{ Datacenter: "dc1", AuthMethodName: existingMethod.Name, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } var ignored bool @@ -3601,7 +3282,7 @@ func TestACLEndpoint_AuthMethodDelete(t *testing.T) { require.NoError(t, err) // Make sure the method is gone - methodResp, err := retrieveTestAuthMethod(codec, "root", "dc1", existingMethod.Name) + methodResp, err := retrieveTestAuthMethod(codec, TestDefaultMasterToken, "dc1", existingMethod.Name) require.NoError(t, err) require.Nil(t, methodResp.AuthMethod) }) @@ -3610,7 +3291,7 @@ func TestACLEndpoint_AuthMethodDelete(t *testing.T) { req := structs.ACLAuthMethodDeleteRequest{ Datacenter: "dc1", AuthMethodName: "missing", - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } var ignored bool @@ -3623,17 +3304,8 @@ func TestACLEndpoint_AuthMethodDelete(t *testing.T) { func TestACLEndpoint_AuthMethodDelete_RuleAndTokenCascade(t *testing.T) { t.Parallel() - dir1, s1 := testServerWithConfig(t, func(c *Config) { - c.ACLDatacenter = "dc1" - c.ACLsEnabled = true - c.ACLMasterToken = "root" - }) - defer os.RemoveAll(dir1) - defer s1.Shutdown() - codec := rpcClient(t, s1) - defer codec.Close() - - testrpc.WaitForLeader(t, s1.RPC, "dc1") + _, srv, codec := testACLServerWithConfig(t, nil, false) + waitForLeaderEstablishment(t, srv) testSessionID1 := testauth.StartSession() defer testauth.ResetSession(testSessionID1) @@ -3644,7 +3316,7 @@ func TestACLEndpoint_AuthMethodDelete_RuleAndTokenCascade(t *testing.T) { testauth.InstallSessionToken(testSessionID2, "fake-token2", "default", "abc", "abc123") createToken := func(methodName, bearerToken string) *structs.ACLToken { - acl := ACL{srv: s1} + acl := ACL{srv: srv} resp := structs.ACLToken{} @@ -3659,10 +3331,10 @@ func TestACLEndpoint_AuthMethodDelete_RuleAndTokenCascade(t *testing.T) { return &resp } - method1, err := upsertTestAuthMethod(codec, "root", "dc1", testSessionID1) + method1, err := upsertTestAuthMethod(codec, TestDefaultMasterToken, "dc1", testSessionID1) require.NoError(t, err) i1_r1, err := upsertTestBindingRule( - codec, "root", "dc1", + codec, TestDefaultMasterToken, "dc1", method1.Name, "serviceaccount.name==abc", structs.BindingRuleBindTypeService, @@ -3670,7 +3342,7 @@ func TestACLEndpoint_AuthMethodDelete_RuleAndTokenCascade(t *testing.T) { ) require.NoError(t, err) i1_r2, err := upsertTestBindingRule( - codec, "root", "dc1", + codec, TestDefaultMasterToken, "dc1", method1.Name, "serviceaccount.name==def", structs.BindingRuleBindTypeService, @@ -3680,10 +3352,10 @@ func TestACLEndpoint_AuthMethodDelete_RuleAndTokenCascade(t *testing.T) { i1_t1 := createToken(method1.Name, "fake-token1") i1_t2 := createToken(method1.Name, "fake-token1") - method2, err := upsertTestAuthMethod(codec, "root", "dc1", testSessionID2) + method2, err := upsertTestAuthMethod(codec, TestDefaultMasterToken, "dc1", testSessionID2) require.NoError(t, err) i2_r1, err := upsertTestBindingRule( - codec, "root", "dc1", + codec, TestDefaultMasterToken, "dc1", method2.Name, "serviceaccount.name==abc", structs.BindingRuleBindTypeService, @@ -3691,7 +3363,7 @@ func TestACLEndpoint_AuthMethodDelete_RuleAndTokenCascade(t *testing.T) { ) require.NoError(t, err) i2_r2, err := upsertTestBindingRule( - codec, "root", "dc1", + codec, TestDefaultMasterToken, "dc1", method2.Name, "serviceaccount.name==def", structs.BindingRuleBindTypeService, @@ -3701,12 +3373,12 @@ func TestACLEndpoint_AuthMethodDelete_RuleAndTokenCascade(t *testing.T) { i2_t1 := createToken(method2.Name, "fake-token2") i2_t2 := createToken(method2.Name, "fake-token2") - acl := ACL{srv: s1} + acl := ACL{srv: srv} req := structs.ACLAuthMethodDeleteRequest{ Datacenter: "dc1", AuthMethodName: method1.Name, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } var ignored bool @@ -3714,30 +3386,30 @@ func TestACLEndpoint_AuthMethodDelete_RuleAndTokenCascade(t *testing.T) { require.NoError(t, err) // Make sure the method is gone. - methodResp, err := retrieveTestAuthMethod(codec, "root", "dc1", method1.Name) + methodResp, err := retrieveTestAuthMethod(codec, TestDefaultMasterToken, "dc1", method1.Name) require.NoError(t, err) require.Nil(t, methodResp.AuthMethod) // Make sure the rules and tokens are gone. for _, id := range []string{i1_r1.ID, i1_r2.ID} { - ruleResp, err := retrieveTestBindingRule(codec, "root", "dc1", id) + ruleResp, err := retrieveTestBindingRule(codec, TestDefaultMasterToken, "dc1", id) require.NoError(t, err) require.Nil(t, ruleResp.BindingRule) } for _, id := range []string{i1_t1.AccessorID, i1_t2.AccessorID} { - tokResp, err := retrieveTestToken(codec, "root", "dc1", id) + tokResp, err := retrieveTestToken(codec, TestDefaultMasterToken, "dc1", id) require.NoError(t, err) require.Nil(t, tokResp.Token) } // Make sure the rules and tokens for the untouched auth method are still there. for _, id := range []string{i2_r1.ID, i2_r2.ID} { - ruleResp, err := retrieveTestBindingRule(codec, "root", "dc1", id) + ruleResp, err := retrieveTestBindingRule(codec, TestDefaultMasterToken, "dc1", id) require.NoError(t, err) require.NotNil(t, ruleResp.BindingRule) } for _, id := range []string{i2_t1.AccessorID, i2_t2.AccessorID} { - tokResp, err := retrieveTestToken(codec, "root", "dc1", id) + tokResp, err := retrieveTestToken(codec, TestDefaultMasterToken, "dc1", id) require.NoError(t, err) require.NotNil(t, tokResp.Token) } @@ -3746,29 +3418,20 @@ func TestACLEndpoint_AuthMethodDelete_RuleAndTokenCascade(t *testing.T) { func TestACLEndpoint_AuthMethodList(t *testing.T) { t.Parallel() - dir1, s1 := testServerWithConfig(t, func(c *Config) { - c.ACLDatacenter = "dc1" - c.ACLsEnabled = true - c.ACLMasterToken = "root" - }) - defer os.RemoveAll(dir1) - defer s1.Shutdown() - codec := rpcClient(t, s1) - defer codec.Close() + _, srv, codec := testACLServerWithConfig(t, nil, false) + waitForLeaderEstablishment(t, srv) - testrpc.WaitForLeader(t, s1.RPC, "dc1") - - i1, err := upsertTestAuthMethod(codec, "root", "dc1", "") + i1, err := upsertTestAuthMethod(codec, TestDefaultMasterToken, "dc1", "") require.NoError(t, err) - i2, err := upsertTestAuthMethod(codec, "root", "dc1", "") + i2, err := upsertTestAuthMethod(codec, TestDefaultMasterToken, "dc1", "") require.NoError(t, err) - acl := ACL{srv: s1} + acl := ACL{srv: srv} req := structs.ACLAuthMethodListRequest{ Datacenter: "dc1", - QueryOptions: structs.QueryOptions{Token: "root"}, + QueryOptions: structs.QueryOptions{Token: TestDefaultMasterToken}, } resp := structs.ACLAuthMethodListResponse{} @@ -3781,25 +3444,16 @@ func TestACLEndpoint_AuthMethodList(t *testing.T) { func TestACLEndpoint_BindingRuleSet(t *testing.T) { t.Parallel() - dir1, s1 := testServerWithConfig(t, func(c *Config) { - c.ACLDatacenter = "dc1" - c.ACLsEnabled = true - c.ACLMasterToken = "root" - }) - defer os.RemoveAll(dir1) - defer s1.Shutdown() - codec := rpcClient(t, s1) - defer codec.Close() + _, srv, codec := testACLServerWithConfig(t, nil, false) + waitForLeaderEstablishment(t, srv) + acl := ACL{srv: srv} - testrpc.WaitForLeader(t, s1.RPC, "dc1") - - acl := ACL{srv: s1} var ruleID string - testAuthMethod, err := upsertTestAuthMethod(codec, "root", "dc1", "") + testAuthMethod, err := upsertTestAuthMethod(codec, TestDefaultMasterToken, "dc1", "") require.NoError(t, err) - otherTestAuthMethod, err := upsertTestAuthMethod(codec, "root", "dc1", "") + otherTestAuthMethod, err := upsertTestAuthMethod(codec, TestDefaultMasterToken, "dc1", "") require.NoError(t, err) newRule := func() structs.ACLBindingRule { @@ -3816,7 +3470,7 @@ func TestACLEndpoint_BindingRuleSet(t *testing.T) { req := structs.ACLBindingRuleSetRequest{ Datacenter: "dc1", BindingRule: reqRule, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } resp := structs.ACLBindingRule{} @@ -3828,7 +3482,7 @@ func TestACLEndpoint_BindingRuleSet(t *testing.T) { req := structs.ACLBindingRuleSetRequest{ Datacenter: "dc1", BindingRule: reqRule, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } resp := structs.ACLBindingRule{} @@ -3844,7 +3498,7 @@ func TestACLEndpoint_BindingRuleSet(t *testing.T) { req := structs.ACLBindingRuleSetRequest{ Datacenter: "dc1", BindingRule: reqRule, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } resp := structs.ACLBindingRule{} @@ -3853,7 +3507,7 @@ func TestACLEndpoint_BindingRuleSet(t *testing.T) { require.NotNil(t, resp.ID) // Get the rule directly to validate that it exists - ruleResp, err := retrieveTestBindingRule(codec, "root", "dc1", resp.ID) + ruleResp, err := retrieveTestBindingRule(codec, TestDefaultMasterToken, "dc1", resp.ID) require.NoError(t, err) rule := ruleResp.BindingRule @@ -3867,6 +3521,37 @@ func TestACLEndpoint_BindingRuleSet(t *testing.T) { ruleID = rule.ID }) + t.Run("Bind Node", func(t *testing.T) { + req := structs.ACLBindingRuleSetRequest{ + Datacenter: "dc1", + BindingRule: structs.ACLBindingRule{ + Description: "foobar", + AuthMethod: testAuthMethod.Name, + Selector: "serviceaccount.name==abc", + BindType: structs.BindingRuleBindTypeNode, + BindName: "test-node", + }, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, + } + var resp structs.ACLBindingRule + + err := acl.BindingRuleSet(&req, &resp) + require.NoError(t, err) + require.NotNil(t, resp.ID) + + // Get the rule directly to validate that it exists + ruleResp, err := retrieveTestBindingRule(codec, TestDefaultMasterToken, "dc1", resp.ID) + require.NoError(t, err) + rule := ruleResp.BindingRule + + require.NotEmpty(t, rule.ID) + require.Equal(t, rule.Description, "foobar") + require.Equal(t, rule.AuthMethod, testAuthMethod.Name) + require.Equal(t, "serviceaccount.name==abc", rule.Selector) + require.Equal(t, structs.BindingRuleBindTypeNode, rule.BindType) + require.Equal(t, "test-node", rule.BindName) + }) + t.Run("Update fails; cannot change method name", func(t *testing.T) { reqRule := newRule() reqRule.ID = ruleID @@ -3886,7 +3571,7 @@ func TestACLEndpoint_BindingRuleSet(t *testing.T) { req := structs.ACLBindingRuleSetRequest{ Datacenter: "dc1", BindingRule: reqRule, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } resp := structs.ACLBindingRule{} @@ -3895,7 +3580,7 @@ func TestACLEndpoint_BindingRuleSet(t *testing.T) { require.NotNil(t, resp.ID) // Get the rule directly to validate that it exists - ruleResp, err := retrieveTestBindingRule(codec, "root", "dc1", resp.ID) + ruleResp, err := retrieveTestBindingRule(codec, TestDefaultMasterToken, "dc1", resp.ID) require.NoError(t, err) rule := ruleResp.BindingRule @@ -3918,7 +3603,7 @@ func TestACLEndpoint_BindingRuleSet(t *testing.T) { req := structs.ACLBindingRuleSetRequest{ Datacenter: "dc1", BindingRule: reqRule, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } resp := structs.ACLBindingRule{} @@ -3927,7 +3612,7 @@ func TestACLEndpoint_BindingRuleSet(t *testing.T) { require.NotNil(t, resp.ID) // Get the rule directly to validate that it exists - ruleResp, err := retrieveTestBindingRule(codec, "root", "dc1", resp.ID) + ruleResp, err := retrieveTestBindingRule(codec, TestDefaultMasterToken, "dc1", resp.ID) require.NoError(t, err) rule := ruleResp.BindingRule @@ -4016,23 +3701,14 @@ func TestACLEndpoint_BindingRuleSet(t *testing.T) { func TestACLEndpoint_BindingRuleDelete(t *testing.T) { t.Parallel() - dir1, s1 := testServerWithConfig(t, func(c *Config) { - c.ACLDatacenter = "dc1" - c.ACLsEnabled = true - c.ACLMasterToken = "root" - }) - defer os.RemoveAll(dir1) - defer s1.Shutdown() - codec := rpcClient(t, s1) - defer codec.Close() + _, srv, codec := testACLServerWithConfig(t, nil, false) + waitForLeaderEstablishment(t, srv) - testrpc.WaitForLeader(t, s1.RPC, "dc1") - - testAuthMethod, err := upsertTestAuthMethod(codec, "root", "dc1", "") + testAuthMethod, err := upsertTestAuthMethod(codec, TestDefaultMasterToken, "dc1", "") require.NoError(t, err) existingRule, err := upsertTestBindingRule( - codec, "root", "dc1", + codec, TestDefaultMasterToken, "dc1", testAuthMethod.Name, "serviceaccount.name==abc", structs.BindingRuleBindTypeService, @@ -4040,13 +3716,13 @@ func TestACLEndpoint_BindingRuleDelete(t *testing.T) { ) require.NoError(t, err) - acl := ACL{srv: s1} + acl := ACL{srv: srv} t.Run("normal", func(t *testing.T) { req := structs.ACLBindingRuleDeleteRequest{ Datacenter: "dc1", BindingRuleID: existingRule.ID, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } var ignored bool @@ -4054,7 +3730,7 @@ func TestACLEndpoint_BindingRuleDelete(t *testing.T) { require.NoError(t, err) // Make sure the rule is gone - ruleResp, err := retrieveTestBindingRule(codec, "root", "dc1", existingRule.ID) + ruleResp, err := retrieveTestBindingRule(codec, TestDefaultMasterToken, "dc1", existingRule.ID) require.NoError(t, err) require.Nil(t, ruleResp.BindingRule) }) @@ -4066,7 +3742,7 @@ func TestACLEndpoint_BindingRuleDelete(t *testing.T) { req := structs.ACLBindingRuleDeleteRequest{ Datacenter: "dc1", BindingRuleID: fakeID, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } var ignored bool @@ -4078,23 +3754,14 @@ func TestACLEndpoint_BindingRuleDelete(t *testing.T) { func TestACLEndpoint_BindingRuleList(t *testing.T) { t.Parallel() - dir1, s1 := testServerWithConfig(t, func(c *Config) { - c.ACLDatacenter = "dc1" - c.ACLsEnabled = true - c.ACLMasterToken = "root" - }) - defer os.RemoveAll(dir1) - defer s1.Shutdown() - codec := rpcClient(t, s1) - defer codec.Close() + _, srv, codec := testACLServerWithConfig(t, nil, false) + waitForLeaderEstablishment(t, srv) - testrpc.WaitForLeader(t, s1.RPC, "dc1") - - testAuthMethod, err := upsertTestAuthMethod(codec, "root", "dc1", "") + testAuthMethod, err := upsertTestAuthMethod(codec, TestDefaultMasterToken, "dc1", "") require.NoError(t, err) r1, err := upsertTestBindingRule( - codec, "root", "dc1", + codec, TestDefaultMasterToken, "dc1", testAuthMethod.Name, "serviceaccount.name==abc", structs.BindingRuleBindTypeService, @@ -4103,7 +3770,7 @@ func TestACLEndpoint_BindingRuleList(t *testing.T) { require.NoError(t, err) r2, err := upsertTestBindingRule( - codec, "root", "dc1", + codec, TestDefaultMasterToken, "dc1", testAuthMethod.Name, "serviceaccount.name==def", structs.BindingRuleBindTypeService, @@ -4111,11 +3778,11 @@ func TestACLEndpoint_BindingRuleList(t *testing.T) { ) require.NoError(t, err) - acl := ACL{srv: s1} + acl := ACL{srv: srv} req := structs.ACLBindingRuleListRequest{ Datacenter: "dc1", - QueryOptions: structs.QueryOptions{Token: "root"}, + QueryOptions: structs.QueryOptions{Token: TestDefaultMasterToken}, } resp := structs.ACLBindingRuleListResponse{} @@ -4128,38 +3795,20 @@ func TestACLEndpoint_BindingRuleList(t *testing.T) { func TestACLEndpoint_SecureIntroEndpoints_LocalTokensDisabled(t *testing.T) { t.Parallel() - dir1, s1 := testServerWithConfig(t, func(c *Config) { - c.ACLDatacenter = "dc1" - c.ACLsEnabled = true - c.ACLMasterToken = "root" + _, s1, _ := testACLServerWithConfig(t, func(c *Config) { c.ACLTokenMinExpirationTTL = 10 * time.Millisecond c.ACLTokenMaxExpirationTTL = 5 * time.Second - }) - defer os.RemoveAll(dir1) - defer s1.Shutdown() - codec := rpcClient(t, s1) - defer codec.Close() + }, false) + waitForLeaderEstablishment(t, s1) - testrpc.WaitForLeader(t, s1.RPC, "dc1") - - dir2, s2 := testServerWithConfig(t, func(c *Config) { - c.ACLDatacenter = "dc1" - c.ACLsEnabled = true + _, s2, _ := testACLServerWithConfig(t, func(c *Config) { c.Datacenter = "dc2" c.ACLTokenMinExpirationTTL = 10 * time.Millisecond c.ACLTokenMaxExpirationTTL = 5 * time.Second // disable local tokens c.ACLTokenReplication = false - }) - defer os.RemoveAll(dir2) - defer s2.Shutdown() - codec2 := rpcClient(t, s2) - defer codec2.Close() - - s2.tokens.UpdateReplicationToken("root", tokenStore.TokenSourceConfig) - - testrpc.WaitForLeader(t, s1.RPC, "dc1") - testrpc.WaitForLeader(t, s2.RPC, "dc2") + }, true) + waitForLeaderEstablishment(t, s2) // Try to join joinWAN(t, s2, s1) @@ -4246,38 +3895,20 @@ func TestACLEndpoint_SecureIntroEndpoints_LocalTokensDisabled(t *testing.T) { func TestACLEndpoint_SecureIntroEndpoints_OnlyCreateLocalData(t *testing.T) { t.Parallel() - dir1, s1 := testServerWithConfig(t, func(c *Config) { - c.ACLDatacenter = "dc1" - c.ACLsEnabled = true - c.ACLMasterToken = "root" + _, s1, codec1 := testACLServerWithConfig(t, func(c *Config) { c.ACLTokenMinExpirationTTL = 10 * time.Millisecond c.ACLTokenMaxExpirationTTL = 5 * time.Second - }) - defer os.RemoveAll(dir1) - defer s1.Shutdown() - codec1 := rpcClient(t, s1) - defer codec1.Close() + }, false) + waitForLeaderEstablishment(t, s1) - testrpc.WaitForLeader(t, s1.RPC, "dc1") - - dir2, s2 := testServerWithConfig(t, func(c *Config) { - c.ACLDatacenter = "dc1" - c.ACLsEnabled = true + _, s2, codec2 := testACLServerWithConfig(t, func(c *Config) { c.Datacenter = "dc2" c.ACLTokenMinExpirationTTL = 10 * time.Millisecond c.ACLTokenMaxExpirationTTL = 5 * time.Second // enable token replication so secure intro works c.ACLTokenReplication = true - }) - defer os.RemoveAll(dir2) - defer s2.Shutdown() - codec2 := rpcClient(t, s2) - defer codec2.Close() - - s2.tokens.UpdateReplicationToken("root", tokenStore.TokenSourceConfig) - - testrpc.WaitForLeader(t, s1.RPC, "dc1") - testrpc.WaitForLeader(t, s2.RPC, "dc2") + }, true) + waitForLeaderEstablishment(t, s2) // Try to join joinWAN(t, s2, s1) @@ -4323,19 +3954,19 @@ func TestACLEndpoint_SecureIntroEndpoints_OnlyCreateLocalData(t *testing.T) { "SessionID": testSessionID_2, }, }, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } resp := structs.ACLAuthMethod{} require.NoError(t, acl2.AuthMethodSet(&req, &resp)) // present in dc2 - resp2, err := retrieveTestAuthMethod(codec2, "root", "dc2", "testmethod") + resp2, err := retrieveTestAuthMethod(codec2, TestDefaultMasterToken, "dc2", "testmethod") require.NoError(t, err) require.NotNil(t, resp2.AuthMethod) require.Equal(t, "test original", resp2.AuthMethod.Description) // absent in dc1 - resp2, err = retrieveTestAuthMethod(codec1, "root", "dc1", "testmethod") + resp2, err = retrieveTestAuthMethod(codec1, TestDefaultMasterToken, "dc1", "testmethod") require.NoError(t, err) require.Nil(t, resp2.AuthMethod) }) @@ -4350,19 +3981,19 @@ func TestACLEndpoint_SecureIntroEndpoints_OnlyCreateLocalData(t *testing.T) { "SessionID": testSessionID_2, }, }, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } resp := structs.ACLAuthMethod{} require.NoError(t, acl2.AuthMethodSet(&req, &resp)) // present in dc2 - resp2, err := retrieveTestAuthMethod(codec2, "root", "dc2", "testmethod") + resp2, err := retrieveTestAuthMethod(codec2, TestDefaultMasterToken, "dc2", "testmethod") require.NoError(t, err) require.NotNil(t, resp2.AuthMethod) require.Equal(t, "test updated", resp2.AuthMethod.Description) // absent in dc1 - resp2, err = retrieveTestAuthMethod(codec1, "root", "dc1", "testmethod") + resp2, err = retrieveTestAuthMethod(codec1, TestDefaultMasterToken, "dc1", "testmethod") require.NoError(t, err) require.Nil(t, resp2.AuthMethod) }) @@ -4372,7 +4003,7 @@ func TestACLEndpoint_SecureIntroEndpoints_OnlyCreateLocalData(t *testing.T) { req := structs.ACLAuthMethodGetRequest{ Datacenter: "dc2", AuthMethodName: "testmethod", - QueryOptions: structs.QueryOptions{Token: "root"}, + QueryOptions: structs.QueryOptions{Token: TestDefaultMasterToken}, } resp := structs.ACLAuthMethodResponse{} require.NoError(t, acl2.AuthMethodRead(&req, &resp)) @@ -4383,7 +4014,7 @@ func TestACLEndpoint_SecureIntroEndpoints_OnlyCreateLocalData(t *testing.T) { req = structs.ACLAuthMethodGetRequest{ Datacenter: "dc1", AuthMethodName: "testmethod", - QueryOptions: structs.QueryOptions{Token: "root"}, + QueryOptions: structs.QueryOptions{Token: TestDefaultMasterToken}, } resp = structs.ACLAuthMethodResponse{} require.NoError(t, acl.AuthMethodRead(&req, &resp)) @@ -4394,7 +4025,7 @@ func TestACLEndpoint_SecureIntroEndpoints_OnlyCreateLocalData(t *testing.T) { // present in dc2 req := structs.ACLAuthMethodListRequest{ Datacenter: "dc2", - QueryOptions: structs.QueryOptions{Token: "root"}, + QueryOptions: structs.QueryOptions{Token: TestDefaultMasterToken}, } resp := structs.ACLAuthMethodListResponse{} require.NoError(t, acl2.AuthMethodList(&req, &resp)) @@ -4403,7 +4034,7 @@ func TestACLEndpoint_SecureIntroEndpoints_OnlyCreateLocalData(t *testing.T) { // absent in dc1 req = structs.ACLAuthMethodListRequest{ Datacenter: "dc1", - QueryOptions: structs.QueryOptions{Token: "root"}, + QueryOptions: structs.QueryOptions{Token: TestDefaultMasterToken}, } resp = structs.ACLAuthMethodListResponse{} require.NoError(t, acl.AuthMethodList(&req, &resp)) @@ -4420,7 +4051,7 @@ func TestACLEndpoint_SecureIntroEndpoints_OnlyCreateLocalData(t *testing.T) { BindType: structs.BindingRuleBindTypeService, BindName: "${serviceaccount.name}", }, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } resp := structs.ACLBindingRule{} @@ -4429,12 +4060,12 @@ func TestACLEndpoint_SecureIntroEndpoints_OnlyCreateLocalData(t *testing.T) { ruleID = resp.ID // present in dc2 - resp2, err := retrieveTestBindingRule(codec2, "root", "dc2", ruleID) + resp2, err := retrieveTestBindingRule(codec2, TestDefaultMasterToken, "dc2", ruleID) require.NoError(t, err) require.NotNil(t, resp2.BindingRule) require.Equal(t, "test original", resp2.BindingRule.Description) // absent in dc1 - resp2, err = retrieveTestBindingRule(codec1, "root", "dc1", ruleID) + resp2, err = retrieveTestBindingRule(codec1, TestDefaultMasterToken, "dc1", ruleID) require.NoError(t, err) require.Nil(t, resp2.BindingRule) }) @@ -4449,7 +4080,7 @@ func TestACLEndpoint_SecureIntroEndpoints_OnlyCreateLocalData(t *testing.T) { BindType: structs.BindingRuleBindTypeService, BindName: "${serviceaccount.name}", }, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } resp := structs.ACLBindingRule{} @@ -4458,12 +4089,12 @@ func TestACLEndpoint_SecureIntroEndpoints_OnlyCreateLocalData(t *testing.T) { ruleID = resp.ID // present in dc2 - resp2, err := retrieveTestBindingRule(codec2, "root", "dc2", ruleID) + resp2, err := retrieveTestBindingRule(codec2, TestDefaultMasterToken, "dc2", ruleID) require.NoError(t, err) require.NotNil(t, resp2.BindingRule) require.Equal(t, "test updated", resp2.BindingRule.Description) // absent in dc1 - resp2, err = retrieveTestBindingRule(codec1, "root", "dc1", ruleID) + resp2, err = retrieveTestBindingRule(codec1, TestDefaultMasterToken, "dc1", ruleID) require.NoError(t, err) require.Nil(t, resp2.BindingRule) }) @@ -4473,7 +4104,7 @@ func TestACLEndpoint_SecureIntroEndpoints_OnlyCreateLocalData(t *testing.T) { req := structs.ACLBindingRuleGetRequest{ Datacenter: "dc2", BindingRuleID: ruleID, - QueryOptions: structs.QueryOptions{Token: "root"}, + QueryOptions: structs.QueryOptions{Token: TestDefaultMasterToken}, } resp := structs.ACLBindingRuleResponse{} require.NoError(t, acl2.BindingRuleRead(&req, &resp)) @@ -4484,7 +4115,7 @@ func TestACLEndpoint_SecureIntroEndpoints_OnlyCreateLocalData(t *testing.T) { req = structs.ACLBindingRuleGetRequest{ Datacenter: "dc1", BindingRuleID: ruleID, - QueryOptions: structs.QueryOptions{Token: "root"}, + QueryOptions: structs.QueryOptions{Token: TestDefaultMasterToken}, } resp = structs.ACLBindingRuleResponse{} require.NoError(t, acl.BindingRuleRead(&req, &resp)) @@ -4495,7 +4126,7 @@ func TestACLEndpoint_SecureIntroEndpoints_OnlyCreateLocalData(t *testing.T) { // present in dc2 req := structs.ACLBindingRuleListRequest{ Datacenter: "dc2", - QueryOptions: structs.QueryOptions{Token: "root"}, + QueryOptions: structs.QueryOptions{Token: TestDefaultMasterToken}, } resp := structs.ACLBindingRuleListResponse{} require.NoError(t, acl2.BindingRuleList(&req, &resp)) @@ -4504,7 +4135,7 @@ func TestACLEndpoint_SecureIntroEndpoints_OnlyCreateLocalData(t *testing.T) { // absent in dc1 req = structs.ACLBindingRuleListRequest{ Datacenter: "dc1", - QueryOptions: structs.QueryOptions{Token: "root"}, + QueryOptions: structs.QueryOptions{Token: TestDefaultMasterToken}, } resp = structs.ACLBindingRuleListResponse{} require.NoError(t, acl.BindingRuleList(&req, &resp)) @@ -4526,13 +4157,13 @@ func TestACLEndpoint_SecureIntroEndpoints_OnlyCreateLocalData(t *testing.T) { remoteToken = &resp // present in dc2 - resp2, err := retrieveTestToken(codec2, "root", "dc2", remoteToken.AccessorID) + resp2, err := retrieveTestToken(codec2, TestDefaultMasterToken, "dc2", remoteToken.AccessorID) require.NoError(t, err) require.NotNil(t, resp2.Token) require.Len(t, resp2.Token.ServiceIdentities, 1) require.Equal(t, "web2", resp2.Token.ServiceIdentities[0].ServiceName) // absent in dc1 - resp2, err = retrieveTestToken(codec1, "root", "dc1", remoteToken.AccessorID) + resp2, err = retrieveTestToken(codec1, TestDefaultMasterToken, "dc1", remoteToken.AccessorID) require.NoError(t, err) require.Nil(t, resp2.Token) }) @@ -4551,7 +4182,7 @@ func TestACLEndpoint_SecureIntroEndpoints_OnlyCreateLocalData(t *testing.T) { "SessionID": testSessionID_1, }, }, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } respAM := structs.ACLAuthMethod{} require.NoError(t, acl.AuthMethodSet(&reqAM, &respAM)) @@ -4563,7 +4194,7 @@ func TestACLEndpoint_SecureIntroEndpoints_OnlyCreateLocalData(t *testing.T) { BindType: structs.BindingRuleBindTypeService, BindName: "${serviceaccount.name}", }, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } respBR := structs.ACLBindingRule{} @@ -4585,13 +4216,13 @@ func TestACLEndpoint_SecureIntroEndpoints_OnlyCreateLocalData(t *testing.T) { primaryToken = &resp // present in dc1 - resp2, err := retrieveTestToken(codec1, "root", "dc1", primaryToken.AccessorID) + resp2, err := retrieveTestToken(codec1, TestDefaultMasterToken, "dc1", primaryToken.AccessorID) require.NoError(t, err) require.NotNil(t, resp2.Token) require.Len(t, resp2.Token.ServiceIdentities, 1) require.Equal(t, "web1", resp2.Token.ServiceIdentities[0].ServiceName) // absent in dc2 - resp2, err = retrieveTestToken(codec2, "root", "dc2", primaryToken.AccessorID) + resp2, err = retrieveTestToken(codec2, TestDefaultMasterToken, "dc2", primaryToken.AccessorID) require.NoError(t, err) require.Nil(t, resp2.Token) }) @@ -4609,11 +4240,11 @@ func TestACLEndpoint_SecureIntroEndpoints_OnlyCreateLocalData(t *testing.T) { require.NoError(t, acl.Logout(&req, &ignored)) // absent in dc2 - resp2, err := retrieveTestToken(codec2, "root", "dc2", remoteToken.AccessorID) + resp2, err := retrieveTestToken(codec2, TestDefaultMasterToken, "dc2", remoteToken.AccessorID) require.NoError(t, err) require.Nil(t, resp2.Token) // absent in dc1 - resp2, err = retrieveTestToken(codec1, "root", "dc1", remoteToken.AccessorID) + resp2, err = retrieveTestToken(codec1, TestDefaultMasterToken, "dc1", remoteToken.AccessorID) require.NoError(t, err) require.Nil(t, resp2.Token) }) @@ -4628,13 +4259,13 @@ func TestACLEndpoint_SecureIntroEndpoints_OnlyCreateLocalData(t *testing.T) { testutil.RequireErrorContains(t, acl.Logout(&req, &ignored), "ACL not found") // present in dc1 - resp2, err := retrieveTestToken(codec1, "root", "dc1", primaryToken.AccessorID) + resp2, err := retrieveTestToken(codec1, TestDefaultMasterToken, "dc1", primaryToken.AccessorID) require.NoError(t, err) require.NotNil(t, resp2.Token) require.Len(t, resp2.Token.ServiceIdentities, 1) require.Equal(t, "web1", resp2.Token.ServiceIdentities[0].ServiceName) // absent in dc2 - resp2, err = retrieveTestToken(codec2, "root", "dc2", primaryToken.AccessorID) + resp2, err = retrieveTestToken(codec2, TestDefaultMasterToken, "dc2", primaryToken.AccessorID) require.NoError(t, err) require.Nil(t, resp2.Token) }) @@ -4646,18 +4277,18 @@ func TestACLEndpoint_SecureIntroEndpoints_OnlyCreateLocalData(t *testing.T) { req := structs.ACLBindingRuleDeleteRequest{ Datacenter: "dc2", BindingRuleID: ruleID, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } var ignored bool require.NoError(t, acl2.BindingRuleDelete(&req, &ignored)) // absent in dc2 - resp2, err := retrieveTestBindingRule(codec2, "root", "dc2", ruleID) + resp2, err := retrieveTestBindingRule(codec2, TestDefaultMasterToken, "dc2", ruleID) require.NoError(t, err) require.Nil(t, resp2.BindingRule) // absent in dc1 - resp2, err = retrieveTestBindingRule(codec1, "root", "dc1", ruleID) + resp2, err = retrieveTestBindingRule(codec1, TestDefaultMasterToken, "dc1", ruleID) require.NoError(t, err) require.Nil(t, resp2.BindingRule) }) @@ -4666,18 +4297,18 @@ func TestACLEndpoint_SecureIntroEndpoints_OnlyCreateLocalData(t *testing.T) { req := structs.ACLAuthMethodDeleteRequest{ Datacenter: "dc2", AuthMethodName: "testmethod", - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } var ignored bool require.NoError(t, acl2.AuthMethodDelete(&req, &ignored)) // absent in dc2 - resp2, err := retrieveTestAuthMethod(codec2, "root", "dc2", "testmethod") + resp2, err := retrieveTestAuthMethod(codec2, TestDefaultMasterToken, "dc2", "testmethod") require.NoError(t, err) require.Nil(t, resp2.AuthMethod) // absent in dc1 - resp2, err = retrieveTestAuthMethod(codec1, "root", "dc1", "testmethod") + resp2, err = retrieveTestAuthMethod(codec1, TestDefaultMasterToken, "dc1", "testmethod") require.NoError(t, err) require.Nil(t, resp2.AuthMethod) }) @@ -4686,19 +4317,10 @@ func TestACLEndpoint_SecureIntroEndpoints_OnlyCreateLocalData(t *testing.T) { func TestACLEndpoint_Login(t *testing.T) { t.Parallel() - dir1, s1 := testServerWithConfig(t, func(c *Config) { - c.ACLDatacenter = "dc1" - c.ACLsEnabled = true - c.ACLMasterToken = "root" - }) - defer os.RemoveAll(dir1) - defer s1.Shutdown() - codec := rpcClient(t, s1) - defer codec.Close() + _, srv, codec := testACLServerWithConfig(t, nil, false) + waitForLeaderEstablishment(t, srv) - testrpc.WaitForLeader(t, s1.RPC, "dc1") - - acl := ACL{srv: s1} + acl := ACL{srv: srv} testSessionID := testauth.StartSession() defer testauth.ResetSession(testSessionID) @@ -4723,13 +4345,18 @@ func TestACLEndpoint_Login(t *testing.T) { "fake-monolith", // 2 rules (one of each) "default", "monolith", "ghi789", ) + testauth.InstallSessionToken( + testSessionID, + "fake-node", + "default", "mynode", "jkl101", + ) - method, err := upsertTestAuthMethod(codec, "root", "dc1", testSessionID) + method, err := upsertTestAuthMethod(codec, TestDefaultMasterToken, "dc1", testSessionID) require.NoError(t, err) // 'fake-db' rules ruleDB, err := upsertTestBindingRule( - codec, "root", "dc1", method.Name, + codec, TestDefaultMasterToken, "dc1", method.Name, "serviceaccount.namespace==default and serviceaccount.name==db", structs.BindingRuleBindTypeService, "method-${serviceaccount.name}", @@ -4738,7 +4365,7 @@ func TestACLEndpoint_Login(t *testing.T) { // 'fake-vault' rules _, err = upsertTestBindingRule( - codec, "root", "dc1", method.Name, + codec, TestDefaultMasterToken, "dc1", method.Name, "serviceaccount.namespace==default and serviceaccount.name==vault", structs.BindingRuleBindTypeRole, "method-${serviceaccount.name}", @@ -4747,20 +4374,28 @@ func TestACLEndpoint_Login(t *testing.T) { // 'fake-monolith' rules _, err = upsertTestBindingRule( - codec, "root", "dc1", method.Name, + codec, TestDefaultMasterToken, "dc1", method.Name, "serviceaccount.namespace==default and serviceaccount.name==monolith", structs.BindingRuleBindTypeService, "method-${serviceaccount.name}", ) require.NoError(t, err) _, err = upsertTestBindingRule( - codec, "root", "dc1", method.Name, + codec, TestDefaultMasterToken, "dc1", method.Name, "serviceaccount.namespace==default and serviceaccount.name==monolith", structs.BindingRuleBindTypeRole, "method-${serviceaccount.name}", ) require.NoError(t, err) + // node identity rule + _, err = upsertTestBindingRule( + codec, TestDefaultMasterToken, "dc1", method.Name, + "serviceaccount.namespace==default and serviceaccount.name==mynode", + structs.BindingRuleBindTypeNode, + "${serviceaccount.name}", + ) + t.Run("do not provide a token", func(t *testing.T) { req := structs.ACLLoginRequest{ Auth: &structs.ACLLoginParams{ @@ -4840,7 +4475,7 @@ func TestACLEndpoint_Login(t *testing.T) { Role: structs.ACLRole{ Name: "method-vault", }, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } var out structs.ACLRole @@ -4903,7 +4538,7 @@ func TestACLEndpoint_Login(t *testing.T) { Role: structs.ACLRole{ Name: "method-monolith", }, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } var out structs.ACLRole @@ -4961,6 +4596,30 @@ func TestACLEndpoint_Login(t *testing.T) { require.Equal(t, "method-db", svcid.ServiceName) }) + t.Run("valid bearer token 1 node binding", func(t *testing.T) { + req := structs.ACLLoginRequest{ + Auth: &structs.ACLLoginParams{ + AuthMethod: method.Name, + BearerToken: "fake-node", + Meta: map[string]string{"node": "true"}, + }, + Datacenter: "dc1", + } + resp := structs.ACLToken{} + + require.NoError(t, acl.Login(&req, &resp)) + + require.Equal(t, method.Name, resp.AuthMethod) + require.Equal(t, `token created via login: {"node":"true"}`, resp.Description) + require.True(t, resp.Local) + require.Empty(t, resp.Roles) + require.Empty(t, resp.ServiceIdentities) + require.Len(t, resp.NodeIdentities, 1) + nodeid := resp.NodeIdentities[0] + require.Equal(t, "mynode", nodeid.NodeName) + require.Equal(t, "dc1", nodeid.Datacenter) + }) + { req := structs.ACLBindingRuleSetRequest{ Datacenter: "dc1", @@ -4970,7 +4629,7 @@ func TestACLEndpoint_Login(t *testing.T) { BindName: ruleDB.BindName, Selector: "", }, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } var out structs.ACLBindingRule @@ -5014,7 +4673,7 @@ func TestACLEndpoint_Login(t *testing.T) { req := structs.ACLAuthMethodSetRequest{ Datacenter: "dc1", AuthMethod: updated, - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } var ignored structs.ACLAuthMethod @@ -5041,19 +4700,10 @@ func TestACLEndpoint_Login(t *testing.T) { func TestACLEndpoint_Login_with_MaxTokenTTL(t *testing.T) { t.Parallel() - dir1, s1 := testServerWithConfig(t, func(c *Config) { - c.ACLDatacenter = "dc1" - c.ACLsEnabled = true - c.ACLMasterToken = "root" - }) - defer os.RemoveAll(dir1) - defer s1.Shutdown() - codec := rpcClient(t, s1) - defer codec.Close() + _, srv, codec := testACLServerWithConfig(t, nil, false) + waitForLeaderEstablishment(t, srv) - testrpc.WaitForLeader(t, s1.RPC, "dc1") - - acl := ACL{srv: s1} + acl := ACL{srv: srv} testSessionID := testauth.StartSession() defer testauth.ResetSession(testSessionID) @@ -5064,7 +4714,7 @@ func TestACLEndpoint_Login_with_MaxTokenTTL(t *testing.T) { "default", "web", "abc123", ) - method, err := upsertTestCustomizedAuthMethod(codec, "root", "dc1", func(method *structs.ACLAuthMethod) { + method, err := upsertTestCustomizedAuthMethod(codec, TestDefaultMasterToken, "dc1", func(method *structs.ACLAuthMethod) { method.MaxTokenTTL = 5 * time.Minute method.Config = map[string]interface{}{ "SessionID": testSessionID, @@ -5073,7 +4723,7 @@ func TestACLEndpoint_Login_with_MaxTokenTTL(t *testing.T) { require.NoError(t, err) _, err = upsertTestBindingRule( - codec, "root", "dc1", method.Name, + codec, TestDefaultMasterToken, "dc1", method.Name, "", structs.BindingRuleBindTypeService, "web", @@ -5117,7 +4767,7 @@ func TestACLEndpoint_Login_with_MaxTokenTTL(t *testing.T) { } func TestACLEndpoint_Login_with_TokenLocality(t *testing.T) { - t.Parallel() + go t.Parallel() dir1, s1 := testServerWithConfig(t, func(c *Config) { c.ACLDatacenter = "dc1" @@ -5129,7 +4779,7 @@ func TestACLEndpoint_Login_with_TokenLocality(t *testing.T) { codec := rpcClient(t, s1) defer codec.Close() - testrpc.WaitForLeader(t, s1.RPC, "dc1") + waitForLeaderEstablishment(t, s1) acl := ACL{srv: s1} @@ -5221,19 +4871,10 @@ func TestACLEndpoint_Login_with_TokenLocality(t *testing.T) { func TestACLEndpoint_Login_k8s(t *testing.T) { t.Parallel() - dir1, s1 := testServerWithConfig(t, func(c *Config) { - c.ACLDatacenter = "dc1" - c.ACLsEnabled = true - c.ACLMasterToken = "root" - }) - defer os.RemoveAll(dir1) - defer s1.Shutdown() - codec := rpcClient(t, s1) - defer codec.Close() + _, srv, codec := testACLServerWithConfig(t, nil, false) + waitForLeaderEstablishment(t, srv) - testrpc.WaitForLeader(t, s1.RPC, "dc1") - - acl := ACL{srv: s1} + acl := ACL{srv: srv} // spin up a fake api server testSrv := kubeauth.StartTestAPIServer(t) @@ -5249,7 +4890,7 @@ func TestACLEndpoint_Login_k8s(t *testing.T) { ) method, err := upsertTestKubernetesAuthMethod( - codec, "root", "dc1", + codec, TestDefaultMasterToken, "dc1", testSrv.CACert(), testSrv.Addr(), goodJWT_A, @@ -5285,7 +4926,7 @@ func TestACLEndpoint_Login_k8s(t *testing.T) { }) _, err = upsertTestBindingRule( - codec, "root", "dc1", method.Name, + codec, TestDefaultMasterToken, "dc1", method.Name, "serviceaccount.namespace==default", structs.BindingRuleBindTypeService, "${serviceaccount.name}", @@ -5351,19 +4992,10 @@ func TestACLEndpoint_Login_k8s(t *testing.T) { func TestACLEndpoint_Login_jwt(t *testing.T) { t.Parallel() - dir1, s1 := testServerWithConfig(t, func(c *Config) { - c.ACLDatacenter = "dc1" - c.ACLsEnabled = true - c.ACLMasterToken = "root" - }) - defer os.RemoveAll(dir1) - defer s1.Shutdown() - codec := rpcClient(t, s1) - defer codec.Close() + _, srv, codec := testACLServerWithConfig(t, nil, false) + waitForLeaderEstablishment(t, srv) - testrpc.WaitForLeader(t, s1.RPC, "dc1") - - acl := ACL{srv: s1} + acl := ACL{srv: srv} // spin up a fake oidc server oidcServer := startSSOTestServer(t) @@ -5398,7 +5030,7 @@ func TestACLEndpoint_Login_jwt(t *testing.T) { for name, tc := range cases { tc := tc t.Run(name, func(t *testing.T) { - method, err := upsertTestCustomizedAuthMethod(codec, "root", "dc1", func(method *structs.ACLAuthMethod) { + method, err := upsertTestCustomizedAuthMethod(codec, TestDefaultMasterToken, "dc1", func(method *structs.ACLAuthMethod) { method.Type = "jwt" method.Config = map[string]interface{}{ "JWTSupportedAlgs": []string{"ES256"}, @@ -5469,7 +5101,7 @@ func TestACLEndpoint_Login_jwt(t *testing.T) { }) _, err = upsertTestBindingRule( - codec, "root", "dc1", method.Name, + codec, TestDefaultMasterToken, "dc1", method.Name, "value.name == jeff2 and value.primary_org == engineering and foo in list.groups", structs.BindingRuleBindTypeService, "test--${value.name}--${value.primary_org}", @@ -5512,19 +5144,10 @@ func startSSOTestServer(t *testing.T) *oidcauthtest.Server { func TestACLEndpoint_Logout(t *testing.T) { t.Parallel() - dir1, s1 := testServerWithConfig(t, func(c *Config) { - c.ACLDatacenter = "dc1" - c.ACLsEnabled = true - c.ACLMasterToken = "root" - }) - defer os.RemoveAll(dir1) - defer s1.Shutdown() - codec := rpcClient(t, s1) - defer codec.Close() + _, srv, codec := testACLServerWithConfig(t, nil, false) + waitForLeaderEstablishment(t, srv) - testrpc.WaitForLeader(t, s1.RPC, "dc1") - - acl := ACL{srv: s1} + acl := ACL{srv: srv} testSessionID := testauth.StartSession() defer testauth.ResetSession(testSessionID) @@ -5534,11 +5157,11 @@ func TestACLEndpoint_Logout(t *testing.T) { "default", "db", "def456", ) - method, err := upsertTestAuthMethod(codec, "root", "dc1", testSessionID) + method, err := upsertTestAuthMethod(codec, TestDefaultMasterToken, "dc1", testSessionID) require.NoError(t, err) _, err = upsertTestBindingRule( - codec, "root", "dc1", method.Name, + codec, TestDefaultMasterToken, "dc1", method.Name, "", structs.BindingRuleBindTypeService, "method-${serviceaccount.name}", @@ -5548,7 +5171,7 @@ func TestACLEndpoint_Logout(t *testing.T) { t.Run("you must provide a token", func(t *testing.T) { req := structs.ACLLogoutRequest{ Datacenter: "dc1", - // WriteRequest: structs.WriteRequest{Token: "root"}, + // WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } req.Token = "" var ignored bool @@ -5568,7 +5191,7 @@ func TestACLEndpoint_Logout(t *testing.T) { t.Run("logout from non-auth method-linked token should fail", func(t *testing.T) { req := structs.ACLLogoutRequest{ Datacenter: "dc1", - WriteRequest: structs.WriteRequest{Token: "root"}, + WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, } var ignored bool testutil.RequireErrorContains(t, acl.Logout(&req, &ignored), "Permission denied") diff --git a/agent/consul/acl_test.go b/agent/consul/acl_test.go index c49ac1a057..248faa2c9a 100644 --- a/agent/consul/acl_test.go +++ b/agent/consul/acl_test.go @@ -179,6 +179,48 @@ func testIdentityForToken(token string) (bool, structs.ACLIdentity, error) { }, }, }, nil + case "found-synthetic-policy-3": + return true, &structs.ACLToken{ + AccessorID: "bebccc92-3987-489d-84c2-ffd00d93ef93", + SecretID: "de70f2e2-69d9-4e88-9815-f91c03c6bcb1", + NodeIdentities: []*structs.ACLNodeIdentity{ + &structs.ACLNodeIdentity{ + NodeName: "test-node1", + Datacenter: "dc1", + }, + // as the resolver is in dc1 this identity should be ignored + &structs.ACLNodeIdentity{ + NodeName: "test-node-dc2", + Datacenter: "dc2", + }, + }, + }, nil + case "found-synthetic-policy-4": + return true, &structs.ACLToken{ + AccessorID: "359b9927-25fd-46b9-bd14-3470f848ec65", + SecretID: "83c4d500-847d-49f7-8c08-0483f6b4156e", + NodeIdentities: []*structs.ACLNodeIdentity{ + &structs.ACLNodeIdentity{ + NodeName: "test-node2", + Datacenter: "dc1", + }, + // as the resolver is in dc1 this identity should be ignored + &structs.ACLNodeIdentity{ + NodeName: "test-node-dc2", + Datacenter: "dc2", + }, + }, + }, nil + case "found-role-node-identity": + return true, &structs.ACLToken{ + AccessorID: "f3f47a09-de29-4c57-8f54-b65a9be79641", + SecretID: "e96aca00-5951-4b97-b0e5-5816f42dfb93", + Roles: []structs.ACLTokenRoleLink{ + { + ID: "node-identity", + }, + }, + }, nil case "acl-ro": return true, &structs.ACLToken{ AccessorID: "435a75af-1763-4980-89f4-f0951dda53b4", @@ -443,6 +485,22 @@ func testRoleForID(roleID string) (bool, *structs.ACLRole, error) { }, }, }, nil + case "node-identity": + return true, &structs.ACLRole{ + ID: "node-identity", + Name: "node-identity", + Description: "node-identity", + NodeIdentities: []*structs.ACLNodeIdentity{ + &structs.ACLNodeIdentity{ + NodeName: "test-node", + Datacenter: "dc1", + }, + &structs.ACLNodeIdentity{ + NodeName: "test-node-dc2", + Datacenter: "dc2", + }, + }, + }, nil default: return testRoleForIDEnterprise(roleID) } @@ -1729,8 +1787,18 @@ func testACLResolver_variousTokens(t *testing.T, delegate *ACLResolverTestDelega require.Equal(t, acl.Allow, authz.ServiceRead("bar", nil)) }) + runTwiceAndReset("Role With Node Identity", func(t *testing.T) { + authz, err := r.ResolveToken("found-role-node-identity") + require.NoError(t, err) + require.NotNil(t, authz) + require.Equal(t, acl.Allow, authz.NodeWrite("test-node", nil)) + require.Equal(t, acl.Deny, authz.NodeWrite("test-node-dc2", nil)) + require.Equal(t, acl.Allow, authz.ServiceRead("something", nil)) + require.Equal(t, acl.Deny, authz.ServiceWrite("something", nil)) + }) + runTwiceAndReset("Synthetic Policies Independently Cache", func(t *testing.T) { - // We resolve both of these tokens in the same cache session + // We resolve these tokens in the same cache session // to verify that the keys for caching synthetic policies don't bleed // over between each other. { @@ -1761,6 +1829,38 @@ func testACLResolver_variousTokens(t *testing.T, delegate *ACLResolverTestDelega require.Equal(t, acl.Allow, authz.ServiceRead("literally-anything", nil)) require.Equal(t, acl.Allow, authz.NodeRead("any-node", nil)) } + { + authz, err := r.ResolveToken("found-synthetic-policy-3") + require.NoError(t, err) + require.NotNil(t, authz) + + // spot check some random perms + require.Equal(t, acl.Deny, authz.ACLRead(nil)) + require.Equal(t, acl.Deny, authz.NodeWrite("foo", nil)) + // ensure we didn't bleed over to the other synthetic policy + require.Equal(t, acl.Deny, authz.NodeWrite("test-node2", nil)) + // check our own synthetic policy + require.Equal(t, acl.Allow, authz.ServiceRead("literally-anything", nil)) + require.Equal(t, acl.Allow, authz.NodeWrite("test-node1", nil)) + // ensure node identity for other DC is ignored + require.Equal(t, acl.Deny, authz.NodeWrite("test-node-dc2", nil)) + } + { + authz, err := r.ResolveToken("found-synthetic-policy-4") + require.NoError(t, err) + require.NotNil(t, authz) + + // spot check some random perms + require.Equal(t, acl.Deny, authz.ACLRead(nil)) + require.Equal(t, acl.Deny, authz.NodeWrite("foo", nil)) + // ensure we didn't bleed over to the other synthetic policy + require.Equal(t, acl.Deny, authz.NodeWrite("test-node1", nil)) + // check our own synthetic policy + require.Equal(t, acl.Allow, authz.ServiceRead("literally-anything", nil)) + require.Equal(t, acl.Allow, authz.NodeWrite("test-node2", nil)) + // ensure node identity for other DC is ignored + require.Equal(t, acl.Deny, authz.NodeWrite("test-node-dc2", nil)) + } }) runTwiceAndReset("Anonymous", func(t *testing.T) { diff --git a/agent/consul/intention_endpoint_test.go b/agent/consul/intention_endpoint_test.go index c0a5d9dfc4..7123f9fd7e 100644 --- a/agent/consul/intention_endpoint_test.go +++ b/agent/consul/intention_endpoint_test.go @@ -392,13 +392,8 @@ service "foo" { func TestIntention_WildcardACLEnforcement(t *testing.T) { t.Parallel() - dir, srv := testACLServerWithConfig(t, nil, false) - defer os.RemoveAll(dir) - defer srv.Shutdown() - codec := rpcClient(t, srv) - defer codec.Close() - - testrpc.WaitForLeader(t, srv.RPC, "dc1") + _, srv, codec := testACLServerWithConfig(t, nil, false) + waitForLeaderEstablishment(t, srv) // create some test policies. @@ -1222,13 +1217,8 @@ func TestIntentionMatch_good(t *testing.T) { func TestIntentionMatch_acl(t *testing.T) { t.Parallel() - dir1, s1 := testACLServerWithConfig(t, nil, false) - defer os.RemoveAll(dir1) - defer s1.Shutdown() - codec := rpcClient(t, s1) - defer codec.Close() - - testrpc.WaitForLeader(t, s1.RPC, "dc1") + _, srv, codec := testACLServerWithConfig(t, nil, false) + waitForLeaderEstablishment(t, srv) token, err := upsertTestTokenWithPolicyRules(codec, TestDefaultMasterToken, "dc1", `service "bar" { policy = "write" }`) require.NoError(t, err) @@ -1464,13 +1454,8 @@ service "bar" { func TestIntentionCheck_match(t *testing.T) { t.Parallel() - dir1, s1 := testACLServerWithConfig(t, nil, false) - defer os.RemoveAll(dir1) - defer s1.Shutdown() - codec := rpcClient(t, s1) - defer codec.Close() - - testrpc.WaitForLeader(t, s1.RPC, "dc1") + _, srv, codec := testACLServerWithConfig(t, nil, false) + waitForLeaderEstablishment(t, srv) token, err := upsertTestTokenWithPolicyRules(codec, TestDefaultMasterToken, "dc1", `service "api" { policy = "read" }`) require.NoError(t, err) diff --git a/agent/consul/leader.go b/agent/consul/leader.go index c7cd037f42..8ae0ddc50d 100644 --- a/agent/consul/leader.go +++ b/agent/consul/leader.go @@ -730,6 +730,7 @@ func (s *Server) legacyACLTokenUpgrade(ctx context.Context) error { // Assign the global-management policy to legacy management tokens if len(newToken.Policies) == 0 && len(newToken.ServiceIdentities) == 0 && + len(newToken.NodeIdentities) == 0 && len(newToken.Roles) == 0 && newToken.Type == structs.ACLTokenTypeManagement { newToken.Policies = append(newToken.Policies, structs.ACLTokenPolicyLink{ID: structs.ACLPolicyGlobalManagementID}) diff --git a/agent/consul/leader_test.go b/agent/consul/leader_test.go index a7b5fae4cb..3793f01c7b 100644 --- a/agent/consul/leader_test.go +++ b/agent/consul/leader_test.go @@ -1216,9 +1216,7 @@ func TestLeader_ACLLegacyReplication(t *testing.T) { c.Datacenter = "dc2" c.ACLTokenReplication = true } - dir, srv := testACLServerWithConfig(t, cb, true) - defer os.RemoveAll(dir) - defer srv.Shutdown() + _, srv, _ := testACLServerWithConfig(t, cb, true) waitForLeaderEstablishment(t, srv) require.True(t, srv.leaderRoutineManager.IsRunning(legacyACLReplicationRoutineName)) diff --git a/agent/consul/server_test.go b/agent/consul/server_test.go index a3d21ef0c9..a53cb16e49 100644 --- a/agent/consul/server_test.go +++ b/agent/consul/server_test.go @@ -4,6 +4,7 @@ import ( "bytes" "fmt" "net" + "net/rpc" "os" "strings" "sync/atomic" @@ -235,14 +236,19 @@ func testServerWithConfig(t *testing.T, cb func(*Config)) (string, *Server) { } // cb is a function that can alter the test servers configuration prior to the server starting. -func testACLServerWithConfig(t *testing.T, cb func(*Config), initReplicationToken bool) (string, *Server) { +func testACLServerWithConfig(t *testing.T, cb func(*Config), initReplicationToken bool) (string, *Server, rpc.ClientCodec) { dir, srv := testServerWithConfig(t, testServerACLConfig(cb)) + t.Cleanup(func() { os.RemoveAll(dir) }) + t.Cleanup(func() { srv.Shutdown() }) if initReplicationToken { // setup some tokens here so we get less warnings in the logs srv.tokens.UpdateReplicationToken(TestDefaultMasterToken, token.TokenSourceConfig) } - return dir, srv + + codec := rpcClient(t, srv) + t.Cleanup(func() { codec.Close() }) + return dir, srv, codec } func newServer(c *Config) (*Server, error) { diff --git a/agent/consul/state/acl.go b/agent/consul/state/acl.go index 8b2a218f20..87ce9a5e86 100644 --- a/agent/consul/state/acl.go +++ b/agent/consul/state/acl.go @@ -742,8 +742,17 @@ func (s *Store) aclTokenSetTxn(tx *memdb.Txn, idx uint64, token *structs.ACLToke } } + for _, nodeid := range token.NodeIdentities { + if nodeid.NodeName == "" { + return fmt.Errorf("Encountered a Token with an empty node identity name in the state store") + } + if nodeid.Datacenter == "" { + return fmt.Errorf("Encountered a Token with an empty node identity datacenter in the state store") + } + } + if prohibitUnprivileged { - if numValidRoles == 0 && numValidPolicies == 0 && len(token.ServiceIdentities) == 0 { + if numValidRoles == 0 && numValidPolicies == 0 && len(token.ServiceIdentities) == 0 && len(token.NodeIdentities) == 0 { return ErrTokenHasNoPrivileges } } @@ -1369,6 +1378,15 @@ func (s *Store) aclRoleSetTxn(tx *memdb.Txn, idx uint64, role *structs.ACLRole, } } + for _, nodeid := range role.NodeIdentities { + if nodeid.NodeName == "" { + return fmt.Errorf("Encountered a Role with an empty node identity name in the state store") + } + if nodeid.Datacenter == "" { + return fmt.Errorf("Encountered a Role with an empty node identity datacenter in the state store") + } + } + if err := s.aclRoleUpsertValidateEnterprise(tx, role, existing); err != nil { return err } diff --git a/agent/structs/acl.go b/agent/structs/acl.go index 0045bef72d..de01c6e2f7 100644 --- a/agent/structs/acl.go +++ b/agent/structs/acl.go @@ -119,6 +119,7 @@ type ACLIdentity interface { RoleIDs() []string EmbeddedPolicy() *ACLPolicy ServiceIdentityList() []*ACLServiceIdentity + NodeIdentityList() []*ACLNodeIdentity IsExpired(asOf time.Time) bool IsLocal() bool EnterpriseMetadata() *EnterpriseMeta @@ -189,6 +190,50 @@ func (s *ACLServiceIdentity) SyntheticPolicy(entMeta *EnterpriseMeta) *ACLPolicy return policy } +// ACLNodeIdentity represents a high-level grant of all privileges +// necessary to assume the identity of that node and manage it. +type ACLNodeIdentity struct { + // NodeName identities the Node that this identity authorizes access to + NodeName string + + // Datacenter is required and specifies the datacenter of the node. + Datacenter string +} + +func (s *ACLNodeIdentity) Clone() *ACLNodeIdentity { + s2 := *s + return &s2 +} + +func (s *ACLNodeIdentity) AddToHash(h hash.Hash) { + h.Write([]byte(s.NodeName)) + h.Write([]byte(s.Datacenter)) +} + +func (s *ACLNodeIdentity) EstimateSize() int { + return len(s.NodeName) + len(s.Datacenter) +} + +func (s *ACLNodeIdentity) SyntheticPolicy() *ACLPolicy { + // Given that we validate this string name before persisting, we do not + // have to escape it before doing the following interpolation. + rules := fmt.Sprintf(aclPolicyTemplateNodeIdentity, s.NodeName) + + hasher := fnv.New128a() + hashID := fmt.Sprintf("%x", hasher.Sum([]byte(rules))) + + policy := &ACLPolicy{} + policy.ID = hashID + policy.Name = fmt.Sprintf("synthetic-policy-%s", hashID) + policy.Description = "synthetic policy" + policy.Rules = rules + policy.Syntax = acl.SyntaxCurrent + policy.Datacenters = []string{s.Datacenter} + policy.EnterpriseMeta = *DefaultEnterpriseMeta() + policy.SetHash(true) + return policy +} + type ACLToken struct { // This is the UUID used for tracking and management purposes AccessorID string @@ -212,6 +257,9 @@ type ACLToken struct { // List of services to generate synthetic policies for. ServiceIdentities []*ACLServiceIdentity `json:",omitempty"` + // The node identities that this token should be allowed to manage. + NodeIdentities []*ACLNodeIdentity `json:",omitempty"` + // Type is the V1 Token Type // DEPRECATED (ACL-Legacy-Compat) - remove once we no longer support v1 ACL compat // Even though we are going to auto upgrade management tokens we still @@ -302,6 +350,7 @@ func (t *ACLToken) Clone() *ACLToken { t2.Policies = nil t2.Roles = nil t2.ServiceIdentities = nil + t2.NodeIdentities = nil if len(t.Policies) > 0 { t2.Policies = make([]ACLTokenPolicyLink, len(t.Policies)) @@ -317,6 +366,13 @@ func (t *ACLToken) Clone() *ACLToken { t2.ServiceIdentities[i] = s.Clone() } } + if len(t.NodeIdentities) > 0 { + t2.NodeIdentities = make([]*ACLNodeIdentity, len(t.NodeIdentities)) + for i, n := range t.NodeIdentities { + t2.NodeIdentities[i] = n.Clone() + } + } + return &t2 } @@ -382,6 +438,7 @@ func (t *ACLToken) HasExpirationTime() bool { func (t *ACLToken) UsesNonLegacyFields() bool { return len(t.Policies) > 0 || len(t.ServiceIdentities) > 0 || + len(t.NodeIdentities) > 0 || len(t.Roles) > 0 || t.Type == "" || t.HasExpirationTime() || @@ -462,6 +519,10 @@ func (t *ACLToken) SetHash(force bool) []byte { srvid.AddToHash(hash) } + for _, nodeID := range t.NodeIdentities { + nodeID.AddToHash(hash) + } + t.EnterpriseMeta.addToHash(hash, false) // Finalize the hash @@ -485,6 +546,9 @@ func (t *ACLToken) EstimateSize() int { for _, srvid := range t.ServiceIdentities { size += srvid.EstimateSize() } + for _, nodeID := range t.NodeIdentities { + size += nodeID.EstimateSize() + } return size + t.EnterpriseMeta.estimateSize() } @@ -497,6 +561,7 @@ type ACLTokenListStub struct { Policies []ACLTokenPolicyLink `json:",omitempty"` Roles []ACLTokenRoleLink `json:",omitempty"` ServiceIdentities []*ACLServiceIdentity `json:",omitempty"` + NodeIdentities []*ACLNodeIdentity `json:",omitempty"` Local bool AuthMethod string `json:",omitempty"` ExpirationTime *time.Time `json:",omitempty"` @@ -517,6 +582,7 @@ func (token *ACLToken) Stub() *ACLTokenListStub { Policies: token.Policies, Roles: token.Roles, ServiceIdentities: token.ServiceIdentities, + NodeIdentities: token.NodeIdentities, Local: token.Local, AuthMethod: token.AuthMethod, ExpirationTime: token.ExpirationTime, @@ -811,6 +877,9 @@ type ACLRole struct { // List of services to generate synthetic policies for. ServiceIdentities []*ACLServiceIdentity `json:",omitempty"` + // List of nodes to generate synthetic policies for. + NodeIdentities []*ACLNodeIdentity `json:",omitempty"` + // Hash of the contents of the role // This does not take into account the ID (which is immutable) // nor the raft metadata. @@ -849,6 +918,7 @@ func (r *ACLRole) Clone() *ACLRole { r2 := *r r2.Policies = nil r2.ServiceIdentities = nil + r2.NodeIdentities = nil if len(r.Policies) > 0 { r2.Policies = make([]ACLRolePolicyLink, len(r.Policies)) @@ -860,6 +930,12 @@ func (r *ACLRole) Clone() *ACLRole { r2.ServiceIdentities[i] = s.Clone() } } + if len(r.NodeIdentities) > 0 { + r2.NodeIdentities = make([]*ACLNodeIdentity, len(r.NodeIdentities)) + for i, n := range r.NodeIdentities { + r2.NodeIdentities[i] = n.Clone() + } + } return &r2 } @@ -888,6 +964,9 @@ func (r *ACLRole) SetHash(force bool) []byte { for _, srvid := range r.ServiceIdentities { srvid.AddToHash(hash) } + for _, nodeID := range r.NodeIdentities { + nodeID.AddToHash(hash) + } r.EnterpriseMeta.addToHash(hash, false) @@ -912,6 +991,9 @@ func (r *ACLRole) EstimateSize() int { for _, srvid := range r.ServiceIdentities { size += srvid.EstimateSize() } + for _, nodeID := range r.NodeIdentities { + size += nodeID.EstimateSize() + } return size + r.EnterpriseMeta.estimateSize() } @@ -945,6 +1027,21 @@ const ( // // If it does not exist at login-time the rule is ignored. BindingRuleBindTypeRole = "role" + + // BindingRuleBindTypeNode is the binding rule bind type that assigns + // a Node Identity to the token that is created using the value of + // the computed BindName as the NodeName like: + // + // &ACLToken{ + // ...other fields... + // NodeIdentities: []*ACLNodeIdentity{ + // &ACLNodeIdentity{ + // NodeName: "", + // Datacenter: "" + // } + // } + // } + BindingRuleBindTypeNode = "node" ) type ACLBindingRule struct { diff --git a/agent/structs/acl_legacy.go b/agent/structs/acl_legacy.go index 6e3ac351a1..b7b466a1d5 100644 --- a/agent/structs/acl_legacy.go +++ b/agent/structs/acl_legacy.go @@ -78,6 +78,7 @@ func (a *ACL) Convert() *ACLToken { Description: a.Name, Policies: nil, ServiceIdentities: nil, + NodeIdentities: nil, Type: a.Type, Rules: a.Rules, Local: false, diff --git a/agent/structs/acl_oss.go b/agent/structs/acl_oss.go index 43c11c691b..0a43ee0b46 100644 --- a/agent/structs/acl_oss.go +++ b/agent/structs/acl_oss.go @@ -26,6 +26,24 @@ service_prefix "" { node_prefix "" { policy = "read" }` + + // A typical Consul node requires two permissions for itself. + // node:write + // - register itself in the catalog + // - update its network coordinates + // - potentially used to delete services during anti-entropy + // service:read + // - used during anti-entropy to discover all services that + // are registered to the node. That way the node can diff + // its local state against an accurate depiction of the + // remote state. + aclPolicyTemplateNodeIdentity = ` +node "%[1]s" { + policy = "write" +} +service_prefix "" { + policy = "read" +}` ) type ACLAuthMethodEnterpriseFields struct{} @@ -51,3 +69,27 @@ func (p *ACLPolicy) EnterprisePolicyMeta() *acl.EnterprisePolicyMeta { func (m *ACLAuthMethod) TargetEnterpriseMeta(_ *EnterpriseMeta) *EnterpriseMeta { return &m.EnterpriseMeta } + +func (t *ACLToken) NodeIdentityList() []*ACLNodeIdentity { + if len(t.NodeIdentities) == 0 { + return nil + } + + out := make([]*ACLNodeIdentity, 0, len(t.NodeIdentities)) + for _, n := range t.NodeIdentities { + out = append(out, n.Clone()) + } + return out +} + +func (r *ACLRole) NodeIdentityList() []*ACLNodeIdentity { + if len(r.NodeIdentities) == 0 { + return nil + } + + out := make([]*ACLNodeIdentity, 0, len(r.NodeIdentities)) + for _, n := range r.NodeIdentities { + out = append(out, n.Clone()) + } + return out +} diff --git a/api/acl.go b/api/acl.go index 618a49d6b4..aa4411b244 100644 --- a/api/acl.go +++ b/api/acl.go @@ -37,6 +37,7 @@ type ACLToken struct { Policies []*ACLTokenPolicyLink `json:",omitempty"` Roles []*ACLTokenRoleLink `json:",omitempty"` ServiceIdentities []*ACLServiceIdentity `json:",omitempty"` + NodeIdentities []*ACLNodeIdentity `json:",omitempty"` Local bool AuthMethod string `json:",omitempty"` ExpirationTTL time.Duration `json:",omitempty"` @@ -61,6 +62,7 @@ type ACLTokenListEntry struct { Policies []*ACLTokenPolicyLink `json:",omitempty"` Roles []*ACLTokenRoleLink `json:",omitempty"` ServiceIdentities []*ACLServiceIdentity `json:",omitempty"` + NodeIdentities []*ACLNodeIdentity `json:",omitempty"` Local bool AuthMethod string `json:",omitempty"` ExpirationTime *time.Time `json:",omitempty"` @@ -105,6 +107,13 @@ type ACLServiceIdentity struct { Datacenters []string `json:",omitempty"` } +// ACLNodeIdentity represents a high-level grant of all necessary privileges +// to assume the identity of the named Node in the Catalog and within Connect. +type ACLNodeIdentity struct { + NodeName string + Datacenter string +} + // ACLPolicy represents an ACL Policy. type ACLPolicy struct { ID string @@ -144,6 +153,7 @@ type ACLRole struct { Description string Policies []*ACLRolePolicyLink `json:",omitempty"` ServiceIdentities []*ACLServiceIdentity `json:",omitempty"` + NodeIdentities []*ACLNodeIdentity `json:",omitempty"` Hash []byte CreateIndex uint64 ModifyIndex uint64 diff --git a/command/acl/acl_helpers.go b/command/acl/acl_helpers.go index 590a22b3bb..296a6b9f90 100644 --- a/command/acl/acl_helpers.go +++ b/command/acl/acl_helpers.go @@ -217,6 +217,23 @@ func ExtractServiceIdentities(serviceIdents []string) ([]*api.ACLServiceIdentity return out, nil } +func ExtractNodeIdentities(nodeIdents []string) ([]*api.ACLNodeIdentity, error) { + var out []*api.ACLNodeIdentity + for _, nodeidRaw := range nodeIdents { + parts := strings.Split(nodeidRaw, ":") + switch len(parts) { + case 2: + out = append(out, &api.ACLNodeIdentity{ + NodeName: parts[0], + Datacenter: parts[1], + }) + default: + return nil, fmt.Errorf("Malformed -node-identity argument: %q", nodeidRaw) + } + } + return out, nil +} + // TestKubernetesJWT_A is a valid service account jwt extracted from a minikube setup. // // { diff --git a/command/acl/role/create/role_create.go b/command/acl/role/create/role_create.go index aae712a46c..c86848986e 100644 --- a/command/acl/role/create/role_create.go +++ b/command/acl/role/create/role_create.go @@ -29,6 +29,7 @@ type cmd struct { policyIDs []string policyNames []string serviceIdents []string + nodeIdents []string showMeta bool format string @@ -47,6 +48,9 @@ func (c *cmd) init() { c.flags.Var((*flags.AppendSliceValue)(&c.serviceIdents), "service-identity", "Name of a "+ "service identity to use for this role. May be specified multiple times. Format is "+ "the SERVICENAME or SERVICENAME:DATACENTER1,DATACENTER2,...") + c.flags.Var((*flags.AppendSliceValue)(&c.nodeIdents), "node-identity", "Name of a "+ + "node identity to use for this role. May be specified multiple times. Format is "+ + "NODENAME:DATACENTER") c.flags.StringVar( &c.format, "format", @@ -71,8 +75,8 @@ func (c *cmd) Run(args []string) int { return 1 } - if len(c.policyNames) == 0 && len(c.policyIDs) == 0 && len(c.serviceIdents) == 0 { - c.UI.Error(fmt.Sprintf("Cannot create a role without specifying -policy-name, -policy-id, or -service-identity at least once")) + if len(c.policyNames) == 0 && len(c.policyIDs) == 0 && len(c.serviceIdents) == 0 && len(c.nodeIdents) == 0 { + c.UI.Error(fmt.Sprintf("Cannot create a role without specifying -policy-name, -policy-id, -service-identity, or -node-identity at least once")) return 1 } @@ -109,6 +113,13 @@ func (c *cmd) Run(args []string) int { } newRole.ServiceIdentities = parsedServiceIdents + parsedNodeIdents, err := acl.ExtractNodeIdentities(c.nodeIdents) + if err != nil { + c.UI.Error(err.Error()) + return 1 + } + newRole.NodeIdentities = parsedNodeIdents + r, _, err := client.ACL().RoleCreate(newRole, nil) if err != nil { c.UI.Error(fmt.Sprintf("Failed to create new role: %v", err)) diff --git a/command/acl/role/create/role_create_test.go b/command/acl/role/create/role_create_test.go index 13053b872d..b3c03a662a 100644 --- a/command/acl/role/create/role_create_test.go +++ b/command/acl/role/create/role_create_test.go @@ -41,8 +41,18 @@ func TestRoleCreateCommand_Pretty(t *testing.T) { defer a.Shutdown() testrpc.WaitForLeader(t, a.RPC, "dc1") - ui := cli.NewMockUi() - cmd := New(ui) + run := func(t *testing.T, args []string) *api.ACLRole { + ui := cli.NewMockUi() + cmd := New(ui) + + code := cmd.Run(append(args, "-format=json", "-http-addr="+a.HTTPAddr())) + require.Equal(t, 0, code) + require.Empty(t, ui.ErrorWriter.String()) + + var role api.ACLRole + require.NoError(t, json.Unmarshal(ui.OutputWriter.Bytes(), &role)) + return &role + } // Create a policy client := a.Client() @@ -54,64 +64,55 @@ func TestRoleCreateCommand_Pretty(t *testing.T) { require.NoError(t, err) // create with policy by name - { - args := []string{ - "-http-addr=" + a.HTTPAddr(), + t.Run("policy-name", func(t *testing.T) { + _ = run(t, []string{ "-token=root", "-name=role-with-policy-by-name", "-description=test-role", "-policy-name=" + policy.Name, - } - - code := cmd.Run(args) - require.Equal(t, code, 0) - require.Empty(t, ui.ErrorWriter.String()) - } + }) + }) // create with policy by id - { - args := []string{ - "-http-addr=" + a.HTTPAddr(), + t.Run("policy-id", func(t *testing.T) { + _ = run(t, []string{ "-token=root", "-name=role-with-policy-by-id", "-description=test-role", "-policy-id=" + policy.ID, - } - - code := cmd.Run(args) - require.Equal(t, code, 0) - require.Empty(t, ui.ErrorWriter.String()) - } + }) + }) // create with service identity - { - args := []string{ - "-http-addr=" + a.HTTPAddr(), + t.Run("service-identity", func(t *testing.T) { + _ = run(t, []string{ "-token=root", "-name=role-with-service-identity", "-description=test-role", "-service-identity=web", - } - - code := cmd.Run(args) - require.Equal(t, code, 0) - require.Empty(t, ui.ErrorWriter.String()) - } + }) + }) // create with service identity scoped to 2 DCs - { - args := []string{ - "-http-addr=" + a.HTTPAddr(), + t.Run("dc-scoped-service-identity", func(t *testing.T) { + _ = run(t, []string{ "-token=root", "-name=role-with-service-identity-in-2-dcs", "-description=test-role", "-service-identity=db:abc,xyz", - } + }) + }) - code := cmd.Run(args) - require.Equal(t, code, 0) - require.Empty(t, ui.ErrorWriter.String()) - } + t.Run("node-identity", func(t *testing.T) { + role := run(t, []string{ + "-token=root", + "-name=role-with-node-identity", + "-description=test-role", + "-node-identity=foo:bar", + }) + + require.Len(t, role.NodeIdentities, 1) + }) } func TestRoleCreateCommand_JSON(t *testing.T) { diff --git a/command/acl/role/formatter.go b/command/acl/role/formatter.go index b000e88add..37a77e896a 100644 --- a/command/acl/role/formatter.go +++ b/command/acl/role/formatter.go @@ -77,6 +77,12 @@ func (f *prettyFormatter) FormatRole(role *api.ACLRole) (string, error) { } } } + if len(role.NodeIdentities) > 0 { + buffer.WriteString(fmt.Sprintln("Node Identities:")) + for _, nodeid := range role.NodeIdentities { + buffer.WriteString(fmt.Sprintf(" %s (Datacenter: %s)\n", nodeid.NodeName, nodeid.Datacenter)) + } + } return buffer.String(), nil } @@ -122,6 +128,13 @@ func (f *prettyFormatter) formatRoleListEntry(role *api.ACLRole) string { } } + if len(role.NodeIdentities) > 0 { + buffer.WriteString(fmt.Sprintln(" Node Identities:")) + for _, nodeid := range role.NodeIdentities { + buffer.WriteString(fmt.Sprintf(" %s (Datacenter: %s)\n", nodeid.NodeName, nodeid.Datacenter)) + } + } + return buffer.String() } diff --git a/command/acl/role/formatter_test.go b/command/acl/role/formatter_test.go new file mode 100644 index 0000000000..c986d23d37 --- /dev/null +++ b/command/acl/role/formatter_test.go @@ -0,0 +1,195 @@ +package role + +import ( + "flag" + "fmt" + "io/ioutil" + "path" + "path/filepath" + "testing" + + "github.com/hashicorp/consul/api" + "github.com/stretchr/testify/require" +) + +// update allows golden files to be updated based on the current output. +var update = flag.Bool("update", false, "update golden files") + +// golden reads and optionally writes the expected data to the golden file, +// returning the contents as a string. +func golden(t *testing.T, name, got string) string { + t.Helper() + + golden := filepath.Join("testdata", name+".golden") + if *update && got != "" { + err := ioutil.WriteFile(golden, []byte(got), 0644) + require.NoError(t, err) + } + + expected, err := ioutil.ReadFile(golden) + require.NoError(t, err) + + return string(expected) +} + +func TestFormatRole(t *testing.T) { + type testCase struct { + role api.ACLRole + overrideGoldenName string + } + + cases := map[string]testCase{ + "basic": { + role: api.ACLRole{ + ID: "bd6c9fb0-2d1a-4b96-acaf-669f5d7e7852", + Name: "basic", + Description: "test role", + Hash: []byte{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'}, + CreateIndex: 42, + ModifyIndex: 100, + }, + }, + "complex": { + role: api.ACLRole{ + ID: "c29c4ee4-bca6-474e-be37-7d9606f9582a", + Name: "complex", + Namespace: "foo", + Description: "test role complex", + Hash: []byte{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'}, + CreateIndex: 5, + ModifyIndex: 10, + Policies: []*api.ACLLink{ + &api.ACLLink{ + ID: "beb04680-815b-4d7c-9e33-3d707c24672c", + Name: "hobbiton", + }, + &api.ACLLink{ + ID: "18788457-584c-4812-80d3-23d403148a90", + Name: "bywater", + }, + }, + ServiceIdentities: []*api.ACLServiceIdentity{ + &api.ACLServiceIdentity{ + ServiceName: "gardener", + Datacenters: []string{"middleearth-northwest"}, + }, + }, + NodeIdentities: []*api.ACLNodeIdentity{ + &api.ACLNodeIdentity{ + NodeName: "bagend", + Datacenter: "middleearth-northwest", + }, + }, + }, + }, + } + + formatters := map[string]Formatter{ + "pretty": newPrettyFormatter(false), + "pretty-meta": newPrettyFormatter(true), + // the JSON formatter ignores the showMeta + "json": newJSONFormatter(false), + } + + for name, tcase := range cases { + t.Run(name, func(t *testing.T) { + for fmtName, formatter := range formatters { + t.Run(fmtName, func(t *testing.T) { + actual, err := formatter.FormatRole(&tcase.role) + require.NoError(t, err) + + gName := fmt.Sprintf("%s.%s", name, fmtName) + if tcase.overrideGoldenName != "" { + gName = tcase.overrideGoldenName + } + + expected := golden(t, path.Join("FormatRole", gName), actual) + require.Equal(t, expected, actual) + }) + } + }) + } +} + +func TestFormatTokenList(t *testing.T) { + type testCase struct { + roles []*api.ACLRole + overrideGoldenName string + } + + cases := map[string]testCase{ + "basic": { + roles: []*api.ACLRole{ + &api.ACLRole{ + ID: "bd6c9fb0-2d1a-4b96-acaf-669f5d7e7852", + Name: "basic", + Description: "test role", + Hash: []byte{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'}, + CreateIndex: 42, + ModifyIndex: 100, + }, + }, + }, + "complex": { + roles: []*api.ACLRole{ + &api.ACLRole{ + ID: "c29c4ee4-bca6-474e-be37-7d9606f9582a", + Name: "complex", + Namespace: "foo", + Description: "test role complex", + Hash: []byte{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'}, + CreateIndex: 5, + ModifyIndex: 10, + Policies: []*api.ACLLink{ + &api.ACLLink{ + ID: "beb04680-815b-4d7c-9e33-3d707c24672c", + Name: "hobbiton", + }, + &api.ACLLink{ + ID: "18788457-584c-4812-80d3-23d403148a90", + Name: "bywater", + }, + }, + ServiceIdentities: []*api.ACLServiceIdentity{ + &api.ACLServiceIdentity{ + ServiceName: "gardener", + Datacenters: []string{"middleearth-northwest"}, + }, + }, + NodeIdentities: []*api.ACLNodeIdentity{ + &api.ACLNodeIdentity{ + NodeName: "bagend", + Datacenter: "middleearth-northwest", + }, + }, + }, + }, + }, + } + + formatters := map[string]Formatter{ + "pretty": newPrettyFormatter(false), + "pretty-meta": newPrettyFormatter(true), + // the JSON formatter ignores the showMeta + "json": newJSONFormatter(false), + } + + for name, tcase := range cases { + t.Run(name, func(t *testing.T) { + for fmtName, formatter := range formatters { + t.Run(fmtName, func(t *testing.T) { + actual, err := formatter.FormatRoleList(tcase.roles) + require.NoError(t, err) + + gName := fmt.Sprintf("%s.%s", name, fmtName) + if tcase.overrideGoldenName != "" { + gName = tcase.overrideGoldenName + } + + expected := golden(t, path.Join("FormatRoleList", gName), actual) + require.Equal(t, expected, actual) + }) + } + }) + } +} diff --git a/command/acl/role/testdata/FormatRole/basic.json.golden b/command/acl/role/testdata/FormatRole/basic.json.golden new file mode 100644 index 0000000000..a9c8d6c487 --- /dev/null +++ b/command/acl/role/testdata/FormatRole/basic.json.golden @@ -0,0 +1,8 @@ +{ + "ID": "bd6c9fb0-2d1a-4b96-acaf-669f5d7e7852", + "Name": "basic", + "Description": "test role", + "Hash": "YWJjZGVmZ2g=", + "CreateIndex": 42, + "ModifyIndex": 100 +} \ No newline at end of file diff --git a/command/acl/role/testdata/FormatRole/basic.pretty-meta.golden b/command/acl/role/testdata/FormatRole/basic.pretty-meta.golden new file mode 100644 index 0000000000..31ba775f6f --- /dev/null +++ b/command/acl/role/testdata/FormatRole/basic.pretty-meta.golden @@ -0,0 +1,6 @@ +ID: bd6c9fb0-2d1a-4b96-acaf-669f5d7e7852 +Name: basic +Description: test role +Hash: 6162636465666768 +Create Index: 42 +Modify Index: 100 diff --git a/command/acl/role/testdata/FormatRole/basic.pretty.golden b/command/acl/role/testdata/FormatRole/basic.pretty.golden new file mode 100644 index 0000000000..56a4105ea1 --- /dev/null +++ b/command/acl/role/testdata/FormatRole/basic.pretty.golden @@ -0,0 +1,3 @@ +ID: bd6c9fb0-2d1a-4b96-acaf-669f5d7e7852 +Name: basic +Description: test role diff --git a/command/acl/role/testdata/FormatRole/complex.json.golden b/command/acl/role/testdata/FormatRole/complex.json.golden new file mode 100644 index 0000000000..b8d9b64950 --- /dev/null +++ b/command/acl/role/testdata/FormatRole/complex.json.golden @@ -0,0 +1,33 @@ +{ + "ID": "c29c4ee4-bca6-474e-be37-7d9606f9582a", + "Name": "complex", + "Description": "test role complex", + "Policies": [ + { + "ID": "beb04680-815b-4d7c-9e33-3d707c24672c", + "Name": "hobbiton" + }, + { + "ID": "18788457-584c-4812-80d3-23d403148a90", + "Name": "bywater" + } + ], + "ServiceIdentities": [ + { + "ServiceName": "gardener", + "Datacenters": [ + "middleearth-northwest" + ] + } + ], + "NodeIdentities": [ + { + "NodeName": "bagend", + "Datacenter": "middleearth-northwest" + } + ], + "Hash": "YWJjZGVmZ2g=", + "CreateIndex": 5, + "ModifyIndex": 10, + "Namespace": "foo" +} \ No newline at end of file diff --git a/command/acl/role/testdata/FormatRole/complex.pretty-meta.golden b/command/acl/role/testdata/FormatRole/complex.pretty-meta.golden new file mode 100644 index 0000000000..56e61f7780 --- /dev/null +++ b/command/acl/role/testdata/FormatRole/complex.pretty-meta.golden @@ -0,0 +1,14 @@ +ID: c29c4ee4-bca6-474e-be37-7d9606f9582a +Name: complex +Namespace: foo +Description: test role complex +Hash: 6162636465666768 +Create Index: 5 +Modify Index: 10 +Policies: + beb04680-815b-4d7c-9e33-3d707c24672c - hobbiton + 18788457-584c-4812-80d3-23d403148a90 - bywater +Service Identities: + gardener (Datacenters: middleearth-northwest) +Node Identities: + bagend (Datacenter: middleearth-northwest) diff --git a/command/acl/role/testdata/FormatRole/complex.pretty.golden b/command/acl/role/testdata/FormatRole/complex.pretty.golden new file mode 100644 index 0000000000..b3d347ed00 --- /dev/null +++ b/command/acl/role/testdata/FormatRole/complex.pretty.golden @@ -0,0 +1,11 @@ +ID: c29c4ee4-bca6-474e-be37-7d9606f9582a +Name: complex +Namespace: foo +Description: test role complex +Policies: + beb04680-815b-4d7c-9e33-3d707c24672c - hobbiton + 18788457-584c-4812-80d3-23d403148a90 - bywater +Service Identities: + gardener (Datacenters: middleearth-northwest) +Node Identities: + bagend (Datacenter: middleearth-northwest) diff --git a/command/acl/role/testdata/FormatRoleList/basic.json.golden b/command/acl/role/testdata/FormatRoleList/basic.json.golden new file mode 100644 index 0000000000..daa3134992 --- /dev/null +++ b/command/acl/role/testdata/FormatRoleList/basic.json.golden @@ -0,0 +1,10 @@ +[ + { + "ID": "bd6c9fb0-2d1a-4b96-acaf-669f5d7e7852", + "Name": "basic", + "Description": "test role", + "Hash": "YWJjZGVmZ2g=", + "CreateIndex": 42, + "ModifyIndex": 100 + } +] \ No newline at end of file diff --git a/command/acl/role/testdata/FormatRoleList/basic.pretty-meta.golden b/command/acl/role/testdata/FormatRoleList/basic.pretty-meta.golden new file mode 100644 index 0000000000..b2fbc3d6ef --- /dev/null +++ b/command/acl/role/testdata/FormatRoleList/basic.pretty-meta.golden @@ -0,0 +1,6 @@ +basic: + ID: bd6c9fb0-2d1a-4b96-acaf-669f5d7e7852 + Description: test role + Hash: 6162636465666768 + Create Index: 42 + Modify Index: 100 diff --git a/command/acl/role/testdata/FormatRoleList/basic.pretty.golden b/command/acl/role/testdata/FormatRoleList/basic.pretty.golden new file mode 100644 index 0000000000..587392e7b1 --- /dev/null +++ b/command/acl/role/testdata/FormatRoleList/basic.pretty.golden @@ -0,0 +1,3 @@ +basic: + ID: bd6c9fb0-2d1a-4b96-acaf-669f5d7e7852 + Description: test role diff --git a/command/acl/role/testdata/FormatRoleList/complex.json.golden b/command/acl/role/testdata/FormatRoleList/complex.json.golden new file mode 100644 index 0000000000..58c6d850b8 --- /dev/null +++ b/command/acl/role/testdata/FormatRoleList/complex.json.golden @@ -0,0 +1,35 @@ +[ + { + "ID": "c29c4ee4-bca6-474e-be37-7d9606f9582a", + "Name": "complex", + "Description": "test role complex", + "Policies": [ + { + "ID": "beb04680-815b-4d7c-9e33-3d707c24672c", + "Name": "hobbiton" + }, + { + "ID": "18788457-584c-4812-80d3-23d403148a90", + "Name": "bywater" + } + ], + "ServiceIdentities": [ + { + "ServiceName": "gardener", + "Datacenters": [ + "middleearth-northwest" + ] + } + ], + "NodeIdentities": [ + { + "NodeName": "bagend", + "Datacenter": "middleearth-northwest" + } + ], + "Hash": "YWJjZGVmZ2g=", + "CreateIndex": 5, + "ModifyIndex": 10, + "Namespace": "foo" + } +] \ No newline at end of file diff --git a/command/acl/role/testdata/FormatRoleList/complex.pretty-meta.golden b/command/acl/role/testdata/FormatRoleList/complex.pretty-meta.golden new file mode 100644 index 0000000000..76355e91dd --- /dev/null +++ b/command/acl/role/testdata/FormatRoleList/complex.pretty-meta.golden @@ -0,0 +1,14 @@ +complex: + ID: c29c4ee4-bca6-474e-be37-7d9606f9582a + Namespace: foo + Description: test role complex + Hash: 6162636465666768 + Create Index: 5 + Modify Index: 10 + Policies: + beb04680-815b-4d7c-9e33-3d707c24672c - hobbiton + 18788457-584c-4812-80d3-23d403148a90 - bywater + Service Identities: + gardener (Datacenters: middleearth-northwest) + Node Identities: + bagend (Datacenter: middleearth-northwest) diff --git a/command/acl/role/testdata/FormatRoleList/complex.pretty.golden b/command/acl/role/testdata/FormatRoleList/complex.pretty.golden new file mode 100644 index 0000000000..ab2b0c87f8 --- /dev/null +++ b/command/acl/role/testdata/FormatRoleList/complex.pretty.golden @@ -0,0 +1,11 @@ +complex: + ID: c29c4ee4-bca6-474e-be37-7d9606f9582a + Namespace: foo + Description: test role complex + Policies: + beb04680-815b-4d7c-9e33-3d707c24672c - hobbiton + 18788457-584c-4812-80d3-23d403148a90 - bywater + Service Identities: + gardener (Datacenters: middleearth-northwest) + Node Identities: + bagend (Datacenter: middleearth-northwest) diff --git a/command/acl/role/update/role_update.go b/command/acl/role/update/role_update.go index a26858e29c..afbadeb207 100644 --- a/command/acl/role/update/role_update.go +++ b/command/acl/role/update/role_update.go @@ -30,6 +30,7 @@ type cmd struct { policyIDs []string policyNames []string serviceIdents []string + nodeIdents []string noMerge bool showMeta bool @@ -52,6 +53,9 @@ func (c *cmd) init() { c.flags.Var((*flags.AppendSliceValue)(&c.serviceIdents), "service-identity", "Name of a "+ "service identity to use for this role. May be specified multiple times. Format is "+ "the SERVICENAME or SERVICENAME:DATACENTER1,DATACENTER2,...") + c.flags.Var((*flags.AppendSliceValue)(&c.nodeIdents), "node-identity", "Name of a "+ + "node identity to use for this role. May be specified multiple times. Format is "+ + "NODENAME:DATACENTER") c.flags.BoolVar(&c.noMerge, "no-merge", false, "Do not merge the current role "+ "information with what is provided to the command. Instead overwrite all fields "+ "with the exception of the role ID which is immutable.") @@ -97,6 +101,12 @@ func (c *cmd) Run(args []string) int { return 1 } + parsedNodeIdents, err := acl.ExtractNodeIdentities(c.nodeIdents) + if err != nil { + c.UI.Error(err.Error()) + return 1 + } + // Read the current role in both cases so we can fail better if not found. currentRole, _, err := client.ACL().RoleRead(roleID, nil) if err != nil { @@ -114,6 +124,7 @@ func (c *cmd) Run(args []string) int { Name: c.name, Description: c.description, ServiceIdentities: parsedServiceIdents, + NodeIdentities: parsedNodeIdents, } for _, policyName := range c.policyNames { @@ -192,6 +203,20 @@ func (c *cmd) Run(args []string) int { r.ServiceIdentities = append(r.ServiceIdentities, svcid) } } + + for _, nodeid := range parsedNodeIdents { + found := false + for _, link := range r.NodeIdentities { + if link.NodeName == nodeid.NodeName && link.Datacenter != nodeid.Datacenter { + found = true + break + } + } + + if !found { + r.NodeIdentities = append(r.NodeIdentities, nodeid) + } + } } r, _, err = client.ACL().RoleUpdate(r, nil) diff --git a/command/acl/role/update/role_update_test.go b/command/acl/role/update/role_update_test.go index 684a9643e6..ad3df1309a 100644 --- a/command/acl/role/update/role_update_test.go +++ b/command/acl/role/update/role_update_test.go @@ -71,6 +71,19 @@ func TestRoleUpdateCommand(t *testing.T) { ) require.NoError(t, err) + run := func(t *testing.T, args []string) *api.ACLRole { + ui := cli.NewMockUi() + cmd := New(ui) + + code := cmd.Run(append(args, "-format=json", "-http-addr="+a.HTTPAddr())) + require.Equal(t, 0, code, "err: %s", ui.ErrorWriter.String()) + require.Empty(t, ui.ErrorWriter.String()) + + var role api.ACLRole + require.NoError(t, json.Unmarshal(ui.OutputWriter.Bytes(), &role)) + return &role + } + t.Run("update a role that does not exist", func(t *testing.T) { fakeID, err := uuid.GenerateUUID() require.NoError(t, err) @@ -91,19 +104,12 @@ func TestRoleUpdateCommand(t *testing.T) { }) t.Run("update with policy by name", func(t *testing.T) { - ui := cli.NewMockUi() - cmd := New(ui) - args := []string{ - "-http-addr=" + a.HTTPAddr(), + _ = run(t, []string{ "-id=" + role.ID, "-token=root", "-policy-name=" + policy1.Name, "-description=test role edited", - } - - code := cmd.Run(args) - require.Equal(t, code, 0, "err: %s", ui.ErrorWriter.String()) - require.Empty(t, ui.ErrorWriter.String()) + }) role, _, err := client.ACL().RoleRead( role.ID, @@ -119,18 +125,11 @@ func TestRoleUpdateCommand(t *testing.T) { t.Run("update with policy by id", func(t *testing.T) { // also update with no description shouldn't delete the current // description - ui := cli.NewMockUi() - cmd := New(ui) - args := []string{ - "-http-addr=" + a.HTTPAddr(), + _ = run(t, []string{ "-id=" + role.ID, "-token=root", "-policy-id=" + policy2.ID, - } - - code := cmd.Run(args) - require.Equal(t, code, 0, "err: %s", ui.ErrorWriter.String()) - require.Empty(t, ui.ErrorWriter.String()) + }) role, _, err := client.ACL().RoleRead( role.ID, @@ -144,18 +143,11 @@ func TestRoleUpdateCommand(t *testing.T) { }) t.Run("update with service identity", func(t *testing.T) { - ui := cli.NewMockUi() - cmd := New(ui) - args := []string{ - "-http-addr=" + a.HTTPAddr(), + _ = run(t, []string{ "-id=" + role.ID, "-token=root", "-service-identity=web", - } - - code := cmd.Run(args) - require.Equal(t, code, 0, "err: %s", ui.ErrorWriter.String()) - require.Empty(t, ui.ErrorWriter.String()) + }) role, _, err := client.ACL().RoleRead( role.ID, @@ -169,18 +161,11 @@ func TestRoleUpdateCommand(t *testing.T) { }) t.Run("update with service identity scoped to 2 DCs", func(t *testing.T) { - ui := cli.NewMockUi() - cmd := New(ui) - args := []string{ - "-http-addr=" + a.HTTPAddr(), + _ = run(t, []string{ "-id=" + role.ID, "-token=root", "-service-identity=db:abc,xyz", - } - - code := cmd.Run(args) - require.Equal(t, code, 0, "err: %s", ui.ErrorWriter.String()) - require.Empty(t, ui.ErrorWriter.String()) + }) role, _, err := client.ACL().RoleRead( role.ID, @@ -192,6 +177,25 @@ func TestRoleUpdateCommand(t *testing.T) { require.Len(t, role.Policies, 2) require.Len(t, role.ServiceIdentities, 3) }) + + t.Run("update with node identity", func(t *testing.T) { + _ = run(t, []string{ + "-id=" + role.ID, + "-token=root", + "-node-identity=foo:bar", + }) + + role, _, err := client.ACL().RoleRead( + role.ID, + &api.QueryOptions{Token: "root"}, + ) + require.NoError(t, err) + require.NotNil(t, role) + require.Equal(t, "test role edited", role.Description) + require.Len(t, role.Policies, 2) + require.Len(t, role.ServiceIdentities, 3) + require.Len(t, role.NodeIdentities, 1) + }) } func TestRoleUpdateCommand_JSON(t *testing.T) { diff --git a/command/acl/token/create/token_create.go b/command/acl/token/create/token_create.go index 03e04e5331..14bac71afa 100644 --- a/command/acl/token/create/token_create.go +++ b/command/acl/token/create/token_create.go @@ -33,6 +33,7 @@ type cmd struct { roleIDs []string roleNames []string serviceIdents []string + nodeIdents []string expirationTTL time.Duration local bool showMeta bool @@ -60,6 +61,9 @@ func (c *cmd) init() { c.flags.Var((*flags.AppendSliceValue)(&c.serviceIdents), "service-identity", "Name of a "+ "service identity to use for this token. May be specified multiple times. Format is "+ "the SERVICENAME or SERVICENAME:DATACENTER1,DATACENTER2,...") + c.flags.Var((*flags.AppendSliceValue)(&c.nodeIdents), "node-identity", "Name of a "+ + "node identity to use for this token. May be specified multiple times. Format is "+ + "NODENAME:DATACENTER") c.flags.DurationVar(&c.expirationTTL, "expires-ttl", 0, "Duration of time this "+ "token should be valid for") c.flags.StringVar( @@ -82,8 +86,8 @@ func (c *cmd) Run(args []string) int { if len(c.policyNames) == 0 && len(c.policyIDs) == 0 && len(c.roleNames) == 0 && len(c.roleIDs) == 0 && - len(c.serviceIdents) == 0 { - c.UI.Error(fmt.Sprintf("Cannot create a token without specifying -policy-name, -policy-id, -role-name, -role-id, or -service-identity at least once")) + len(c.serviceIdents) == 0 && len(c.nodeIdents) == 0 { + c.UI.Error(fmt.Sprintf("Cannot create a token without specifying -policy-name, -policy-id, -role-name, -role-id, -service-identity, or -node-identity at least once")) return 1 } @@ -110,6 +114,13 @@ func (c *cmd) Run(args []string) int { } newToken.ServiceIdentities = parsedServiceIdents + parsedNodeIdents, err := acl.ExtractNodeIdentities(c.nodeIdents) + if err != nil { + c.UI.Error(err.Error()) + return 1 + } + newToken.NodeIdentities = parsedNodeIdents + for _, policyName := range c.policyNames { // We could resolve names to IDs here but there isn't any reason why its would be better // than allowing the agent to do it. diff --git a/command/acl/token/create/token_create_test.go b/command/acl/token/create/token_create_test.go index 464771611b..a128c2f14b 100644 --- a/command/acl/token/create/token_create_test.go +++ b/command/acl/token/create/token_create_test.go @@ -24,13 +24,13 @@ func TestTokenCreateCommand_noTabs(t *testing.T) { func TestTokenCreateCommand_Pretty(t *testing.T) { t.Parallel() - require := require.New(t) testDir := testutil.TempDir(t, "acl") defer os.RemoveAll(testDir) a := agent.NewTestAgent(t, ` primary_datacenter = "dc1" + node_name = "test-node" acl { enabled = true tokens { @@ -41,9 +41,6 @@ func TestTokenCreateCommand_Pretty(t *testing.T) { defer a.Shutdown() testrpc.WaitForLeader(t, a.RPC, "dc1") - ui := cli.NewMockUi() - cmd := New(ui) - // Create a policy client := a.Client() @@ -51,66 +48,75 @@ func TestTokenCreateCommand_Pretty(t *testing.T) { &api.ACLPolicy{Name: "test-policy"}, &api.WriteOptions{Token: "root"}, ) - require.NoError(err) + require.NoError(t, err) + + run := func(t *testing.T, args []string) *api.ACLToken { + ui := cli.NewMockUi() + cmd := New(ui) + + code := cmd.Run(append(args, "-format=json")) + require.Equal(t, 0, code) + require.Empty(t, ui.ErrorWriter.String()) + + var token api.ACLToken + require.NoError(t, json.Unmarshal(ui.OutputWriter.Bytes(), &token)) + return &token + } // create with policy by name - { - args := []string{ + t.Run("policy-name", func(t *testing.T) { + _ = run(t, []string{ "-http-addr=" + a.HTTPAddr(), "-token=root", "-policy-name=" + policy.Name, "-description=test token", - } - - code := cmd.Run(args) - require.Equal(code, 0) - require.Empty(ui.ErrorWriter.String()) - } + }) + }) // create with policy by id - { - args := []string{ + t.Run("policy-id", func(t *testing.T) { + _ = run(t, []string{ "-http-addr=" + a.HTTPAddr(), "-token=root", "-policy-id=" + policy.ID, "-description=test token", - } + }) + }) - code := cmd.Run(args) - require.Empty(ui.ErrorWriter.String()) - require.Equal(code, 0) - } + // create with a node identity + t.Run("node-identity", func(t *testing.T) { + token := run(t, []string{ + "-http-addr=" + a.HTTPAddr(), + "-token=root", + "-node-identity=" + a.Config.NodeName + ":" + a.Config.Datacenter, + }) + + conf := api.DefaultConfig() + conf.Address = a.HTTPAddr() + conf.Token = token.SecretID + client, err := api.NewClient(conf) + require.NoError(t, err) + + nodes, _, err := client.Catalog().Nodes(nil) + require.NoError(t, err) + require.Len(t, nodes, 1) + require.Equal(t, a.Config.NodeName, nodes[0].Node) + }) // create with accessor and secret - { - args := []string{ + t.Run("predefined-ids", func(t *testing.T) { + token := run(t, []string{ "-http-addr=" + a.HTTPAddr(), "-token=root", "-policy-id=" + policy.ID, "-description=test token", "-accessor=3d852bb8-5153-4388-a3ca-8ca78661889f", "-secret=3a69a8d8-c4d4-485d-9b19-b5b61648ea0c", - } + }) - code := cmd.Run(args) - require.Empty(ui.ErrorWriter.String()) - require.Equal(code, 0) - - conf := api.DefaultConfig() - conf.Address = a.HTTPAddr() - conf.Token = "root" - - // going to use the API client to grab the token - we could potentially try to grab the values - // out of the command output but this seems easier. - client, err := api.NewClient(conf) - require.NoError(err) - require.NotNil(client) - - token, _, err := client.ACL().TokenRead("3d852bb8-5153-4388-a3ca-8ca78661889f", nil) - require.NoError(err) - require.Equal("3d852bb8-5153-4388-a3ca-8ca78661889f", token.AccessorID) - require.Equal("3a69a8d8-c4d4-485d-9b19-b5b61648ea0c", token.SecretID) - } + require.Equal(t, "3d852bb8-5153-4388-a3ca-8ca78661889f", token.AccessorID) + require.Equal(t, "3a69a8d8-c4d4-485d-9b19-b5b61648ea0c", token.SecretID) + }) } func TestTokenCreateCommand_JSON(t *testing.T) { diff --git a/command/acl/token/formatter.go b/command/acl/token/formatter.go index d5bb781c5e..790b9f73b7 100644 --- a/command/acl/token/formatter.go +++ b/command/acl/token/formatter.go @@ -91,6 +91,12 @@ func (f *prettyFormatter) FormatToken(token *api.ACLToken) (string, error) { } } } + if len(token.NodeIdentities) > 0 { + buffer.WriteString(fmt.Sprintln("Node Identities:")) + for _, nodeid := range token.NodeIdentities { + buffer.WriteString(fmt.Sprintf(" %s (Datacenter: %s)\n", nodeid.NodeName, nodeid.Datacenter)) + } + } if token.Rules != "" { buffer.WriteString(fmt.Sprintln("Rules:")) buffer.WriteString(fmt.Sprintln(token.Rules)) @@ -159,6 +165,16 @@ func (f *prettyFormatter) formatTokenListEntry(token *api.ACLTokenListEntry) str } } } + if len(token.NodeIdentities) > 0 { + buffer.WriteString(fmt.Sprintln("Service Identities:")) + for _, svcid := range token.ServiceIdentities { + if len(svcid.Datacenters) > 0 { + buffer.WriteString(fmt.Sprintf(" %s (Datacenters: %s)\n", svcid.ServiceName, strings.Join(svcid.Datacenters, ", "))) + } else { + buffer.WriteString(fmt.Sprintf(" %s (Datacenters: all)\n", svcid.ServiceName)) + } + } + } return buffer.String() } diff --git a/command/acl/token/formatter_test.go b/command/acl/token/formatter_test.go new file mode 100644 index 0000000000..84d2411565 --- /dev/null +++ b/command/acl/token/formatter_test.go @@ -0,0 +1,251 @@ +package token + +import ( + "flag" + "fmt" + "io/ioutil" + "path" + "path/filepath" + "testing" + "time" + + "github.com/hashicorp/consul/api" + "github.com/stretchr/testify/require" +) + +// update allows golden files to be updated based on the current output. +var update = flag.Bool("update", false, "update golden files") + +// golden reads and optionally writes the expected data to the golden file, +// returning the contents as a string. +func golden(t *testing.T, name, got string) string { + t.Helper() + + golden := filepath.Join("testdata", name+".golden") + if *update && got != "" { + err := ioutil.WriteFile(golden, []byte(got), 0644) + require.NoError(t, err) + } + + expected, err := ioutil.ReadFile(golden) + require.NoError(t, err) + + return string(expected) +} + +func TestFormatToken(t *testing.T) { + type testCase struct { + token api.ACLToken + overrideGoldenName string + } + + timeRef := func(in time.Time) *time.Time { + return &in + } + + cases := map[string]testCase{ + "basic": { + token: api.ACLToken{ + AccessorID: "fbd2447f-7479-4329-ad13-b021d74f86ba", + SecretID: "869c6e91-4de9-4dab-b56e-87548435f9c6", + Description: "test token", + Local: false, + CreateTime: time.Date(2020, 5, 22, 18, 52, 31, 0, time.UTC), + Hash: []byte{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'}, + CreateIndex: 42, + ModifyIndex: 100, + }, + }, + "legacy": { + token: api.ACLToken{ + AccessorID: "8acc7486-ca54-4d3c-9aed-5cd85651b0ee", + SecretID: "legacy-secret", + Description: "legacy", + Rules: `operator = "read"`, + }, + }, + "complex": { + token: api.ACLToken{ + AccessorID: "fbd2447f-7479-4329-ad13-b021d74f86ba", + SecretID: "869c6e91-4de9-4dab-b56e-87548435f9c6", + Namespace: "foo", + Description: "test token", + Local: false, + AuthMethod: "bar", + CreateTime: time.Date(2020, 5, 22, 18, 52, 31, 0, time.UTC), + ExpirationTime: timeRef(time.Date(2020, 5, 22, 19, 52, 31, 0, time.UTC)), + Hash: []byte{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'}, + CreateIndex: 5, + ModifyIndex: 10, + Policies: []*api.ACLLink{ + &api.ACLLink{ + ID: "beb04680-815b-4d7c-9e33-3d707c24672c", + Name: "hobbiton", + }, + &api.ACLLink{ + ID: "18788457-584c-4812-80d3-23d403148a90", + Name: "bywater", + }, + }, + Roles: []*api.ACLLink{ + &api.ACLLink{ + ID: "3b0a78fe-b9c3-40de-b8ea-7d4d6674b366", + Name: "shire", + }, + &api.ACLLink{ + ID: "6c9d1e1d-34bc-4d55-80f3-add0890ad791", + Name: "west-farthing", + }, + }, + ServiceIdentities: []*api.ACLServiceIdentity{ + &api.ACLServiceIdentity{ + ServiceName: "gardener", + Datacenters: []string{"middleearth-northwest"}, + }, + }, + NodeIdentities: []*api.ACLNodeIdentity{ + &api.ACLNodeIdentity{ + NodeName: "bagend", + Datacenter: "middleearth-northwest", + }, + }, + }, + }, + } + + formatters := map[string]Formatter{ + "pretty": newPrettyFormatter(false), + "pretty-meta": newPrettyFormatter(true), + // the JSON formatter ignores the showMeta + "json": newJSONFormatter(false), + } + + for name, tcase := range cases { + t.Run(name, func(t *testing.T) { + for fmtName, formatter := range formatters { + t.Run(fmtName, func(t *testing.T) { + actual, err := formatter.FormatToken(&tcase.token) + require.NoError(t, err) + + gName := fmt.Sprintf("%s.%s", name, fmtName) + if tcase.overrideGoldenName != "" { + gName = tcase.overrideGoldenName + } + + expected := golden(t, path.Join("FormatToken", gName), actual) + require.Equal(t, expected, actual) + }) + } + }) + } +} + +func TestFormatTokenList(t *testing.T) { + type testCase struct { + tokens []*api.ACLTokenListEntry + overrideGoldenName string + } + + timeRef := func(in time.Time) *time.Time { + return &in + } + + cases := map[string]testCase{ + "basic": { + tokens: []*api.ACLTokenListEntry{ + &api.ACLTokenListEntry{ + AccessorID: "fbd2447f-7479-4329-ad13-b021d74f86ba", + Description: "test token", + Local: false, + CreateTime: time.Date(2020, 5, 22, 18, 52, 31, 0, time.UTC), + Hash: []byte{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'}, + CreateIndex: 42, + ModifyIndex: 100, + }, + }, + }, + "legacy": { + tokens: []*api.ACLTokenListEntry{ + &api.ACLTokenListEntry{ + AccessorID: "8acc7486-ca54-4d3c-9aed-5cd85651b0ee", + Description: "legacy", + Legacy: true, + }, + }, + }, + "complex": { + tokens: []*api.ACLTokenListEntry{ + &api.ACLTokenListEntry{ + AccessorID: "fbd2447f-7479-4329-ad13-b021d74f86ba", + Namespace: "foo", + Description: "test token", + Local: false, + AuthMethod: "bar", + CreateTime: time.Date(2020, 5, 22, 18, 52, 31, 0, time.UTC), + ExpirationTime: timeRef(time.Date(2020, 5, 22, 19, 52, 31, 0, time.UTC)), + Hash: []byte{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'}, + CreateIndex: 5, + ModifyIndex: 10, + Policies: []*api.ACLLink{ + &api.ACLLink{ + ID: "beb04680-815b-4d7c-9e33-3d707c24672c", + Name: "hobbiton", + }, + &api.ACLLink{ + ID: "18788457-584c-4812-80d3-23d403148a90", + Name: "bywater", + }, + }, + Roles: []*api.ACLLink{ + &api.ACLLink{ + ID: "3b0a78fe-b9c3-40de-b8ea-7d4d6674b366", + Name: "shire", + }, + &api.ACLLink{ + ID: "6c9d1e1d-34bc-4d55-80f3-add0890ad791", + Name: "west-farthing", + }, + }, + ServiceIdentities: []*api.ACLServiceIdentity{ + &api.ACLServiceIdentity{ + ServiceName: "gardener", + Datacenters: []string{"middleearth-northwest"}, + }, + }, + NodeIdentities: []*api.ACLNodeIdentity{ + &api.ACLNodeIdentity{ + NodeName: "bagend", + Datacenter: "middleearth-northwest", + }, + }, + }, + }, + }, + } + + formatters := map[string]Formatter{ + "pretty": newPrettyFormatter(false), + "pretty-meta": newPrettyFormatter(true), + // the JSON formatter ignores the showMeta + "json": newJSONFormatter(false), + } + + for name, tcase := range cases { + t.Run(name, func(t *testing.T) { + for fmtName, formatter := range formatters { + t.Run(fmtName, func(t *testing.T) { + actual, err := formatter.FormatTokenList(tcase.tokens) + require.NoError(t, err) + + gName := fmt.Sprintf("%s.%s", name, fmtName) + if tcase.overrideGoldenName != "" { + gName = tcase.overrideGoldenName + } + + expected := golden(t, path.Join("FormatTokenList", gName), actual) + require.Equal(t, expected, actual) + }) + } + }) + } +} diff --git a/command/acl/token/testdata/FormatToken/basic.json.golden b/command/acl/token/testdata/FormatToken/basic.json.golden new file mode 100644 index 0000000000..6562a9a88a --- /dev/null +++ b/command/acl/token/testdata/FormatToken/basic.json.golden @@ -0,0 +1,10 @@ +{ + "CreateIndex": 42, + "ModifyIndex": 100, + "AccessorID": "fbd2447f-7479-4329-ad13-b021d74f86ba", + "SecretID": "869c6e91-4de9-4dab-b56e-87548435f9c6", + "Description": "test token", + "Local": false, + "CreateTime": "2020-05-22T18:52:31Z", + "Hash": "YWJjZGVmZ2g=" +} \ No newline at end of file diff --git a/command/acl/token/testdata/FormatToken/basic.pretty-meta.golden b/command/acl/token/testdata/FormatToken/basic.pretty-meta.golden new file mode 100644 index 0000000000..232176e72d --- /dev/null +++ b/command/acl/token/testdata/FormatToken/basic.pretty-meta.golden @@ -0,0 +1,8 @@ +AccessorID: fbd2447f-7479-4329-ad13-b021d74f86ba +SecretID: 869c6e91-4de9-4dab-b56e-87548435f9c6 +Description: test token +Local: false +Create Time: 2020-05-22 18:52:31 +0000 UTC +Hash: 6162636465666768 +Create Index: 42 +Modify Index: 100 diff --git a/command/acl/token/testdata/FormatToken/basic.pretty.golden b/command/acl/token/testdata/FormatToken/basic.pretty.golden new file mode 100644 index 0000000000..44b9768945 --- /dev/null +++ b/command/acl/token/testdata/FormatToken/basic.pretty.golden @@ -0,0 +1,5 @@ +AccessorID: fbd2447f-7479-4329-ad13-b021d74f86ba +SecretID: 869c6e91-4de9-4dab-b56e-87548435f9c6 +Description: test token +Local: false +Create Time: 2020-05-22 18:52:31 +0000 UTC diff --git a/command/acl/token/testdata/FormatToken/complex.json.golden b/command/acl/token/testdata/FormatToken/complex.json.golden new file mode 100644 index 0000000000..4462e29b1b --- /dev/null +++ b/command/acl/token/testdata/FormatToken/complex.json.golden @@ -0,0 +1,47 @@ +{ + "CreateIndex": 5, + "ModifyIndex": 10, + "AccessorID": "fbd2447f-7479-4329-ad13-b021d74f86ba", + "SecretID": "869c6e91-4de9-4dab-b56e-87548435f9c6", + "Description": "test token", + "Policies": [ + { + "ID": "beb04680-815b-4d7c-9e33-3d707c24672c", + "Name": "hobbiton" + }, + { + "ID": "18788457-584c-4812-80d3-23d403148a90", + "Name": "bywater" + } + ], + "Roles": [ + { + "ID": "3b0a78fe-b9c3-40de-b8ea-7d4d6674b366", + "Name": "shire" + }, + { + "ID": "6c9d1e1d-34bc-4d55-80f3-add0890ad791", + "Name": "west-farthing" + } + ], + "ServiceIdentities": [ + { + "ServiceName": "gardener", + "Datacenters": [ + "middleearth-northwest" + ] + } + ], + "NodeIdentities": [ + { + "NodeName": "bagend", + "Datacenter": "middleearth-northwest" + } + ], + "Local": false, + "AuthMethod": "bar", + "ExpirationTime": "2020-05-22T19:52:31Z", + "CreateTime": "2020-05-22T18:52:31Z", + "Hash": "YWJjZGVmZ2g=", + "Namespace": "foo" +} \ No newline at end of file diff --git a/command/acl/token/testdata/FormatToken/complex.pretty-meta.golden b/command/acl/token/testdata/FormatToken/complex.pretty-meta.golden new file mode 100644 index 0000000000..dfce557713 --- /dev/null +++ b/command/acl/token/testdata/FormatToken/complex.pretty-meta.golden @@ -0,0 +1,21 @@ +AccessorID: fbd2447f-7479-4329-ad13-b021d74f86ba +SecretID: 869c6e91-4de9-4dab-b56e-87548435f9c6 +Namespace: foo +Description: test token +Local: false +Auth Method: bar +Create Time: 2020-05-22 18:52:31 +0000 UTC +Expiration Time: 2020-05-22 19:52:31 +0000 UTC +Hash: 6162636465666768 +Create Index: 5 +Modify Index: 10 +Policies: + beb04680-815b-4d7c-9e33-3d707c24672c - hobbiton + 18788457-584c-4812-80d3-23d403148a90 - bywater +Roles: + 3b0a78fe-b9c3-40de-b8ea-7d4d6674b366 - shire + 6c9d1e1d-34bc-4d55-80f3-add0890ad791 - west-farthing +Service Identities: + gardener (Datacenters: middleearth-northwest) +Node Identities: + bagend (Datacenter: middleearth-northwest) diff --git a/command/acl/token/testdata/FormatToken/complex.pretty.golden b/command/acl/token/testdata/FormatToken/complex.pretty.golden new file mode 100644 index 0000000000..4437281b8d --- /dev/null +++ b/command/acl/token/testdata/FormatToken/complex.pretty.golden @@ -0,0 +1,18 @@ +AccessorID: fbd2447f-7479-4329-ad13-b021d74f86ba +SecretID: 869c6e91-4de9-4dab-b56e-87548435f9c6 +Namespace: foo +Description: test token +Local: false +Auth Method: bar +Create Time: 2020-05-22 18:52:31 +0000 UTC +Expiration Time: 2020-05-22 19:52:31 +0000 UTC +Policies: + beb04680-815b-4d7c-9e33-3d707c24672c - hobbiton + 18788457-584c-4812-80d3-23d403148a90 - bywater +Roles: + 3b0a78fe-b9c3-40de-b8ea-7d4d6674b366 - shire + 6c9d1e1d-34bc-4d55-80f3-add0890ad791 - west-farthing +Service Identities: + gardener (Datacenters: middleearth-northwest) +Node Identities: + bagend (Datacenter: middleearth-northwest) diff --git a/command/acl/token/testdata/FormatToken/legacy.json.golden b/command/acl/token/testdata/FormatToken/legacy.json.golden new file mode 100644 index 0000000000..71536fc2fe --- /dev/null +++ b/command/acl/token/testdata/FormatToken/legacy.json.golden @@ -0,0 +1,10 @@ +{ + "CreateIndex": 0, + "ModifyIndex": 0, + "AccessorID": "8acc7486-ca54-4d3c-9aed-5cd85651b0ee", + "SecretID": "legacy-secret", + "Description": "legacy", + "Local": false, + "CreateTime": "0001-01-01T00:00:00Z", + "Rules": "operator = \"read\"" +} \ No newline at end of file diff --git a/command/acl/token/testdata/FormatToken/legacy.pretty-meta.golden b/command/acl/token/testdata/FormatToken/legacy.pretty-meta.golden new file mode 100644 index 0000000000..cb477af5a5 --- /dev/null +++ b/command/acl/token/testdata/FormatToken/legacy.pretty-meta.golden @@ -0,0 +1,10 @@ +AccessorID: 8acc7486-ca54-4d3c-9aed-5cd85651b0ee +SecretID: legacy-secret +Description: legacy +Local: false +Create Time: 0001-01-01 00:00:00 +0000 UTC +Hash: +Create Index: 0 +Modify Index: 0 +Rules: +operator = "read" diff --git a/command/acl/token/testdata/FormatToken/legacy.pretty.golden b/command/acl/token/testdata/FormatToken/legacy.pretty.golden new file mode 100644 index 0000000000..ea6c5b7159 --- /dev/null +++ b/command/acl/token/testdata/FormatToken/legacy.pretty.golden @@ -0,0 +1,7 @@ +AccessorID: 8acc7486-ca54-4d3c-9aed-5cd85651b0ee +SecretID: legacy-secret +Description: legacy +Local: false +Create Time: 0001-01-01 00:00:00 +0000 UTC +Rules: +operator = "read" diff --git a/command/acl/token/testdata/FormatTokenList/basic.json.golden b/command/acl/token/testdata/FormatTokenList/basic.json.golden new file mode 100644 index 0000000000..180f84c52e --- /dev/null +++ b/command/acl/token/testdata/FormatTokenList/basic.json.golden @@ -0,0 +1,12 @@ +[ + { + "CreateIndex": 42, + "ModifyIndex": 100, + "AccessorID": "fbd2447f-7479-4329-ad13-b021d74f86ba", + "Description": "test token", + "Local": false, + "CreateTime": "2020-05-22T18:52:31Z", + "Hash": "YWJjZGVmZ2g=", + "Legacy": false + } +] \ No newline at end of file diff --git a/command/acl/token/testdata/FormatTokenList/basic.pretty-meta.golden b/command/acl/token/testdata/FormatTokenList/basic.pretty-meta.golden new file mode 100644 index 0000000000..ab00607741 --- /dev/null +++ b/command/acl/token/testdata/FormatTokenList/basic.pretty-meta.golden @@ -0,0 +1,8 @@ +AccessorID: fbd2447f-7479-4329-ad13-b021d74f86ba +Description: test token +Local: false +Create Time: 2020-05-22 18:52:31 +0000 UTC +Legacy: false +Hash: 6162636465666768 +Create Index: 42 +Modify Index: 100 diff --git a/command/acl/token/testdata/FormatTokenList/basic.pretty.golden b/command/acl/token/testdata/FormatTokenList/basic.pretty.golden new file mode 100644 index 0000000000..c13d1babb1 --- /dev/null +++ b/command/acl/token/testdata/FormatTokenList/basic.pretty.golden @@ -0,0 +1,5 @@ +AccessorID: fbd2447f-7479-4329-ad13-b021d74f86ba +Description: test token +Local: false +Create Time: 2020-05-22 18:52:31 +0000 UTC +Legacy: false diff --git a/command/acl/token/testdata/FormatTokenList/complex.json.golden b/command/acl/token/testdata/FormatTokenList/complex.json.golden new file mode 100644 index 0000000000..abaaac0caa --- /dev/null +++ b/command/acl/token/testdata/FormatTokenList/complex.json.golden @@ -0,0 +1,49 @@ +[ + { + "CreateIndex": 5, + "ModifyIndex": 10, + "AccessorID": "fbd2447f-7479-4329-ad13-b021d74f86ba", + "Description": "test token", + "Policies": [ + { + "ID": "beb04680-815b-4d7c-9e33-3d707c24672c", + "Name": "hobbiton" + }, + { + "ID": "18788457-584c-4812-80d3-23d403148a90", + "Name": "bywater" + } + ], + "Roles": [ + { + "ID": "3b0a78fe-b9c3-40de-b8ea-7d4d6674b366", + "Name": "shire" + }, + { + "ID": "6c9d1e1d-34bc-4d55-80f3-add0890ad791", + "Name": "west-farthing" + } + ], + "ServiceIdentities": [ + { + "ServiceName": "gardener", + "Datacenters": [ + "middleearth-northwest" + ] + } + ], + "NodeIdentities": [ + { + "NodeName": "bagend", + "Datacenter": "middleearth-northwest" + } + ], + "Local": false, + "AuthMethod": "bar", + "ExpirationTime": "2020-05-22T19:52:31Z", + "CreateTime": "2020-05-22T18:52:31Z", + "Hash": "YWJjZGVmZ2g=", + "Legacy": false, + "Namespace": "foo" + } +] \ No newline at end of file diff --git a/command/acl/token/testdata/FormatTokenList/complex.pretty-meta.golden b/command/acl/token/testdata/FormatTokenList/complex.pretty-meta.golden new file mode 100644 index 0000000000..17cd912438 --- /dev/null +++ b/command/acl/token/testdata/FormatTokenList/complex.pretty-meta.golden @@ -0,0 +1,21 @@ +AccessorID: fbd2447f-7479-4329-ad13-b021d74f86ba +Namespace: foo +Description: test token +Local: false +Auth Method: bar +Create Time: 2020-05-22 18:52:31 +0000 UTC +Expiration Time: 2020-05-22 19:52:31 +0000 UTC +Legacy: false +Hash: 6162636465666768 +Create Index: 5 +Modify Index: 10 +Policies: + beb04680-815b-4d7c-9e33-3d707c24672c - hobbiton + 18788457-584c-4812-80d3-23d403148a90 - bywater +Roles: + 3b0a78fe-b9c3-40de-b8ea-7d4d6674b366 - shire + 6c9d1e1d-34bc-4d55-80f3-add0890ad791 - west-farthing +Service Identities: + gardener (Datacenters: middleearth-northwest) +Service Identities: + gardener (Datacenters: middleearth-northwest) diff --git a/command/acl/token/testdata/FormatTokenList/complex.pretty.golden b/command/acl/token/testdata/FormatTokenList/complex.pretty.golden new file mode 100644 index 0000000000..e6965f33b5 --- /dev/null +++ b/command/acl/token/testdata/FormatTokenList/complex.pretty.golden @@ -0,0 +1,18 @@ +AccessorID: fbd2447f-7479-4329-ad13-b021d74f86ba +Namespace: foo +Description: test token +Local: false +Auth Method: bar +Create Time: 2020-05-22 18:52:31 +0000 UTC +Expiration Time: 2020-05-22 19:52:31 +0000 UTC +Legacy: false +Policies: + beb04680-815b-4d7c-9e33-3d707c24672c - hobbiton + 18788457-584c-4812-80d3-23d403148a90 - bywater +Roles: + 3b0a78fe-b9c3-40de-b8ea-7d4d6674b366 - shire + 6c9d1e1d-34bc-4d55-80f3-add0890ad791 - west-farthing +Service Identities: + gardener (Datacenters: middleearth-northwest) +Service Identities: + gardener (Datacenters: middleearth-northwest) diff --git a/command/acl/token/testdata/FormatTokenList/legacy.json.golden b/command/acl/token/testdata/FormatTokenList/legacy.json.golden new file mode 100644 index 0000000000..d639b80252 --- /dev/null +++ b/command/acl/token/testdata/FormatTokenList/legacy.json.golden @@ -0,0 +1,12 @@ +[ + { + "CreateIndex": 0, + "ModifyIndex": 0, + "AccessorID": "8acc7486-ca54-4d3c-9aed-5cd85651b0ee", + "Description": "legacy", + "Local": false, + "CreateTime": "0001-01-01T00:00:00Z", + "Hash": null, + "Legacy": true + } +] \ No newline at end of file diff --git a/command/acl/token/testdata/FormatTokenList/legacy.pretty-meta.golden b/command/acl/token/testdata/FormatTokenList/legacy.pretty-meta.golden new file mode 100644 index 0000000000..8553219006 --- /dev/null +++ b/command/acl/token/testdata/FormatTokenList/legacy.pretty-meta.golden @@ -0,0 +1,8 @@ +AccessorID: 8acc7486-ca54-4d3c-9aed-5cd85651b0ee +Description: legacy +Local: false +Create Time: 0001-01-01 00:00:00 +0000 UTC +Legacy: true +Hash: +Create Index: 0 +Modify Index: 0 diff --git a/command/acl/token/testdata/FormatTokenList/legacy.pretty.golden b/command/acl/token/testdata/FormatTokenList/legacy.pretty.golden new file mode 100644 index 0000000000..ee2a5299b5 --- /dev/null +++ b/command/acl/token/testdata/FormatTokenList/legacy.pretty.golden @@ -0,0 +1,5 @@ +AccessorID: 8acc7486-ca54-4d3c-9aed-5cd85651b0ee +Description: legacy +Local: false +Create Time: 0001-01-01 00:00:00 +0000 UTC +Legacy: true diff --git a/command/acl/token/update/token_update.go b/command/acl/token/update/token_update.go index 5e2c2224cd..2a5592ab30 100644 --- a/command/acl/token/update/token_update.go +++ b/command/acl/token/update/token_update.go @@ -30,10 +30,12 @@ type cmd struct { roleIDs []string roleNames []string serviceIdents []string + nodeIdents []string description string mergePolicies bool mergeRoles bool mergeServiceIdents bool + mergeNodeIdents bool showMeta bool upgradeLegacy bool format string @@ -49,6 +51,8 @@ func (c *cmd) init() { "with the existing roles") c.flags.BoolVar(&c.mergeServiceIdents, "merge-service-identities", false, "Merge the new service identities "+ "with the existing service identities") + c.flags.BoolVar(&c.mergeNodeIdents, "merge-node-identities", false, "Merge the new node identities "+ + "with the existing node identities") c.flags.StringVar(&c.tokenID, "id", "", "The Accessor ID of the token to update. "+ "It may be specified as a unique ID prefix but will error if the prefix "+ "matches multiple token Accessor IDs") @@ -64,6 +68,9 @@ func (c *cmd) init() { c.flags.Var((*flags.AppendSliceValue)(&c.serviceIdents), "service-identity", "Name of a "+ "service identity to use for this token. May be specified multiple times. Format is "+ "the SERVICENAME or SERVICENAME:DATACENTER1,DATACENTER2,...") + c.flags.Var((*flags.AppendSliceValue)(&c.nodeIdents), "node-identity", "Name of a "+ + "node identity to use for this token. May be specified multiple times. Format is "+ + "NODENAME:DATACENTER") c.flags.BoolVar(&c.upgradeLegacy, "upgrade-legacy", false, "Add new polices "+ "to a legacy token replacing all existing rules. This will cause the legacy "+ "token to behave exactly like a new token but keep the same Secret.\n"+ @@ -139,6 +146,12 @@ func (c *cmd) Run(args []string) int { return 1 } + parsedNodeIdents, err := acl.ExtractNodeIdentities(c.nodeIdents) + if err != nil { + c.UI.Error(err.Error()) + return 1 + } + if c.mergePolicies { for _, policyName := range c.policyNames { found := false @@ -269,6 +282,24 @@ func (c *cmd) Run(args []string) int { t.ServiceIdentities = parsedServiceIdents } + if c.mergeNodeIdents { + for _, nodeid := range parsedNodeIdents { + found := false + for _, link := range t.NodeIdentities { + if link.NodeName == nodeid.NodeName && link.Datacenter == nodeid.Datacenter { + found = true + break + } + } + + if !found { + t.NodeIdentities = append(t.NodeIdentities, nodeid) + } + } + } else { + t.NodeIdentities = parsedNodeIdents + } + t, _, err = client.ACL().TokenUpdate(t, nil) if err != nil { c.UI.Error(fmt.Sprintf("Failed to update token %s: %v", tokenID, err)) diff --git a/command/acl/token/update/token_update_test.go b/command/acl/token/update/token_update_test.go index c4881af572..2877bc61ad 100644 --- a/command/acl/token/update/token_update_test.go +++ b/command/acl/token/update/token_update_test.go @@ -6,8 +6,6 @@ import ( "strings" "testing" - "github.com/stretchr/testify/require" - "github.com/hashicorp/consul/agent" "github.com/hashicorp/consul/api" "github.com/hashicorp/consul/sdk/testutil" @@ -15,6 +13,7 @@ import ( "github.com/hashicorp/consul/testrpc" "github.com/mitchellh/cli" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestTokenUpdateCommand_noTabs(t *testing.T) { @@ -27,9 +26,6 @@ func TestTokenUpdateCommand_noTabs(t *testing.T) { func TestTokenUpdateCommand(t *testing.T) { t.Parallel() - assert := assert.New(t) - // Alias because we need to access require package in Retry below - req := require.New(t) testDir := testutil.TempDir(t, "acl") defer os.RemoveAll(testDir) @@ -46,8 +42,6 @@ func TestTokenUpdateCommand(t *testing.T) { defer a.Shutdown() testrpc.WaitForLeader(t, a.RPC, "dc1") - ui := cli.NewMockUi() - // Create a policy client := a.Client() @@ -55,16 +49,17 @@ func TestTokenUpdateCommand(t *testing.T) { &api.ACLPolicy{Name: "test-policy"}, &api.WriteOptions{Token: "root"}, ) - req.NoError(err) + require.NoError(t, err) // create a token token, _, err := client.ACL().TokenCreate( &api.ACLToken{Description: "test"}, &api.WriteOptions{Token: "root"}, ) - req.NoError(err) + require.NoError(t, err) - // nolint: staticcheck // we want the deprecated legacy token + // create a legacy token + // nolint: staticcheck // we have to use the deprecated API to create a legacy token legacyTokenSecretID, _, err := client.ACL().Create(&api.ACLEntry{ Name: "Legacy token", Type: "client", @@ -72,79 +67,100 @@ func TestTokenUpdateCommand(t *testing.T) { }, &api.WriteOptions{Token: "root"}, ) - req.NoError(err) + require.NoError(t, err) // We fetch the legacy token later to give server time to async background // upgrade it. - // update with policy by name - { + run := func(t *testing.T, args []string) *api.ACLToken { + ui := cli.NewMockUi() cmd := New(ui) - args := []string{ + + code := cmd.Run(append(args, "-format=json")) + require.Equal(t, 0, code) + require.Empty(t, ui.ErrorWriter.String()) + + var token api.ACLToken + require.NoError(t, json.Unmarshal(ui.OutputWriter.Bytes(), &token)) + return &token + } + + // update with node identity + t.Run("node-identity", func(t *testing.T) { + token := run(t, []string{ + "-http-addr=" + a.HTTPAddr(), + "-id=" + token.AccessorID, + "-token=root", + "-node-identity=foo:bar", + "-description=test token", + }) + + require.Len(t, token.NodeIdentities, 1) + require.Equal(t, "foo", token.NodeIdentities[0].NodeName) + require.Equal(t, "bar", token.NodeIdentities[0].Datacenter) + }) + + t.Run("node-identity-merge", func(t *testing.T) { + token := run(t, []string{ + "-http-addr=" + a.HTTPAddr(), + "-id=" + token.AccessorID, + "-token=root", + "-node-identity=bar:baz", + "-description=test token", + "-merge-node-identities", + }) + + require.Len(t, token.NodeIdentities, 2) + expected := []*api.ACLNodeIdentity{ + &api.ACLNodeIdentity{ + NodeName: "foo", + Datacenter: "bar", + }, + &api.ACLNodeIdentity{ + NodeName: "bar", + Datacenter: "baz", + }, + } + require.ElementsMatch(t, expected, token.NodeIdentities) + }) + + // update with policy by name + t.Run("policy-name", func(t *testing.T) { + token := run(t, []string{ "-http-addr=" + a.HTTPAddr(), "-id=" + token.AccessorID, "-token=root", "-policy-name=" + policy.Name, "-description=test token", - } + }) - code := cmd.Run(args) - assert.Equal(code, 0) - assert.Empty(ui.ErrorWriter.String()) - - token, _, err := client.ACL().TokenRead( - token.AccessorID, - &api.QueryOptions{Token: "root"}, - ) - assert.NoError(err) - assert.NotNil(token) - } + require.Len(t, token.Policies, 1) + }) // update with policy by id - { - cmd := New(ui) - args := []string{ + t.Run("policy-id", func(t *testing.T) { + token := run(t, []string{ "-http-addr=" + a.HTTPAddr(), "-id=" + token.AccessorID, "-token=root", "-policy-id=" + policy.ID, "-description=test token", - } + }) - code := cmd.Run(args) - assert.Equal(code, 0) - assert.Empty(ui.ErrorWriter.String()) - - token, _, err := client.ACL().TokenRead( - token.AccessorID, - &api.QueryOptions{Token: "root"}, - ) - assert.NoError(err) - assert.NotNil(token) - } + require.Len(t, token.Policies, 1) + }) // update with no description shouldn't delete the current description - { - cmd := New(ui) - args := []string{ + t.Run("merge-description", func(t *testing.T) { + token := run(t, []string{ "-http-addr=" + a.HTTPAddr(), "-id=" + token.AccessorID, "-token=root", "-policy-name=" + policy.Name, - } + }) - code := cmd.Run(args) - assert.Equal(code, 0) - assert.Empty(ui.ErrorWriter.String()) - - token, _, err := client.ACL().TokenRead( - token.AccessorID, - &api.QueryOptions{Token: "root"}, - ) - assert.NoError(err) - assert.NotNil(token) - assert.Equal("test token", token.Description) - } + require.Equal(t, "test token", token.Description) + }) // Need legacy token now, hopefully server had time to generate an accessor ID // in the background but wait for it if not. @@ -153,39 +169,28 @@ func TestTokenUpdateCommand(t *testing.T) { // Fetch the legacy token via new API so we can use it's accessor ID legacyToken, _, err = client.ACL().TokenReadSelf( &api.QueryOptions{Token: legacyTokenSecretID}) - r.Check(err) + require.NoError(r, err) require.NotEmpty(r, legacyToken.AccessorID) }) // upgrade legacy token should replace rules and leave token in a "new" state! - { - cmd := New(ui) - args := []string{ + t.Run("legacy-upgrade", func(t *testing.T) { + token := run(t, []string{ "-http-addr=" + a.HTTPAddr(), "-id=" + legacyToken.AccessorID, "-token=root", "-policy-name=" + policy.Name, "-upgrade-legacy", - } + }) - code := cmd.Run(args) - assert.Equal(code, 0) - assert.Empty(ui.ErrorWriter.String()) - - gotToken, _, err := client.ACL().TokenRead( - legacyToken.AccessorID, - &api.QueryOptions{Token: "root"}, - ) - assert.NoError(err) - assert.NotNil(gotToken) // Description shouldn't change - assert.Equal("Legacy token", gotToken.Description) - assert.Len(gotToken.Policies, 1) + require.Equal(t, "Legacy token", token.Description) + require.Len(t, token.Policies, 1) // Rules should now be empty meaning this is no longer a legacy token - assert.Empty(gotToken.Rules) + require.Empty(t, token.Rules) // Secret should not have changes - assert.Equal(legacyToken.SecretID, gotToken.SecretID) - } + require.Equal(t, legacyToken.SecretID, token.SecretID) + }) } func TestTokenUpdateCommand_JSON(t *testing.T) { diff --git a/website/pages/api-docs/acl/binding-rules.mdx b/website/pages/api-docs/acl/binding-rules.mdx index c02af9542f..a896e73d7b 100644 --- a/website/pages/api-docs/acl/binding-rules.mdx +++ b/website/pages/api-docs/acl/binding-rules.mdx @@ -63,6 +63,17 @@ The table below shows this endpoint's support for ] } ``` + + - `BindType=node` - The computed bind name value is used as an + `ACLNodeIdentity.NodeName` field in the token that is created. + + ```json + { ...other fields... + "NodeIdentities": [ + { "NodeName": "", "Datacenter": "" } + ] + } + ``` - `BindType=role` - The computed bind name value is used as a `RoleLink.Name` field in the token that is created. This binding rule will only apply if a @@ -232,7 +243,18 @@ The table below shows this endpoint's support for ] } ``` + + - `BindType=node` - The computed bind name value is used as an + `ACLNodeIdentity.NodeName` field in the token that is created. + ```json + { ...other fields... + "NodeIdentities": [ + { "NodeName": "", "Datacenter": "" } + ] + } + ``` + - `BindType=role` - The computed bind name value is used as a `RoleLink.Name` field in the token that is created. This binding rule will only apply if a role with the given name exists at login-time. If it does not then this @@ -394,6 +416,7 @@ $ curl -X GET http://127.0.0.1:8500/v1/acl/binding-rules "ID": "b4f0a0a3-69f2-7a4f-6bef-326034ace9fa", "Description": "example 2", "AuthMethod": "minikube-2", + "BindType": "service", "Selector": "serviceaccount.namespace==default", "BindName": "k8s-{{ serviceaccount.name }}", "CreateIndex": 18, diff --git a/website/pages/api-docs/acl/roles.mdx b/website/pages/api-docs/acl/roles.mdx index 0b4797970c..544633fd08 100644 --- a/website/pages/api-docs/acl/roles.mdx +++ b/website/pages/api-docs/acl/roles.mdx @@ -62,6 +62,18 @@ The table below shows this endpoint's support for policy is valid in all datacenters including those which do not yet exist but may in the future. +- `NodeIdentities` `(array)` - The list of [node + identities](/docs/acl/acl-system#acl-node-identities) that should be + applied to the role. Added in Consul 1.8.1. + + - `NodeName` `(string: )` - The name of the node. The name + must be no longer than 256 characters, must start and end with a lowercase + alphanumeric character, and can only contain lowercase alphanumeric + characters as well as `-` and `_`. + + - `Datacenter` `(string: )` - Specifies the nodes datacenter. This + will result in effective policy only being valid in that datacenter. + - `Namespace` `(string: "")` - Specifies the namespace to create the role. If not provided in the JSON body, the value of the `ns` URL query parameter or in the `X-Consul-Namespace` header will be used. @@ -90,6 +102,12 @@ The table below shows this endpoint's support for "ServiceName": "db", "Datacenters": ["dc1"] } + ], + "NodeIdentities": [ + { + "NodeName": "node-1", + "Datacenter": "dc2" + } ] } ``` @@ -124,6 +142,12 @@ $ curl -X PUT \ "Datacenters": ["dc1"] } ], + "NodeIdentities": [ + { + "NodeName": "node-1", + "Datacenter": "dc2" + } + ], "Hash": "mBWMIeX9zyUTdDMq8vWB0iYod+mKBArJoAhj6oPz3BI=", "CreateIndex": 57, "ModifyIndex": 57 @@ -188,6 +212,12 @@ $ curl -X GET http://127.0.0.1:8500/v1/acl/role/aa770e5b-8b0b-7fcf-e5a1-8535fcc3 "Datacenters": ["dc1"] } ], + "NodeIdentities": [ + { + "NodeName": "node-1", + "Datacenter": "dc2" + } + ], "Hash": "mBWMIeX9zyUTdDMq8vWB0iYod+mKBArJoAhj6oPz3BI=", "CreateIndex": 57, "ModifyIndex": 57 @@ -252,6 +282,12 @@ $ curl -X GET http://127.0.0.1:8500/v1/acl/role/name/example-role "Datacenters": ["dc1"] } ], + "NodeIdentities": [ + { + "NodeName": "node-1", + "Datacenter": "dc2" + } + ], "Hash": "mBWMIeX9zyUTdDMq8vWB0iYod+mKBArJoAhj6oPz3BI=", "CreateIndex": 57, "ModifyIndex": 57 @@ -299,6 +335,10 @@ The table below shows this endpoint's support for identities](/docs/acl/acl-system#acl-service-identities) that should be applied to the role. Added in Consul 1.5.0. +- `NodeIdentities` `(array)` - The list of [node + identities](/docs/acl/acl-system#acl-node-identities) that should be + applied to the role. Added in Consul 1.8.1. + - `Namespace` `(string: "")` - Specifies the namespace of the role to update. If not provided in the JSON body, the value of the `ns` URL query parameter or in the `X-Consul-Namespace` header will be used. @@ -320,6 +360,12 @@ The table below shows this endpoint's support for { "ServiceName": "db" } + ], + "NodeIdentities": [ + { + "NodeName": "node-1", + "Datacenter": "dc2" + } ] } ``` @@ -349,6 +395,12 @@ $ curl -X PUT \ "ServiceName": "db" } ], + "NodeIdentities": [ + { + "NodeName": "node-1", + "Datacenter": "dc2" + } + ], "Hash": "OtZUUKhInTLEqTPfNSSOYbRiSBKm3c4vI2p6MxZnGWc=", "CreateIndex": 14, "ModifyIndex": 28 @@ -475,6 +527,12 @@ $ curl -X GET http://127.0.0.1:8500/v1/acl/roles "Datacenters": ["dc1"] } ], + "NodeIdentities": [ + { + "NodeName": "node-1", + "Datacenter": "dc2" + } + ], "Hash": "mBWMIeX9zyUTdDMq8vWB0iYod+mKBArJoAhj6oPz3BI=", "CreateIndex": 57, "ModifyIndex": 57 diff --git a/website/pages/api-docs/acl/tokens.mdx b/website/pages/api-docs/acl/tokens.mdx index d49d5ef883..5e77a3e2b0 100644 --- a/website/pages/api-docs/acl/tokens.mdx +++ b/website/pages/api-docs/acl/tokens.mdx @@ -74,6 +74,18 @@ The table below shows this endpoint's support for policy is valid in all datacenters including those which do not yet exist but may in the future. +- `NodeIdentities` `(array)` - The list of [node + identities](/docs/acl/acl-system#acl-node-identities) that should be + applied to the token. Added in Consul 1.8.1. + + - `NodeName` `(string: )` - The name of the node. The name + must be no longer than 256 characters, must start and end with a lowercase + alphanumeric character, and can only contain lowercase alphanumeric + characters as well as `-` and `_`. + + - `Datacenter` `(string: )` - Specifies the nodes datacenter. This + will result in effective policy only being valid in that datacenter. + - `Local` `(bool: false)` - If true, indicates that the token should not be replicated globally and instead be local to the current datacenter. @@ -323,6 +335,18 @@ The table below shows this endpoint's support for policy is valid in all datacenters including those which do not yet exist but may in the future. +- `NodeIdentities` `(array)` - The list of [node + identities](/docs/acl/acl-system#acl-node-identities) that should be + applied to the token. Added in Consul 1.8.1. + + - `NodeName` `(string: )` - The name of the node. The name + must be no longer than 256 characters, must start and end with a lowercase + alphanumeric character, and can only contain lowercase alphanumeric + characters as well as `-` and `_`. + + - `Datacenter` `(string: )` - Specifies the nodes datacenter. This + will result in effective policy only being valid in that datacenter. + - `Local` `(bool: false)` - If true, indicates that this token should not be replicated globally and instead be local to the current datacenter. This value must match the existing value or the request will return an error. diff --git a/website/pages/docs/acl/acl-system.mdx b/website/pages/docs/acl/acl-system.mdx index 04ab4441f2..3d384718f9 100644 --- a/website/pages/docs/acl/acl-system.mdx +++ b/website/pages/docs/acl/acl-system.mdx @@ -45,6 +45,13 @@ may benefit from additional components in the ACL system: additional policy was attached, the contents of which are described further below. These are directly attached to tokens and roles and are not independently configured. (Added in Consul 1.5.0) + +- **ACL Node Identities** - Node identities are a policy template for + expressing a link to a policy suitable for use as an [Consul `agent` token + ](/docs/agent/options#acl_tokens_agent). At authorization time this acts like an + additional policy was attached, the contents of which are described further + below. These are directly attached to tokens and roles and are not + independently configured. (Added in Consul 1.8.1) - **ACL Auth Methods and Binding Rules** - To learn more about these topics, see the [auth methods documentation page](/docs/acl/auth-methods). @@ -123,6 +130,38 @@ examples of using a service identity. -> **Consul Enterprise Namespacing** - Service Identity rules will be scoped to the single namespace that the corresponding ACL Token or Role resides within. +### ACL Node Identities + +-> Added in Consul 1.8.1 + +An ACL node identity is an [ACL policy](/docs/acl/acl-system#acl-policies) template for expressing a link to a policy +suitable for use as an [Consul `agent` token](/docs/agent/options#acl_tokens_agent). They are usable +on both tokens and roles and are composed of the following elements: + +- **Node Name** - The name of the node to grant access to. +- **Datacenter** - The datacenter that the node resides within. + +During the authorization process, the configured node identity is automatically +applied as a policy with the following preconfigured [ACL +rules](/docs/acl/acl-system#acl-rules-and-scope): + +```hcl +# Allow the agent to register its own node in the Catalog and update its network coordinates +node "" { + policy = "write" +} + +# Allows the agent to detect and diff services registered to itself. This is used during +# anti-entropy to reconcile difference between the agents knowledge of registered +# services and checks in comparison with what is known in the Catalog. +service_prefix "" { + policy = "read" +} +``` + +-> **Consul Enterprise Namespacing** - Node Identities can only be applied to tokens and roles in the `default` namespace. +The synthetic policy rules allow for `service:read` permissions on all services in all namespaces. + ### ACL Roles -> Added in Consul 1.5.0