mirror of
https://github.com/openziti/ziti.git
synced 2026-10-26 21:47:05 +00:00
f18ce1b580
- collapses the edge service boltz child store into the unified service store:
EdgeService becomes a type alias for Service, and RoleAttributes, Configs, and
EncryptionRequired become top-level Service fields
- adds an IsFabricOnly discriminator separating pure-fabric ("management")
services from edge services
- adds a forward-only, irreversible migration that moves edge service data out of
the edge child bucket, classifies pre-existing services (fabric -> IsFabricOnly
with encryptionRequired=false; edge -> IsFabricOnly=false), deletes the old edge
bucket and its index, and rebuilds the role-attributes index via CheckIntegrity
- guards every edge-facing path on the unified store against fabric-only services
so they stay invisible to the edge API: by-id Read/ReadByName/Delete, list/query
and the detail lister, ReadForIdentity, identity service-config overrides,
service policy / SERP @id and #all/role denormalization, the association-list
routes (terminators, policies, configs), and the router data-model sync
- rejects edge-surface updates of fabric-only services, and preserves edge fields
on fabric-surface updates of edge services
- marks the fabric-only guards TEMPORARY(fabric-edge-collapse): they exist only to
keep seldom-used management services hidden, and are removed when the fabric/edge
distinction is erased
- adds in-CI migration tests (forward plus a round-trip asserting FK sub-bucket
relocation and forward/reverse refcount preservation), durable fabric-only
behavior tests, and store-level policy-exclusion tests
- adds one-time authentic-migration gate tooling under zititest/migration-test
(create-model.sh, query, verify) and its operator runbook
158 lines
5.5 KiB
Go
158 lines
5.5 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 model
|
|
|
|
import (
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/openziti/foundation/v2/errorz"
|
|
"github.com/openziti/ziti/v2/controller/storage/boltz"
|
|
"github.com/openziti/ziti/v2/controller/db"
|
|
"github.com/openziti/ziti/v2/controller/models"
|
|
"go.etcd.io/bbolt"
|
|
)
|
|
|
|
type EdgeService struct {
|
|
models.BaseEntity
|
|
Name string `json:"name"`
|
|
MaxIdleTime time.Duration `json:"maxIdleTime"`
|
|
TerminatorStrategy string `json:"terminatorStrategy"`
|
|
RoleAttributes []string `json:"roleAttributes"`
|
|
Configs []string `json:"configs"`
|
|
EncryptionRequired bool `json:"encryptionRequired"`
|
|
}
|
|
|
|
func (entity *EdgeService) toBoltEntity(tx *bbolt.Tx, env Env) (*db.Service, error) {
|
|
if err := entity.validateConfigs(tx, env); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
edgeService := &db.Service{
|
|
BaseExtEntity: *boltz.NewExtEntity(entity.Id, entity.Tags),
|
|
Name: entity.Name,
|
|
MaxIdleTime: entity.MaxIdleTime,
|
|
TerminatorStrategy: entity.TerminatorStrategy,
|
|
RoleAttributes: entity.RoleAttributes,
|
|
Configs: entity.Configs,
|
|
EncryptionRequired: entity.EncryptionRequired,
|
|
}
|
|
return edgeService, nil
|
|
}
|
|
|
|
func (entity *EdgeService) toBoltEntityForCreate(tx *bbolt.Tx, env Env) (*db.Service, error) {
|
|
return entity.toBoltEntity(tx, env)
|
|
}
|
|
|
|
func (entity *EdgeService) validateConfigs(tx *bbolt.Tx, env Env) error {
|
|
if len(entity.Configs) == 0 {
|
|
return nil
|
|
}
|
|
|
|
typeMap := map[string]*db.Config{}
|
|
configStore := env.GetStores().Config
|
|
configTypeStore := env.GetStores().ConfigType
|
|
for _, id := range entity.Configs {
|
|
config, _ := configStore.LoadById(tx, id)
|
|
if config == nil {
|
|
return boltz.NewNotFoundError(db.EntityTypeConfigs, "id", id)
|
|
}
|
|
|
|
configType, _ := configTypeStore.LoadById(tx, config.TypeId)
|
|
configTypeName := "<not found>"
|
|
if configType != nil {
|
|
configTypeName = configType.Name
|
|
if configType.Target != db.ConfigTypeTargetService {
|
|
msg := fmt.Sprintf("config %v has config type %v which does not target services",
|
|
config.Name, configTypeName)
|
|
return errorz.NewFieldError(msg, "configs", entity.Configs)
|
|
}
|
|
}
|
|
|
|
if conflictConfig, found := typeMap[config.TypeId]; found {
|
|
msg := fmt.Sprintf("duplicate configs named %v and %v found for config type %v. Only one config of a given typed is allowed per service ",
|
|
conflictConfig.Name, config.Name, configTypeName)
|
|
return errorz.NewFieldError(msg, "configs", entity.Configs)
|
|
}
|
|
typeMap[config.TypeId] = config
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (entity *EdgeService) toBoltEntityForUpdate(tx *bbolt.Tx, env Env, _ boltz.FieldChecker) (*db.Service, error) {
|
|
// Reject edge updates to fabric-only services: they are not visible through the edge service
|
|
// API (see EdgeServiceManager.ReadForIdentityInTx), so allowing an update would convert a
|
|
// fabric service into an edge service. Also preserve the stored IsFabricOnly value so an edge
|
|
// update can never flip the discriminator.
|
|
existing, err := env.GetStores().Service.LoadById(tx, entity.Id)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if existing.IsFabricOnly {
|
|
return nil, boltz.NewNotFoundError(env.GetStores().Service.GetSingularEntityType(), "id", entity.Id)
|
|
}
|
|
|
|
boltEntity, err := entity.toBoltEntity(tx, env)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
boltEntity.IsFabricOnly = existing.IsFabricOnly
|
|
return boltEntity, nil
|
|
}
|
|
|
|
func (entity *EdgeService) fillFrom(_ Env, _ *bbolt.Tx, boltService *db.Service) error {
|
|
entity.FillCommon(boltService)
|
|
entity.Name = boltService.Name
|
|
entity.TerminatorStrategy = boltService.TerminatorStrategy
|
|
entity.RoleAttributes = boltService.RoleAttributes
|
|
entity.Configs = boltService.Configs
|
|
entity.EncryptionRequired = boltService.EncryptionRequired
|
|
return nil
|
|
}
|
|
|
|
type ServiceDetail struct {
|
|
models.BaseEntity
|
|
Name string `json:"name"`
|
|
MaxIdleTime time.Duration `json:"maxIdleTime"`
|
|
TerminatorStrategy string `json:"terminatorStrategy"`
|
|
RoleAttributes []string `json:"roleAttributes"`
|
|
Permissions []string `json:"permissions"`
|
|
Configs []string `json:"configs"`
|
|
Config map[string]map[string]interface{} `json:"config"`
|
|
EncryptionRequired bool `json:"encryptionRequired"`
|
|
}
|
|
|
|
func (entity *ServiceDetail) toBoltEntityForCreate(*bbolt.Tx, Env) (*db.Service, error) {
|
|
panic("should never be called")
|
|
}
|
|
|
|
func (entity *ServiceDetail) toBoltEntityForUpdate(*bbolt.Tx, Env, boltz.FieldChecker) (*db.Service, error) {
|
|
panic("should never be called")
|
|
}
|
|
|
|
func (entity *ServiceDetail) fillFrom(_ Env, _ *bbolt.Tx, boltService *db.Service) error {
|
|
entity.FillCommon(boltService)
|
|
entity.MaxIdleTime = boltService.MaxIdleTime
|
|
entity.Name = boltService.Name
|
|
entity.TerminatorStrategy = boltService.TerminatorStrategy
|
|
entity.RoleAttributes = boltService.RoleAttributes
|
|
entity.Configs = boltService.Configs
|
|
entity.EncryptionRequired = boltService.EncryptionRequired
|
|
|
|
return nil
|
|
}
|