mirror of
https://github.com/openziti/ziti.git
synced 2026-10-12 15:28:05 +00:00
267 lines
8.2 KiB
Go
267 lines
8.2 KiB
Go
/*
|
|
Copyright NetFoundry, Inc.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
https://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package persistence
|
|
|
|
import (
|
|
"fmt"
|
|
"github.com/michaelquigley/pfxlog"
|
|
"github.com/netfoundry/ziti-edge/controller/validation"
|
|
"github.com/netfoundry/ziti-fabric/controller/db"
|
|
"github.com/netfoundry/ziti-fabric/controller/network"
|
|
"github.com/netfoundry/ziti-foundation/storage/ast"
|
|
"github.com/netfoundry/ziti-foundation/storage/boltz"
|
|
"github.com/netfoundry/ziti-foundation/util/errorz"
|
|
"github.com/netfoundry/ziti-foundation/util/stringz"
|
|
"github.com/pkg/errors"
|
|
"go.etcd.io/bbolt"
|
|
)
|
|
|
|
type DbProvider interface {
|
|
GetDb() boltz.Db
|
|
GetServiceCache() network.Cache
|
|
GetStores() *db.Stores
|
|
GetControllers() *network.Controllers
|
|
}
|
|
|
|
type Store interface {
|
|
boltz.CrudStore
|
|
initializeLocal()
|
|
initializeLinked()
|
|
initializeIndexes(tx *bbolt.Tx, errorHolder errorz.ErrorHolder)
|
|
}
|
|
|
|
func newBaseStore(stores *stores, entityType string) *baseStore {
|
|
singularEntityType := boltz.GetSingularEntityType(entityType)
|
|
return &baseStore{
|
|
stores: stores,
|
|
BaseStore: boltz.NewBaseStore(nil, entityType, func(id string) error {
|
|
return boltz.NewNotFoundError(singularEntityType, "id", id)
|
|
}, boltz.RootBucket),
|
|
}
|
|
}
|
|
|
|
func newChildBaseStore(stores *stores, parent boltz.CrudStore, entityType string) *baseStore {
|
|
singularEntityType := boltz.GetSingularEntityType(entityType)
|
|
return &baseStore{
|
|
stores: stores,
|
|
BaseStore: boltz.NewBaseStore(parent, entityType, func(id string) error {
|
|
return boltz.NewNotFoundError(singularEntityType, "id", id)
|
|
}, EdgeBucket),
|
|
}
|
|
}
|
|
|
|
type baseStore struct {
|
|
stores *stores
|
|
*boltz.BaseStore
|
|
}
|
|
|
|
func (store *baseStore) addUniqueNameField() boltz.ReadIndex {
|
|
symbolName := store.AddSymbol(FieldName, ast.NodeTypeString)
|
|
return store.AddUniqueIndex(symbolName)
|
|
}
|
|
|
|
func (store *baseStore) addRoleAttributesField() boltz.SetReadIndex {
|
|
symbol := store.AddSetSymbol(FieldRoleAttributes, ast.NodeTypeString)
|
|
return store.AddSetIndex(symbol)
|
|
}
|
|
|
|
func (store *baseStore) initializeIndexes(tx *bbolt.Tx, errorHolder errorz.ErrorHolder) {
|
|
store.InitializeIndexes(tx, errorHolder)
|
|
}
|
|
|
|
func (store *baseStore) baseLoadOneById(tx *bbolt.Tx, id string, entity boltz.Entity) error {
|
|
found, err := store.BaseLoadOneById(tx, id, entity)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !found {
|
|
return boltz.NewNotFoundError(store.GetSingularEntityType(), "id", id)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (store *baseStore) updateEntityNameReferences(bucket *boltz.TypedBucket, rolesSymbol boltz.EntitySetSymbol, entity boltz.NamedExtEntity, oldName string) {
|
|
// If the entity name is the same as entity ID, bail out. We don't want to remove any references by ID, since
|
|
// those take precedence over named references
|
|
if store.IsEntityPresent(bucket.Tx(), oldName) {
|
|
pfxlog.Logger().Warnf("%v has name %v which is also used as an ID", store.GetSingularEntityType(), oldName)
|
|
return
|
|
}
|
|
oldNameRef := entityRef(oldName)
|
|
newNameRef := entityRef(entity.GetName())
|
|
for _, policyHolderId := range store.GetRelatedEntitiesIdList(bucket.Tx(), entity.GetId(), rolesSymbol.GetStore().GetEntityType()) {
|
|
err := rolesSymbol.Map(bucket.Tx(), []byte(policyHolderId), func(ctx *boltz.MapContext) {
|
|
if ctx.ValueS() == oldNameRef {
|
|
ctx.ReplaceS(newNameRef)
|
|
}
|
|
})
|
|
if err != nil {
|
|
bucket.SetError(err)
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
func (store *baseStore) deleteEntityReferences(tx *bbolt.Tx, entity boltz.NamedExtEntity, rolesSymbol boltz.EntitySetSymbol) error {
|
|
// If the entity name is the same as entity ID, don't remove any of those references as id references take precedence
|
|
checkName := !store.IsEntityPresent(tx, entity.GetName())
|
|
if !checkName {
|
|
pfxlog.Logger().Warnf("%v has name %v which is also used as an ID", store.GetSingularEntityType(), entity.GetName())
|
|
}
|
|
idRef := entityRef(entity.GetId())
|
|
nameRef := entityRef(entity.GetName())
|
|
|
|
for _, policyHolderId := range store.GetRelatedEntitiesIdList(tx, entity.GetId(), rolesSymbol.GetStore().GetEntityType()) {
|
|
err := rolesSymbol.Map(tx, []byte(policyHolderId), func(ctx *boltz.MapContext) {
|
|
if ctx.ValueS() == idRef || (checkName && ctx.ValueS() == nameRef) {
|
|
ctx.Delete()
|
|
}
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (store *baseStore) getEntityIdsForRoleSet(tx *bbolt.Tx, field string, roleSet []string, semantic string, index boltz.SetReadIndex, targetStore NameIndexedStore) ([]string, error) {
|
|
entityStore := index.GetSymbol().GetStore()
|
|
if stringz.Contains(roleSet, AllRole) {
|
|
ids, _, err := entityStore.QueryIds(tx, "true")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return ids, nil
|
|
}
|
|
|
|
roles, ids, err := splitRolesAndIds(roleSet)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err := validateAndConvertNamesToIds(tx, targetStore, field, ids); err != nil {
|
|
return nil, err
|
|
}
|
|
var roleIds []string
|
|
if semantic == SemanticAllOf {
|
|
roleIds = store.FindMatching(tx, index, roles)
|
|
} else if semantic == SemanticAnyOf {
|
|
roleIds = store.FindMatchingAnyOf(tx, index, roles)
|
|
} else {
|
|
return nil, errors.Errorf("unsupported policy semantic %v", semantic)
|
|
}
|
|
|
|
for _, id := range ids {
|
|
if entityStore.IsEntityPresent(tx, id) {
|
|
roleIds = append(roleIds, id)
|
|
}
|
|
}
|
|
return roleIds, nil
|
|
}
|
|
|
|
func validateAndConvertNamesToIds(tx *bbolt.Tx, store NameIndexedStore, field string, ids []string) error {
|
|
var invalid []string
|
|
for idx, val := range ids {
|
|
if result := ValidateAndConvertNameToId(tx, store, val); result != nil {
|
|
ids[idx] = *result
|
|
} else {
|
|
invalid = append(invalid, val)
|
|
}
|
|
}
|
|
if len(invalid) > 0 {
|
|
return validation.NewFieldError(fmt.Sprintf("no %v found with the given names/ids", store.GetEntityType()), field, invalid)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func ValidateAndConvertNameToId(tx *bbolt.Tx, store NameIndexedStore, val string) *string {
|
|
if store.IsEntityPresent(tx, val) {
|
|
return &val
|
|
}
|
|
id := string(store.GetNameIndex().Read(tx, []byte(val)))
|
|
if id != "" && store.IsEntityPresent(tx, id) {
|
|
return &id
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func UpdateRelatedRoles(store NameIndexedStore, tx *bbolt.Tx, entityId string, roleSymbol boltz.EntitySetSymbol,
|
|
linkCollection boltz.LinkCollection, new []boltz.FieldTypeAndValue, holder errorz.ErrorHolder, semanticSymbol boltz.EntitySymbol) {
|
|
ids, _, err := roleSymbol.GetStore().QueryIds(tx, "true")
|
|
holder.SetError(err)
|
|
|
|
entityRoles := FieldValuesToIds(new)
|
|
|
|
for _, id := range ids {
|
|
roleSet := roleSymbol.EvalStringList(tx, []byte(id))
|
|
roles, ids, err := splitRolesAndIds(roleSet)
|
|
if err != nil {
|
|
holder.SetError(err)
|
|
return
|
|
}
|
|
convertNamesToIds(tx, store, ids)
|
|
semantic := SemanticAllOf
|
|
if semanticSymbol != nil {
|
|
if _, semanticValue := semanticSymbol.Eval(tx, []byte(id)); semanticValue != nil {
|
|
semantic = string(semanticValue)
|
|
}
|
|
}
|
|
|
|
if stringz.Contains(ids, entityId) || stringz.Contains(roles, "all") {
|
|
err = linkCollection.AddLinks(tx, id, entityId)
|
|
} else if semantic == SemanticAllOf && len(roles) > 0 && stringz.ContainsAll(entityRoles, roles...) {
|
|
err = linkCollection.AddLinks(tx, id, entityId)
|
|
} else if semantic == SemanticAnyOf && len(roles) > 0 && stringz.ContainsAny(entityRoles, roles...) {
|
|
err = linkCollection.AddLinks(tx, id, entityId)
|
|
} else {
|
|
err = linkCollection.RemoveLinks(tx, id, entityId)
|
|
}
|
|
|
|
holder.SetError(err)
|
|
if holder.HasError() {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
func convertNamesToIds(tx *bbolt.Tx, store NameIndexedStore, ids []string) {
|
|
nameIndex := store.GetNameIndex()
|
|
for idx, val := range ids {
|
|
id := nameIndex.Read(tx, []byte(val))
|
|
if id != nil {
|
|
ids[idx] = string(id)
|
|
}
|
|
}
|
|
}
|
|
|
|
type NameIndexedStore interface {
|
|
Store
|
|
GetNameIndex() boltz.ReadIndex
|
|
}
|
|
|
|
func (store *baseStore) GetName(tx *bbolt.Tx, id string) *string {
|
|
symbol := store.GetSymbol(FieldName)
|
|
if symbol == nil {
|
|
return nil
|
|
}
|
|
_, val := symbol.Eval(tx, []byte(id))
|
|
if val != nil {
|
|
result := string(val)
|
|
return &result
|
|
}
|
|
return nil
|
|
}
|