From 16188e7604effe25ce63da1c24923a3985cb535b Mon Sep 17 00:00:00 2001 From: Ryan Uber Date: Sun, 6 Sep 2015 21:13:45 -0700 Subject: [PATCH] consul/state: basic acl set/get/delete --- consul/state/state_store.go | 69 ++++++++++++++++++++++++- consul/state/state_store_test.go | 88 ++++++++++++++++++++++++++++++++ consul/structs/structs.go | 12 ++--- 3 files changed, 162 insertions(+), 7 deletions(-) diff --git a/consul/state/state_store.go b/consul/state/state_store.go index 21e78e92d9..e6b47a2d26 100644 --- a/consul/state/state_store.go +++ b/consul/state/state_store.go @@ -23,6 +23,10 @@ var ( // ErrMissingSessionID is returned when a session registration // is attempted with an empty session ID. ErrMissingSessionID = errors.New("Missing session ID") + + // ErrMissingACLID is returned when a session set is called on + // a session with an empty ID. + ErrMissingACLID = errors.New("Missing ACL ID") ) // StateStore is where we store all of Consul's state, including @@ -897,7 +901,7 @@ func (s *StateStore) KVSDeleteCAS(idx, cidx uint64, key string) (bool, error) { return false, fmt.Errorf("failed kvs lookup: %s", err) } - // If the existing index does not match the provided CAS + // If the existing index does not match the provided CAS // index arg, then we shouldn't update anything and can safely // return early here. e, ok := entry.(*structs.DirEntry) @@ -1179,3 +1183,66 @@ func (s *StateStore) sessionDestroyTxn(idx uint64, sessionID string, tx *memdb.T return nil } + +// ACLSet is used to insert an ACL rule into the state store. +func (s *StateStore) ACLSet(idx uint64, acl *structs.ACL) error { + tx := s.db.Txn(true) + defer tx.Abort() + + // Call set on the ACL + if err := s.aclSetTxn(idx, acl, tx); err != nil { + return err + } + + tx.Commit() + return nil +} + +// aclSetTxn is the inner method used to insert an ACL rule with the +// proper indexes into the state store. +func (s *StateStore) aclSetTxn(idx uint64, acl *structs.ACL, tx *memdb.Txn) error { + // Check that the ID is set + if acl.ID == "" { + return ErrMissingACLID + } + + // Check for an existing ACL + existing, err := tx.First("acls", "id", acl.ID) + if err != nil { + return fmt.Errorf("failed acl lookup: %s", err) + } + + // Set the indexes + if existing != nil { + acl.CreateIndex = existing.(*structs.ACL).CreateIndex + acl.ModifyIndex = idx + } else { + acl.CreateIndex = idx + acl.ModifyIndex = idx + } + + // Insert the ACL + if err := tx.Insert("acls", acl); err != nil { + return fmt.Errorf("failed inserting acl: %s", err) + } + if err := tx.Insert("index", &IndexEntry{"acls", idx}); err != nil { + return fmt.Errorf("failed updating index: %s", err) + } + return nil +} + +// ACLGet is used to look up an existing ACL by ID. +func (s *StateStore) ACLGet(aclID string) (*structs.ACL, error) { + tx := s.db.Txn(false) + defer tx.Abort() + + // Query for the existing ACL + acl, err := tx.First("acls", "id", aclID) + if err != nil { + return nil, fmt.Errorf("failed acl lookup: %s", err) + } + if acl != nil { + return acl.(*structs.ACL), nil + } + return nil, nil +} diff --git a/consul/state/state_store_test.go b/consul/state/state_store_test.go index 651d7f95d7..e3a423ecf3 100644 --- a/consul/state/state_store_test.go +++ b/consul/state/state_store_test.go @@ -1477,3 +1477,91 @@ func TestStateStore_SessionDestroy(t *testing.T) { t.Fatalf("bad index: %d", idx) } } + +func TestStateStore_ACLSet(t *testing.T) { + s := testStateStore(t) + + // Querying ACL's with no results returns nil + res, err := s.ACLGet("nope") + if res != nil || err != nil { + t.Fatalf("expected (nil, nil), got: (%#v, %#v)", res, err) + } + + // Inserting an ACL with empty ID is disallowed + if err := s.ACLSet(1, &structs.ACL{}); err == nil { + t.Fatalf("expected %#v, got: %#v", ErrMissingACLID, err) + } + + // Index is not updated if nothing is saved + if idx := s.maxIndex("acls"); idx != 0 { + t.Fatalf("bad index: %d", idx) + } + + // Inserting valid ACL works + acl := &structs.ACL{ + ID: "acl1", + Name: "First ACL", + Type: structs.ACLTypeClient, + Rules: "rules1", + } + if err := s.ACLSet(1, acl); err != nil { + t.Fatalf("err: %s", err) + } + + // Check that the index was updated + if idx := s.maxIndex("acls"); idx != 1 { + t.Fatalf("err: %s", err) + } + + // Retrieve the ACL again + result, err := s.ACLGet("acl1") + if err != nil { + t.Fatalf("err: %s", err) + } + + // Check that the ACL matches the result + expect := &structs.ACL{ + ID: "acl1", + Name: "First ACL", + Type: structs.ACLTypeClient, + Rules: "rules1", + RaftIndex: structs.RaftIndex{ + CreateIndex: 1, + ModifyIndex: 1, + }, + } + if !reflect.DeepEqual(result, expect) { + t.Fatalf("bad: %#v", result) + } + + // Update the ACL + acl = &structs.ACL{ + ID: "acl1", + Name: "First ACL", + Type: structs.ACLTypeClient, + Rules: "rules2", + } + if err := s.ACLSet(2, acl); err != nil { + t.Fatalf("err: %s", err) + } + + // Index was updated + if idx := s.maxIndex("acls"); idx != 2 { + t.Fatalf("bad: %d", idx) + } + + // ACL was updated and matches expected value + expect = &structs.ACL{ + ID: "acl1", + Name: "First ACL", + Type: structs.ACLTypeClient, + Rules: "rules2", + RaftIndex: structs.RaftIndex{ + CreateIndex: 1, + ModifyIndex: 2, + }, + } + if !reflect.DeepEqual(acl, expect) { + t.Fatalf("bad: %#v", acl) + } +} diff --git a/consul/structs/structs.go b/consul/structs/structs.go index fdcfaa8fd8..2d49bb9bda 100644 --- a/consul/structs/structs.go +++ b/consul/structs/structs.go @@ -479,12 +479,12 @@ type IndexedSessions struct { // ACL is used to represent a token and it's rules type ACL struct { - CreateIndex uint64 - ModifyIndex uint64 - ID string - Name string - Type string - Rules string + ID string + Name string + Type string + Rules string + + RaftIndex } type ACLs []*ACL