open-consul/agent/consul/state/acl.go
Matt Keeler 99e0a124cb
New ACLs (#4791)
This PR is almost a complete rewrite of the ACL system within Consul. It brings the features more in line with other HashiCorp products. Obviously there is quite a bit left to do here but most of it is related docs, testing and finishing the last few commands in the CLI. I will update the PR description and check off the todos as I finish them over the next few days/week.
Description

At a high level this PR is mainly to split ACL tokens from Policies and to split the concepts of Authorization from Identities. A lot of this PR is mostly just to support CRUD operations on ACLTokens and ACLPolicies. These in and of themselves are not particularly interesting. The bigger conceptual changes are in how tokens get resolved, how backwards compatibility is handled and the separation of policy from identity which could lead the way to allowing for alternative identity providers.

On the surface and with a new cluster the ACL system will look very similar to that of Nomads. Both have tokens and policies. Both have local tokens. The ACL management APIs for both are very similar. I even ripped off Nomad's ACL bootstrap resetting procedure. There are a few key differences though.

    Nomad requires token and policy replication where Consul only requires policy replication with token replication being opt-in. In Consul local tokens only work with token replication being enabled though.
    All policies in Nomad are globally applicable. In Consul all policies are stored and replicated globally but can be scoped to a subset of the datacenters. This allows for more granular access management.
    Unlike Nomad, Consul has legacy baggage in the form of the original ACL system. The ramifications of this are:
        A server running the new system must still support other clients using the legacy system.
        A client running the new system must be able to use the legacy RPCs when the servers in its datacenter are running the legacy system.
        The primary ACL DC's servers running in legacy mode needs to be a gate that keeps everything else in the entire multi-DC cluster running in legacy mode.

So not only does this PR implement the new ACL system but has a legacy mode built in for when the cluster isn't ready for new ACLs. Also detecting that new ACLs can be used is automatic and requires no configuration on the part of administrators. This process is detailed more in the "Transitioning from Legacy to New ACL Mode" section below.
2018-10-19 12:04:07 -04:00

805 lines
21 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 ACLTokenPolicyLink")
}
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",
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,
},
},
"datacenters": &memdb.IndexSchema{
Name: "datacenters",
AllowMissing: true,
Unique: false,
Indexer: &memdb.StringSliceFieldIndex{
Field: "Datacenters",
Lowercase: false,
},
},
},
}
}
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 {
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, true, 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
}
// 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, true, 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) ACLTokensUpsert(idx uint64, tokens structs.ACLTokens, allowCreate 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, allowCreate, 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, allowCreate, allowMissingPolicyIDs, legacy bool) error {
// Check that the ID is set
if token.SecretID == "" {
return ErrMissingACLTokenSecret
}
if !legacy && token.AccessorID == "" {
return ErrMissingACLTokenAccessor
}
// 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)
}
if existing == nil && !allowCreate {
return nil
}
if legacy && existing != nil {
original := existing.(*structs.ACLToken)
if len(original.Policies) > 0 {
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 existing != nil {
token.CreateIndex = existing.(*structs.ACLToken).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) ACLTokenBatchRead(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 := rawToken.(*structs.ACLToken)
if err := s.resolveTokenPolicyLinks(tx, token, true); 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 != "" {
fmt.Println("Listing by 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
}
fmt.Printf("Filtering")
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 := raw.(*structs.ACLToken)
if err := s.resolveTokenPolicyLinks(tx, token, true); 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
}
// ACLTokenDeleteSecret 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) ACLTokenDeleteSecret(idx uint64, secret string) error {
return s.aclTokenDelete(idx, secret, "id")
}
// ACLTokenDeleteAccessor 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) ACLTokenDeleteAccessor(idx uint64, accessor string) error {
return s.aclTokenDelete(idx, accessor, "accessor")
}
func (s *Store) ACLTokensDelete(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()
s.aclTokenDeleteTxn(tx, idx, value, index)
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 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) ACLPoliciesUpsert(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
}
var policyMatch *structs.ACLPolicy
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) ACLPolicyBatchRead(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, datacenter string) (uint64, structs.ACLPolicies, error) {
tx := s.db.Txn(false)
defer tx.Abort()
var iter memdb.ResultIterator
var err error
if datacenter != "" {
iter, err = tx.Get("acl-policies", "datacenters", datacenter)
} else {
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) ACLPoliciesDelete(idx uint64, policyIDs []string) error {
tx := s.db.Txn(true)
defer tx.Abort()
for _, policyID := range policyIDs {
s.aclPolicyDeleteTxn(tx, idx, policyID, "id")
}
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
}