open-consul/agent/consul/state/acl_schema.go
Chris S. Kim aaf3c051f2
Make memdb indexers generic (#13558)
We have many indexer functions in Consul which take interface{} and type assert before building the index. We can use generics to get rid of the initial plumbing and pass around functions with better defined signatures. This has two benefits: 1) Less verbosity; 2) Developers can parse the argument types to memdb schemas without having to introspect the function for the type assertion.
2022-06-23 11:07:19 -04:00

442 lines
10 KiB
Go

package state
import (
"fmt"
"strings"
"github.com/hashicorp/go-memdb"
"github.com/hashicorp/consul/agent/structs"
)
const (
tableACLTokens = "acl-tokens"
tableACLPolicies = "acl-policies"
tableACLRoles = "acl-roles"
tableACLBindingRules = "acl-binding-rules"
tableACLAuthMethods = "acl-auth-methods"
indexAccessor = "accessor"
indexPolicies = "policies"
indexRoles = "roles"
indexAuthMethod = "authmethod"
indexLocality = "locality"
indexName = "name"
indexExpiresGlobal = "expires-global"
indexExpiresLocal = "expires-local"
)
func tokensTableSchema() *memdb.TableSchema {
return &memdb.TableSchema{
Name: tableACLTokens,
Indexes: map[string]*memdb.IndexSchema{
indexAccessor: {
Name: indexAccessor,
// DEPRECATED (ACL-Legacy-Compat) - we should not AllowMissing here once legacy compat is removed
AllowMissing: true,
Unique: true,
Indexer: indexerSingle[string, *structs.ACLToken]{
readIndex: indexFromUUIDString,
writeIndex: indexAccessorIDFromACLToken,
},
},
indexID: {
Name: indexID,
AllowMissing: false,
Unique: true,
Indexer: indexerSingle[string, *structs.ACLToken]{
readIndex: indexFromStringCaseSensitive,
writeIndex: indexSecretIDFromACLToken,
},
},
indexPolicies: {
Name: indexPolicies,
// Need to allow missing for the anonymous token
AllowMissing: true,
Unique: false,
Indexer: indexerMulti[Query, *structs.ACLToken]{
readIndex: indexFromUUIDQuery,
writeIndexMulti: indexPoliciesFromACLToken,
},
},
indexRoles: {
Name: indexRoles,
AllowMissing: true,
Unique: false,
Indexer: indexerMulti[Query, *structs.ACLToken]{
readIndex: indexFromUUIDQuery,
writeIndexMulti: indexRolesFromACLToken,
},
},
indexAuthMethod: {
Name: indexAuthMethod,
AllowMissing: true,
Unique: false,
Indexer: indexerSingle[AuthMethodQuery, *structs.ACLToken]{
readIndex: indexFromAuthMethodQuery,
writeIndex: indexAuthMethodFromACLToken,
},
},
indexLocality: {
Name: indexLocality,
AllowMissing: false,
Unique: false,
Indexer: indexerSingle[BoolQuery, *structs.ACLToken]{
readIndex: indexFromBoolQuery,
writeIndex: indexLocalFromACLToken,
},
},
indexExpiresGlobal: {
Name: indexExpiresGlobal,
AllowMissing: true,
Unique: false,
Indexer: indexerSingle[*TimeQuery, *structs.ACLToken]{
readIndex: indexFromTimeQuery,
writeIndex: indexExpiresGlobalFromACLToken,
},
},
indexExpiresLocal: {
Name: indexExpiresLocal,
AllowMissing: true,
Unique: false,
Indexer: indexerSingle[*TimeQuery, *structs.ACLToken]{
readIndex: indexFromTimeQuery,
writeIndex: indexExpiresLocalFromACLToken,
},
},
// 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
// TODO(ACL-Legacy-Compat): remove in phase 2
"needs-upgrade": {
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: tableACLPolicies,
Indexes: map[string]*memdb.IndexSchema{
indexID: {
Name: indexID,
AllowMissing: false,
Unique: true,
Indexer: &memdb.UUIDFieldIndex{
Field: "ID",
},
},
indexName: {
Name: indexName,
AllowMissing: false,
Unique: true,
Indexer: indexerSingleWithPrefix[Query, *structs.ACLPolicy, any]{
readIndex: indexFromQuery,
writeIndex: indexNameFromACLPolicy,
prefixIndex: prefixIndexFromQuery,
},
},
},
}
}
func indexNameFromACLPolicy(p *structs.ACLPolicy) ([]byte, error) {
if p.Name == "" {
return nil, errMissingValueForIndex
}
var b indexBuilder
b.String(strings.ToLower(p.Name))
return b.Bytes(), nil
}
func rolesTableSchema() *memdb.TableSchema {
return &memdb.TableSchema{
Name: tableACLRoles,
Indexes: map[string]*memdb.IndexSchema{
indexID: {
Name: indexID,
AllowMissing: false,
Unique: true,
Indexer: &memdb.UUIDFieldIndex{
Field: "ID",
},
},
indexName: {
Name: indexName,
AllowMissing: false,
Unique: true,
Indexer: indexerSingleWithPrefix[Query, *structs.ACLRole, any]{
readIndex: indexFromQuery,
writeIndex: indexNameFromACLRole,
prefixIndex: prefixIndexFromQuery,
},
},
indexPolicies: {
Name: indexPolicies,
// Need to allow missing for the anonymous token
AllowMissing: true,
Unique: false,
Indexer: indexerMulti[Query, *structs.ACLRole]{
readIndex: indexFromUUIDQuery,
writeIndexMulti: multiIndexPolicyFromACLRole,
},
},
},
}
}
func indexNameFromACLRole(r *structs.ACLRole) ([]byte, error) {
if r.Name == "" {
return nil, errMissingValueForIndex
}
var b indexBuilder
b.String(strings.ToLower(r.Name))
return b.Bytes(), nil
}
func indexFromUUIDQuery(q Query) ([]byte, error) {
return uuidStringToBytes(q.Value)
}
func prefixIndexFromUUIDWithPeerQuery(q Query) ([]byte, error) {
var b indexBuilder
peername := q.PeerOrEmpty()
if peername == "" {
b.String(structs.LocalPeerKeyword)
} else {
b.String(strings.ToLower(peername))
}
uuidBytes, err := variableLengthUUIDStringToBytes(q.Value)
if err != nil {
return nil, err
}
return append(b.Bytes(), uuidBytes...), nil
}
func multiIndexPolicyFromACLRole(r *structs.ACLRole) ([][]byte, error) {
count := len(r.Policies)
if count == 0 {
return nil, errMissingValueForIndex
}
vals := make([][]byte, 0, count)
for _, link := range r.Policies {
v, err := uuidStringToBytes(link.ID)
if err != nil {
return nil, err
}
vals = append(vals, v)
}
return vals, nil
}
func bindingRulesTableSchema() *memdb.TableSchema {
return &memdb.TableSchema{
Name: tableACLBindingRules,
Indexes: map[string]*memdb.IndexSchema{
indexID: {
Name: indexID,
AllowMissing: false,
Unique: true,
Indexer: indexerSingle[string, *structs.ACLBindingRule]{
readIndex: indexFromUUIDString,
writeIndex: indexIDFromACLBindingRule,
},
},
indexAuthMethod: {
Name: indexAuthMethod,
AllowMissing: false,
Unique: false,
Indexer: indexerSingle[Query, *structs.ACLBindingRule]{
readIndex: indexFromQuery,
writeIndex: indexAuthMethodFromACLBindingRule,
},
},
},
}
}
func indexIDFromACLBindingRule(r *structs.ACLBindingRule) ([]byte, error) {
vv, err := uuidStringToBytes(r.ID)
if err != nil {
return nil, err
}
return vv, err
}
func indexAuthMethodFromACLBindingRule(r *structs.ACLBindingRule) ([]byte, error) {
if r.AuthMethod == "" {
return nil, errMissingValueForIndex
}
var b indexBuilder
b.String(strings.ToLower(r.AuthMethod))
return b.Bytes(), nil
}
func indexFromUUIDString(raw string) ([]byte, error) {
uuid, err := uuidStringToBytes(raw)
if err != nil {
return nil, err
}
var b indexBuilder
b.Raw(uuid)
return b.Bytes(), nil
}
func indexAccessorIDFromACLToken(t *structs.ACLToken) ([]byte, error) {
if t.AccessorID == "" {
return nil, errMissingValueForIndex
}
uuid, err := uuidStringToBytes(t.AccessorID)
if err != nil {
return nil, err
}
var b indexBuilder
b.Raw(uuid)
return b.Bytes(), nil
}
func indexSecretIDFromACLToken(t *structs.ACLToken) ([]byte, error) {
if t.SecretID == "" {
return nil, errMissingValueForIndex
}
var b indexBuilder
b.String(t.SecretID)
return b.Bytes(), nil
}
func indexFromStringCaseSensitive(s string) ([]byte, error) {
var b indexBuilder
b.String(s)
return b.Bytes(), nil
}
func indexPoliciesFromACLToken(token *structs.ACLToken) ([][]byte, error) {
links := token.Policies
numLinks := len(links)
if numLinks == 0 {
return nil, errMissingValueForIndex
}
vals := make([][]byte, numLinks)
for i, link := range links {
id, err := uuidStringToBytes(link.ID)
if err != nil {
return nil, err
}
vals[i] = id
}
return vals, nil
}
func indexRolesFromACLToken(token *structs.ACLToken) ([][]byte, error) {
links := token.Roles
numLinks := len(links)
if numLinks == 0 {
return nil, errMissingValueForIndex
}
vals := make([][]byte, numLinks)
for i, link := range links {
id, err := uuidStringToBytes(link.ID)
if err != nil {
return nil, err
}
vals[i] = id
}
return vals, nil
}
func indexFromBoolQuery(q BoolQuery) ([]byte, error) {
var b indexBuilder
b.Bool(q.Value)
return b.Bytes(), nil
}
func indexLocalFromACLToken(token *structs.ACLToken) ([]byte, error) {
var b indexBuilder
b.Bool(token.Local)
return b.Bytes(), nil
}
func indexFromTimeQuery(q *TimeQuery) ([]byte, error) {
var b indexBuilder
b.Time(q.Value)
return b.Bytes(), nil
}
func indexExpiresLocalFromACLToken(token *structs.ACLToken) ([]byte, error) {
return indexExpiresFromACLToken(token, true)
}
func indexExpiresGlobalFromACLToken(token *structs.ACLToken) ([]byte, error) {
return indexExpiresFromACLToken(token, false)
}
func indexExpiresFromACLToken(t *structs.ACLToken, local bool) ([]byte, error) {
if t.Local != local {
return nil, errMissingValueForIndex
}
if !t.HasExpirationTime() {
return nil, errMissingValueForIndex
}
if t.ExpirationTime.Unix() < 0 {
return nil, fmt.Errorf("token expiration time cannot be before the unix epoch: %s", t.ExpirationTime)
}
var b indexBuilder
b.Time(*t.ExpirationTime)
return b.Bytes(), nil
}
func authMethodsTableSchema() *memdb.TableSchema {
return &memdb.TableSchema{
Name: tableACLAuthMethods,
Indexes: map[string]*memdb.IndexSchema{
indexID: {
Name: indexID,
AllowMissing: false,
Unique: true,
Indexer: indexerSingle[Query, *structs.ACLAuthMethod]{
readIndex: indexFromQuery,
writeIndex: indexNameFromACLAuthMethod,
},
},
},
}
}
func indexNameFromACLAuthMethod(m *structs.ACLAuthMethod) ([]byte, error) {
if m.Name == "" {
return nil, errMissingValueForIndex
}
var b indexBuilder
b.String(strings.ToLower(m.Name))
return b.Bytes(), nil
}