Merge pull request #858 from openziti/add.router.re-enroll.logic

Add.router.reenroll.logic
This commit is contained in:
Andrew
2022-01-07 08:53:03 -05:00
committed by GitHub
7 changed files with 263 additions and 26 deletions
+10
View File
@@ -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,
}
}
+4
View File
@@ -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()
}
+61
View File
@@ -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
View File
@@ -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 {
+124
View File
@@ -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)
})
})
})
})
}