open-consul/agent/consul/state/acl.go

1872 lines
52 KiB
Go

package state
import (
"encoding/binary"
"fmt"
"time"
memdb "github.com/hashicorp/go-memdb"
"github.com/hashicorp/consul/agent/structs"
pbacl "github.com/hashicorp/consul/proto/pbacl"
)
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
}
type TokenRolesIndex struct {
}
func (s *TokenRolesIndex) 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.Roles
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 *TokenRolesIndex) 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 *TokenRolesIndex) 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
}
type TokenExpirationIndex struct {
LocalFilter bool
}
func (s *TokenExpirationIndex) encodeTime(t time.Time) []byte {
val := t.Unix()
buf := make([]byte, 8)
binary.BigEndian.PutUint64(buf, uint64(val))
return buf
}
func (s *TokenExpirationIndex) FromObject(obj interface{}) (bool, []byte, error) {
token, ok := obj.(*structs.ACLToken)
if !ok {
return false, nil, fmt.Errorf("object is not an ACLToken")
}
if s.LocalFilter != token.Local {
return false, nil, nil
}
if !token.HasExpirationTime() {
return false, nil, nil
}
if token.ExpirationTime.Unix() < 0 {
return false, nil, fmt.Errorf("token expiration time cannot be before the unix epoch: %s", token.ExpirationTime)
}
buf := s.encodeTime(*token.ExpirationTime)
return true, buf, nil
}
func (s *TokenExpirationIndex) FromArgs(args ...interface{}) ([]byte, error) {
if len(args) != 1 {
return nil, fmt.Errorf("must provide only a single argument")
}
arg, ok := args[0].(time.Time)
if !ok {
return nil, fmt.Errorf("argument must be a time.Time: %#v", args[0])
}
if arg.Unix() < 0 {
return nil, fmt.Errorf("argument must be a time.Time after the unix epoch: %s", args[0])
}
buf := s.encodeTime(arg)
return buf, nil
}
// ACLTokens is used when saving a snapshot
func (s *Snapshot) ACLTokens() (memdb.ResultIterator, error) {
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 {
return aclTokenInsert(s.tx, token)
}
// ACLPolicies is used when saving a snapshot
func (s *Snapshot) ACLPolicies() (memdb.ResultIterator, error) {
return s.tx.Get(tableACLPolicies, indexID)
}
func (s *Restore) ACLPolicy(policy *structs.ACLPolicy) error {
return aclPolicyInsert(s.tx, policy)
}
// ACLRoles is used when saving a snapshot
func (s *Snapshot) ACLRoles() (memdb.ResultIterator, error) {
iter, err := s.tx.Get(tableACLRoles, indexID)
if err != nil {
return nil, err
}
return iter, nil
}
func (s *Restore) ACLRole(role *structs.ACLRole) error {
return aclRoleInsert(s.tx, role)
}
// ACLBindingRules is used when saving a snapshot
func (s *Snapshot) ACLBindingRules() (memdb.ResultIterator, error) {
iter, err := s.tx.Get("acl-binding-rules", "id")
if err != nil {
return nil, err
}
return iter, nil
}
func (s *Restore) ACLBindingRule(rule *structs.ACLBindingRule) error {
return aclBindingRuleInsert(s.tx, rule)
}
// ACLAuthMethods is used when saving a snapshot
func (s *Snapshot) ACLAuthMethods() (memdb.ResultIterator, error) {
iter, err := s.tx.Get("acl-auth-methods", "id")
if err != nil {
return nil, err
}
return iter, nil
}
func (s *Restore) ACLAuthMethod(method *structs.ACLAuthMethod) error {
return aclAuthMethodInsert(s.tx, method)
}
// 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.WriteTxn(idx)
defer tx.Abort()
// We must have initialized before this will ever be possible.
existing, err := tx.First(tableIndex, indexID, "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 := aclTokenSetTxn(tx, idx, token, ACLTokenSetOptions{Legacy: legacy}); err != nil {
return fmt.Errorf("failed inserting bootstrap token: %v", err)
}
if err := tx.Insert(tableIndex, &IndexEntry{"acl-token-bootstrap", idx}); err != nil {
return fmt.Errorf("failed to mark ACL bootstrapping as complete: %v", err)
}
return tx.Commit()
}
// CanBootstrapACLToken checks if bootstrapping is possible and returns the reset index
func (s *Store) CanBootstrapACLToken() (bool, uint64, error) {
tx := s.db.Txn(false)
// Lookup the bootstrap sentinel
out, err := tx.First(tableIndex, indexID, "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
}
// resolveACLLinks is used to populate the links Name given its ID as the object is inserted
// into the Store. This will modify the links in place and should not be performed on data
// already inserted into the store without copying first. This is an optimization so that when
// the link is read back out of the Store, we hopefully will not have to copy the object being retrieved
// to update the name. Unlike the older functions to operate specifically on role or policy links
// this function does not itself handle the case where the id cannot be found. Instead the
// getName function should handle that and return an error if necessary
func resolveACLLinks(tx ReadTxn, links []pbacl.ACLLink, getName func(ReadTxn, string) (string, error)) (int, error) {
var numValid int
for linkIndex, link := range links {
if link.ID != "" {
name, err := getName(tx, link.ID)
if err != nil {
return 0, err
}
// the name doesn't matter here
if name != "" {
links[linkIndex].Name = name
numValid++
}
} else {
return 0, fmt.Errorf("Encountered an ACL resource linked by Name in the state store")
}
}
return numValid, nil
}
// fixupACLLinks is used to ensure data returned by read operations have the most up to date name
// associated with the ID of the link. Ideally this will be a no-op if the names are already correct
// however if a linked resource was renamed it might be stale. This function will treat the incoming
// links with copy-on-write semantics and its output will indicate whether any modifications were made.
func fixupACLLinks(tx ReadTxn, original []pbacl.ACLLink, getName func(ReadTxn, string) (string, error)) ([]pbacl.ACLLink, bool, error) {
owned := false
links := original
cloneLinks := func(l []pbacl.ACLLink, copyNumLinks int) []pbacl.ACLLink {
clone := make([]pbacl.ACLLink, copyNumLinks)
copy(clone, l[:copyNumLinks])
return clone
}
for linkIndex, link := range original {
name, err := getName(tx, link.ID)
if err != nil {
return nil, false, err
}
if name == "" {
if !owned {
// clone the original as we cannot modify anything stored in memdb
links = cloneLinks(original, linkIndex)
owned = true
}
// if already owned then we just don't append it.
} else if name != link.Name {
if !owned {
links = cloneLinks(original, linkIndex)
owned = true
}
// append the corrected link
links = append(links, pbacl.ACLLink{ID: link.ID, Name: name})
} else if owned {
links = append(links, link)
}
}
return links, owned, nil
}
func resolveTokenPolicyLinks(tx ReadTxn, token *structs.ACLToken, allowMissing bool) (int, error) {
var numValid int
for linkIndex, link := range token.Policies {
if link.ID != "" {
policy, err := getPolicyWithTxn(tx, nil, link.ID, aclPolicyGetByID, &token.EnterpriseMeta)
if err != nil {
return 0, err
}
if policy != nil {
// the name doesn't matter here
token.Policies[linkIndex].Name = policy.Name
numValid++
} else if !allowMissing {
return 0, fmt.Errorf("No such policy with ID: %s", link.ID)
}
} else {
return 0, fmt.Errorf("Encountered a Token with policies linked by Name in the state store")
}
}
return numValid, 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 fixupTokenPolicyLinks(tx ReadTxn, 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 := getPolicyWithTxn(tx, nil, link.ID, aclPolicyGetByID, &token.EnterpriseMeta)
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
}
func resolveTokenRoleLinks(tx ReadTxn, token *structs.ACLToken, allowMissing bool) (int, error) {
var numValid int
for linkIndex, link := range token.Roles {
if link.ID != "" {
role, err := getRoleWithTxn(tx, nil, link.ID, aclRoleGetByID, &token.EnterpriseMeta)
if err != nil {
return 0, err
}
if role != nil {
// the name doesn't matter here
token.Roles[linkIndex].Name = role.Name
numValid++
} else if !allowMissing {
return 0, fmt.Errorf("No such role with ID: %s", link.ID)
}
} else {
return 0, fmt.Errorf("Encountered a Token with roles linked by Name in the state store")
}
}
return numValid, nil
}
// fixupTokenRoleLinks is to be used when retrieving tokens from memdb. The role links could have gotten
// stale when a linked role was deleted or renamed. This will correct them and generate a newly allocated
// token only when fixes are needed. If the role links are still accurate then we just return the original
// token.
func fixupTokenRoleLinks(tx ReadTxn, original *structs.ACLToken) (*structs.ACLToken, error) {
owned := false
token := original
cloneToken := func(t *structs.ACLToken, copyNumLinks int) *structs.ACLToken {
clone := *t
clone.Roles = make([]structs.ACLTokenRoleLink, copyNumLinks)
copy(clone.Roles, t.Roles[:copyNumLinks])
return &clone
}
for linkIndex, link := range original.Roles {
if link.ID == "" {
return nil, fmt.Errorf("Detected corrupted token within the state store - missing role link ID")
}
role, err := getRoleWithTxn(tx, nil, link.ID, aclRoleGetByID, &original.EnterpriseMeta)
if err != nil {
return nil, err
}
if role == 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 role.Name != link.Name {
if !owned {
token = cloneToken(original, linkIndex)
owned = true
}
// append the corrected policy
token.Roles = append(token.Roles, structs.ACLTokenRoleLink{ID: link.ID, Name: role.Name})
} else if owned {
token.Roles = append(token.Roles, link)
}
}
return token, nil
}
func resolveRolePolicyLinks(tx ReadTxn, role *structs.ACLRole, allowMissing bool) error {
for linkIndex, link := range role.Policies {
if link.ID == "" {
return fmt.Errorf("Encountered a Role with policies linked by Name in the state store")
}
policy, err := getPolicyWithTxn(tx, nil, link.ID, aclPolicyGetByID, &role.EnterpriseMeta)
if err != nil {
return err
}
if policy != nil {
// the name doesn't matter here
role.Policies[linkIndex].Name = policy.Name
} else if !allowMissing {
return fmt.Errorf("No such policy with ID: %s", link.ID)
}
}
return nil
}
// fixupRolePolicyLinks is to be used when retrieving roles 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
// role only when fixes are needed. If the policy links are still accurate then we just return the original
// role.
func fixupRolePolicyLinks(tx ReadTxn, original *structs.ACLRole) (*structs.ACLRole, error) {
owned := false
role := original
cloneRole := func(t *structs.ACLRole, copyNumLinks int) *structs.ACLRole {
clone := *t
clone.Policies = make([]structs.ACLRolePolicyLink, copyNumLinks)
copy(clone.Policies, t.Policies[:copyNumLinks])
return &clone
}
for linkIndex, link := range original.Policies {
if link.ID == "" {
return nil, fmt.Errorf("Detected corrupted role within the state store - missing policy link ID")
}
policy, err := getPolicyWithTxn(tx, nil, link.ID, aclPolicyGetByID, &original.EnterpriseMeta)
if err != nil {
return nil, err
}
if policy == nil {
if !owned {
// clone the token as we cannot touch the original
role = cloneRole(original, linkIndex)
owned = true
}
// if already owned then we just don't append it.
} else if policy.Name != link.Name {
if !owned {
role = cloneRole(original, linkIndex)
owned = true
}
// append the corrected policy
role.Policies = append(role.Policies, structs.ACLRolePolicyLink{ID: link.ID, Name: policy.Name})
} else if owned {
role.Policies = append(role.Policies, link)
}
}
return role, 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.WriteTxn(idx)
defer tx.Abort()
// Call set on the ACL
if err := aclTokenSetTxn(tx, idx, token, ACLTokenSetOptions{Legacy: legacy}); err != nil {
return err
}
return tx.Commit()
}
type ACLTokenSetOptions struct {
CAS bool
AllowMissingPolicyAndRoleIDs bool
ProhibitUnprivileged bool
Legacy bool
FromReplication bool
}
func (s *Store) ACLTokenBatchSet(idx uint64, tokens structs.ACLTokens, opts ACLTokenSetOptions) error {
if opts.Legacy {
return fmt.Errorf("failed inserting acl token: cannot use this endpoint to persist legacy tokens")
}
tx := s.db.WriteTxn(idx)
defer tx.Abort()
for _, token := range tokens {
if err := aclTokenSetTxn(tx, idx, token, opts); err != nil {
return err
}
}
return tx.Commit()
}
// aclTokenSetTxn is the inner method used to insert an ACL token with the
// proper indexes into the state store.
func aclTokenSetTxn(tx WriteTxn, idx uint64, token *structs.ACLToken, opts ACLTokenSetOptions) error {
// Check that the ID is set
if token.SecretID == "" {
return ErrMissingACLTokenSecret
}
if !opts.Legacy && token.AccessorID == "" {
return ErrMissingACLTokenAccessor
}
if opts.FromReplication && token.Local {
return fmt.Errorf("Cannot replicate local tokens")
}
// 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 := aclTokenGetFromIndex(tx, token.SecretID, "id", nil)
if err != nil {
return fmt.Errorf("failed token lookup: %s", err)
}
var original *structs.ACLToken
if existing != nil {
original = existing.(*structs.ACLToken)
}
if opts.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 opts.Legacy && original != nil {
if original.UsesNonLegacyFields() {
return fmt.Errorf("failed inserting acl token: cannot use legacy endpoint to modify a non-legacy token")
}
token.AccessorID = original.AccessorID
}
if err := aclTokenUpsertValidateEnterprise(tx, token, original); err != nil {
return err
}
var numValidPolicies int
if numValidPolicies, err = resolveTokenPolicyLinks(tx, token, opts.AllowMissingPolicyAndRoleIDs); err != nil {
return err
}
var numValidRoles int
if numValidRoles, err = resolveTokenRoleLinks(tx, token, opts.AllowMissingPolicyAndRoleIDs); err != nil {
return err
}
if token.AuthMethod != "" && !opts.FromReplication {
method, err := getAuthMethodWithTxn(tx, nil, token.AuthMethod, token.ACLAuthMethodEnterpriseMeta.ToEnterpriseMeta())
if err != nil {
return err
} else if method == nil {
return fmt.Errorf("No such auth method with Name: %s", token.AuthMethod)
}
}
for _, svcid := range token.ServiceIdentities {
if svcid.ServiceName == "" {
return fmt.Errorf("Encountered a Token with an empty service identity name in the state store")
}
}
for _, nodeid := range token.NodeIdentities {
if nodeid.NodeName == "" {
return fmt.Errorf("Encountered a Token with an empty node identity name in the state store")
}
if nodeid.Datacenter == "" {
return fmt.Errorf("Encountered a Token with an empty node identity datacenter in the state store")
}
}
if opts.ProhibitUnprivileged {
if numValidRoles == 0 && numValidPolicies == 0 && len(token.ServiceIdentities) == 0 && len(token.NodeIdentities) == 0 {
return ErrTokenHasNoPrivileges
}
}
// 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
}
// ensure that a hash is set
token.SetHash(false)
return aclTokenInsert(tx, token)
}
// ACLTokenGetBySecret is used to look up an existing ACL token by its SecretID.
func (s *Store) ACLTokenGetBySecret(ws memdb.WatchSet, secret string, entMeta *structs.EnterpriseMeta) (uint64, *structs.ACLToken, error) {
return s.aclTokenGet(ws, secret, "id", entMeta)
}
// ACLTokenGetByAccessor is used to look up an existing ACL token by its AccessorID.
func (s *Store) ACLTokenGetByAccessor(ws memdb.WatchSet, accessor string, entMeta *structs.EnterpriseMeta) (uint64, *structs.ACLToken, error) {
return s.aclTokenGet(ws, accessor, "accessor", entMeta)
}
// aclTokenGet looks up a token using one of the indexes provided
func (s *Store) aclTokenGet(ws memdb.WatchSet, value, index string, entMeta *structs.EnterpriseMeta) (uint64, *structs.ACLToken, error) {
tx := s.db.Txn(false)
defer tx.Abort()
token, err := aclTokenGetTxn(tx, ws, value, index, entMeta)
if err != nil {
return 0, nil, err
}
idx := aclTokenMaxIndex(tx, token, entMeta)
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 := aclTokenGetTxn(tx, ws, accessor, "accessor", nil)
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 aclTokenGetTxn(tx ReadTxn, ws memdb.WatchSet, value, index string, entMeta *structs.EnterpriseMeta) (*structs.ACLToken, error) {
watchCh, rawToken, err := aclTokenGetFromIndex(tx, value, index, entMeta)
if err != nil {
return nil, fmt.Errorf("failed acl token lookup: %v", err)
}
ws.Add(watchCh)
if rawToken != nil {
token := rawToken.(*structs.ACLToken)
token, err := fixupTokenPolicyLinks(tx, token)
if err != nil {
return nil, err
}
token, err = fixupTokenRoleLinks(tx, token)
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, role, methodName string, methodMeta, entMeta *structs.EnterpriseMeta) (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
needLocalityFilter := false
if policy == "" && role == "" && methodName == "" {
if global == local {
iter, err = aclTokenListAll(tx, entMeta)
} else if global {
iter, err = aclTokenListGlobal(tx, entMeta)
} else {
iter, err = aclTokenListLocal(tx, entMeta)
}
} else if policy != "" && role == "" && methodName == "" {
iter, err = aclTokenListByPolicy(tx, policy, entMeta)
needLocalityFilter = true
} else if policy == "" && role != "" && methodName == "" {
iter, err = aclTokenListByRole(tx, role, entMeta)
needLocalityFilter = true
} else if policy == "" && role == "" && methodName != "" {
iter, err = aclTokenListByAuthMethod(tx, methodName, methodMeta, entMeta)
needLocalityFilter = true
} else {
return 0, nil, fmt.Errorf("can only filter by one of policy, role, or methodName at a time")
}
if err != nil {
return 0, nil, fmt.Errorf("failed acl token lookup: %v", err)
}
if needLocalityFilter && 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
})
}
ws.Add(iter.WatchCh())
var result structs.ACLTokens
for raw := iter.Next(); raw != nil; raw = iter.Next() {
token := raw.(*structs.ACLToken)
token, err := fixupTokenPolicyLinks(tx, token)
if err != nil {
return 0, nil, err
}
token, err = fixupTokenRoleLinks(tx, token)
if err != nil {
return 0, nil, err
}
result = append(result, token)
}
// Get the table index.
idx := aclTokenMaxIndex(tx, nil, entMeta)
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
}
func (s *Store) ACLTokenMinExpirationTime(local bool) (time.Time, error) {
tx := s.db.Txn(false)
defer tx.Abort()
item, err := tx.First("acl-tokens", s.expiresIndexName(local))
if err != nil {
return time.Time{}, fmt.Errorf("failed acl token listing: %v", err)
}
if item == nil {
return time.Time{}, nil
}
token := item.(*structs.ACLToken)
return *token.ExpirationTime, nil
}
// ACLTokenListExpires lists tokens that are expired as of the provided time.
// The returned set will be no larger than the max value provided.
func (s *Store) ACLTokenListExpired(local bool, asOf time.Time, max int) (structs.ACLTokens, <-chan struct{}, error) {
tx := s.db.Txn(false)
defer tx.Abort()
iter, err := tx.Get("acl-tokens", s.expiresIndexName(local))
if err != nil {
return nil, nil, fmt.Errorf("failed acl token listing: %v", err)
}
var (
tokens structs.ACLTokens
i int
)
for raw := iter.Next(); raw != nil; raw = iter.Next() {
token := raw.(*structs.ACLToken)
if token.ExpirationTime != nil && !token.ExpirationTime.Before(asOf) {
return tokens, nil, nil
}
tokens = append(tokens, token)
i += 1
if i >= max {
return tokens, nil, nil
}
}
return tokens, iter.WatchCh(), nil
}
func (s *Store) expiresIndexName(local bool) string {
if local {
return "expires-local"
}
return "expires-global"
}
// 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, entMeta *structs.EnterpriseMeta) error {
return s.aclTokenDelete(idx, secret, "id", entMeta)
}
// 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, entMeta *structs.EnterpriseMeta) error {
return s.aclTokenDelete(idx, accessor, "accessor", entMeta)
}
func (s *Store) ACLTokenBatchDelete(idx uint64, tokenIDs []string) error {
tx := s.db.WriteTxn(idx)
defer tx.Abort()
for _, tokenID := range tokenIDs {
if err := aclTokenDeleteTxn(tx, idx, tokenID, "accessor", nil); err != nil {
return err
}
}
return tx.Commit()
}
func (s *Store) aclTokenDelete(idx uint64, value, index string, entMeta *structs.EnterpriseMeta) error {
tx := s.db.WriteTxn(idx)
defer tx.Abort()
if err := aclTokenDeleteTxn(tx, idx, value, index, entMeta); err != nil {
return err
}
return tx.Commit()
}
func aclTokenDeleteTxn(tx WriteTxn, idx uint64, value, index string, entMeta *structs.EnterpriseMeta) error {
// Look up the existing token
_, token, err := aclTokenGetFromIndex(tx, value, index, entMeta)
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")
}
return aclTokenDeleteWithToken(tx, token.(*structs.ACLToken), idx)
}
func aclTokenDeleteAllForAuthMethodTxn(tx WriteTxn, idx uint64, methodName string, methodGlobalLocality bool, methodMeta *structs.EnterpriseMeta) error {
// collect all the tokens linked with the given auth method.
iter, err := aclTokenListByAuthMethod(tx, methodName, methodMeta, structs.WildcardEnterpriseMeta())
if err != nil {
return fmt.Errorf("failed acl token lookup: %v", err)
}
var tokens structs.ACLTokens
for raw := iter.Next(); raw != nil; raw = iter.Next() {
token := raw.(*structs.ACLToken)
tokenIsGlobal := !token.Local
// Need to ensure that if we have an auth method named "blah" in the
// primary and secondary datacenters, and the primary instance has
// TokenLocality==global that when we delete the secondary instance we
// don't also blow away replicated tokens from the primary.
if methodGlobalLocality == tokenIsGlobal {
tokens = append(tokens, token)
}
}
if len(tokens) > 0 {
// delete them all
for _, token := range tokens {
if err := aclTokenDeleteWithToken(tx, token, idx); err != nil {
return err
}
}
}
return nil
}
func (s *Store) ACLPolicyBatchSet(idx uint64, policies structs.ACLPolicies) error {
tx := s.db.WriteTxn(idx)
defer tx.Abort()
for _, policy := range policies {
if err := aclPolicySetTxn(tx, idx, policy); err != nil {
return err
}
}
return tx.Commit()
}
func (s *Store) ACLPolicySet(idx uint64, policy *structs.ACLPolicy) error {
tx := s.db.WriteTxn(idx)
defer tx.Abort()
if err := aclPolicySetTxn(tx, idx, policy); err != nil {
return err
}
return tx.Commit()
}
func aclPolicySetTxn(tx WriteTxn, idx uint64, policy *structs.ACLPolicy) error {
// Check that the ID is set
if policy.ID == "" {
return ErrMissingACLPolicyID
}
if policy.Name == "" {
return ErrMissingACLPolicyName
}
var existing *structs.ACLPolicy
_, existingRaw, err := aclPolicyGetByID(tx, policy.ID, nil)
if err != nil {
return err
}
if existingRaw != nil {
existing = existingRaw.(*structs.ACLPolicy)
}
if existing != nil {
if policy.ID == structs.ACLPolicyGlobalManagementID {
// Only the name and description are modifiable
// Here we specifically check that the rules on the global management policy
// are identical to the correct policy rules within the binary. This is opposed
// to checking against the current rules to allow us to update the rules during
// upgrades.
if policy.Rules != structs.ACLPolicyGlobalManagement {
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)
q := Query{Value: policy.Name, EnterpriseMeta: policy.EnterpriseMeta}
nameMatch, err := tx.First(tableACLPolicies, indexName, q)
if err != nil {
return err
}
if nameMatch != nil && policy.ID != nameMatch.(*structs.ACLPolicy).ID {
return fmt.Errorf("A policy with name %q already exists", policy.Name)
}
if err := aclPolicyUpsertValidateEnterprise(tx, policy, existing); err != nil {
return err
}
// Set the indexes
if existing != nil {
policy.CreateIndex = existing.CreateIndex
policy.ModifyIndex = idx
} else {
policy.CreateIndex = idx
policy.ModifyIndex = idx
}
// Insert the ACL
return aclPolicyInsert(tx, policy)
}
func (s *Store) ACLPolicyGetByID(ws memdb.WatchSet, id string, entMeta *structs.EnterpriseMeta) (uint64, *structs.ACLPolicy, error) {
return s.aclPolicyGet(ws, id, aclPolicyGetByID, entMeta)
}
func (s *Store) ACLPolicyGetByName(ws memdb.WatchSet, name string, entMeta *structs.EnterpriseMeta) (uint64, *structs.ACLPolicy, error) {
return s.aclPolicyGet(ws, name, aclPolicyGetByName, entMeta)
}
func aclPolicyGetByName(tx ReadTxn, name string, entMeta *structs.EnterpriseMeta) (<-chan struct{}, interface{}, error) {
// todo: accept non-pointer value
if entMeta == nil {
entMeta = structs.DefaultEnterpriseMeta()
}
q := Query{Value: name, EnterpriseMeta: *entMeta}
return tx.FirstWatch(tableACLPolicies, indexName, q)
}
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 := getPolicyWithTxn(tx, ws, pid, aclPolicyGetByID, nil)
if err != nil {
return 0, nil, err
}
if policy != nil {
policies = append(policies, policy)
}
}
// We are specifically not wanting to call aclPolicyMaxIndex here as we always want the
// index entry for the tableACLPolicies table.
idx := maxIndexTxn(tx, tableACLPolicies)
return idx, policies, nil
}
type aclPolicyGetFn func(ReadTxn, string, *structs.EnterpriseMeta) (<-chan struct{}, interface{}, error)
func getPolicyWithTxn(tx ReadTxn, ws memdb.WatchSet, value string, fn aclPolicyGetFn, entMeta *structs.EnterpriseMeta) (*structs.ACLPolicy, error) {
watchCh, policy, err := fn(tx, value, entMeta)
if err != nil {
return nil, fmt.Errorf("failed acl policy lookup: %v", err)
}
ws.Add(watchCh)
if policy == nil {
return nil, err
}
return policy.(*structs.ACLPolicy), nil
}
func (s *Store) aclPolicyGet(ws memdb.WatchSet, value string, fn aclPolicyGetFn, entMeta *structs.EnterpriseMeta) (uint64, *structs.ACLPolicy, error) {
tx := s.db.Txn(false)
defer tx.Abort()
policy, err := getPolicyWithTxn(tx, ws, value, fn, entMeta)
if err != nil {
return 0, nil, err
}
idx := aclPolicyMaxIndex(tx, policy, entMeta)
return idx, policy, nil
}
func (s *Store) ACLPolicyList(ws memdb.WatchSet, entMeta *structs.EnterpriseMeta) (uint64, structs.ACLPolicies, error) {
tx := s.db.Txn(false)
defer tx.Abort()
iter, err := tx.Get(tableACLPolicies, indexName+"_prefix", entMeta)
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 := aclPolicyMaxIndex(tx, nil, entMeta)
return idx, result, nil
}
func (s *Store) ACLPolicyDeleteByID(idx uint64, id string, entMeta *structs.EnterpriseMeta) error {
return s.aclPolicyDelete(idx, id, aclPolicyGetByID, entMeta)
}
func (s *Store) ACLPolicyDeleteByName(idx uint64, name string, entMeta *structs.EnterpriseMeta) error {
return s.aclPolicyDelete(idx, name, aclPolicyGetByName, entMeta)
}
func (s *Store) ACLPolicyBatchDelete(idx uint64, policyIDs []string) error {
tx := s.db.WriteTxn(idx)
defer tx.Abort()
for _, policyID := range policyIDs {
if err := aclPolicyDeleteTxn(tx, idx, policyID, aclPolicyGetByID, nil); err != nil {
return err
}
}
return tx.Commit()
}
func (s *Store) aclPolicyDelete(idx uint64, value string, fn aclPolicyGetFn, entMeta *structs.EnterpriseMeta) error {
tx := s.db.WriteTxn(idx)
defer tx.Abort()
if err := aclPolicyDeleteTxn(tx, idx, value, fn, entMeta); err != nil {
return err
}
return tx.Commit()
}
func aclPolicyDeleteTxn(tx WriteTxn, idx uint64, value string, fn aclPolicyGetFn, entMeta *structs.EnterpriseMeta) error {
// Look up the existing token
_, rawPolicy, err := fn(tx, value, entMeta)
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")
}
return aclPolicyDeleteWithPolicy(tx, policy, idx)
}
func (s *Store) ACLRoleBatchSet(idx uint64, roles structs.ACLRoles, allowMissingPolicyIDs bool) error {
tx := s.db.WriteTxn(idx)
defer tx.Abort()
for _, role := range roles {
if err := aclRoleSetTxn(tx, idx, role, allowMissingPolicyIDs); err != nil {
return err
}
}
return tx.Commit()
}
func (s *Store) ACLRoleSet(idx uint64, role *structs.ACLRole) error {
tx := s.db.WriteTxn(idx)
defer tx.Abort()
if err := aclRoleSetTxn(tx, idx, role, false); err != nil {
return err
}
return tx.Commit()
}
func aclRoleSetTxn(tx WriteTxn, idx uint64, role *structs.ACLRole, allowMissing bool) error {
// Check that the ID is set
if role.ID == "" {
return ErrMissingACLRoleID
}
if role.Name == "" {
return ErrMissingACLRoleName
}
_, existingRaw, err := aclRoleGetByID(tx, role.ID, nil)
if err != nil {
return fmt.Errorf("failed acl role lookup: %v", err)
}
var existing *structs.ACLRole
if existingRaw != nil {
existing = existingRaw.(*structs.ACLRole)
}
// ensure the name is unique (cannot conflict with another role with a different ID)
q := Query{EnterpriseMeta: role.EnterpriseMeta, Value: role.Name}
nameMatch, err := tx.First(tableACLRoles, indexName, q)
if err != nil {
return fmt.Errorf("failed acl role lookup: %v", err)
}
if nameMatch != nil && role.ID != nameMatch.(*structs.ACLRole).ID {
return fmt.Errorf("A role with name %q already exists", role.Name)
}
if err := resolveRolePolicyLinks(tx, role, allowMissing); err != nil {
return err
}
for _, svcid := range role.ServiceIdentities {
if svcid.ServiceName == "" {
return fmt.Errorf("Encountered a Role with an empty service identity name in the state store")
}
}
for _, nodeid := range role.NodeIdentities {
if nodeid.NodeName == "" {
return fmt.Errorf("Encountered a Role with an empty node identity name in the state store")
}
if nodeid.Datacenter == "" {
return fmt.Errorf("Encountered a Role with an empty node identity datacenter in the state store")
}
}
if err := aclRoleUpsertValidateEnterprise(tx, role, existing); err != nil {
return err
}
// Set the indexes
if existing != nil {
role.CreateIndex = existing.CreateIndex
role.ModifyIndex = idx
} else {
role.CreateIndex = idx
role.ModifyIndex = idx
}
return aclRoleInsert(tx, role)
}
type aclRoleGetFn func(ReadTxn, string, *structs.EnterpriseMeta) (<-chan struct{}, interface{}, error)
func (s *Store) ACLRoleGetByID(ws memdb.WatchSet, id string, entMeta *structs.EnterpriseMeta) (uint64, *structs.ACLRole, error) {
return s.aclRoleGet(ws, id, aclRoleGetByID, entMeta)
}
func (s *Store) ACLRoleGetByName(ws memdb.WatchSet, name string, entMeta *structs.EnterpriseMeta) (uint64, *structs.ACLRole, error) {
return s.aclRoleGet(ws, name, aclRoleGetByName, entMeta)
}
func aclRoleGetByName(tx ReadTxn, name string, entMeta *structs.EnterpriseMeta) (<-chan struct{}, interface{}, error) {
// TODO: accept non-pointer value
if entMeta == nil {
entMeta = structs.DefaultEnterpriseMeta()
}
q := Query{EnterpriseMeta: *entMeta, Value: name}
return tx.FirstWatch(tableACLRoles, indexName, q)
}
func (s *Store) ACLRoleBatchGet(ws memdb.WatchSet, ids []string) (uint64, structs.ACLRoles, error) {
tx := s.db.Txn(false)
defer tx.Abort()
roles := make(structs.ACLRoles, 0, len(ids))
for _, rid := range ids {
role, err := getRoleWithTxn(tx, ws, rid, aclRoleGetByID, nil)
if err != nil {
return 0, nil, err
}
if role != nil {
roles = append(roles, role)
}
}
idx := maxIndexTxn(tx, tableACLRoles)
return idx, roles, nil
}
func getRoleWithTxn(tx ReadTxn, ws memdb.WatchSet, value string, fn aclRoleGetFn, entMeta *structs.EnterpriseMeta) (*structs.ACLRole, error) {
watchCh, rawRole, err := fn(tx, value, entMeta)
if err != nil {
return nil, fmt.Errorf("failed acl role lookup: %v", err)
}
ws.Add(watchCh)
if rawRole != nil {
role := rawRole.(*structs.ACLRole)
role, err := fixupRolePolicyLinks(tx, role)
if err != nil {
return nil, err
}
return role, nil
}
return nil, nil
}
func (s *Store) aclRoleGet(ws memdb.WatchSet, value string, fn aclRoleGetFn, entMeta *structs.EnterpriseMeta) (uint64, *structs.ACLRole, error) {
tx := s.db.Txn(false)
defer tx.Abort()
role, err := getRoleWithTxn(tx, ws, value, fn, entMeta)
if err != nil {
return 0, nil, err
}
idx := aclRoleMaxIndex(tx, role, entMeta)
return idx, role, nil
}
func (s *Store) ACLRoleList(ws memdb.WatchSet, policy string, entMeta *structs.EnterpriseMeta) (uint64, structs.ACLRoles, error) {
tx := s.db.Txn(false)
defer tx.Abort()
var iter memdb.ResultIterator
var err error
// TODO: accept non-pointer value
if entMeta == nil {
entMeta = structs.DefaultEnterpriseMeta()
}
if policy != "" {
q := Query{Value: policy, EnterpriseMeta: *entMeta}
iter, err = tx.Get(tableACLRoles, indexPolicies, q)
} else {
iter, err = tx.Get(tableACLRoles, indexName+"_prefix", entMeta)
}
if err != nil {
return 0, nil, fmt.Errorf("failed acl role lookup: %v", err)
}
ws.Add(iter.WatchCh())
var result structs.ACLRoles
for raw := iter.Next(); raw != nil; raw = iter.Next() {
role := raw.(*structs.ACLRole)
role, err := fixupRolePolicyLinks(tx, role)
if err != nil {
return 0, nil, err
}
result = append(result, role)
}
// Get the table index.
idx := aclRoleMaxIndex(tx, nil, entMeta)
return idx, result, nil
}
func (s *Store) ACLRoleDeleteByID(idx uint64, id string, entMeta *structs.EnterpriseMeta) error {
return s.aclRoleDelete(idx, id, aclRoleGetByID, entMeta)
}
func (s *Store) ACLRoleDeleteByName(idx uint64, name string, entMeta *structs.EnterpriseMeta) error {
return s.aclRoleDelete(idx, name, aclRoleGetByName, entMeta)
}
func (s *Store) ACLRoleBatchDelete(idx uint64, roleIDs []string) error {
tx := s.db.WriteTxn(idx)
defer tx.Abort()
for _, roleID := range roleIDs {
if err := aclRoleDeleteTxn(tx, idx, roleID, aclRoleGetByID, nil); err != nil {
return err
}
}
return tx.Commit()
}
func (s *Store) aclRoleDelete(idx uint64, value string, fn aclRoleGetFn, entMeta *structs.EnterpriseMeta) error {
tx := s.db.WriteTxn(idx)
defer tx.Abort()
if err := aclRoleDeleteTxn(tx, idx, value, fn, entMeta); err != nil {
return err
}
return tx.Commit()
}
func aclRoleDeleteTxn(tx WriteTxn, idx uint64, value string, fn aclRoleGetFn, entMeta *structs.EnterpriseMeta) error {
// Look up the existing role
_, rawRole, err := fn(tx, value, entMeta)
if err != nil {
return fmt.Errorf("failed acl role lookup: %v", err)
}
if rawRole == nil {
return nil
}
role := rawRole.(*structs.ACLRole)
return aclRoleDeleteWithRole(tx, role, idx)
}
func (s *Store) ACLBindingRuleBatchSet(idx uint64, rules structs.ACLBindingRules) error {
tx := s.db.WriteTxn(idx)
defer tx.Abort()
for _, rule := range rules {
if err := aclBindingRuleSetTxn(tx, idx, rule); err != nil {
return err
}
}
return tx.Commit()
}
func (s *Store) ACLBindingRuleSet(idx uint64, rule *structs.ACLBindingRule) error {
tx := s.db.WriteTxn(idx)
defer tx.Abort()
if err := aclBindingRuleSetTxn(tx, idx, rule); err != nil {
return err
}
return tx.Commit()
}
func aclBindingRuleSetTxn(tx WriteTxn, idx uint64, rule *structs.ACLBindingRule) error {
// Check that the ID and AuthMethod are set
if rule.ID == "" {
return ErrMissingACLBindingRuleID
} else if rule.AuthMethod == "" {
return ErrMissingACLBindingRuleAuthMethod
}
var existing *structs.ACLBindingRule
_, existingRaw, err := aclBindingRuleGetByID(tx, rule.ID, nil)
if err != nil {
return fmt.Errorf("failed acl binding rule lookup: %v", err)
}
// Set the indexes
if existingRaw != nil {
existing = existingRaw.(*structs.ACLBindingRule)
rule.CreateIndex = existing.CreateIndex
rule.ModifyIndex = idx
} else {
rule.CreateIndex = idx
rule.ModifyIndex = idx
}
if err := aclBindingRuleUpsertValidateEnterprise(tx, rule, existing); err != nil {
return err
}
if _, method, err := aclAuthMethodGetByName(tx, rule.AuthMethod, &rule.EnterpriseMeta); err != nil {
return fmt.Errorf("failed acl auth method lookup: %v", err)
} else if method == nil {
return fmt.Errorf("failed inserting acl binding rule: auth method not found")
}
return aclBindingRuleInsert(tx, rule)
}
func (s *Store) ACLBindingRuleGetByID(ws memdb.WatchSet, id string, entMeta *structs.EnterpriseMeta) (uint64, *structs.ACLBindingRule, error) {
return s.aclBindingRuleGet(ws, id, entMeta)
}
func (s *Store) aclBindingRuleGet(ws memdb.WatchSet, value string, entMeta *structs.EnterpriseMeta) (uint64, *structs.ACLBindingRule, error) {
tx := s.db.Txn(false)
defer tx.Abort()
watchCh, rawRule, err := aclBindingRuleGetByID(tx, value, entMeta)
if err != nil {
return 0, nil, fmt.Errorf("failed acl binding rule lookup: %v", err)
}
ws.Add(watchCh)
var rule *structs.ACLBindingRule
if rawRule != nil {
rule = rawRule.(*structs.ACLBindingRule)
}
idx := aclBindingRuleMaxIndex(tx, rule, entMeta)
return idx, rule, nil
}
func (s *Store) ACLBindingRuleList(ws memdb.WatchSet, methodName string, entMeta *structs.EnterpriseMeta) (uint64, structs.ACLBindingRules, error) {
tx := s.db.Txn(false)
defer tx.Abort()
var (
iter memdb.ResultIterator
err error
)
if methodName != "" {
iter, err = aclBindingRuleListByAuthMethod(tx, methodName, entMeta)
} else {
iter, err = aclBindingRuleList(tx, entMeta)
}
if err != nil {
return 0, nil, fmt.Errorf("failed acl binding rule lookup: %v", err)
}
ws.Add(iter.WatchCh())
var result structs.ACLBindingRules
for raw := iter.Next(); raw != nil; raw = iter.Next() {
rule := raw.(*structs.ACLBindingRule)
result = append(result, rule)
}
// Get the table index.
idx := aclBindingRuleMaxIndex(tx, nil, entMeta)
return idx, result, nil
}
func (s *Store) ACLBindingRuleDeleteByID(idx uint64, id string, entMeta *structs.EnterpriseMeta) error {
return s.aclBindingRuleDelete(idx, id, entMeta)
}
func (s *Store) ACLBindingRuleBatchDelete(idx uint64, bindingRuleIDs []string) error {
tx := s.db.WriteTxn(idx)
defer tx.Abort()
for _, bindingRuleID := range bindingRuleIDs {
aclBindingRuleDeleteTxn(tx, idx, bindingRuleID, nil)
}
return tx.Commit()
}
func (s *Store) aclBindingRuleDelete(idx uint64, id string, entMeta *structs.EnterpriseMeta) error {
tx := s.db.WriteTxn(idx)
defer tx.Abort()
if err := aclBindingRuleDeleteTxn(tx, idx, id, entMeta); err != nil {
return err
}
return tx.Commit()
}
func aclBindingRuleDeleteTxn(tx WriteTxn, idx uint64, id string, entMeta *structs.EnterpriseMeta) error {
// Look up the existing binding rule
_, rawRule, err := aclBindingRuleGetByID(tx, id, entMeta)
if err != nil {
return fmt.Errorf("failed acl binding rule lookup: %v", err)
}
if rawRule == nil {
return nil
}
rule := rawRule.(*structs.ACLBindingRule)
if err := aclBindingRuleDeleteWithRule(tx, rule, idx); err != nil {
return fmt.Errorf("failed deleting acl binding rule: %v", err)
}
return nil
}
func aclBindingRuleDeleteAllForAuthMethodTxn(tx WriteTxn, idx uint64, methodName string, entMeta *structs.EnterpriseMeta) error {
// collect them all
iter, err := aclBindingRuleListByAuthMethod(tx, methodName, entMeta)
if err != nil {
return fmt.Errorf("failed acl binding rule lookup: %v", err)
}
var rules structs.ACLBindingRules
for raw := iter.Next(); raw != nil; raw = iter.Next() {
rule := raw.(*structs.ACLBindingRule)
rules = append(rules, rule)
}
if len(rules) > 0 {
// delete them all
for _, rule := range rules {
if err := aclBindingRuleDeleteWithRule(tx, rule, idx); err != nil {
return err
}
}
}
return nil
}
func (s *Store) ACLAuthMethodBatchSet(idx uint64, methods structs.ACLAuthMethods) error {
tx := s.db.WriteTxn(idx)
defer tx.Abort()
for _, method := range methods {
// this is only used when doing batch insertions for upgrades and replication. Therefore
// we take whatever those said.
if err := aclAuthMethodSetTxn(tx, idx, method); err != nil {
return err
}
}
return tx.Commit()
}
func (s *Store) ACLAuthMethodSet(idx uint64, method *structs.ACLAuthMethod) error {
tx := s.db.WriteTxn(idx)
defer tx.Abort()
if err := aclAuthMethodSetTxn(tx, idx, method); err != nil {
return err
}
return tx.Commit()
}
func aclAuthMethodSetTxn(tx WriteTxn, idx uint64, method *structs.ACLAuthMethod) error {
// Check that the Name and Type are set
if method.Name == "" {
return ErrMissingACLAuthMethodName
} else if method.Type == "" {
return ErrMissingACLAuthMethodType
}
var existing *structs.ACLAuthMethod
_, existingRaw, err := aclAuthMethodGetByName(tx, method.Name, &method.EnterpriseMeta)
if err != nil {
return fmt.Errorf("failed acl auth method lookup: %v", err)
}
if err := aclAuthMethodUpsertValidateEnterprise(tx, method, existing); err != nil {
return err
}
// Set the indexes
if existingRaw != nil {
existing = existingRaw.(*structs.ACLAuthMethod)
method.CreateIndex = existing.CreateIndex
method.ModifyIndex = idx
} else {
method.CreateIndex = idx
method.ModifyIndex = idx
}
return aclAuthMethodInsert(tx, method)
}
func (s *Store) ACLAuthMethodGetByName(ws memdb.WatchSet, name string, entMeta *structs.EnterpriseMeta) (uint64, *structs.ACLAuthMethod, error) {
return s.aclAuthMethodGet(ws, name, entMeta)
}
func (s *Store) aclAuthMethodGet(ws memdb.WatchSet, name string, entMeta *structs.EnterpriseMeta) (uint64, *structs.ACLAuthMethod, error) {
tx := s.db.Txn(false)
defer tx.Abort()
method, err := getAuthMethodWithTxn(tx, ws, name, entMeta)
if err != nil {
return 0, nil, err
}
idx := aclAuthMethodMaxIndex(tx, method, entMeta)
return idx, method, nil
}
func getAuthMethodWithTxn(tx ReadTxn, ws memdb.WatchSet, name string, entMeta *structs.EnterpriseMeta) (*structs.ACLAuthMethod, error) {
watchCh, rawMethod, err := aclAuthMethodGetByName(tx, name, entMeta)
if err != nil {
return nil, fmt.Errorf("failed acl auth method lookup: %v", err)
}
ws.Add(watchCh)
if rawMethod != nil {
return rawMethod.(*structs.ACLAuthMethod), nil
}
return nil, nil
}
func (s *Store) ACLAuthMethodList(ws memdb.WatchSet, entMeta *structs.EnterpriseMeta) (uint64, structs.ACLAuthMethods, error) {
tx := s.db.Txn(false)
defer tx.Abort()
iter, err := aclAuthMethodList(tx, entMeta)
if err != nil {
return 0, nil, fmt.Errorf("failed acl auth method lookup: %v", err)
}
ws.Add(iter.WatchCh())
var result structs.ACLAuthMethods
for raw := iter.Next(); raw != nil; raw = iter.Next() {
method := raw.(*structs.ACLAuthMethod)
result = append(result, method)
}
// Get the table index.
idx := aclAuthMethodMaxIndex(tx, nil, entMeta)
return idx, result, nil
}
func (s *Store) ACLAuthMethodDeleteByName(idx uint64, name string, entMeta *structs.EnterpriseMeta) error {
return s.aclAuthMethodDelete(idx, name, entMeta)
}
func (s *Store) ACLAuthMethodBatchDelete(idx uint64, names []string, entMeta *structs.EnterpriseMeta) error {
tx := s.db.WriteTxn(idx)
defer tx.Abort()
for _, name := range names {
// NOTE: it may seem odd that one EnterpriseMeta is being used for all of the auth methods being
// deleted. However we never actually batch these deletions as auth methods are not replicated
// Therefore this is fine but if we ever change that precondition then this will be wrong (unless
// we ensure all deletions in a batch should have the same enterprise meta)
aclAuthMethodDeleteTxn(tx, idx, name, entMeta)
}
return tx.Commit()
}
func (s *Store) aclAuthMethodDelete(idx uint64, name string, entMeta *structs.EnterpriseMeta) error {
tx := s.db.WriteTxn(idx)
defer tx.Abort()
if err := aclAuthMethodDeleteTxn(tx, idx, name, entMeta); err != nil {
return err
}
return tx.Commit()
}
func aclAuthMethodDeleteTxn(tx WriteTxn, idx uint64, name string, entMeta *structs.EnterpriseMeta) error {
// Look up the existing method
_, rawMethod, err := aclAuthMethodGetByName(tx, name, entMeta)
if err != nil {
return fmt.Errorf("failed acl auth method lookup: %v", err)
}
if rawMethod == nil {
return nil
}
method := rawMethod.(*structs.ACLAuthMethod)
if err := aclBindingRuleDeleteAllForAuthMethodTxn(tx, idx, method.Name, entMeta); err != nil {
return err
}
if err := aclTokenDeleteAllForAuthMethodTxn(tx, idx, method.Name, method.TokenLocality == "global", entMeta); err != nil {
return err
}
return aclAuthMethodDeleteWithMethod(tx, method, idx)
}