consul: Create anonymous and master tokens
This commit is contained in:
parent
bbde4beefd
commit
5da5df716d
|
@ -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() {
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue