ab57b02ff8
The inversion wasn't noticed because the parallel execution of TokenList tests was operating incorrectly due to variable shadowing.
879 lines
23 KiB
Go
879 lines
23 KiB
Go
package state
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/hashicorp/consul/agent/structs"
|
|
"github.com/hashicorp/go-memdb"
|
|
)
|
|
|
|
type TokenPoliciesIndex struct {
|
|
}
|
|
|
|
func (s *TokenPoliciesIndex) FromObject(obj interface{}) (bool, [][]byte, error) {
|
|
token, ok := obj.(*structs.ACLToken)
|
|
if !ok {
|
|
return false, nil, fmt.Errorf("object is not an ACLToken")
|
|
}
|
|
|
|
links := token.Policies
|
|
|
|
numLinks := len(links)
|
|
if numLinks == 0 {
|
|
return false, nil, nil
|
|
}
|
|
|
|
vals := make([][]byte, 0, numLinks)
|
|
for _, link := range links {
|
|
vals = append(vals, []byte(link.ID+"\x00"))
|
|
}
|
|
|
|
return true, vals, nil
|
|
}
|
|
|
|
func (s *TokenPoliciesIndex) FromArgs(args ...interface{}) ([]byte, error) {
|
|
if len(args) != 1 {
|
|
return nil, fmt.Errorf("must provide only a single argument")
|
|
}
|
|
arg, ok := args[0].(string)
|
|
if !ok {
|
|
return nil, fmt.Errorf("argument must be a string: %#v", args[0])
|
|
}
|
|
// Add the null character as a terminator
|
|
arg += "\x00"
|
|
return []byte(arg), nil
|
|
}
|
|
|
|
func (s *TokenPoliciesIndex) PrefixFromArgs(args ...interface{}) ([]byte, error) {
|
|
val, err := s.FromArgs(args...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Strip the null terminator, the rest is a prefix
|
|
n := len(val)
|
|
if n > 0 {
|
|
return val[:n-1], nil
|
|
}
|
|
return val, nil
|
|
}
|
|
|
|
func tokensTableSchema() *memdb.TableSchema {
|
|
return &memdb.TableSchema{
|
|
Name: "acl-tokens",
|
|
Indexes: map[string]*memdb.IndexSchema{
|
|
"accessor": &memdb.IndexSchema{
|
|
Name: "accessor",
|
|
// DEPRECATED (ACL-Legacy-Compat) - we should not AllowMissing here once legacy compat is removed
|
|
AllowMissing: true,
|
|
Unique: true,
|
|
Indexer: &memdb.UUIDFieldIndex{
|
|
Field: "AccessorID",
|
|
},
|
|
},
|
|
"id": &memdb.IndexSchema{
|
|
Name: "id",
|
|
AllowMissing: false,
|
|
Unique: true,
|
|
Indexer: &memdb.StringFieldIndex{
|
|
Field: "SecretID",
|
|
Lowercase: false,
|
|
},
|
|
},
|
|
"policies": &memdb.IndexSchema{
|
|
Name: "policies",
|
|
// Need to allow missing for the anonymous token
|
|
AllowMissing: true,
|
|
Unique: false,
|
|
Indexer: &TokenPoliciesIndex{},
|
|
},
|
|
"local": &memdb.IndexSchema{
|
|
Name: "local",
|
|
AllowMissing: false,
|
|
Unique: false,
|
|
Indexer: &memdb.ConditionalIndex{
|
|
Conditional: func(obj interface{}) (bool, error) {
|
|
if token, ok := obj.(*structs.ACLToken); ok {
|
|
return token.Local, nil
|
|
}
|
|
return false, nil
|
|
},
|
|
},
|
|
},
|
|
|
|
//DEPRECATED (ACL-Legacy-Compat) - This index is only needed while we support upgrading v1 to v2 acls
|
|
// This table indexes all the ACL tokens that do not have an AccessorID
|
|
"needs-upgrade": &memdb.IndexSchema{
|
|
Name: "needs-upgrade",
|
|
AllowMissing: false,
|
|
Unique: false,
|
|
Indexer: &memdb.ConditionalIndex{
|
|
Conditional: func(obj interface{}) (bool, error) {
|
|
if token, ok := obj.(*structs.ACLToken); ok {
|
|
return token.AccessorID == "", nil
|
|
}
|
|
return false, nil
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func policiesTableSchema() *memdb.TableSchema {
|
|
return &memdb.TableSchema{
|
|
Name: "acl-policies",
|
|
Indexes: map[string]*memdb.IndexSchema{
|
|
"id": &memdb.IndexSchema{
|
|
Name: "id",
|
|
AllowMissing: false,
|
|
Unique: true,
|
|
Indexer: &memdb.UUIDFieldIndex{
|
|
Field: "ID",
|
|
},
|
|
},
|
|
"name": &memdb.IndexSchema{
|
|
Name: "name",
|
|
AllowMissing: false,
|
|
Unique: true,
|
|
Indexer: &memdb.StringFieldIndex{
|
|
Field: "Name",
|
|
// TODO (ACL-V2) - should we coerce to lowercase?
|
|
Lowercase: true,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func init() {
|
|
registerSchema(tokensTableSchema)
|
|
registerSchema(policiesTableSchema)
|
|
}
|
|
|
|
// ACLTokens is used when saving a snapshot
|
|
func (s *Snapshot) ACLTokens() (memdb.ResultIterator, error) {
|
|
// DEPRECATED (ACL-Legacy-Compat) - This could use the "id" index when we remove v1 compat
|
|
iter, err := s.tx.Get("acl-tokens", "id")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return iter, nil
|
|
}
|
|
|
|
// ACLToken is used when restoring from a snapshot. For general inserts, use ACL.
|
|
func (s *Restore) ACLToken(token *structs.ACLToken) error {
|
|
if err := s.tx.Insert("acl-tokens", token); err != nil {
|
|
return fmt.Errorf("failed restoring acl token: %s", err)
|
|
}
|
|
|
|
if err := indexUpdateMaxTxn(s.tx, token.ModifyIndex, "acl-tokens"); err != nil {
|
|
return fmt.Errorf("failed updating index: %s", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ACLPolicies is used when saving a snapshot
|
|
func (s *Snapshot) ACLPolicies() (memdb.ResultIterator, error) {
|
|
iter, err := s.tx.Get("acl-policies", "id")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return iter, nil
|
|
}
|
|
|
|
func (s *Restore) ACLPolicy(policy *structs.ACLPolicy) error {
|
|
if err := s.tx.Insert("acl-policies", policy); err != nil {
|
|
return fmt.Errorf("failed restoring acl policy: %s", err)
|
|
}
|
|
|
|
if err := indexUpdateMaxTxn(s.tx, policy.ModifyIndex, "acl-policies"); err != nil {
|
|
return fmt.Errorf("failed updating index: %s", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ACLBootstrap is used to perform a one-time ACL bootstrap operation on a
|
|
// cluster to get the first management token.
|
|
func (s *Store) ACLBootstrap(idx, resetIndex uint64, token *structs.ACLToken, legacy bool) error {
|
|
tx := s.db.Txn(true)
|
|
defer tx.Abort()
|
|
|
|
// We must have initialized before this will ever be possible.
|
|
existing, err := tx.First("index", "id", "acl-token-bootstrap")
|
|
if err != nil {
|
|
return fmt.Errorf("bootstrap check failed: %v", err)
|
|
}
|
|
if existing != nil {
|
|
if resetIndex == 0 {
|
|
return structs.ACLBootstrapNotAllowedErr
|
|
} else if resetIndex != existing.(*IndexEntry).Value {
|
|
return structs.ACLBootstrapInvalidResetIndexErr
|
|
}
|
|
}
|
|
|
|
if err := s.aclTokenSetTxn(tx, idx, token, false, false, legacy); err != nil {
|
|
return fmt.Errorf("failed inserting bootstrap token: %v", err)
|
|
}
|
|
if err := indexUpdateMaxTxn(tx, idx, "acl-tokens"); err != nil {
|
|
return fmt.Errorf("failed updating index: %s", err)
|
|
}
|
|
if err := tx.Insert("index", &IndexEntry{"acl-token-bootstrap", idx}); err != nil {
|
|
return fmt.Errorf("failed to mark ACL bootstrapping as complete: %v", err)
|
|
}
|
|
tx.Commit()
|
|
return nil
|
|
}
|
|
|
|
// CanBootstrapACLToken checks if bootstrapping is possible and returns the reset index
|
|
func (s *Store) CanBootstrapACLToken() (bool, uint64, error) {
|
|
txn := s.db.Txn(false)
|
|
|
|
// Lookup the bootstrap sentinel
|
|
out, err := txn.First("index", "id", "acl-token-bootstrap")
|
|
if err != nil {
|
|
return false, 0, err
|
|
}
|
|
|
|
// No entry, we haven't bootstrapped yet
|
|
if out == nil {
|
|
return true, 0, nil
|
|
}
|
|
|
|
// Return the reset index if we've already bootstrapped
|
|
return false, out.(*IndexEntry).Value, nil
|
|
}
|
|
|
|
func (s *Store) resolveTokenPolicyLinks(tx *memdb.Txn, token *structs.ACLToken, allowMissing bool) error {
|
|
for linkIndex, link := range token.Policies {
|
|
if link.ID != "" {
|
|
policy, err := s.getPolicyWithTxn(tx, nil, link.ID, "id")
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if policy != nil {
|
|
// the name doesn't matter here
|
|
token.Policies[linkIndex].Name = policy.Name
|
|
} else if !allowMissing {
|
|
return fmt.Errorf("No such policy with ID: %s", link.ID)
|
|
}
|
|
} else {
|
|
return fmt.Errorf("Encountered a Token with policies linked by Name in the state store")
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// fixupTokenPolicyLinks is to be used when retrieving tokens from memdb. The policy links could have gotten
|
|
// stale when a linked policy was deleted or renamed. This will correct them and generate a newly allocated
|
|
// token only when fixes are needed. If the policy links are still accurate then we just return the original
|
|
// token.
|
|
func (s *Store) fixupTokenPolicyLinks(tx *memdb.Txn, original *structs.ACLToken) (*structs.ACLToken, error) {
|
|
owned := false
|
|
token := original
|
|
|
|
cloneToken := func(t *structs.ACLToken, copyNumLinks int) *structs.ACLToken {
|
|
clone := *t
|
|
clone.Policies = make([]structs.ACLTokenPolicyLink, copyNumLinks)
|
|
copy(clone.Policies, t.Policies[:copyNumLinks])
|
|
return &clone
|
|
}
|
|
|
|
for linkIndex, link := range original.Policies {
|
|
if link.ID == "" {
|
|
return nil, fmt.Errorf("Detected corrupted token within the state store - missing policy link ID")
|
|
}
|
|
|
|
policy, err := s.getPolicyWithTxn(tx, nil, link.ID, "id")
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if policy == nil {
|
|
if !owned {
|
|
// clone the token as we cannot touch the original
|
|
token = cloneToken(original, linkIndex)
|
|
owned = true
|
|
}
|
|
// if already owned then we just don't append it.
|
|
} else if policy.Name != link.Name {
|
|
if !owned {
|
|
token = cloneToken(original, linkIndex)
|
|
owned = true
|
|
}
|
|
|
|
// append the corrected policy
|
|
token.Policies = append(token.Policies, structs.ACLTokenPolicyLink{ID: link.ID, Name: policy.Name})
|
|
} else if owned {
|
|
token.Policies = append(token.Policies, link)
|
|
}
|
|
}
|
|
|
|
return token, nil
|
|
}
|
|
|
|
// ACLTokenSet is used to insert an ACL rule into the state store.
|
|
func (s *Store) ACLTokenSet(idx uint64, token *structs.ACLToken, legacy bool) error {
|
|
tx := s.db.Txn(true)
|
|
defer tx.Abort()
|
|
|
|
// Call set on the ACL
|
|
if err := s.aclTokenSetTxn(tx, idx, token, false, false, legacy); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := indexUpdateMaxTxn(tx, idx, "acl-tokens"); err != nil {
|
|
return fmt.Errorf("failed updating index: %s", err)
|
|
}
|
|
|
|
tx.Commit()
|
|
return nil
|
|
}
|
|
|
|
func (s *Store) ACLTokenBatchSet(idx uint64, tokens structs.ACLTokens, cas bool) error {
|
|
tx := s.db.Txn(true)
|
|
defer tx.Abort()
|
|
|
|
for _, token := range tokens {
|
|
// this is only used when doing batch insertions for upgrades and replication. Therefore
|
|
// we take whatever those said.
|
|
if err := s.aclTokenSetTxn(tx, idx, token, cas, true, false); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if err := indexUpdateMaxTxn(tx, idx, "acl-tokens"); err != nil {
|
|
return fmt.Errorf("failed updating index: %s", err)
|
|
}
|
|
|
|
tx.Commit()
|
|
return nil
|
|
}
|
|
|
|
// aclTokenSetTxn is the inner method used to insert an ACL token with the
|
|
// proper indexes into the state store.
|
|
func (s *Store) aclTokenSetTxn(tx *memdb.Txn, idx uint64, token *structs.ACLToken, cas, allowMissingPolicyIDs, legacy bool) error {
|
|
// Check that the ID is set
|
|
if token.SecretID == "" {
|
|
return ErrMissingACLTokenSecret
|
|
}
|
|
|
|
if !legacy && token.AccessorID == "" {
|
|
return ErrMissingACLTokenAccessor
|
|
}
|
|
|
|
// DEPRECATED (ACL-Legacy-Compat)
|
|
if token.Rules != "" {
|
|
// When we update a legacy acl token we may have to correct old HCL to
|
|
// prevent the propagation of older syntax into the state store and
|
|
// into in-memory representations.
|
|
correctedRules := structs.SanitizeLegacyACLTokenRules(token.Rules)
|
|
if correctedRules != "" {
|
|
token.Rules = correctedRules
|
|
}
|
|
}
|
|
|
|
// Check for an existing ACL
|
|
// DEPRECATED (ACL-Legacy-Compat) - transition to using accessor index instead of secret once v1 compat is removed
|
|
existing, err := tx.First("acl-tokens", "id", token.SecretID)
|
|
if err != nil {
|
|
return fmt.Errorf("failed token lookup: %s", err)
|
|
}
|
|
|
|
var original *structs.ACLToken
|
|
|
|
if existing != nil {
|
|
original = existing.(*structs.ACLToken)
|
|
}
|
|
|
|
if cas {
|
|
// set-if-unset case
|
|
if token.ModifyIndex == 0 && original != nil {
|
|
return nil
|
|
}
|
|
// token already deleted
|
|
if token.ModifyIndex != 0 && original == nil {
|
|
return nil
|
|
}
|
|
// check for other modifications
|
|
if token.ModifyIndex != 0 && token.ModifyIndex != original.ModifyIndex {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
if legacy && original != nil {
|
|
if len(original.Policies) > 0 || original.Type == "" {
|
|
return fmt.Errorf("failed inserting acl token: cannot use legacy endpoint to modify a non-legacy token")
|
|
}
|
|
|
|
token.AccessorID = original.AccessorID
|
|
}
|
|
|
|
if err := s.resolveTokenPolicyLinks(tx, token, allowMissingPolicyIDs); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Set the indexes
|
|
if original != nil {
|
|
if original.AccessorID != "" && token.AccessorID != original.AccessorID {
|
|
return fmt.Errorf("The ACL Token AccessorID field is immutable")
|
|
}
|
|
|
|
if token.SecretID != original.SecretID {
|
|
return fmt.Errorf("The ACL Token SecretID field is immutable")
|
|
}
|
|
|
|
token.CreateIndex = original.CreateIndex
|
|
token.ModifyIndex = idx
|
|
} else {
|
|
token.CreateIndex = idx
|
|
token.ModifyIndex = idx
|
|
}
|
|
|
|
// Insert the ACL
|
|
if err := tx.Insert("acl-tokens", token); err != nil {
|
|
return fmt.Errorf("failed inserting acl token: %v", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ACLTokenGetBySecret is used to look up an existing ACL token by its SecretID.
|
|
func (s *Store) ACLTokenGetBySecret(ws memdb.WatchSet, secret string) (uint64, *structs.ACLToken, error) {
|
|
return s.aclTokenGet(ws, secret, "id")
|
|
}
|
|
|
|
// ACLTokenGetByAccessor is used to look up an existing ACL token by its AccessorID.
|
|
func (s *Store) ACLTokenGetByAccessor(ws memdb.WatchSet, accessor string) (uint64, *structs.ACLToken, error) {
|
|
return s.aclTokenGet(ws, accessor, "accessor")
|
|
}
|
|
|
|
// aclTokenGet looks up a token using one of the indexes provided
|
|
func (s *Store) aclTokenGet(ws memdb.WatchSet, value, index string) (uint64, *structs.ACLToken, error) {
|
|
tx := s.db.Txn(false)
|
|
defer tx.Abort()
|
|
|
|
token, err := s.aclTokenGetTxn(tx, ws, value, index)
|
|
if err != nil {
|
|
return 0, nil, fmt.Errorf("failed acl token lookup: %v", err)
|
|
}
|
|
|
|
idx := maxIndexTxn(tx, "acl-tokens")
|
|
return idx, token, nil
|
|
}
|
|
|
|
func (s *Store) ACLTokenBatchGet(ws memdb.WatchSet, accessors []string) (uint64, structs.ACLTokens, error) {
|
|
tx := s.db.Txn(false)
|
|
defer tx.Abort()
|
|
|
|
tokens := make(structs.ACLTokens, 0)
|
|
for _, accessor := range accessors {
|
|
token, err := s.aclTokenGetTxn(tx, ws, accessor, "accessor")
|
|
if err != nil {
|
|
return 0, nil, fmt.Errorf("failed acl token lookup: %v", err)
|
|
}
|
|
|
|
// token == nil is valid and will indic
|
|
if token != nil {
|
|
tokens = append(tokens, token)
|
|
}
|
|
}
|
|
|
|
idx := maxIndexTxn(tx, "acl-tokens")
|
|
|
|
return idx, tokens, nil
|
|
}
|
|
|
|
func (s *Store) aclTokenGetTxn(tx *memdb.Txn, ws memdb.WatchSet, value, index string) (*structs.ACLToken, error) {
|
|
watchCh, rawToken, err := tx.FirstWatch("acl-tokens", index, value)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed acl token lookup: %v", err)
|
|
}
|
|
ws.Add(watchCh)
|
|
|
|
if rawToken != nil {
|
|
token, err := s.fixupTokenPolicyLinks(tx, rawToken.(*structs.ACLToken))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return token, nil
|
|
}
|
|
|
|
return nil, nil
|
|
}
|
|
|
|
// ACLTokenList is used to list out all of the ACLs in the state store.
|
|
func (s *Store) ACLTokenList(ws memdb.WatchSet, local, global bool, policy string) (uint64, structs.ACLTokens, error) {
|
|
tx := s.db.Txn(false)
|
|
defer tx.Abort()
|
|
|
|
var iter memdb.ResultIterator
|
|
var err error
|
|
|
|
// Note global == local works when both are true or false. It is not valid to set both
|
|
// to false but for defaulted structs (zero values for both) we want it to list out
|
|
// all tokens so our checks just ensure that global == local
|
|
|
|
if policy != "" {
|
|
iter, err = tx.Get("acl-tokens", "policies", policy)
|
|
if err == nil && global != local {
|
|
iter = memdb.NewFilterIterator(iter, func(raw interface{}) bool {
|
|
token, ok := raw.(*structs.ACLToken)
|
|
if !ok {
|
|
return true
|
|
}
|
|
|
|
if global && !token.Local {
|
|
return false
|
|
} else if local && token.Local {
|
|
return false
|
|
}
|
|
|
|
return true
|
|
})
|
|
}
|
|
} else if global == local {
|
|
iter, err = tx.Get("acl-tokens", "id")
|
|
} else if global {
|
|
iter, err = tx.Get("acl-tokens", "local", false)
|
|
} else {
|
|
iter, err = tx.Get("acl-tokens", "local", true)
|
|
}
|
|
|
|
if err != nil {
|
|
return 0, nil, fmt.Errorf("failed acl token lookup: %v", err)
|
|
}
|
|
ws.Add(iter.WatchCh())
|
|
|
|
var result structs.ACLTokens
|
|
for raw := iter.Next(); raw != nil; raw = iter.Next() {
|
|
token, err := s.fixupTokenPolicyLinks(tx, raw.(*structs.ACLToken))
|
|
|
|
if err != nil {
|
|
return 0, nil, err
|
|
}
|
|
result = append(result, token)
|
|
}
|
|
|
|
// Get the table index.
|
|
idx := maxIndexTxn(tx, "acl-tokens")
|
|
|
|
return idx, result, nil
|
|
}
|
|
|
|
func (s *Store) ACLTokenListUpgradeable(max int) (structs.ACLTokens, <-chan struct{}, error) {
|
|
tx := s.db.Txn(false)
|
|
defer tx.Abort()
|
|
|
|
iter, err := tx.Get("acl-tokens", "needs-upgrade", true)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("failed acl token listing: %v", err)
|
|
}
|
|
|
|
var tokens structs.ACLTokens
|
|
i := 0
|
|
for token := iter.Next(); token != nil; token = iter.Next() {
|
|
tokens = append(tokens, token.(*structs.ACLToken))
|
|
i += 1
|
|
if i >= max {
|
|
return tokens, nil, nil
|
|
}
|
|
}
|
|
|
|
return tokens, iter.WatchCh(), nil
|
|
}
|
|
|
|
// ACLTokenDeleteBySecret is used to remove an existing ACL from the state store. If
|
|
// the ACL does not exist this is a no-op and no error is returned.
|
|
func (s *Store) ACLTokenDeleteBySecret(idx uint64, secret string) error {
|
|
return s.aclTokenDelete(idx, secret, "id")
|
|
}
|
|
|
|
// ACLTokenDeleteByAccessor is used to remove an existing ACL from the state store. If
|
|
// the ACL does not exist this is a no-op and no error is returned.
|
|
func (s *Store) ACLTokenDeleteByAccessor(idx uint64, accessor string) error {
|
|
return s.aclTokenDelete(idx, accessor, "accessor")
|
|
}
|
|
|
|
func (s *Store) ACLTokenBatchDelete(idx uint64, tokenIDs []string) error {
|
|
tx := s.db.Txn(true)
|
|
defer tx.Abort()
|
|
|
|
for _, tokenID := range tokenIDs {
|
|
if err := s.aclTokenDeleteTxn(tx, idx, tokenID, "accessor"); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
tx.Commit()
|
|
return nil
|
|
}
|
|
|
|
func (s *Store) aclTokenDelete(idx uint64, value, index string) error {
|
|
tx := s.db.Txn(true)
|
|
defer tx.Abort()
|
|
|
|
if err := s.aclTokenDeleteTxn(tx, idx, value, index); err != nil {
|
|
return err
|
|
}
|
|
|
|
tx.Commit()
|
|
return nil
|
|
}
|
|
|
|
func (s *Store) aclTokenDeleteTxn(tx *memdb.Txn, idx uint64, value, index string) error {
|
|
// Look up the existing token
|
|
token, err := tx.First("acl-tokens", index, value)
|
|
if err != nil {
|
|
return fmt.Errorf("failed acl token lookup: %v", err)
|
|
}
|
|
|
|
if token == nil {
|
|
return nil
|
|
}
|
|
|
|
if token.(*structs.ACLToken).AccessorID == structs.ACLTokenAnonymousID {
|
|
return fmt.Errorf("Deletion of the builtin anonymous token is not permitted")
|
|
}
|
|
|
|
if err := tx.Delete("acl-tokens", token); err != nil {
|
|
return fmt.Errorf("failed deleting acl token: %v", err)
|
|
}
|
|
if err := indexUpdateMaxTxn(tx, idx, "acl-tokens"); err != nil {
|
|
return fmt.Errorf("failed updating index: %v", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *Store) ACLPolicyBatchSet(idx uint64, policies structs.ACLPolicies) error {
|
|
tx := s.db.Txn(true)
|
|
defer tx.Abort()
|
|
|
|
for _, policy := range policies {
|
|
if err := s.aclPolicySetTxn(tx, idx, policy); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if err := indexUpdateMaxTxn(tx, idx, "acl-policies"); err != nil {
|
|
return fmt.Errorf("failed updating index: %s", err)
|
|
}
|
|
|
|
tx.Commit()
|
|
return nil
|
|
}
|
|
|
|
func (s *Store) ACLPolicySet(idx uint64, policy *structs.ACLPolicy) error {
|
|
tx := s.db.Txn(true)
|
|
defer tx.Abort()
|
|
|
|
if err := s.aclPolicySetTxn(tx, idx, policy); err != nil {
|
|
return err
|
|
}
|
|
if err := indexUpdateMaxTxn(tx, idx, "acl-policies"); err != nil {
|
|
return fmt.Errorf("failed updating index: %s", err)
|
|
}
|
|
|
|
tx.Commit()
|
|
return nil
|
|
}
|
|
|
|
func (s *Store) aclPolicySetTxn(tx *memdb.Txn, idx uint64, policy *structs.ACLPolicy) error {
|
|
// Check that the ID is set
|
|
if policy.ID == "" {
|
|
return ErrMissingACLPolicyID
|
|
}
|
|
|
|
if policy.Name == "" {
|
|
return ErrMissingACLPolicyName
|
|
}
|
|
|
|
existing, err := tx.First("acl-policies", "id", policy.ID)
|
|
if err != nil {
|
|
return fmt.Errorf("failed acl policy lookup: %v", err)
|
|
}
|
|
|
|
if existing != nil {
|
|
policyMatch := existing.(*structs.ACLPolicy)
|
|
|
|
if policy.ID == structs.ACLPolicyGlobalManagementID {
|
|
// Only the name and description are modifiable
|
|
if policy.Rules != policyMatch.Rules {
|
|
return fmt.Errorf("Changing the Rules for the builtin global-management policy is not permitted")
|
|
}
|
|
|
|
if policy.Datacenters != nil && len(policy.Datacenters) != 0 {
|
|
return fmt.Errorf("Changing the Datacenters of the builtin global-management policy is not permitted")
|
|
}
|
|
}
|
|
}
|
|
|
|
// ensure the name is unique (cannot conflict with another policy with a different ID)
|
|
nameMatch, err := tx.First("acl-policies", "name", policy.Name)
|
|
if err != nil {
|
|
return fmt.Errorf("failed acl policy lookup: %v", err)
|
|
}
|
|
if nameMatch != nil && policy.ID != nameMatch.(*structs.ACLPolicy).ID {
|
|
return fmt.Errorf("A policy with name %q already exists", policy.Name)
|
|
}
|
|
|
|
// Set the indexes
|
|
if existing != nil {
|
|
policy.CreateIndex = existing.(*structs.ACLPolicy).CreateIndex
|
|
policy.ModifyIndex = idx
|
|
} else {
|
|
policy.CreateIndex = idx
|
|
policy.ModifyIndex = idx
|
|
}
|
|
|
|
// Insert the ACL
|
|
if err := tx.Insert("acl-policies", policy); err != nil {
|
|
return fmt.Errorf("failed inserting acl policy: %v", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *Store) ACLPolicyGetByID(ws memdb.WatchSet, id string) (uint64, *structs.ACLPolicy, error) {
|
|
return s.aclPolicyGet(ws, id, "id")
|
|
}
|
|
|
|
func (s *Store) ACLPolicyGetByName(ws memdb.WatchSet, name string) (uint64, *structs.ACLPolicy, error) {
|
|
return s.aclPolicyGet(ws, name, "name")
|
|
}
|
|
|
|
func (s *Store) ACLPolicyBatchGet(ws memdb.WatchSet, ids []string) (uint64, structs.ACLPolicies, error) {
|
|
tx := s.db.Txn(false)
|
|
defer tx.Abort()
|
|
|
|
policies := make(structs.ACLPolicies, 0)
|
|
for _, pid := range ids {
|
|
policy, err := s.getPolicyWithTxn(tx, ws, pid, "id")
|
|
if err != nil {
|
|
return 0, nil, err
|
|
}
|
|
|
|
if policy != nil {
|
|
policies = append(policies, policy)
|
|
}
|
|
}
|
|
|
|
idx := maxIndexTxn(tx, "acl-policies")
|
|
|
|
return idx, policies, nil
|
|
}
|
|
|
|
func (s *Store) getPolicyWithTxn(tx *memdb.Txn, ws memdb.WatchSet, value, index string) (*structs.ACLPolicy, error) {
|
|
watchCh, policy, err := tx.FirstWatch("acl-policies", index, value)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed acl policy lookup: %v", err)
|
|
}
|
|
ws.Add(watchCh)
|
|
|
|
if err != nil || policy == nil {
|
|
return nil, err
|
|
}
|
|
|
|
return policy.(*structs.ACLPolicy), nil
|
|
}
|
|
|
|
func (s *Store) aclPolicyGet(ws memdb.WatchSet, value, index string) (uint64, *structs.ACLPolicy, error) {
|
|
tx := s.db.Txn(false)
|
|
defer tx.Abort()
|
|
|
|
policy, err := s.getPolicyWithTxn(tx, ws, value, index)
|
|
if err != nil {
|
|
return 0, nil, err
|
|
}
|
|
|
|
idx := maxIndexTxn(tx, "acl-policies")
|
|
|
|
return idx, policy, nil
|
|
}
|
|
|
|
func (s *Store) ACLPolicyList(ws memdb.WatchSet) (uint64, structs.ACLPolicies, error) {
|
|
tx := s.db.Txn(false)
|
|
defer tx.Abort()
|
|
|
|
iter, err := tx.Get("acl-policies", "id")
|
|
if err != nil {
|
|
return 0, nil, fmt.Errorf("failed acl policy lookup: %v", err)
|
|
}
|
|
ws.Add(iter.WatchCh())
|
|
|
|
var result structs.ACLPolicies
|
|
for policy := iter.Next(); policy != nil; policy = iter.Next() {
|
|
result = append(result, policy.(*structs.ACLPolicy))
|
|
}
|
|
|
|
// Get the table index.
|
|
idx := maxIndexTxn(tx, "acl-policies")
|
|
|
|
return idx, result, nil
|
|
}
|
|
|
|
func (s *Store) ACLPolicyDeleteByID(idx uint64, id string) error {
|
|
return s.aclPolicyDelete(idx, id, "id")
|
|
}
|
|
|
|
func (s *Store) ACLPolicyDeleteByName(idx uint64, name string) error {
|
|
return s.aclPolicyDelete(idx, name, "name")
|
|
}
|
|
|
|
func (s *Store) ACLPolicyBatchDelete(idx uint64, policyIDs []string) error {
|
|
tx := s.db.Txn(true)
|
|
defer tx.Abort()
|
|
|
|
for _, policyID := range policyIDs {
|
|
if err := s.aclPolicyDeleteTxn(tx, idx, policyID, "id"); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if err := indexUpdateMaxTxn(tx, idx, "acl-policies"); err != nil {
|
|
return fmt.Errorf("failed updating index: %v", err)
|
|
}
|
|
tx.Commit()
|
|
return nil
|
|
}
|
|
|
|
func (s *Store) aclPolicyDelete(idx uint64, value, index string) error {
|
|
tx := s.db.Txn(true)
|
|
defer tx.Abort()
|
|
|
|
if err := s.aclPolicyDeleteTxn(tx, idx, value, index); err != nil {
|
|
return err
|
|
}
|
|
if err := indexUpdateMaxTxn(tx, idx, "acl-policies"); err != nil {
|
|
return fmt.Errorf("failed updating index: %v", err)
|
|
}
|
|
|
|
tx.Commit()
|
|
return nil
|
|
}
|
|
|
|
func (s *Store) aclPolicyDeleteTxn(tx *memdb.Txn, idx uint64, value, index string) error {
|
|
// Look up the existing token
|
|
rawPolicy, err := tx.First("acl-policies", index, value)
|
|
if err != nil {
|
|
return fmt.Errorf("failed acl policy lookup: %v", err)
|
|
}
|
|
|
|
if rawPolicy == nil {
|
|
return nil
|
|
}
|
|
|
|
policy := rawPolicy.(*structs.ACLPolicy)
|
|
|
|
if policy.ID == structs.ACLPolicyGlobalManagementID {
|
|
return fmt.Errorf("Deletion of the builtin global-management policy is not permitted")
|
|
}
|
|
|
|
if err := tx.Delete("acl-policies", policy); err != nil {
|
|
return fmt.Errorf("failed deleting acl policy: %v", err)
|
|
}
|
|
return nil
|
|
}
|