1package agent 2 3import ( 4 "fmt" 5 "io" 6 "testing" 7 "time" 8 9 "github.com/armon/go-metrics" 10 "github.com/hashicorp/go-hclog" 11 "github.com/hashicorp/serf/serf" 12 13 "github.com/hashicorp/consul/acl" 14 "github.com/hashicorp/consul/agent/config" 15 "github.com/hashicorp/consul/agent/consul" 16 "github.com/hashicorp/consul/agent/local" 17 "github.com/hashicorp/consul/agent/structs" 18 "github.com/hashicorp/consul/lib" 19 "github.com/hashicorp/consul/sdk/testutil" 20 "github.com/hashicorp/consul/types" 21 22 "github.com/stretchr/testify/require" 23) 24 25type authzResolver func(string) (structs.ACLIdentity, acl.Authorizer, error) 26type identResolver func(string) (structs.ACLIdentity, error) 27 28type TestACLAgent struct { 29 resolveAuthzFn authzResolver 30 resolveIdentFn identResolver 31 32 *Agent 33} 34 35// NewTestACLAgent does just enough so that all the code within agent/acl.go can work 36// Basically it needs a local state for some of the vet* functions, a logger and a delegate. 37// The key is that we are the delegate so we can control the ResolveToken responses 38func NewTestACLAgent(t *testing.T, name string, hcl string, resolveAuthz authzResolver, resolveIdent identResolver) *TestACLAgent { 39 t.Helper() 40 41 a := &TestACLAgent{resolveAuthzFn: resolveAuthz, resolveIdentFn: resolveIdent} 42 43 dataDir := testutil.TempDir(t, "acl-agent") 44 45 logBuffer := testutil.NewLogBuffer(t) 46 loader := func(source config.Source) (config.LoadResult, error) { 47 dataDir := fmt.Sprintf(`data_dir = "%s"`, dataDir) 48 opts := config.LoadOpts{ 49 HCL: []string{TestConfigHCL(NodeID()), hcl, dataDir}, 50 DefaultConfig: source, 51 } 52 result, err := config.Load(opts) 53 if result.RuntimeConfig != nil { 54 result.RuntimeConfig.Telemetry.Disable = true 55 } 56 return result, err 57 } 58 bd, err := NewBaseDeps(loader, logBuffer) 59 require.NoError(t, err) 60 61 bd.Logger = hclog.NewInterceptLogger(&hclog.LoggerOptions{ 62 Name: name, 63 Level: hclog.Debug, 64 Output: logBuffer, 65 TimeFormat: "04:05.000", 66 }) 67 bd.MetricsHandler = metrics.NewInmemSink(1*time.Second, time.Minute) 68 69 agent, err := New(bd) 70 require.NoError(t, err) 71 72 agent.delegate = a 73 agent.State = local.NewState(LocalConfig(bd.RuntimeConfig), bd.Logger, bd.Tokens) 74 agent.State.TriggerSyncChanges = func() {} 75 a.Agent = agent 76 return a 77} 78 79func (a *TestACLAgent) UseLegacyACLs() bool { 80 return false 81} 82 83func (a *TestACLAgent) ResolveToken(secretID string) (acl.Authorizer, error) { 84 if a.resolveAuthzFn == nil { 85 return nil, fmt.Errorf("ResolveToken call is unexpected - no authz resolver callback set") 86 } 87 88 _, authz, err := a.resolveAuthzFn(secretID) 89 return authz, err 90} 91 92func (a *TestACLAgent) ResolveTokenToIdentityAndAuthorizer(secretID string) (structs.ACLIdentity, acl.Authorizer, error) { 93 if a.resolveAuthzFn == nil { 94 return nil, nil, fmt.Errorf("ResolveTokenToIdentityAndAuthorizer call is unexpected - no authz resolver callback set") 95 } 96 97 return a.resolveAuthzFn(secretID) 98} 99 100func (a *TestACLAgent) ResolveTokenToIdentity(secretID string) (structs.ACLIdentity, error) { 101 if a.resolveIdentFn == nil { 102 return nil, fmt.Errorf("ResolveTokenToIdentity call is unexpected - no ident resolver callback set") 103 } 104 105 return a.resolveIdentFn(secretID) 106} 107 108func (a *TestACLAgent) ResolveTokenAndDefaultMeta(secretID string, entMeta *structs.EnterpriseMeta, authzContext *acl.AuthorizerContext) (acl.Authorizer, error) { 109 identity, authz, err := a.ResolveTokenToIdentityAndAuthorizer(secretID) 110 if err != nil { 111 return nil, err 112 } 113 114 // Default the EnterpriseMeta based on the Tokens meta or actual defaults 115 // in the case of unknown identity 116 if identity != nil { 117 entMeta.Merge(identity.EnterpriseMetadata()) 118 } else { 119 entMeta.Merge(structs.DefaultEnterpriseMeta()) 120 } 121 122 // Use the meta to fill in the ACL authorization context 123 entMeta.FillAuthzContext(authzContext) 124 125 return authz, err 126} 127 128// All of these are stubs to satisfy the interface 129func (a *TestACLAgent) GetLANCoordinate() (lib.CoordinateSet, error) { 130 return nil, fmt.Errorf("Unimplemented") 131} 132func (a *TestACLAgent) Leave() error { 133 return fmt.Errorf("Unimplemented") 134} 135func (a *TestACLAgent) LANMembers() []serf.Member { 136 return nil 137} 138func (a *TestACLAgent) LANMembersAllSegments() ([]serf.Member, error) { 139 return nil, fmt.Errorf("Unimplemented") 140} 141func (a *TestACLAgent) LANSegmentMembers(segment string) ([]serf.Member, error) { 142 return nil, fmt.Errorf("Unimplemented") 143} 144func (a *TestACLAgent) LocalMember() serf.Member { 145 return serf.Member{} 146} 147func (a *TestACLAgent) JoinLAN(addrs []string) (n int, err error) { 148 return 0, fmt.Errorf("Unimplemented") 149} 150func (a *TestACLAgent) RemoveFailedNode(node string, prune bool) error { 151 return fmt.Errorf("Unimplemented") 152} 153 154func (a *TestACLAgent) RPC(method string, args interface{}, reply interface{}) error { 155 return fmt.Errorf("Unimplemented") 156} 157func (a *TestACLAgent) SnapshotRPC(args *structs.SnapshotRequest, in io.Reader, out io.Writer, replyFn structs.SnapshotReplyFn) error { 158 return fmt.Errorf("Unimplemented") 159} 160func (a *TestACLAgent) Shutdown() error { 161 return fmt.Errorf("Unimplemented") 162} 163func (a *TestACLAgent) Stats() map[string]map[string]string { 164 return nil 165} 166func (a *TestACLAgent) ReloadConfig(_ consul.ReloadableConfig) error { 167 return fmt.Errorf("Unimplemented") 168} 169 170func TestACL_Version8EnabledByDefault(t *testing.T) { 171 t.Parallel() 172 173 called := false 174 resolveFn := func(string) (structs.ACLIdentity, acl.Authorizer, error) { 175 called = true 176 return nil, nil, acl.ErrNotFound 177 } 178 a := NewTestACLAgent(t, t.Name(), TestACLConfig(), resolveFn, nil) 179 180 _, err := a.delegate.ResolveTokenAndDefaultMeta("nope", nil, nil) 181 require.Error(t, err) 182 require.True(t, called) 183} 184 185func authzFromPolicy(policy *acl.Policy, cfg *acl.Config) (acl.Authorizer, error) { 186 return acl.NewPolicyAuthorizerWithDefaults(acl.DenyAll(), []*acl.Policy{policy}, cfg) 187} 188 189type testToken struct { 190 token structs.ACLToken 191 // yes the rules can exist on the token itself but that is legacy behavior 192 // that I would prefer these tests not rely on 193 rules string 194} 195 196var ( 197 nodeROSecret = "7e80d017-bccc-492f-8dec-65f03aeaebf3" 198 nodeRWSecret = "e3586ee5-02a2-4bf4-9ec3-9c4be7606e8c" 199 serviceROSecret = "3d2c8552-df3b-4da7-9890-36885cbf56ac" 200 serviceRWSecret = "4a1017a2-f788-4be3-93f2-90566f1340bb" 201 otherRWSecret = "a38e8016-91b6-4876-b3e7-a307abbb2002" 202 203 testTokens = map[string]testToken{ 204 nodeROSecret: { 205 token: structs.ACLToken{ 206 AccessorID: "9df2d1a4-2d07-414e-8ead-6053f56ed2eb", 207 SecretID: nodeROSecret, 208 }, 209 rules: `node_prefix "Node" { policy = "read" }`, 210 }, 211 nodeRWSecret: { 212 token: structs.ACLToken{ 213 AccessorID: "efb6b7d5-d343-47c1-b4cb-aa6b94d2f490", 214 SecretID: nodeRWSecret, 215 }, 216 rules: `node_prefix "Node" { policy = "write" }`, 217 }, 218 serviceROSecret: { 219 token: structs.ACLToken{ 220 AccessorID: "0da53edb-36e5-4603-9c31-79965bad45f5", 221 SecretID: serviceROSecret, 222 }, 223 rules: `service_prefix "service" { policy = "read" }`, 224 }, 225 serviceRWSecret: { 226 token: structs.ACLToken{ 227 AccessorID: "52504258-137a-41e6-9326-01f40e80872e", 228 SecretID: serviceRWSecret, 229 }, 230 rules: `service_prefix "service" { policy = "write" }`, 231 }, 232 otherRWSecret: { 233 token: structs.ACLToken{ 234 AccessorID: "5e032c5b-c39e-4552-b5ad-8a9365b099c4", 235 SecretID: otherRWSecret, 236 }, 237 rules: `service_prefix "other" { policy = "write" }`, 238 }, 239 } 240) 241 242func catalogPolicy(token string) (structs.ACLIdentity, acl.Authorizer, error) { 243 tok, ok := testTokens[token] 244 if !ok { 245 return nil, nil, acl.ErrNotFound 246 } 247 248 policy, err := acl.NewPolicyFromSource("", 0, tok.rules, acl.SyntaxCurrent, nil, nil) 249 if err != nil { 250 return nil, nil, err 251 } 252 253 authz, err := authzFromPolicy(policy, nil) 254 return &tok.token, authz, err 255} 256 257func catalogIdent(token string) (structs.ACLIdentity, error) { 258 tok, ok := testTokens[token] 259 if !ok { 260 return nil, acl.ErrNotFound 261 } 262 263 return &tok.token, nil 264} 265 266func TestACL_vetServiceRegister(t *testing.T) { 267 t.Parallel() 268 a := NewTestACLAgent(t, t.Name(), TestACLConfig(), catalogPolicy, catalogIdent) 269 270 // Register a new service, with permission. 271 err := a.vetServiceRegister(serviceRWSecret, &structs.NodeService{ 272 ID: "my-service", 273 Service: "service", 274 }) 275 require.NoError(t, err) 276 277 // Register a new service without write privs. 278 err = a.vetServiceRegister(serviceROSecret, &structs.NodeService{ 279 ID: "my-service", 280 Service: "service", 281 }) 282 require.True(t, acl.IsErrPermissionDenied(err)) 283 284 // Try to register over a service without write privs to the existing 285 // service. 286 a.State.AddService(&structs.NodeService{ 287 ID: "my-service", 288 Service: "other", 289 }, "") 290 err = a.vetServiceRegister(serviceRWSecret, &structs.NodeService{ 291 ID: "my-service", 292 Service: "service", 293 }) 294 require.True(t, acl.IsErrPermissionDenied(err)) 295} 296 297func TestACL_vetServiceUpdate(t *testing.T) { 298 t.Parallel() 299 a := NewTestACLAgent(t, t.Name(), TestACLConfig(), catalogPolicy, catalogIdent) 300 301 // Update a service that doesn't exist. 302 err := a.vetServiceUpdate(serviceRWSecret, structs.NewServiceID("my-service", nil)) 303 require.Error(t, err) 304 require.Contains(t, err.Error(), "Unknown service") 305 306 // Update with write privs. 307 a.State.AddService(&structs.NodeService{ 308 ID: "my-service", 309 Service: "service", 310 }, "") 311 err = a.vetServiceUpdate(serviceRWSecret, structs.NewServiceID("my-service", nil)) 312 require.NoError(t, err) 313 314 // Update without write privs. 315 err = a.vetServiceUpdate(serviceROSecret, structs.NewServiceID("my-service", nil)) 316 require.Error(t, err) 317 require.True(t, acl.IsErrPermissionDenied(err)) 318} 319 320func TestACL_vetCheckRegister(t *testing.T) { 321 t.Parallel() 322 a := NewTestACLAgent(t, t.Name(), TestACLConfig(), catalogPolicy, catalogIdent) 323 324 // Register a new service check with write privs. 325 err := a.vetCheckRegister(serviceRWSecret, &structs.HealthCheck{ 326 CheckID: types.CheckID("my-check"), 327 ServiceID: "my-service", 328 ServiceName: "service", 329 }) 330 require.NoError(t, err) 331 332 // Register a new service check without write privs. 333 err = a.vetCheckRegister(serviceROSecret, &structs.HealthCheck{ 334 CheckID: types.CheckID("my-check"), 335 ServiceID: "my-service", 336 ServiceName: "service", 337 }) 338 require.Error(t, err) 339 require.True(t, acl.IsErrPermissionDenied(err)) 340 341 // Register a new node check with write privs. 342 err = a.vetCheckRegister(nodeRWSecret, &structs.HealthCheck{ 343 CheckID: types.CheckID("my-check"), 344 }) 345 require.NoError(t, err) 346 347 // Register a new node check without write privs. 348 err = a.vetCheckRegister(nodeROSecret, &structs.HealthCheck{ 349 CheckID: types.CheckID("my-check"), 350 }) 351 require.Error(t, err) 352 require.True(t, acl.IsErrPermissionDenied(err)) 353 354 // Try to register over a service check without write privs to the 355 // existing service. 356 a.State.AddService(&structs.NodeService{ 357 ID: "my-service", 358 Service: "service", 359 }, "") 360 a.State.AddCheck(&structs.HealthCheck{ 361 CheckID: types.CheckID("my-check"), 362 ServiceID: "my-service", 363 ServiceName: "other", 364 }, "") 365 err = a.vetCheckRegister(serviceRWSecret, &structs.HealthCheck{ 366 CheckID: types.CheckID("my-check"), 367 ServiceID: "my-service", 368 ServiceName: "service", 369 }) 370 require.Error(t, err) 371 require.True(t, acl.IsErrPermissionDenied(err)) 372 373 // Try to register over a node check without write privs to the node. 374 a.State.AddCheck(&structs.HealthCheck{ 375 CheckID: types.CheckID("my-node-check"), 376 }, "") 377 err = a.vetCheckRegister(serviceRWSecret, &structs.HealthCheck{ 378 CheckID: types.CheckID("my-node-check"), 379 ServiceID: "my-service", 380 ServiceName: "service", 381 }) 382 require.Error(t, err) 383 require.True(t, acl.IsErrPermissionDenied(err)) 384} 385 386func TestACL_vetCheckUpdate(t *testing.T) { 387 t.Parallel() 388 a := NewTestACLAgent(t, t.Name(), TestACLConfig(), catalogPolicy, catalogIdent) 389 390 // Update a check that doesn't exist. 391 err := a.vetCheckUpdate(nodeRWSecret, structs.NewCheckID("my-check", nil)) 392 require.Error(t, err) 393 require.Contains(t, err.Error(), "Unknown check") 394 395 // Update service check with write privs. 396 a.State.AddService(&structs.NodeService{ 397 ID: "my-service", 398 Service: "service", 399 }, "") 400 a.State.AddCheck(&structs.HealthCheck{ 401 CheckID: types.CheckID("my-service-check"), 402 ServiceID: "my-service", 403 ServiceName: "service", 404 }, "") 405 err = a.vetCheckUpdate(serviceRWSecret, structs.NewCheckID("my-service-check", nil)) 406 require.NoError(t, err) 407 408 // Update service check without write privs. 409 err = a.vetCheckUpdate(serviceROSecret, structs.NewCheckID("my-service-check", nil)) 410 require.Error(t, err) 411 require.True(t, acl.IsErrPermissionDenied(err), "not permission denied: %s", err.Error()) 412 413 // Update node check with write privs. 414 a.State.AddCheck(&structs.HealthCheck{ 415 CheckID: types.CheckID("my-node-check"), 416 }, "") 417 err = a.vetCheckUpdate(nodeRWSecret, structs.NewCheckID("my-node-check", nil)) 418 require.NoError(t, err) 419 420 // Update without write privs. 421 err = a.vetCheckUpdate(nodeROSecret, structs.NewCheckID("my-node-check", nil)) 422 require.Error(t, err) 423 require.True(t, acl.IsErrPermissionDenied(err)) 424} 425 426func TestACL_filterMembers(t *testing.T) { 427 t.Parallel() 428 a := NewTestACLAgent(t, t.Name(), TestACLConfig(), catalogPolicy, catalogIdent) 429 430 var members []serf.Member 431 require.NoError(t, a.filterMembers(nodeROSecret, &members)) 432 require.Len(t, members, 0) 433 434 members = []serf.Member{ 435 {Name: "Node 1"}, 436 {Name: "Nope"}, 437 {Name: "Node 2"}, 438 } 439 require.NoError(t, a.filterMembers(nodeROSecret, &members)) 440 require.Len(t, members, 2) 441 require.Equal(t, members[0].Name, "Node 1") 442 require.Equal(t, members[1].Name, "Node 2") 443} 444 445func TestACL_filterServices(t *testing.T) { 446 t.Parallel() 447 a := NewTestACLAgent(t, t.Name(), TestACLConfig(), catalogPolicy, catalogIdent) 448 449 services := make(map[structs.ServiceID]*structs.NodeService) 450 require.NoError(t, a.filterServices(nodeROSecret, &services)) 451 452 services[structs.NewServiceID("my-service", nil)] = &structs.NodeService{ID: "my-service", Service: "service"} 453 services[structs.NewServiceID("my-other", nil)] = &structs.NodeService{ID: "my-other", Service: "other"} 454 require.NoError(t, a.filterServices(serviceROSecret, &services)) 455 require.Contains(t, services, structs.NewServiceID("my-service", nil)) 456 require.NotContains(t, services, structs.NewServiceID("my-other", nil)) 457} 458 459func TestACL_filterChecks(t *testing.T) { 460 t.Parallel() 461 a := NewTestACLAgent(t, t.Name(), TestACLConfig(), catalogPolicy, catalogIdent) 462 463 checks := make(map[structs.CheckID]*structs.HealthCheck) 464 require.NoError(t, a.filterChecks(nodeROSecret, &checks)) 465 466 checks[structs.NewCheckID("my-node", nil)] = &structs.HealthCheck{} 467 checks[structs.NewCheckID("my-service", nil)] = &structs.HealthCheck{ServiceName: "service"} 468 checks[structs.NewCheckID("my-other", nil)] = &structs.HealthCheck{ServiceName: "other"} 469 require.NoError(t, a.filterChecks(serviceROSecret, &checks)) 470 _, ok := checks[structs.NewCheckID("my-node", nil)] 471 require.False(t, ok) 472 _, ok = checks[structs.NewCheckID("my-service", nil)] 473 require.True(t, ok) 474 _, ok = checks[structs.NewCheckID("my-other", nil)] 475 require.False(t, ok) 476 477 checks[structs.NewCheckID("my-node", nil)] = &structs.HealthCheck{} 478 checks[structs.NewCheckID("my-service", nil)] = &structs.HealthCheck{ServiceName: "service"} 479 checks[structs.NewCheckID("my-other", nil)] = &structs.HealthCheck{ServiceName: "other"} 480 require.NoError(t, a.filterChecks(nodeROSecret, &checks)) 481 _, ok = checks[structs.NewCheckID("my-node", nil)] 482 require.True(t, ok) 483 _, ok = checks[structs.NewCheckID("my-service", nil)] 484 require.False(t, ok) 485 _, ok = checks[structs.NewCheckID("my-other", nil)] 486 require.False(t, ok) 487} 488 489// TODO: remove? 490func TestACL_ResolveIdentity(t *testing.T) { 491 t.Parallel() 492 a := NewTestACLAgent(t, t.Name(), TestACLConfig(), nil, catalogIdent) 493 494 // this test is meant to ensure we are calling the correct function 495 // which is ResolveTokenToIdentity on the Agent delegate. Our 496 // nil authz resolver will cause it to emit an error if used 497 ident, err := a.delegate.ResolveTokenToIdentity(nodeROSecret) 498 require.NoError(t, err) 499 require.NotNil(t, ident) 500 501 // just double checkingto ensure if we had used the wrong function 502 // that an error would be produced 503 _, err = a.delegate.ResolveTokenAndDefaultMeta(nodeROSecret, nil, nil) 504 require.Error(t, err) 505 506} 507