open-consul/agent/consul/state/acl.go
Matt Keeler 612aba7ced
Dont modify memdb owned token data for get/list requests of tokens (#5412)
Previously we were fixing up the token links directly on the *ACLToken returned by memdb. This invalidated some assumptions that a snapshot is immutable as well as potentially being able to cause a crash.

The fix here is to give the policy link fixing function copy on write semantics. When no fixes are necessary we can return the memdb object directly, otherwise we copy it and create a new list of links.

Eventually we might find a better way to keep those policy links in sync but for now this fixes the issue.
2019-03-04 09:28:46 -05:00

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 false
}
if global && !token.Local {
return true
} else if local && token.Local {
return true
}
return false
})
}
} 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
}