Files
ziti/controller/model/identity_manager_test.go
T
Paul Lorenz f18ce1b580 Collapse fabric and edge services at the db level. Fixes #3934
- 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
2026-06-16 13:04:13 -04:00

142 lines
4.3 KiB
Go

package model
import (
"testing"
"github.com/openziti/ziti/v2/controller/storage/ast"
"github.com/openziti/ziti/v2/controller/storage/boltztest"
"github.com/openziti/ziti/v2/common/eid"
"github.com/openziti/ziti/v2/controller/change"
"github.com/openziti/ziti/v2/controller/db"
)
func TestIdentityManager(t *testing.T) {
ctx := NewTestContext(t)
defer ctx.Cleanup()
ctx.Init()
t.Run("test identity config overrides service delete", ctx.testIdentityConfigOverridesServiceDelete)
t.Run("test identity config overrides identity delete", ctx.testIdentityConfigOverridesIdentityDelete)
}
func (ctx *TestContext) testIdentityConfigOverridesServiceDelete(t *testing.T) {
ctx.requireNewEdgeRouter()
identity := ctx.requireNewIdentity(false)
cfg1 := ctx.requireNewConfig("host.v1", map[string]any{
"address": "localhost",
"port": 8080,
"protocol": "tcp",
})
service := ctx.requireNewService(cfg1.Id)
service.RoleAttributes = []string{eid.New()}
ctx.NoError(ctx.managers.EdgeService.Update(service, nil, change.New()))
ctx.requireNewServicePolicy(db.PolicyTypeDialName, ss("#all"), ss("#all"))
ctx.requireNewServicePolicy(db.PolicyTypeBindName, ss("#all"), ss("#all"))
ctx.requireNewEdgeRouterPolicy(ss("#all"), ss("#all"))
ctx.requireNewServiceNewEdgeRouterPolicy(ss("#all"), ss("#all"))
cfg2 := ctx.requireNewConfig("host.v1", map[string]any{
"address": "localhost",
"port": 8080,
"protocol": "tcp",
})
err := ctx.managers.Identity.AssignServiceConfigs(identity.Id, []ServiceConfig{
{
Service: service.Id,
Config: cfg2.Id,
},
}, change.New())
ctx.NoError(err)
query, err := ast.Parse(ctx.GetStores().Service, "true")
ctx.NoError(err)
result, err := ctx.managers.EdgeService.PublicQueryForIdentity(identity, map[string]struct{}{"all": {}}, query)
ctx.NoError(err)
ctx.Equal(1, len(result.Services))
err = ctx.managers.Config.Delete(cfg1.Id, change.New())
ctx.Nil(err)
err = ctx.managers.Config.Delete(cfg2.Id, change.New())
ctx.NotNil(err)
err = ctx.managers.EdgeService.Delete(service.Id, change.New())
ctx.NoError(err)
err = ctx.managers.Config.Delete(cfg2.Id, change.New())
ctx.NoError(err)
boltztest.ValidateDeleted(ctx, service.Id)
boltztest.ValidateDeleted(ctx, cfg1.Id)
boltztest.ValidateDeleted(ctx, cfg2.Id)
}
func (ctx *TestContext) testIdentityConfigOverridesIdentityDelete(t *testing.T) {
ctx.requireNewEdgeRouter()
identity := ctx.requireNewIdentity(false)
cfg1 := ctx.requireNewConfig("host.v1", map[string]any{
"address": "localhost",
"port": 8080,
"protocol": "tcp",
})
service := ctx.requireNewService(cfg1.Id)
service.RoleAttributes = []string{eid.New()}
ctx.NoError(ctx.managers.EdgeService.Update(service, nil, change.New()))
ctx.requireNewServicePolicy(db.PolicyTypeDialName, ss("#all"), ss("#all"))
ctx.requireNewServicePolicy(db.PolicyTypeBindName, ss("#all"), ss("#all"))
ctx.requireNewEdgeRouterPolicy(ss("#all"), ss("#all"))
ctx.requireNewServiceNewEdgeRouterPolicy(ss("#all"), ss("#all"))
cfg2 := ctx.requireNewConfig("host.v1", map[string]any{
"address": "localhost",
"port": 8080,
"protocol": "tcp",
})
err := ctx.managers.Identity.AssignServiceConfigs(identity.Id, []ServiceConfig{
{
Service: service.Id,
Config: cfg2.Id,
},
}, change.New())
ctx.NoError(err)
query, err := ast.Parse(ctx.GetStores().Service, "true")
ctx.NoError(err)
result, err := ctx.managers.EdgeService.PublicQueryForIdentity(identity, map[string]struct{}{"all": {}}, query)
ctx.NoError(err)
ctx.Equal(1, len(result.Services))
err = ctx.managers.Config.Delete(cfg1.Id, change.New())
ctx.Nil(err)
err = ctx.managers.Config.Delete(cfg2.Id, change.New())
ctx.NotNil(err)
err = ctx.managers.Identity.Delete(identity.Id, change.New())
ctx.NoError(err)
err = ctx.managers.Config.Delete(cfg2.Id, change.New())
ctx.NoError(err)
err = ctx.managers.EdgeService.Delete(service.Id, change.New())
ctx.NoError(err)
boltztest.ValidateDeleted(ctx, service.Id)
// Deleting an identity creates an identity-scoped revocation keyed by the
// identity id (to revoke its live OIDC sessions), which legitimately remains
// under the revocations bucket, so exclude that path from the scan.
boltztest.ValidateDeleted(ctx, identity.Id, "/"+db.RootBucket+"/"+db.EntityTypeRevocations)
boltztest.ValidateDeleted(ctx, cfg1.Id)
boltztest.ValidateDeleted(ctx, cfg2.Id)
}