From 827e7c9efa19d0324d5ea3b61f2167e9ca782ebb Mon Sep 17 00:00:00 2001 From: Armon Dadgar Date: Mon, 11 Aug 2014 14:54:18 -0700 Subject: [PATCH] consul: Create anonymous and master tokens --- consul/acl.go | 11 +++++- consul/acl_test.go | 53 +++++++++++++++++++++++++++ consul/fsm.go | 8 ++++- consul/fsm_test.go | 2 +- consul/leader.go | 73 ++++++++++++++++++++++++++++++++++++++ consul/state_store.go | 10 ++++-- consul/state_store_test.go | 14 ++++---- consul/structs/structs.go | 5 +-- 8 files changed, 162 insertions(+), 14 deletions(-) diff --git a/consul/acl.go b/consul/acl.go index 47767830c5..2909fea8f7 100644 --- a/consul/acl.go +++ b/consul/acl.go @@ -12,6 +12,10 @@ import ( const ( // aclNotFound indicates there is no matching ACL aclNotFound = "ACL not found" + + // anonymousToken is the token ID we re-write to if there + // is no token ID provided + anonymousToken = "anonymous" ) // aclCacheEntry is used to cache non-authoritative ACL's @@ -39,10 +43,15 @@ func (s *Server) aclFault(id string) (string, error) { func (s *Server) resolveToken(id string) (acl.ACL, error) { // Check if there is no ACL datacenter (ACL's disabled) authDC := s.config.ACLDatacenter - if authDC == "" { + if len(authDC) == 0 { return nil, nil } + // Handle the anonymous token + if len(id) == 0 { + id = anonymousToken + } + // Check if we are the ACL datacenter and the leader, use the // authoritative cache if s.config.Datacenter == authDC && s.IsLeader() { diff --git a/consul/acl_test.go b/consul/acl_test.go index 061550da53..b0dca39871 100644 --- a/consul/acl_test.go +++ b/consul/acl_test.go @@ -93,6 +93,59 @@ func TestACL_Authority_Found(t *testing.T) { } } +func TestACL_Authority_Anonymous_Found(t *testing.T) { + dir1, s1 := testServerWithConfig(t, func(c *Config) { + c.ACLDatacenter = "dc1" // Enable ACLs! + }) + defer os.RemoveAll(dir1) + defer s1.Shutdown() + client := rpcClient(t, s1) + defer client.Close() + + testutil.WaitForLeader(t, client.Call, "dc1") + + // Resolve the token + acl, err := s1.resolveToken("") + if err != nil { + t.Fatalf("err: %v", err) + } + if acl == nil { + t.Fatalf("missing acl") + } + + // Check the policy, should allow all + if !acl.KeyRead("foo/test") { + t.Fatalf("unexpected failed read") + } +} + +func TestACL_Authority_Master_Found(t *testing.T) { + dir1, s1 := testServerWithConfig(t, func(c *Config) { + c.ACLDatacenter = "dc1" // Enable ACLs! + c.ACLMasterToken = "foobar" + }) + defer os.RemoveAll(dir1) + defer s1.Shutdown() + client := rpcClient(t, s1) + defer client.Close() + + testutil.WaitForLeader(t, client.Call, "dc1") + + // Resolve the token + acl, err := s1.resolveToken("foobar") + if err != nil { + t.Fatalf("err: %v", err) + } + if acl == nil { + t.Fatalf("missing acl") + } + + // Check the policy, should allow all + if !acl.KeyRead("foo/test") { + t.Fatalf("unexpected failed read") + } +} + func TestACL_NonAuthority_NotFound(t *testing.T) { dir1, s1 := testServerWithConfig(t, func(c *Config) { c.ACLDatacenter = "dc1" diff --git a/consul/fsm.go b/consul/fsm.go index 4c82357e68..9810a289be 100644 --- a/consul/fsm.go +++ b/consul/fsm.go @@ -205,7 +205,13 @@ func (c *consulFSM) applyACLOperation(buf []byte, index uint64) interface{} { } switch req.Op { case structs.ACLSet: - if err := c.state.ACLSet(index, &req.ACL); err != nil { + if err := c.state.ACLSet(index, &req.ACL, false); err != nil { + return err + } else { + return req.ACL.ID + } + case structs.ACLForceSet: + if err := c.state.ACLSet(index, &req.ACL, true); err != nil { return err } else { return req.ACL.ID diff --git a/consul/fsm_test.go b/consul/fsm_test.go index f47c006539..d188de9ffb 100644 --- a/consul/fsm_test.go +++ b/consul/fsm_test.go @@ -329,7 +329,7 @@ func TestFSM_SnapshotRestore(t *testing.T) { session := &structs.Session{Node: "foo"} fsm.state.SessionCreate(9, session) acl := &structs.ACL{Name: "User Token"} - fsm.state.ACLSet(10, acl) + fsm.state.ACLSet(10, acl, false) // Snapshot snap, err := fsm.Snapshot() diff --git a/consul/leader.go b/consul/leader.go index b63f7bbe80..60a6737f9b 100644 --- a/consul/leader.go +++ b/consul/leader.go @@ -1,6 +1,7 @@ package consul import ( + "fmt" "net" "strconv" "time" @@ -54,6 +55,11 @@ func (s *Server) leaderLoop(stopCh chan struct{}) { s.logger.Printf("[WARN] consul: failed to broadcast new leader event: %v", err) } + // Setup ACLs if we are the leader and need to + if err := s.initializeACL(); err != nil { + s.logger.Printf("[ERR] consul: ACL initialization failed: %v", err) + } + // Reconcile channel is only used once initial reconcile // has succeeded var reconcileCh chan serf.Member @@ -99,6 +105,73 @@ WAIT: } } +// initializeACL is used to setup the ACLs if we are the leader +// and need to do this. +func (s *Server) initializeACL() error { + // Bail if not configured or we are not authoritative + authDC := s.config.ACLDatacenter + if len(authDC) == 0 || authDC != s.config.Datacenter { + return nil + } + + // Purge the cache, since it could've changed while we + // were not the leader + s.aclAuthCache.Purge() + + // Look for the anonymous token + state := s.fsm.State() + _, acl, err := state.ACLGet(anonymousToken) + if err != nil { + return fmt.Errorf("failed to get anonymous token: %v", err) + } + + // Create anonymous token if missing + if acl == nil { + req := structs.ACLRequest{ + Datacenter: authDC, + Op: structs.ACLForceSet, + ACL: structs.ACL{ + ID: anonymousToken, + Name: "Anonymous Token", + Type: structs.ACLTypeClient, + }, + } + _, err := s.raftApply(structs.ACLRequestType, &req) + if err != nil { + return fmt.Errorf("failed to create anonymous token: %v", err) + } + } + + // Check for configured master token + master := s.config.ACLMasterToken + if len(master) == 0 { + return nil + } + + // Look for the master token + _, acl, err = state.ACLGet(master) + if err != nil { + return fmt.Errorf("failed to get master token: %v", err) + } + if acl == nil { + req := structs.ACLRequest{ + Datacenter: authDC, + Op: structs.ACLForceSet, + ACL: structs.ACL{ + ID: master, + Name: "Master Token", + Type: structs.ACLTypeManagement, + }, + } + _, err := s.raftApply(structs.ACLRequestType, &req) + if err != nil { + return fmt.Errorf("failed to create master token: %v", err) + } + + } + return nil +} + // reconcile is used to reconcile the differences between Serf // membership and what is reflected in our strongly consistent store. // Mainly we need to ensure all live nodes are registered, all failed diff --git a/consul/state_store.go b/consul/state_store.go index 42c0d553fc..6a43aa0270 100644 --- a/consul/state_store.go +++ b/consul/state_store.go @@ -1504,7 +1504,9 @@ func (s *StateStore) invalidateLocks(index uint64, tx *MDBTxn, } // ACLSet is used to create or update an ACL entry -func (s *StateStore) ACLSet(index uint64, acl *structs.ACL) error { +// allowCreate is used for initialization of the anonymous and master tokens, +// since it permits them to be created with a specified ID that does not exist. +func (s *StateStore) ACLSet(index uint64, acl *structs.ACL, allowCreate bool) error { // Start a new txn tx, err := s.tables.StartTxn(false) if err != nil { @@ -1537,7 +1539,11 @@ func (s *StateStore) ACLSet(index uint64, acl *structs.ACL) error { switch len(res) { case 0: - return fmt.Errorf("Invalid ACL") + if !allowCreate { + return fmt.Errorf("Invalid ACL") + } + acl.CreateIndex = index + acl.ModifyIndex = index case 1: exist := res[0].(*structs.ACL) acl.CreateIndex = exist.CreateIndex diff --git a/consul/state_store_test.go b/consul/state_store_test.go index c1705f5df0..f0cdae90f3 100644 --- a/consul/state_store_test.go +++ b/consul/state_store_test.go @@ -656,7 +656,7 @@ func TestStoreSnapshot(t *testing.T) { Name: "User token", Type: structs.ACLTypeClient, } - if err := store.ACLSet(19, a1); err != nil { + if err := store.ACLSet(19, a1, false); err != nil { t.Fatalf("err: %v", err) } @@ -664,7 +664,7 @@ func TestStoreSnapshot(t *testing.T) { Name: "User token", Type: structs.ACLTypeClient, } - if err := store.ACLSet(20, a2); err != nil { + if err := store.ACLSet(20, a2, false); err != nil { t.Fatalf("err: %v", err) } @@ -2180,7 +2180,7 @@ func TestACLSet_Get(t *testing.T) { Type: structs.ACLTypeClient, Rules: "", } - if err := store.ACLSet(50, a); err != nil { + if err := store.ACLSet(50, a, false); err != nil { t.Fatalf("err: %v", err) } if a.CreateIndex != 50 { @@ -2206,7 +2206,7 @@ func TestACLSet_Get(t *testing.T) { // Update a.Rules = "foo bar baz" - if err := store.ACLSet(52, a); err != nil { + if err := store.ACLSet(52, a, false); err != nil { t.Fatalf("err: %v", err) } if a.CreateIndex != 50 { @@ -2240,7 +2240,7 @@ func TestACLDelete(t *testing.T) { Type: structs.ACLTypeClient, Rules: "", } - if err := store.ACLSet(50, a); err != nil { + if err := store.ACLSet(50, a, false); err != nil { t.Fatalf("err: %v", err) } @@ -2274,7 +2274,7 @@ func TestACLList(t *testing.T) { Name: "User token", Type: structs.ACLTypeClient, } - if err := store.ACLSet(50, a1); err != nil { + if err := store.ACLSet(50, a1, false); err != nil { t.Fatalf("err: %v", err) } @@ -2282,7 +2282,7 @@ func TestACLList(t *testing.T) { Name: "User token", Type: structs.ACLTypeClient, } - if err := store.ACLSet(51, a2); err != nil { + if err := store.ACLSet(51, a2, false); err != nil { t.Fatalf("err: %v", err) } diff --git a/consul/structs/structs.go b/consul/structs/structs.go index b80145c436..3263a6ce28 100644 --- a/consul/structs/structs.go +++ b/consul/structs/structs.go @@ -439,8 +439,9 @@ type ACLs []*ACL type ACLOp string const ( - ACLSet ACLOp = "set" - ACLDelete = "delete" + ACLSet ACLOp = "set" + ACLForceSet = "force-set" + ACLDelete = "delete" ) // ACLRequest is used to create, update or delete an ACL