1package state 2 3import ( 4 "strconv" 5 "strings" 6 "testing" 7 8 "github.com/hashicorp/consul/agent/consul/stream" 9 "github.com/hashicorp/consul/agent/structs" 10 "github.com/stretchr/testify/require" 11) 12 13func TestACLChangeUnsubscribeEvent(t *testing.T) { 14 cases := []struct { 15 Name string 16 Setup func(tx *txn) error 17 Mutate func(tx *txn) error 18 expected stream.Event 19 }{ 20 { 21 Name: "token create", 22 Mutate: func(tx *txn) error { 23 return aclTokenSetTxn(tx, tx.Index, newACLToken(1), ACLTokenSetOptions{}) 24 }, 25 expected: stream.NewCloseSubscriptionEvent(newSecretIDs(1)), 26 }, 27 { 28 Name: "token update", 29 Setup: func(tx *txn) error { 30 return aclTokenSetTxn(tx, tx.Index, newACLToken(1), ACLTokenSetOptions{}) 31 }, 32 Mutate: func(tx *txn) error { 33 // Add a policy to the token (never mind it doesn't exist for now) we 34 // allow it in the set command below. 35 token := newACLToken(1) 36 token.Policies = []structs.ACLTokenPolicyLink{{ID: "33333333-1111-1111-1111-111111111111"}} 37 return aclTokenSetTxn(tx, tx.Index, token, ACLTokenSetOptions{AllowMissingPolicyAndRoleIDs: true}) 38 }, 39 expected: stream.NewCloseSubscriptionEvent(newSecretIDs(1)), 40 }, 41 { 42 Name: "token delete", 43 Setup: func(tx *txn) error { 44 return aclTokenSetTxn(tx, tx.Index, newACLToken(1), ACLTokenSetOptions{}) 45 }, 46 Mutate: func(tx *txn) error { 47 token := newACLToken(1) 48 return aclTokenDeleteTxn(tx, tx.Index, token.AccessorID, "id", nil) 49 }, 50 expected: stream.NewCloseSubscriptionEvent(newSecretIDs(1)), 51 }, 52 { 53 Name: "policy create", 54 Mutate: newACLPolicyWithSingleToken, 55 // two identical tokens, because Mutate has two changes 56 expected: stream.NewCloseSubscriptionEvent(newSecretIDs(1, 1)), 57 }, 58 { 59 Name: "policy update", 60 Setup: newACLPolicyWithSingleToken, 61 Mutate: func(tx *txn) error { 62 policy := newACLPolicy(1) 63 policy.Rules = `operator = "write"` 64 return aclPolicySetTxn(tx, tx.Index, policy) 65 }, 66 expected: stream.NewCloseSubscriptionEvent(newSecretIDs(1)), 67 }, 68 { 69 Name: "policy delete", 70 Setup: newACLPolicyWithSingleToken, 71 Mutate: func(tx *txn) error { 72 policy := newACLPolicy(1) 73 return aclPolicyDeleteTxn(tx, tx.Index, policy.ID, aclPolicyGetByID, nil) 74 }, 75 expected: stream.NewCloseSubscriptionEvent(newSecretIDs(1)), 76 }, 77 { 78 Name: "role create", 79 Mutate: newACLRoleWithSingleToken, 80 // Two tokens with the same ID, because there are two changes in Mutate 81 expected: stream.NewCloseSubscriptionEvent(newSecretIDs(1, 1)), 82 }, 83 { 84 Name: "role update", 85 Setup: newACLRoleWithSingleToken, 86 Mutate: func(tx *txn) error { 87 role := newACLRole(1, newACLRolePolicyLink(1)) 88 policy2 := newACLPolicy(2) 89 role.Policies = append(role.Policies, structs.ACLRolePolicyLink{ 90 ID: policy2.ID, 91 Name: policy2.Name, 92 }) 93 return aclRoleSetTxn(tx, tx.Index, role, true) 94 }, 95 expected: stream.NewCloseSubscriptionEvent(newSecretIDs(1)), 96 }, 97 { 98 Name: "role delete", 99 Setup: newACLRoleWithSingleToken, 100 Mutate: func(tx *txn) error { 101 role := newACLRole(1, newACLRolePolicyLink(1)) 102 return aclRoleDeleteTxn(tx, tx.Index, role.ID, aclRoleGetByID, nil) 103 }, 104 expected: stream.NewCloseSubscriptionEvent(newSecretIDs(1)), 105 }, 106 } 107 108 for _, tc := range cases { 109 tc := tc 110 t.Run(tc.Name, func(t *testing.T) { 111 s := testStateStore(t) 112 113 if tc.Setup != nil { 114 // Bypass the publish mechanism for this test or we get into odd 115 // recursive stuff... 116 setupTx := s.db.WriteTxn(10) 117 require.NoError(t, tc.Setup(setupTx)) 118 // Commit the underlying transaction without using wrapped Commit so we 119 // avoid the whole event publishing system for setup here. It _should_ 120 // work but it makes debugging test hard as it will call the function 121 // under test for the setup data... 122 setupTx.Txn.Commit() 123 } 124 125 tx := s.db.WriteTxn(100) 126 require.NoError(t, tc.Mutate(tx)) 127 128 // Note we call the func under test directly rather than publishChanges so 129 // we can test this in isolation. 130 events, err := aclChangeUnsubscribeEvent(tx, Changes{Index: 100, Changes: tx.Changes()}) 131 require.NoError(t, err) 132 133 require.Len(t, events, 1) 134 actual := events[0] 135 require.Equal(t, tc.expected, actual) 136 }) 137 } 138} 139 140func newACLRoleWithSingleToken(tx *txn) error { 141 role := newACLRole(1, newACLRolePolicyLink(1)) 142 if err := aclRoleSetTxn(tx, tx.Index, role, true); err != nil { 143 return err 144 } 145 token := newACLToken(1) 146 token.Roles = append(token.Roles, structs.ACLTokenRoleLink{ID: role.ID}) 147 return aclTokenSetTxn(tx, tx.Index, token, ACLTokenSetOptions{}) 148} 149 150func newACLPolicyWithSingleToken(tx *txn) error { 151 policy := newACLPolicy(1) 152 if err := aclPolicySetTxn(tx, tx.Index, policy); err != nil { 153 return err 154 } 155 token := newACLToken(1) 156 token.Policies = append(token.Policies, structs.ACLTokenPolicyLink{ID: policy.ID}) 157 return aclTokenSetTxn(tx, tx.Index, token, ACLTokenSetOptions{}) 158} 159 160func newSecretIDs(ids ...int) []string { 161 result := make([]string, 0, len(ids)) 162 for _, id := range ids { 163 uuid := strings.ReplaceAll("11111111-????-????-????-????????????", "?", strconv.Itoa(id)) 164 result = append(result, uuid) 165 } 166 return result 167} 168 169func newACLToken(n int) *structs.ACLToken { 170 uuid := strings.ReplaceAll("11111111-????-????-????-????????????", "?", strconv.Itoa(n)) 171 return &structs.ACLToken{ 172 AccessorID: uuid, 173 SecretID: uuid, 174 } 175} 176 177func newACLPolicy(n int) *structs.ACLPolicy { 178 numStr := strconv.Itoa(n) 179 uuid := strings.ReplaceAll("22222222-????-????-????-????????????", "?", numStr) 180 return &structs.ACLPolicy{ 181 ID: uuid, 182 Name: "test_policy_" + numStr, 183 Rules: `operator = "read"`, 184 } 185} 186 187func newACLRole(n int, policies ...structs.ACLRolePolicyLink) *structs.ACLRole { 188 numStr := strconv.Itoa(n) 189 uuid := strings.ReplaceAll("33333333-????-????-????-????????????", "?", numStr) 190 return &structs.ACLRole{ 191 ID: uuid, 192 Name: "test_role_" + numStr, 193 Policies: policies, 194 } 195} 196 197func newACLRolePolicyLink(n int) structs.ACLRolePolicyLink { 198 policy := newACLPolicy(n) 199 return structs.ACLRolePolicyLink{ 200 ID: policy.ID, 201 Name: policy.Name, 202 } 203} 204