consul: Create anonymous and master tokens

This commit is contained in:
Armon Dadgar 2014-08-11 14:54:18 -07:00
parent bbde4beefd
commit 5da5df716d
8 changed files with 162 additions and 14 deletions

View File

@ -12,6 +12,10 @@ import (
const ( const (
// aclNotFound indicates there is no matching ACL // aclNotFound indicates there is no matching ACL
aclNotFound = "ACL not found" 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 // 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) { func (s *Server) resolveToken(id string) (acl.ACL, error) {
// Check if there is no ACL datacenter (ACL's disabled) // Check if there is no ACL datacenter (ACL's disabled)
authDC := s.config.ACLDatacenter authDC := s.config.ACLDatacenter
if authDC == "" { if len(authDC) == 0 {
return nil, nil 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 // Check if we are the ACL datacenter and the leader, use the
// authoritative cache // authoritative cache
if s.config.Datacenter == authDC && s.IsLeader() { if s.config.Datacenter == authDC && s.IsLeader() {

View File

@ -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) { func TestACL_NonAuthority_NotFound(t *testing.T) {
dir1, s1 := testServerWithConfig(t, func(c *Config) { dir1, s1 := testServerWithConfig(t, func(c *Config) {
c.ACLDatacenter = "dc1" c.ACLDatacenter = "dc1"

View File

@ -205,7 +205,13 @@ func (c *consulFSM) applyACLOperation(buf []byte, index uint64) interface{} {
} }
switch req.Op { switch req.Op {
case structs.ACLSet: 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 return err
} else { } else {
return req.ACL.ID return req.ACL.ID

View File

@ -329,7 +329,7 @@ func TestFSM_SnapshotRestore(t *testing.T) {
session := &structs.Session{Node: "foo"} session := &structs.Session{Node: "foo"}
fsm.state.SessionCreate(9, session) fsm.state.SessionCreate(9, session)
acl := &structs.ACL{Name: "User Token"} acl := &structs.ACL{Name: "User Token"}
fsm.state.ACLSet(10, acl) fsm.state.ACLSet(10, acl, false)
// Snapshot // Snapshot
snap, err := fsm.Snapshot() snap, err := fsm.Snapshot()

View File

@ -1,6 +1,7 @@
package consul package consul
import ( import (
"fmt"
"net" "net"
"strconv" "strconv"
"time" "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) 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 // Reconcile channel is only used once initial reconcile
// has succeeded // has succeeded
var reconcileCh chan serf.Member 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 // reconcile is used to reconcile the differences between Serf
// membership and what is reflected in our strongly consistent store. // membership and what is reflected in our strongly consistent store.
// Mainly we need to ensure all live nodes are registered, all failed // Mainly we need to ensure all live nodes are registered, all failed

View File

@ -1504,7 +1504,9 @@ func (s *StateStore) invalidateLocks(index uint64, tx *MDBTxn,
} }
// ACLSet is used to create or update an ACL entry // 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 // Start a new txn
tx, err := s.tables.StartTxn(false) tx, err := s.tables.StartTxn(false)
if err != nil { if err != nil {
@ -1537,7 +1539,11 @@ func (s *StateStore) ACLSet(index uint64, acl *structs.ACL) error {
switch len(res) { switch len(res) {
case 0: case 0:
return fmt.Errorf("Invalid ACL") if !allowCreate {
return fmt.Errorf("Invalid ACL")
}
acl.CreateIndex = index
acl.ModifyIndex = index
case 1: case 1:
exist := res[0].(*structs.ACL) exist := res[0].(*structs.ACL)
acl.CreateIndex = exist.CreateIndex acl.CreateIndex = exist.CreateIndex

View File

@ -656,7 +656,7 @@ func TestStoreSnapshot(t *testing.T) {
Name: "User token", Name: "User token",
Type: structs.ACLTypeClient, 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) t.Fatalf("err: %v", err)
} }
@ -664,7 +664,7 @@ func TestStoreSnapshot(t *testing.T) {
Name: "User token", Name: "User token",
Type: structs.ACLTypeClient, 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) t.Fatalf("err: %v", err)
} }
@ -2180,7 +2180,7 @@ func TestACLSet_Get(t *testing.T) {
Type: structs.ACLTypeClient, Type: structs.ACLTypeClient,
Rules: "", Rules: "",
} }
if err := store.ACLSet(50, a); err != nil { if err := store.ACLSet(50, a, false); err != nil {
t.Fatalf("err: %v", err) t.Fatalf("err: %v", err)
} }
if a.CreateIndex != 50 { if a.CreateIndex != 50 {
@ -2206,7 +2206,7 @@ func TestACLSet_Get(t *testing.T) {
// Update // Update
a.Rules = "foo bar baz" 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) t.Fatalf("err: %v", err)
} }
if a.CreateIndex != 50 { if a.CreateIndex != 50 {
@ -2240,7 +2240,7 @@ func TestACLDelete(t *testing.T) {
Type: structs.ACLTypeClient, Type: structs.ACLTypeClient,
Rules: "", Rules: "",
} }
if err := store.ACLSet(50, a); err != nil { if err := store.ACLSet(50, a, false); err != nil {
t.Fatalf("err: %v", err) t.Fatalf("err: %v", err)
} }
@ -2274,7 +2274,7 @@ func TestACLList(t *testing.T) {
Name: "User token", Name: "User token",
Type: structs.ACLTypeClient, 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) t.Fatalf("err: %v", err)
} }
@ -2282,7 +2282,7 @@ func TestACLList(t *testing.T) {
Name: "User token", Name: "User token",
Type: structs.ACLTypeClient, 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) t.Fatalf("err: %v", err)
} }

View File

@ -439,8 +439,9 @@ type ACLs []*ACL
type ACLOp string type ACLOp string
const ( const (
ACLSet ACLOp = "set" ACLSet ACLOp = "set"
ACLDelete = "delete" ACLForceSet = "force-set"
ACLDelete = "delete"
) )
// ACLRequest is used to create, update or delete an ACL // ACLRequest is used to create, update or delete an ACL