mirror of
https://github.com/openziti/ziti.git
synced 2026-10-13 23:59:53 +00:00
Merge pull request #858 from openziti/add.router.re-enroll.logic
Add.router.reenroll.logic
This commit is contained in:
@@ -311,3 +311,13 @@ func NewInvalidMfaTokenError() *errorz.ApiError {
|
||||
Status: MfaInvalidTokenStatus,
|
||||
}
|
||||
}
|
||||
|
||||
func NewEdgeRouterFailedReEnrollment(cause error) *errorz.ApiError{
|
||||
return &errorz.ApiError{
|
||||
Code: EdgeRouterFailedReEnrollmentCode,
|
||||
Message: EdgeRouterFailedReEnrollmentMessage,
|
||||
Status: EdgeRouterFailedReEnrollmentStatus,
|
||||
Cause: cause,
|
||||
AppendCause: true,
|
||||
}
|
||||
}
|
||||
@@ -166,4 +166,8 @@ const (
|
||||
MfaNotEnrolledCode string = "MFA_NOT_ENROLLED"
|
||||
MfaNotEnrolledMessage string = "The current identity is not enrolled in MFA"
|
||||
MfaNotEnrolledStatus int = http.StatusConflict
|
||||
|
||||
EdgeRouterFailedReEnrollmentCode = "FAILED_ER_REENROLLMENT"
|
||||
EdgeRouterFailedReEnrollmentMessage = "the edge router failed to be re-enrolled, see cause"
|
||||
EdgeRouterFailedReEnrollmentStatus int = http.StatusInternalServerError
|
||||
)
|
||||
|
||||
@@ -153,19 +153,18 @@ func MapEdgeRouterToRestModel(ae *env.AppEnv, router *model.EdgeRouter) (*rest_m
|
||||
SyncStatus: &syncStatusStr,
|
||||
AppData: &appData,
|
||||
},
|
||||
RoleAttributes: &roleAttributes,
|
||||
EnrollmentToken: nil,
|
||||
EnrollmentCreatedAt: nil,
|
||||
EnrollmentExpiresAt: nil,
|
||||
EnrollmentJwt: nil,
|
||||
IsVerified: &router.IsVerified,
|
||||
Fingerprint: stringz.OrEmpty(router.Fingerprint),
|
||||
VersionInfo: MapVersionInfoToRestModel(routerState.VersionInfo),
|
||||
IsTunnelerEnabled: &router.IsTunnelerEnabled,
|
||||
CertPem: router.CertPem,
|
||||
RoleAttributes: &roleAttributes,
|
||||
EnrollmentToken: nil,
|
||||
EnrollmentCreatedAt: nil,
|
||||
EnrollmentExpiresAt: nil,
|
||||
EnrollmentJwt: nil,
|
||||
IsVerified: &router.IsVerified,
|
||||
Fingerprint: stringz.OrEmpty(router.Fingerprint),
|
||||
VersionInfo: MapVersionInfoToRestModel(routerState.VersionInfo),
|
||||
IsTunnelerEnabled: &router.IsTunnelerEnabled,
|
||||
CertPem: router.CertPem,
|
||||
UnverifiedFingerprint: router.UnverifiedFingerprint,
|
||||
UnverifiedCertPem: router.UnverifiedCertPem,
|
||||
|
||||
UnverifiedCertPem: router.UnverifiedCertPem,
|
||||
}
|
||||
|
||||
if !router.IsVerified {
|
||||
@@ -180,18 +179,17 @@ func MapEdgeRouterToRestModel(ae *env.AppEnv, router *model.EdgeRouter) (*rest_m
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(enrollments) != 1 {
|
||||
return nil, fmt.Errorf("expected enrollment not found for unverified edge router %s", router.Id)
|
||||
if len(enrollments) > 0 {
|
||||
enrollment := enrollments[0]
|
||||
|
||||
createdAt := strfmt.DateTime(enrollment.CreatedAt)
|
||||
expiresAt := strfmt.DateTime(*enrollment.ExpiresAt)
|
||||
|
||||
ret.EnrollmentExpiresAt = &expiresAt
|
||||
ret.EnrollmentCreatedAt = &createdAt
|
||||
ret.EnrollmentJwt = &enrollment.Jwt
|
||||
ret.EnrollmentToken = &enrollment.Token
|
||||
}
|
||||
enrollment := enrollments[0]
|
||||
|
||||
createdAt := strfmt.DateTime(enrollment.CreatedAt)
|
||||
expiresAt := strfmt.DateTime(*enrollment.ExpiresAt)
|
||||
|
||||
ret.EnrollmentExpiresAt = &expiresAt
|
||||
ret.EnrollmentCreatedAt = &createdAt
|
||||
ret.EnrollmentJwt = &enrollment.Jwt
|
||||
ret.EnrollmentToken = &enrollment.Token
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
|
||||
@@ -18,8 +18,10 @@ package routes
|
||||
|
||||
import (
|
||||
"github.com/go-openapi/runtime/middleware"
|
||||
"github.com/openziti/edge/controller/apierror"
|
||||
"github.com/openziti/edge/controller/env"
|
||||
"github.com/openziti/edge/controller/internal/permissions"
|
||||
"github.com/openziti/edge/controller/model"
|
||||
"github.com/openziti/edge/controller/response"
|
||||
"github.com/openziti/edge/rest_management_api_server/operations/edge_router"
|
||||
"github.com/openziti/fabric/controller/api"
|
||||
@@ -68,6 +70,11 @@ func (r *EdgeRouterRouter) Register(ae *env.AppEnv) {
|
||||
return ae.IsAllowed(func(ae *env.AppEnv, rc *response.RequestContext) { r.Patch(ae, rc, params) }, params.HTTPRequest, params.ID, "", permissions.IsAdmin())
|
||||
})
|
||||
|
||||
//special actions
|
||||
ae.ManagementApi.EdgeRouterReEnrollEdgeRouterHandler = edge_router.ReEnrollEdgeRouterHandlerFunc(func(params edge_router.ReEnrollEdgeRouterParams, _ interface{}) middleware.Responder {
|
||||
return ae.IsAllowed(r.ReEnroll, params.HTTPRequest, params.ID, "", permissions.IsAdmin())
|
||||
})
|
||||
|
||||
// additional lists
|
||||
ae.ManagementApi.EdgeRouterListEdgeRouterEdgeRouterPoliciesHandler = edge_router.ListEdgeRouterEdgeRouterPoliciesHandlerFunc(func(params edge_router.ListEdgeRouterEdgeRouterPoliciesParams, _ interface{}) middleware.Responder {
|
||||
return ae.IsAllowed(r.listEdgeRouterPolicies, params.HTTPRequest, params.ID, "", permissions.IsAdmin())
|
||||
@@ -146,3 +153,27 @@ func (r *EdgeRouterRouter) listServices(ae *env.AppEnv, rc *response.RequestCont
|
||||
filterTemplate := `not isEmpty(from serviceEdgeRouterPolicies where anyOf(routers) = "%v")`
|
||||
ListAssociationsWithFilter(ae, rc, filterTemplate, ae.Handlers.EdgeService, MapServiceToRestEntity)
|
||||
}
|
||||
|
||||
func (r *EdgeRouterRouter) ReEnroll(ae *env.AppEnv, rc *response.RequestContext) {
|
||||
id, _ := rc.GetEntityId()
|
||||
|
||||
var router *model.EdgeRouter
|
||||
var err error
|
||||
|
||||
if router, err = ae.GetHandlers().EdgeRouter.Read(id); err != nil {
|
||||
rc.RespondWithError(err)
|
||||
return
|
||||
}
|
||||
|
||||
if router == nil {
|
||||
rc.RespondWithNotFound()
|
||||
return
|
||||
}
|
||||
|
||||
if err := ae.GetHandlers().EdgeRouter.ReEnroll(router); err != nil {
|
||||
rc.RespondWithApiError(apierror.NewEdgeRouterFailedReEnrollment(err))
|
||||
return
|
||||
}
|
||||
|
||||
rc.RespondWithEmptyOk()
|
||||
}
|
||||
|
||||
@@ -260,6 +260,67 @@ func (handler *EdgeRouterHandler) collectEnrollmentsInTx(tx *bbolt.Tx, id string
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReEnroll creates a new JWT enrollment for an existing edge router. If the edge router already exists
|
||||
// with a JWT, a new JWT is created. If the edge router was already enrolled, all record of the enrollment is
|
||||
// reset and the edge router is disconnected forcing the edge router to complete enrollment before connecting.
|
||||
func (handler *EdgeRouterHandler) ReEnroll(router *EdgeRouter) error {
|
||||
log := pfxlog.Logger().WithField("routerId", router.Id)
|
||||
|
||||
log.Info("attempting to set edge router state to unenrolled")
|
||||
enrollment := &Enrollment{
|
||||
BaseEntity: models.BaseEntity{},
|
||||
Method: MethodEnrollEdgeRouterOtt,
|
||||
EdgeRouterId: &router.Id,
|
||||
}
|
||||
|
||||
if err := enrollment.FillJwtInfo(handler.env, router.Id); err != nil {
|
||||
return fmt.Errorf("unable to fill jwt info for re-enrolling edge router: %v", err)
|
||||
}
|
||||
|
||||
err := handler.GetDb().Update(func(tx *bbolt.Tx) error {
|
||||
ctx := boltz.NewMutateContext(tx)
|
||||
if id, err := handler.GetEnv().GetHandlers().Enrollment.createEntityInTx(ctx, enrollment); err != nil {
|
||||
return fmt.Errorf("could not create enrollment for re-enrolling edge router: %v", err)
|
||||
} else {
|
||||
log.WithField("enrollmentId", id).Infof("edge router re-enrollment entity created")
|
||||
}
|
||||
router.Fingerprint = nil
|
||||
router.CertPem = nil
|
||||
router.IsVerified = false
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("unabled to alter db for re-enrolling edge router: %v", err)
|
||||
}
|
||||
|
||||
if err := handler.PatchUnrestricted(router, boltz.MapFieldChecker{
|
||||
db.FieldRouterFingerprint: struct{}{},
|
||||
persistence.FieldEdgeRouterCertPEM: struct{}{},
|
||||
persistence.FieldEdgeRouterIsVerified: struct{}{},
|
||||
}); err != nil {
|
||||
err = fmt.Errorf("unable to patch re-enrolling edge router: %v", err)
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
|
||||
log.Info("clearing cached fingerprint for re-enrolling edge router")
|
||||
handler.env.GetHostController().GetNetwork().Routers.UpdateCachedFingerprint(router.Id, "")
|
||||
|
||||
log.Info("closing existing connections for re-enrolling edge router")
|
||||
connectedRouter := handler.env.GetHostController().GetNetwork().GetConnectedRouter(router.Id)
|
||||
if connectedRouter.Control != nil && !connectedRouter.Control.IsClosed() {
|
||||
log = log.WithField("channel", connectedRouter.Control.Id())
|
||||
log.Info("closing channel, router is flagged for re-enrollment and an existing open channel was found")
|
||||
if err := connectedRouter.Control.Close(); err != nil {
|
||||
log.Warnf("unexpected error closing channel for router flagged for re-enrollment: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type ExtendedCerts struct {
|
||||
RawClientCert []byte
|
||||
RawServerCert []byte
|
||||
|
||||
+12
-3
@@ -329,6 +329,12 @@ func (ctx *TestContext) StartServerFor(test string, clean bool) {
|
||||
}
|
||||
|
||||
func (ctx *TestContext) createAndEnrollEdgeRouter(tunneler bool, roleAttributes ...string) *edgeRouter {
|
||||
ctx.requireCreateEdgeRouter(tunneler, roleAttributes...)
|
||||
ctx.requireEnrollEdgeRouter(tunneler, ctx.edgeRouterEntity.id)
|
||||
return ctx.edgeRouterEntity
|
||||
}
|
||||
|
||||
func (ctx *TestContext) requireCreateEdgeRouter(tunneler bool, roleAttributes ...string) *edgeRouter {
|
||||
// If an edge router has already been created, delete it and create a new one
|
||||
if ctx.edgeRouterEntity != nil {
|
||||
ctx.AdminManagementSession.requireDeleteEntity(ctx.edgeRouterEntity)
|
||||
@@ -342,7 +348,12 @@ func (ctx *TestContext) createAndEnrollEdgeRouter(tunneler bool, roleAttributes
|
||||
} else {
|
||||
ctx.edgeRouterEntity = ctx.AdminManagementSession.requireNewEdgeRouter(roleAttributes...)
|
||||
}
|
||||
jwt := ctx.AdminManagementSession.getEdgeRouterJwt(ctx.edgeRouterEntity.id)
|
||||
|
||||
return ctx.edgeRouterEntity
|
||||
}
|
||||
|
||||
func (ctx *TestContext) requireEnrollEdgeRouter(tunneler bool, routerId string) {
|
||||
jwt := ctx.AdminManagementSession.getEdgeRouterJwt(routerId)
|
||||
|
||||
configFile := EdgeRouterConfFile
|
||||
if tunneler {
|
||||
@@ -356,8 +367,6 @@ func (ctx *TestContext) createAndEnrollEdgeRouter(tunneler bool, roleAttributes
|
||||
var keyAlg sdkConfig.KeyAlgVar
|
||||
_ = keyAlg.Set("RSA")
|
||||
ctx.Req.NoError(enroller.Enroll([]byte(jwt), true, "", keyAlg))
|
||||
|
||||
return ctx.edgeRouterEntity
|
||||
}
|
||||
|
||||
func (ctx *TestContext) createAndEnrollTransitRouter() *transitRouter {
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
//go:build apitests
|
||||
// +build apitests
|
||||
|
||||
/*
|
||||
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 tests
|
||||
|
||||
import (
|
||||
"github.com/openziti/edge/rest_model"
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_Router_ReEnroll(t *testing.T) {
|
||||
ctx := NewTestContext(t)
|
||||
defer ctx.Teardown()
|
||||
ctx.StartServer()
|
||||
ctx.RequireAdminManagementApiLogin()
|
||||
|
||||
t.Run("unenrolled router", func(t *testing.T) {
|
||||
ctx.testContextChanged(t)
|
||||
edgeRouter := ctx.requireCreateEdgeRouter(false)
|
||||
|
||||
t.Run("can start re-enrollment", func(t *testing.T) {
|
||||
ctx.testContextChanged(t)
|
||||
|
||||
resp, err := ctx.AdminManagementSession.newAuthenticatedRequest().SetBody("{}").Post("edge-routers/" + edgeRouter.id + "/re-enroll")
|
||||
ctx.Req.NoError(err)
|
||||
ctx.Req.Equal(http.StatusOK, resp.StatusCode())
|
||||
|
||||
edgeRouterDetail := &rest_model.EdgeRouterDetail{}
|
||||
envelope := &rest_model.DetailedEdgeRouterEnvelope{
|
||||
Data: edgeRouterDetail,
|
||||
Meta: &rest_model.Meta{},
|
||||
}
|
||||
resp, err = ctx.AdminManagementSession.newAuthenticatedRequest().SetResult(envelope).Get("edge-routers/" + edgeRouter.id)
|
||||
|
||||
ctx.Req.NoError(err)
|
||||
ctx.Req.NotNil(resp)
|
||||
ctx.Req.Equal(http.StatusOK, resp.StatusCode())
|
||||
ctx.Req.NotNil(edgeRouterDetail.EnrollmentJwt)
|
||||
|
||||
t.Run("router has no current fingerprint", func(t *testing.T) {
|
||||
ctx.testContextChanged(t)
|
||||
ctx.Req.Empty(edgeRouterDetail.Fingerprint)
|
||||
})
|
||||
|
||||
t.Run("router has a new enrollment JWT", func(t *testing.T) {
|
||||
ctx.testContextChanged(t)
|
||||
ctx.Req.NotNil(edgeRouterDetail.EnrollmentJwt)
|
||||
|
||||
t.Run("router can enroll", func(t *testing.T) {
|
||||
ctx.testContextChanged(t)
|
||||
ctx.requireEnrollEdgeRouter(false, *edgeRouterDetail.ID)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("enrolled router", func(t *testing.T) {
|
||||
|
||||
enrolledRouter := ctx.createAndEnrollEdgeRouter(false)
|
||||
|
||||
t.Run("has no current enrollment", func(t *testing.T) {
|
||||
ctx.testContextChanged(t)
|
||||
|
||||
edgeRouterDetail := &rest_model.EdgeRouterDetail{}
|
||||
resp, err := ctx.AdminManagementSession.newAuthenticatedRequest().SetBody(edgeRouterDetail).Get("edge-routers/" + enrolledRouter.id)
|
||||
|
||||
ctx.Req.NoError(err)
|
||||
ctx.Req.Equal(http.StatusOK, resp.StatusCode())
|
||||
ctx.Req.Nil(edgeRouterDetail.EnrollmentJwt)
|
||||
})
|
||||
|
||||
t.Run("can start re-enroll", func(t *testing.T) {
|
||||
ctx.testContextChanged(t)
|
||||
|
||||
resp, err := ctx.AdminManagementSession.newAuthenticatedRequest().SetBody("{}").Post("edge-routers/" + enrolledRouter.id + "/re-enroll")
|
||||
ctx.Req.NoError(err)
|
||||
ctx.Req.Equal(http.StatusOK, resp.StatusCode())
|
||||
|
||||
edgeRouterDetail := &rest_model.EdgeRouterDetail{}
|
||||
envelope := &rest_model.DetailedEdgeRouterEnvelope{
|
||||
Data: edgeRouterDetail,
|
||||
Meta: &rest_model.Meta{},
|
||||
}
|
||||
resp, err = ctx.AdminManagementSession.newAuthenticatedRequest().SetResult(envelope).Get("edge-routers/" + enrolledRouter.id)
|
||||
|
||||
ctx.Req.NoError(err)
|
||||
ctx.Req.NotNil(resp)
|
||||
ctx.Req.Equal(http.StatusOK, resp.StatusCode())
|
||||
ctx.Req.NotNil(edgeRouterDetail.EnrollmentJwt)
|
||||
|
||||
t.Run("router has no current fingerprint", func(t *testing.T) {
|
||||
ctx.testContextChanged(t)
|
||||
ctx.Req.Empty(edgeRouterDetail.Fingerprint)
|
||||
})
|
||||
|
||||
t.Run("router has a new enrollment JWT", func(t *testing.T) {
|
||||
ctx.testContextChanged(t)
|
||||
ctx.Req.NotNil(edgeRouterDetail.EnrollmentJwt)
|
||||
|
||||
t.Run("router can enroll with JWT", func(t *testing.T) {
|
||||
ctx.testContextChanged(t)
|
||||
ctx.requireEnrollEdgeRouter(false, *edgeRouterDetail.ID)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user