1package consul 2 3import ( 4 "errors" 5 "sync" 6 "time" 7 8 "github.com/hashicorp/consul/api" 9 "github.com/hashicorp/go-hclog" 10 "github.com/hashicorp/nomad/helper/uuid" 11) 12 13var _ ACLsAPI = (*MockACLsAPI)(nil) 14 15// MockACLsAPI is a mock of consul.ACLsAPI 16type MockACLsAPI struct { 17 logger hclog.Logger 18 19 lock sync.Mutex 20 state struct { 21 index uint64 22 error error 23 tokens map[string]*api.ACLToken 24 } 25} 26 27func NewMockACLsAPI(l hclog.Logger) *MockACLsAPI { 28 return &MockACLsAPI{ 29 logger: l.Named("mock_consul"), 30 state: struct { 31 index uint64 32 error error 33 tokens map[string]*api.ACLToken 34 }{tokens: make(map[string]*api.ACLToken)}, 35 } 36} 37 38// Example Consul policies for use in tests. 39const ( 40 ExamplePolicyID1 = "a7c86856-0af5-4ab5-8834-03f4517e5564" 41 ExamplePolicyID2 = "ffa1b66c-967d-4468-8775-c687b5cfc16e" 42 ExamplePolicyID3 = "f68f0c36-51f8-4343-97dd-f0d4816c915f" 43 ExamplePolicyID4 = "1087ff34-b8a0-9bb3-9430-d2f758f52bd3" 44) 45 46func (m *MockACLsAPI) PolicyRead(policyID string, _ *api.QueryOptions) (*api.ACLPolicy, *api.QueryMeta, error) { 47 switch policyID { 48 49 case ExamplePolicyID1: 50 return &api.ACLPolicy{ 51 ID: ExamplePolicyID1, 52 Name: "example-policy-1", 53 Rules: `service "service1" { policy = "write" }`, 54 }, nil, nil 55 56 case ExamplePolicyID2: 57 return &api.ACLPolicy{ 58 ID: ExamplePolicyID2, 59 Rules: `service_prefix "foo-" { policy = "write" }`, 60 }, nil, nil 61 62 case ExamplePolicyID3: 63 return &api.ACLPolicy{ 64 ID: ExamplePolicyID3, 65 Rules: ` 66service "service1" { policy = "read" } 67service "service2" { policy = "write" }`, 68 }, nil, nil 69 70 case ExamplePolicyID4: 71 return &api.ACLPolicy{ 72 ID: ExamplePolicyID4, 73 Rules: `key_prefix "" { policy = "read" }`, 74 }, nil, nil 75 76 default: 77 return nil, nil, errors.New("no such policy") 78 } 79} 80 81// Example Consul roles for use in tests. 82const ( 83 ExampleRoleID1 = "e569a3a8-7dfb-b024-e492-e790fe3c4183" 84 ExampleRoleID2 = "88c825f4-d0da-1c2b-0c1c-cc9fe84c4468" 85 ExampleRoleID3 = "b19b2058-6205-6dff-d2b0-470f29b8e627" 86) 87 88func (m *MockACLsAPI) RoleRead(roleID string, _ *api.QueryOptions) (*api.ACLRole, *api.QueryMeta, error) { 89 switch roleID { 90 case ExampleRoleID1: 91 return &api.ACLRole{ 92 ID: ExampleRoleID1, 93 Name: "example-role-1", 94 Policies: []*api.ACLRolePolicyLink{{ 95 ID: ExamplePolicyID1, 96 Name: "example-policy-1", 97 }}, 98 ServiceIdentities: nil, 99 }, nil, nil 100 case ExampleRoleID2: 101 return &api.ACLRole{ 102 ID: ExampleRoleID2, 103 Name: "example-role-2", 104 Policies: []*api.ACLRolePolicyLink{{ 105 ID: ExamplePolicyID2, 106 Name: "example-policy-2", 107 }}, 108 ServiceIdentities: nil, 109 }, nil, nil 110 case ExampleRoleID3: 111 return &api.ACLRole{ 112 ID: ExampleRoleID3, 113 Name: "example-role-3", 114 Policies: nil, // todo add more if needed 115 ServiceIdentities: nil, // todo add more if needed 116 }, nil, nil 117 default: 118 return nil, nil, nil 119 } 120} 121 122// Example Consul ACL tokens for use in tests. These tokens belong to the 123// default Consul namespace. 124const ( 125 ExampleOperatorTokenID0 = "de591604-86eb-1e6f-8b44-d4db752921ae" 126 ExampleOperatorTokenID1 = "59c219c2-47e4-43f3-bb45-258fd13f59d5" 127 ExampleOperatorTokenID2 = "868cc216-e123-4c2b-b362-f4d4c087de8e" 128 ExampleOperatorTokenID3 = "6177d1b9-c0f6-4118-b891-d818a3cb80b1" 129 ExampleOperatorTokenID4 = "754ae26c-f3cc-e088-d486-9c0d20f5eaea" 130 ExampleOperatorTokenID5 = "097cbb45-506b-c79c-ec38-82eb0dc0794a" 131) 132 133// Example Consul ACL tokens for use in tests that match the policies as the 134// tokens above, but these belong to the "banana' Consul namespace. 135const ( 136 ExampleOperatorTokenID10 = "ddfe688f-655f-e8dd-1db5-5650eed00aeb" 137 ExampleOperatorTokenID11 = "46d09394-598c-1e55-b7fd-64cd2f409707" 138 ExampleOperatorTokenID12 = "a041cb88-0f4b-0314-89f6-10e1e093d2e5" 139 ExampleOperatorTokenID13 = "cc22a583-243f-3258-14ad-db0e56749657" 140 ExampleOperatorTokenID14 = "5b6d0508-13a6-4bc3-33a1-ba1941e1175b" 141 ExampleOperatorTokenID15 = "e9db1754-c075-d0fc-0a7e-de1e9e7bff98" 142) 143 144var ( 145 // In Consul namespace "default" 146 147 ExampleOperatorToken0 = &api.ACLToken{ 148 SecretID: ExampleOperatorTokenID0, 149 AccessorID: "228865c6-3bf6-6683-df03-06dea2779088 ", 150 Description: "Operator Token 0", 151 Namespace: "default", 152 } 153 154 ExampleOperatorToken1 = &api.ACLToken{ 155 SecretID: ExampleOperatorTokenID1, 156 AccessorID: "e341bacd-535e-417c-8f45-f88d7faffcaf", 157 Description: "Operator Token 1", 158 Policies: []*api.ACLTokenPolicyLink{{ 159 ID: ExamplePolicyID1, 160 }}, 161 Namespace: "default", 162 } 163 164 ExampleOperatorToken2 = &api.ACLToken{ 165 SecretID: ExampleOperatorTokenID2, 166 AccessorID: "615b4d77-5164-4ec6-b616-24c0b24ac9cb", 167 Description: "Operator Token 2", 168 Policies: []*api.ACLTokenPolicyLink{{ 169 ID: ExamplePolicyID2, 170 }}, 171 Namespace: "default", 172 } 173 174 ExampleOperatorToken3 = &api.ACLToken{ 175 SecretID: ExampleOperatorTokenID3, 176 AccessorID: "6b7de0d7-15f7-45b4-95eb-fb775bfe3fdc", 177 Description: "Operator Token 3", 178 Policies: []*api.ACLTokenPolicyLink{{ 179 ID: ExamplePolicyID3, 180 }}, 181 Namespace: "default", 182 } 183 184 ExampleOperatorToken4 = &api.ACLToken{ 185 SecretID: ExampleOperatorTokenID4, 186 AccessorID: "7b5fdb1a-71e5-f3d8-2cfe-448d973f327d", 187 Description: "Operator Token 4", 188 Policies: nil, // no direct policy, only roles 189 Roles: []*api.ACLTokenRoleLink{{ 190 ID: ExampleRoleID1, 191 Name: "example-role-1", 192 }}, 193 Namespace: "default", 194 } 195 196 ExampleOperatorToken5 = &api.ACLToken{ 197 SecretID: ExampleOperatorTokenID5, 198 AccessorID: "cf39aad5-00c3-af23-cf0b-75d41e12f28d", 199 Description: "Operator Token 5", 200 Policies: []*api.ACLTokenPolicyLink{{ 201 ID: ExamplePolicyID4, 202 }}, 203 Namespace: "default", 204 } 205 206 // In Consul namespace "banana" 207 208 ExampleOperatorToken10 = &api.ACLToken{ 209 SecretID: ExampleOperatorTokenID0, 210 AccessorID: "76a2c3b5-5d64-9089-f701-660eec2d3554", 211 Description: "Operator Token 0", 212 Namespace: "banana", 213 } 214 215 ExampleOperatorToken11 = &api.ACLToken{ 216 SecretID: ExampleOperatorTokenID1, 217 AccessorID: "40f2a36a-0a65-1972-106c-b2e5dd46d6e8", 218 Description: "Operator Token 1", 219 Policies: []*api.ACLTokenPolicyLink{{ 220 ID: ExamplePolicyID1, 221 }}, 222 Namespace: "banana", 223 } 224 225 ExampleOperatorToken12 = &api.ACLToken{ 226 SecretID: ExampleOperatorTokenID2, 227 AccessorID: "894f2c5c-b285-71bf-4acb-6344cecf71f3", 228 Description: "Operator Token 2", 229 Policies: []*api.ACLTokenPolicyLink{{ 230 ID: ExamplePolicyID2, 231 }}, 232 Namespace: "banana", 233 } 234 235 ExampleOperatorToken13 = &api.ACLToken{ 236 SecretID: ExampleOperatorTokenID3, 237 AccessorID: "2a81ec0b-692e-845e-f5b8-c33c05e5af22", 238 Description: "Operator Token 3", 239 Policies: []*api.ACLTokenPolicyLink{{ 240 ID: ExamplePolicyID3, 241 }}, 242 Namespace: "banana", 243 } 244 245 ExampleOperatorToken14 = &api.ACLToken{ 246 SecretID: ExampleOperatorTokenID4, 247 AccessorID: "4273f1cc-5626-7a77-dc65-1f24af035ed5d", 248 Description: "Operator Token 4", 249 Policies: nil, // no direct policy, only roles 250 Roles: []*api.ACLTokenRoleLink{{ 251 ID: ExampleRoleID1, 252 Name: "example-role-1", 253 }}, 254 Namespace: "banana", 255 } 256 257 ExampleOperatorToken15 = &api.ACLToken{ 258 SecretID: ExampleOperatorTokenID5, 259 AccessorID: "5b78e186-87d8-c1ad-966f-f5fa87b05c9a", 260 Description: "Operator Token 5", 261 Policies: []*api.ACLTokenPolicyLink{{ 262 ID: ExamplePolicyID4, 263 }}, 264 Namespace: "banana", 265 } 266) 267 268func (m *MockACLsAPI) TokenReadSelf(q *api.QueryOptions) (*api.ACLToken, *api.QueryMeta, error) { 269 switch q.Token { 270 271 case ExampleOperatorTokenID1: 272 return ExampleOperatorToken1, nil, nil 273 274 case ExampleOperatorTokenID2: 275 return ExampleOperatorToken2, nil, nil 276 277 case ExampleOperatorTokenID3: 278 return ExampleOperatorToken3, nil, nil 279 280 case ExampleOperatorTokenID4: 281 return ExampleOperatorToken4, nil, nil 282 283 case ExampleOperatorTokenID5: 284 return ExampleOperatorToken5, nil, nil 285 286 case ExampleOperatorTokenID11: 287 return ExampleOperatorToken11, nil, nil 288 289 case ExampleOperatorTokenID12: 290 return ExampleOperatorToken12, nil, nil 291 292 case ExampleOperatorTokenID13: 293 return ExampleOperatorToken13, nil, nil 294 295 case ExampleOperatorTokenID14: 296 return ExampleOperatorToken14, nil, nil 297 298 case ExampleOperatorTokenID15: 299 return ExampleOperatorToken15, nil, nil 300 301 default: 302 return nil, nil, errors.New("no such token") 303 } 304} 305 306// SetError is a helper method for configuring an error that will be returned 307// on future calls to mocked methods. 308func (m *MockACLsAPI) SetError(err error) { 309 m.lock.Lock() 310 defer m.lock.Unlock() 311 m.state.error = err 312} 313 314// TokenCreate is a mock of ACLsAPI.TokenCreate 315func (m *MockACLsAPI) TokenCreate(token *api.ACLToken, opts *api.WriteOptions) (*api.ACLToken, *api.WriteMeta, error) { 316 index, created, meta, err := m.tokenCreate(token, opts) 317 318 services := func(token *api.ACLToken) []string { 319 if token == nil { 320 return nil 321 } 322 var names []string 323 for _, id := range token.ServiceIdentities { 324 names = append(names, id.ServiceName) 325 } 326 return names 327 }(created) 328 329 description := func(token *api.ACLToken) string { 330 if token == nil { 331 return "<nil>" 332 } 333 return token.Description 334 }(created) 335 336 accessor := func(token *api.ACLToken) string { 337 if token == nil { 338 return "<nil>" 339 } 340 return token.AccessorID 341 }(created) 342 343 secret := func(token *api.ACLToken) string { 344 if token == nil { 345 return "<nil>" 346 } 347 return token.SecretID 348 }(created) 349 350 m.logger.Trace("TokenCreate()", "description", description, "service_identities", services, "accessor", accessor, "secret", secret, "index", index, "error", err) 351 return created, meta, err 352} 353 354func (m *MockACLsAPI) tokenCreate(token *api.ACLToken, _ *api.WriteOptions) (uint64, *api.ACLToken, *api.WriteMeta, error) { 355 m.lock.Lock() 356 defer m.lock.Unlock() 357 358 m.state.index++ 359 360 if m.state.error != nil { 361 return m.state.index, nil, nil, m.state.error 362 } 363 364 secret := &api.ACLToken{ 365 CreateIndex: m.state.index, 366 ModifyIndex: m.state.index, 367 AccessorID: uuid.Generate(), 368 SecretID: uuid.Generate(), 369 Description: token.Description, 370 ServiceIdentities: token.ServiceIdentities, 371 Namespace: token.Namespace, 372 CreateTime: time.Now(), 373 } 374 375 m.state.tokens[secret.AccessorID] = secret 376 377 w := &api.WriteMeta{ 378 RequestTime: 1 * time.Millisecond, 379 } 380 381 return m.state.index, secret, w, nil 382} 383 384// TokenDelete is a mock of ACLsAPI.TokenDelete 385func (m *MockACLsAPI) TokenDelete(accessorID string, opts *api.WriteOptions) (*api.WriteMeta, error) { 386 meta, err := m.tokenDelete(accessorID, opts) 387 m.logger.Trace("TokenDelete()", "accessor", accessorID, "error", err) 388 return meta, err 389} 390 391func (m *MockACLsAPI) tokenDelete(tokenID string, _ *api.WriteOptions) (*api.WriteMeta, error) { 392 m.lock.Lock() 393 defer m.lock.Unlock() 394 395 m.state.index++ 396 397 if m.state.error != nil { 398 return nil, m.state.error 399 } 400 401 if _, exists := m.state.tokens[tokenID]; !exists { 402 return nil, nil // consul no-ops delete of non-existent token 403 } 404 405 delete(m.state.tokens, tokenID) 406 407 m.logger.Trace("TokenDelete()") 408 409 return nil, nil 410} 411 412// TokenList is a mock of ACLsAPI.TokenList 413func (m *MockACLsAPI) TokenList(_ *api.QueryOptions) ([]*api.ACLTokenListEntry, *api.QueryMeta, error) { 414 m.lock.Lock() 415 defer m.lock.Unlock() 416 417 //todo(shoenig): will need this for background token reconciliation 418 // coming in another issue 419 420 return nil, nil, nil 421} 422