diff --git a/controller/internal/routes/transit_router_api_model.go b/controller/internal/routes/transit_router_api_model.go index 275f83c7b..c3560b342 100644 --- a/controller/internal/routes/transit_router_api_model.go +++ b/controller/internal/routes/transit_router_api_model.go @@ -119,7 +119,7 @@ func MapTransitRouterToApiEntity(appEnv *env.AppEnv, context *response.RequestCo enrollment := enrollments[0] ret.EnrollmentExpiresAt = enrollment.ExpiresAt - ret.EnrollmentCreatedAt = &enrollment.CreatedAt + ret.EnrollmentCreatedAt = enrollment.IssuedAt ret.EnrollmentJwt = &enrollment.Jwt ret.EnrollmentToken = &enrollment.Token } diff --git a/controller/internal/routes/transit_router_router.go b/controller/internal/routes/transit_router_router.go index 67d8df23d..a185e365b 100644 --- a/controller/internal/routes/transit_router_router.go +++ b/controller/internal/routes/transit_router_router.go @@ -71,13 +71,13 @@ func (ir *TransitRouterRouter) Delete(ae *env.AppEnv, rc *response.RequestContex func (ir *TransitRouterRouter) Update(ae *env.AppEnv, rc *response.RequestContext) { transitRouterUpdate := &TransitRouterApi{} Update(rc, ae.Schemes.TransitRouter.Put, ir.IdType, transitRouterUpdate, func(id string) error { - return ae.Handlers.TransitRouter.Update(transitRouterUpdate.ToModel(id)) + return ae.Handlers.TransitRouter.Update(transitRouterUpdate.ToModel(id), false) }) } func (ir *TransitRouterRouter) Patch(ae *env.AppEnv, rc *response.RequestContext) { transitRouterUpdate := &TransitRouterApi{} Patch(rc, ae.Schemes.TransitRouter.Patch, ir.IdType, transitRouterUpdate, func(id string, fields JsonFields) error { - return ae.Handlers.TransitRouter.Patch(transitRouterUpdate.ToModel(id), fields.ConcatNestedNames().FilterMaps("tags")) + return ae.Handlers.TransitRouter.Patch(transitRouterUpdate.ToModel(id), fields.ConcatNestedNames().FilterMaps("tags"), false) }) } diff --git a/controller/model/enrollment_mod_trott.go b/controller/model/enrollment_mod_trott.go index 18855dd5f..ed503c956 100644 --- a/controller/model/enrollment_mod_trott.go +++ b/controller/model/enrollment_mod_trott.go @@ -23,6 +23,7 @@ import ( "github.com/netfoundry/ziti-edge/controller/validation" "github.com/netfoundry/ziti-edge/internal/cert" "github.com/xeipuuv/gojsonschema" + "strings" "time" ) @@ -152,9 +153,9 @@ func (module *EnrollModuleRouterOtt) Process(context EnrollmentContext) (*Enroll cltFp := module.fingerprintGenerator.FromPem(cltPem) txRouter.IsVerified = true - txRouter.Fingerprint = cltFp + txRouter.Fingerprint = strings.ToLower(strings.Replace(cltFp, ":", "", -1)) - if err := module.env.GetHandlers().TransitRouter.Update(txRouter); err != nil { + if err := module.env.GetHandlers().TransitRouter.Update(txRouter, true); err != nil { return nil, fmt.Errorf("could not update edge router: %s", err) } diff --git a/controller/model/transit_router_handlers.go b/controller/model/transit_router_handlers.go index 4f20fbc12..d56f569e9 100644 --- a/controller/model/transit_router_handlers.go +++ b/controller/model/transit_router_handlers.go @@ -28,6 +28,10 @@ import ( func NewTransitRouterHandler(env Env) *TransitRouterHandler { handler := &TransitRouterHandler{ baseHandler: newBaseHandler(env, env.GetStores().TransitRouter), + allowedFields: boltz.MapFieldChecker{ + persistence.FieldName: struct{}{}, + boltz.FieldTags: struct{}{}, + }, } handler.impl = handler return handler @@ -35,6 +39,7 @@ func NewTransitRouterHandler(env Env) *TransitRouterHandler { type TransitRouterHandler struct { baseHandler + allowedFields boltz.FieldChecker } func (handler *TransitRouterHandler) Delete(id string) error { @@ -97,12 +102,21 @@ func (handler *TransitRouterHandler) CreateWithEnrollment(txRouter *TransitRoute return txRouter.Id, enrollmentId, nil } -func (handler *TransitRouterHandler) Update(entity *TransitRouter) error { - return handler.updateEntity(entity, nil) +func (handler *TransitRouterHandler) Update(entity *TransitRouter, allowAllFields bool) error { + if allowAllFields { + return handler.updateEntity(entity, nil) + } + + return handler.updateEntity(entity, handler.allowedFields) + } -func (handler *TransitRouterHandler) Patch(entity *TransitRouter, checker boltz.FieldChecker) error { - return handler.patchEntity(entity, checker) +func (handler *TransitRouterHandler) Patch(entity *TransitRouter, checker boltz.FieldChecker, allowAllFields bool) error { + if allowAllFields { + return handler.patchEntity(entity, checker) + } + combinedChecker := &AndFieldChecker{first: handler.allowedFields, second: checker} + return handler.patchEntity(entity, combinedChecker) } func (handler *TransitRouterHandler) ReadOneByQuery(query string) (*TransitRouter, error) { diff --git a/controller/model/transit_router_model.go b/controller/model/transit_router_model.go index e947f28f6..41e913c7c 100644 --- a/controller/model/transit_router_model.go +++ b/controller/model/transit_router_model.go @@ -51,8 +51,10 @@ func (entity *TransitRouter) toBoltEntityForUpdate(tx *bbolt.Tx, handler Handler ret := &persistence.TransitRouter{ Router: db.Router{ BaseExtEntity: *boltz.NewExtEntity(entity.Id, entity.Tags), + Fingerprint: entity.Fingerprint, }, - Name: entity.Name, + Name: entity.Name, + IsVerified: entity.IsVerified, } return ret, nil diff --git a/controller/persistence/transit_router_store.go b/controller/persistence/transit_router_store.go index 851fc7c3d..1c161896d 100644 --- a/controller/persistence/transit_router_store.go +++ b/controller/persistence/transit_router_store.go @@ -78,7 +78,11 @@ func (entity *TransitRouter) LoadValues(store boltz.CrudStore, bucket *boltz.Typ func (entity *TransitRouter) SetValues(ctx *boltz.PersistContext) { entity.Router.SetValues(ctx.GetParentContext()) entity.SetBaseValues(ctx) - ctx.SetString(FieldName, entity.Name) + if ctx.Bucket != nil { + ctx.SetString(FieldName, entity.Name) + ctx.SetBool(FieldTransitRouterIsVerified, entity.IsVerified) + ctx.SetStringList(FieldTransitRouterEnrollments, entity.Enrollments) + } } func (entity *TransitRouter) GetEntityType() string { diff --git a/tests/context.go b/tests/context.go index 1d44824d0..ee1c5b038 100644 --- a/tests/context.go +++ b/tests/context.go @@ -267,6 +267,19 @@ func (ctx *TestContext) createAndEnrollTransitRouter() *transitRouter { return ctx.transitRouterEntity } +func (ctx *TestContext) createEnrollAndStartTransitRouter() { + ctx.createAndEnrollTransitRouter() + ctx.startTransitRouter() +} + +func (ctx *TestContext) startTransitRouter() { + config, err := router.LoadConfig(TransitRouterConfFile) + ctx.req.NoError(err) + ctx.router = router.Create(config) + + ctx.req.NoError(ctx.router.Start()) +} + func (ctx *TestContext) createEnrollAndStartEdgeRouter(roleAttributes ...string) { ctx.createAndEnrollEdgeRouter(roleAttributes...) @@ -518,3 +531,58 @@ func (ctx *TestContext) idsJson(ids ...string) *gabs.Container { ctx.setJsonValue(entityData, ids, "ids") return entityData } + +func (ctx *TestContext) requireEntityNotEnrolled(name string, entity *gabs.Container) { + fingerprint, ok := entity.Path("fingerprint").Data().(string) + ctx.req.True(ok, "expected "+name+" with isVerified=false to not have a fingerprint, could not cast") + ctx.req.Empty(fingerprint, "expected "+name+" with isVerified=false to have an empty fingerprint") + + token, ok := entity.Path("enrollmentToken").Data().(string) + ctx.req.True(ok, "expected "+name+" with isVerified=false to have an enrollment token, could not cast") + ctx.req.NotEmpty(token, "expected "+name+" with isVerified=false to have an enrollment token, was empty") + + jwt, ok := entity.Path("enrollmentJwt").Data().(string) + ctx.req.True(ok, "expected "+name+" with isVerified=false to have an enrollment jwt, could not cast") + ctx.req.NotEmpty(jwt, "expected "+name+" with isVerified=false to have an enrollment jwt, was empty") + + createdAtStr, ok := entity.Path("enrollmentCreatedAt").Data().(string) + ctx.req.True(ok, "expected "+name+" with isVerified=false to have an enrollment created at date, could not cast") + ctx.req.NotEmpty(createdAtStr, "expected "+name+" with isVerified=false to have an enrollment created at date string, was empty") + + createdAt, err := time.Parse(time.RFC3339, createdAtStr) + ctx.req.NoError(err, "expected "+name+" with isVerified=false to have a parsable created at date time string") + ctx.req.NotEmpty(createdAt, "expected "+name+" with isVerified=false to have an enrollment created at date, was empty") + + expiresAtStr, ok := entity.Path("enrollmentExpiresAt").Data().(string) + ctx.req.True(ok, "expected "+name+" with isVerified=false to have an enrollment expires at date, could not cast") + ctx.req.NotEmpty(expiresAtStr, "expected "+name+" with isVerified=false to have an enrollment expires at date string, was empty") + + expiresAt, err := time.Parse(time.RFC3339, expiresAtStr) + ctx.req.NoError(err, "expected "+name+" with isVerified=false to have a parsable expires at date time string") + + ctx.req.True(ok, "expected "+name+" with isVerified=false to have an enrollment expires at date, could not cast") + ctx.req.NotEmpty(expiresAt, "expected "+name+" with isVerified=false to have an enrollment expires at date, was empty") + + ctx.req.True(expiresAt.After(createdAt), "expected "+name+" with isVerified=false to have an enrollment expires at date after the created at date") +} + +func (ctx *TestContext) requireEntityEnrolled(name string, entity *gabs.Container) { + fingerprint, ok := entity.Path("fingerprint").Data().(string) + ctx.req.True(ok, "expected "+name+" with isVerified=true to have a fingerprint, could not cast") + ctx.req.NotEmpty(fingerprint, "expected "+name+" with isVerified=true to have a fingerprint, was empty") + ctx.req.False(strings.Contains(fingerprint, ":"), "fingerprint should not contain colons") + ctx.req.False(strings.ToLower(fingerprint) != fingerprint, "fingerprint should not contain uppercase characters") + + token := entity.Path("enrollmentToken").Data() + ctx.req.Nil(token, "expected "+name+" with isVerified=true to have an nil enrollment token") + + jwt := entity.Path("enrollmentJwt").Data() + ctx.req.Nil(jwt, "expected "+name+" with isVerified=true to have an nil enrollment jwt") + + createdAt := entity.Path("enrollmentCreatedAt").Data() + ctx.req.Nil(createdAt, "expected "+name+" with isVerified=true to have an nil enrollment created at date") + + expiresAt := entity.Path("enrollmentExpiresAt").Data() + ctx.req.Nil(expiresAt, "expected "+name+" with isVerified=true to have an nil enrollment expires at date") +} + diff --git a/tests/transit_router_test.go b/tests/transit_router_test.go index b7d823494..49216cba6 100644 --- a/tests/transit_router_test.go +++ b/tests/transit_router_test.go @@ -19,7 +19,10 @@ package tests import ( + "github.com/netfoundry/ziti-fabric/controller/models" + "github.com/netfoundry/ziti-fabric/controller/network" "testing" + "time" ) func Test_TransitRouters(t *testing.T) { @@ -33,6 +36,56 @@ func Test_TransitRouters(t *testing.T) { ctx.createAndEnrollTransitRouter() }) + t.Run("transit routers can be created, enrolled, and started", func(t *testing.T) { + ctx.testContextChanged(t) + ctx.createEnrollAndStartTransitRouter() + }) + + t.Run("transit routers can be created, enrolled, and listed", func(t *testing.T) { + ctx.testContextChanged(t) + ctx.AdminSession.requireQuery("transit-routers") + }) + + t.Run("transit routers can be listed with enrolled and un-enrolled states", func(t *testing.T) { + ctx.testContextChanged(t) + ctx.createAndEnrollTransitRouter() + _ = ctx.AdminSession.requireNewTransitRouter() + + body := ctx.AdminSession.requireQuery("transit-routers") + ctx.logJson(body.Bytes()) + + t.Run("enrolled router is verified and has a fingerprint, un-enrolled router does not", func(t *testing.T) { + ctx.testContextChanged(t) + + routers := body.Path("data") + + children, err := routers.Children() + ctx.req.NoError(err) + + ctx.req.Len(children, 2, "two routers should have been returned") + + router0IsVerified, ok := children[0].Path("isVerified").Data().(bool) + ctx.req.True(ok, "issue getting transit router 0 isVerified state") + + router1IsVerified, ok := children[1].Path("isVerified").Data().(bool) + ctx.req.True(ok, "issue getting transit router 1 isVerified state") + + ctx.req.True(router0IsVerified != router1IsVerified, "expected 1 enrolled transit router and 1 un-enrolled transit router") + + if router0IsVerified { + ctx.requireEntityEnrolled("transit router 0", children[0]) + } else { + ctx.requireEntityNotEnrolled("transit router 0", children[0]) + } + + if router1IsVerified { + ctx.requireEntityEnrolled("transit router 1", children[1]) + } else { + ctx.requireEntityNotEnrolled("transit router 1", children[1]) + } + }) + }) + t.Run("create transit router, then delete", func(t *testing.T) { ctx.testContextChanged(t) router := ctx.AdminSession.requireNewTransitRouter() @@ -44,4 +97,24 @@ func Test_TransitRouters(t *testing.T) { router := ctx.createAndEnrollTransitRouter() ctx.AdminSession.requireDeleteEntity(router) }) + + t.Run("can list transit routers created in fabric", func(t *testing.T) { + ctx.testContextChanged(t) + + fabTxRouter := &network.Router{ + BaseEntity: models.BaseEntity{ + Id: "uMvqq", + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + Tags: nil, + }, + Fingerprint: "f6fc1c03175f674f1f0b505a9ff930e5", + AdvertisedListener: "tls:127.0.0.1", + } + err := ctx.fabricController.GetNetwork().Routers.Create(fabTxRouter) + ctx.req.NoError(err, "could not create router at fabric level") + + body := ctx.AdminSession.requireQuery("transit-routers") + ctx.logJson(body.Bytes()) + }) }