Files
ziti/controller/handler_edge_ctrl/common.go
T
2021-09-08 15:59:35 -04:00

513 lines
16 KiB
Go

package handler_edge_ctrl
import (
"fmt"
"github.com/michaelquigley/pfxlog"
"github.com/openziti/edge/controller/env"
"github.com/openziti/edge/controller/model"
"github.com/openziti/edge/controller/persistence"
"github.com/openziti/edge/edge_common"
"github.com/openziti/edge/pb/edge_ctrl_pb"
"github.com/openziti/fabric/controller/db"
"github.com/openziti/fabric/controller/network"
"github.com/openziti/fabric/controller/xt"
"github.com/openziti/fabric/logcontext"
"github.com/openziti/foundation/channel2"
"github.com/openziti/foundation/identity/identity"
"github.com/openziti/foundation/storage/boltz"
"github.com/openziti/foundation/util/stringz"
"github.com/openziti/sdk-golang/ziti/edge"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"go.etcd.io/bbolt"
"math"
"strings"
"time"
)
type requestHandler interface {
getAppEnv() *env.AppEnv
getNetwork() *network.Network
getChannel() channel2.Channel
ContentType() int32
Label() string
}
type UpdateTerminatorRequest interface {
GetCost() uint32
GetUpdateCost() bool
GetPrecedence() edge_ctrl_pb.TerminatorPrecedence
GetUpdatePrecedence() bool
}
type baseRequestHandler struct {
ch channel2.Channel
appEnv *env.AppEnv
}
func (self *baseRequestHandler) getNetwork() *network.Network {
return self.appEnv.GetHostController().GetNetwork()
}
func (self *baseRequestHandler) getAppEnv() *env.AppEnv {
return self.appEnv
}
func (self *baseRequestHandler) getChannel() channel2.Channel {
return self.ch
}
func (self *baseRequestHandler) returnError(ctx requestContext, err controllerError) {
ctx.CleanupOnError()
responseMsg := channel2.NewMessage(int32(edge_ctrl_pb.ContentType_ErrorType), []byte(err.Error()))
responseMsg.PutUint32Header(edge.ErrorCodeHeader, err.ErrorCode())
responseMsg.ReplyTo(ctx.GetMessage())
logger := pfxlog.
ContextLogger(self.ch.Label()).
WithError(err).
WithField("router", ctx.GetHandler().getChannel().Id().Token).
WithField("operation", ctx.GetHandler().Label())
if sessionCtx, ok := ctx.(sessionRequestContext); ok {
logger = logger.WithField("token", sessionCtx.GetSessionToken())
}
if sendErr := self.ch.Send(responseMsg); sendErr != nil {
logger.WithError(err).WithField("sendError", sendErr).Error("failed to send error response")
} else {
logger.WithError(err).Error("responded with error")
}
}
func (self *baseRequestHandler) logResult(ctx requestContext, err error) {
logger := logrus.
WithField("router", ctx.GetHandler().getChannel().Id().Token).
WithField("operation", ctx.GetHandler().Label())
if sessionCtx, ok := ctx.(sessionRequestContext); ok {
logger = logger.WithField("token", sessionCtx.GetSessionToken())
}
if err != nil {
logger.WithError(err).Error("operation failed")
} else {
logger.Debug("operation success")
}
}
type requestContext interface {
GetHandler() requestHandler
GetMessage() *channel2.Message
CleanupOnError()
}
type sessionRequestContext interface {
requestContext
GetSessionToken() string
}
type baseSessionRequestContext struct {
handler requestHandler
msg *channel2.Message
err controllerError
sourceRouter *network.Router
session *model.Session
service *model.Service
newSession bool
logContext logcontext.Context
}
func (self *baseSessionRequestContext) CleanupOnError() {
if self.newSession && self.session != nil {
logger := logrus.
WithField("operation", self.handler.Label()).
WithField("router", self.sourceRouter.Name)
if err := self.handler.getAppEnv().Handlers.Session.Delete(self.session.Id); err != nil {
logger.WithError(err).Error("unable to delete session created before error encountered")
}
}
}
func (self *baseSessionRequestContext) GetMessage() *channel2.Message {
return self.msg
}
func (self *baseSessionRequestContext) GetHandler() requestHandler {
return self.handler
}
func (self *baseSessionRequestContext) loadRouter() bool {
routerId := self.handler.getChannel().Id().Token
var err error
self.sourceRouter, err = self.handler.getNetwork().GetRouter(routerId)
if err != nil {
self.err = internalError(err)
logrus.
WithField("router", routerId).
WithField("operation", self.handler.Label()).
WithError(self.err).Errorf("could not find router closing channel")
_ = self.handler.getChannel().Close()
return false
}
return true
}
func (self *baseSessionRequestContext) loadSession(token string) {
if self.err == nil {
var err error
self.session, err = self.handler.getAppEnv().Handlers.Session.ReadByToken(token)
if err != nil {
if boltz.IsErrNotFoundErr(err) {
self.err = InvalidApiSessionError{}
} else {
self.err = internalError(err)
}
logrus.
WithField("token", token).
WithField("operation", self.handler.Label()).
WithError(self.err).Errorf("invalid session")
return
}
apiSession, err := self.handler.getAppEnv().Handlers.ApiSession.Read(self.session.ApiSessionId)
if err != nil {
if boltz.IsErrNotFoundErr(err) {
self.err = InvalidApiSessionError{}
} else {
self.err = internalError(err)
}
logrus.
WithField("token", token).
WithField("operation", self.handler.Label()).
WithError(self.err).Errorf("invalid api-session")
return
}
self.logContext = logcontext.NewContext()
traceSpec := self.handler.getAppEnv().TraceManager.GetIdentityTrace(apiSession.IdentityId)
traceEnabled := traceSpec != nil && time.Now().Before(traceSpec.Until)
if traceEnabled {
self.logContext.SetChannelsMask(traceSpec.ChannelMask)
self.logContext.WithField("traceId", traceSpec.TraceId)
}
self.logContext.WithField("sessionId", self.session.Id)
self.logContext.WithField("apiSessionId", apiSession.Id)
if traceEnabled {
pfxlog.ChannelLogger(logcontext.EstablishPath).
Wire(self.logContext).
Debug("tracing enabled for this session")
}
}
}
func (self *baseSessionRequestContext) checkSessionType(sessionType string) {
if self.err == nil {
if self.session.Type != sessionType {
self.err = WrongSessionTypeError{}
logrus.
WithField("sessionId", self.session.Id).
WithField("operation", self.handler.Label()).
WithError(self.err).Errorf("wrong session type")
}
}
}
func (self *baseSessionRequestContext) checkSessionFingerprints(fingerprints []string) {
if self.err == nil {
now := time.Now()
var sessionFingerprints []string
for _, cert := range self.session.SessionCerts {
if !cert.ValidFrom.After(now) && !cert.ValidTo.Before(now) {
sessionFingerprints = append(sessionFingerprints, cert.Fingerprint)
}
}
found := stringz.ContainsAny(sessionFingerprints, fingerprints...)
if !found {
err := self.GetHandler().getAppEnv().Handlers.ApiSession.VisitFingerprintsForApiSessionId(self.session.ApiSessionId, func(fingerprint string) bool {
sessionFingerprints = append(sessionFingerprints, fingerprint)
if stringz.Contains(fingerprints, fingerprint) {
found = true
return true
}
return false
})
self.err = internalError(err)
}
if self.err != nil || !found {
if self.err == nil {
self.err = InvalidApiSessionError{}
}
logrus.
WithField("sessionId", self.session.Id).
WithField("operation", self.handler.Label()).
WithField("sessionFingerprints", sessionFingerprints).
WithField("clientFingerprints", fingerprints).
Error("matching fingerprint not found for connect")
}
}
}
func (self *baseSessionRequestContext) verifyEdgeRouterAccess() {
if self.err == nil {
// validate edge router
result, err := self.handler.getAppEnv().Handlers.EdgeRouter.ListForSession(self.session.Id)
if err != nil {
self.err = internalError(err)
logrus.
WithField("sessionId", self.session.Id).
WithField("operation", self.handler.Label()).
WithError(err).Error("unable to verify edge router access")
return
}
edgeRouterAllowed := false
for _, er := range result.EdgeRouters {
if er.Id == self.sourceRouter.Id {
edgeRouterAllowed = true
break
}
}
if !edgeRouterAllowed {
self.err = InvalidEdgeRouterForSessionError{}
}
}
}
func (self *baseSessionRequestContext) loadService() {
if self.err == nil {
var err error
self.service, err = self.handler.getAppEnv().Handlers.EdgeService.Read(self.session.ServiceId)
if err != nil {
if boltz.IsErrNotFoundErr(err) {
err = InvalidServiceError{}
} else {
err = internalError(err)
}
logrus.
WithField("sessionId", self.session.Id).
WithField("operation", self.handler.Label()).
WithField("serviceId", self.session.ServiceId).
WithError(self.err).
Error("service not found")
}
}
}
func (self *baseSessionRequestContext) validateTerminatorIdentity(tx *bbolt.Tx, terminator terminator) error {
session, err := self.getTerminatorSession(tx, terminator, "")
if err != nil {
return err
}
if terminator.GetIdentity() == "" {
return nil
}
identityTerminators, err := self.GetHandler().getAppEnv().BoltStores.Terminator.GetTerminatorsInIdentityGroup(tx, terminator.GetId())
for _, otherTerminator := range identityTerminators {
otherSession, err := self.getTerminatorSession(tx, otherTerminator, "sibling ")
if err != nil {
return err
}
if otherSession != nil {
if otherSession.ApiSession.IdentityId != session.ApiSession.IdentityId {
return errors.Errorf("sibling terminator %v with shared identity %v belongs to different identity", terminator.GetId(), terminator.GetIdentity())
}
}
}
return nil
}
type terminator interface {
GetId() string
GetIdentity() string
GetBinding() string
GetAddress() string
}
func (self *baseSessionRequestContext) getTerminatorSession(tx *bbolt.Tx, terminator terminator, context string) (*persistence.Session, error) {
if terminator.GetBinding() != edge_common.EdgeBinding {
return nil, errors.Errorf("%vterminator %v with identity %v is not edge terminator. Can't share identity", context, terminator.GetId(), terminator.GetIdentity())
}
addressParts := strings.Split(terminator.GetAddress(), ":")
if len(addressParts) != 2 {
return nil, errors.Errorf("%vterminator %v with identity %v is not edge terminator. Can't share identity", context, terminator.GetId(), terminator.GetIdentity())
}
if addressParts[0] != "hosted" {
return nil, errors.Errorf("%vterminator %v with identity %v is not edge terminator. Can't share identity", context, terminator.GetId(), terminator.GetIdentity())
}
sessionToken := addressParts[1]
session, err := self.GetHandler().getAppEnv().BoltStores.Session.LoadOneByToken(tx, sessionToken)
if err != nil {
pfxlog.Logger().Warnf("sibling terminator %v with shared identity %v has invalid session token %v", terminator.GetId(), terminator.GetIdentity(), sessionToken)
return nil, nil
}
if session.ApiSession == nil {
apiSession, err := self.GetHandler().getAppEnv().BoltStores.ApiSession.LoadOneById(tx, session.ApiSessionId)
if err != nil {
return nil, err
}
session.ApiSession = apiSession
}
return session, nil
}
func (self *baseSessionRequestContext) verifyTerminator(terminatorId string, binding string) *network.Terminator {
if self.err == nil {
var terminator *network.Terminator
var err error
terminator, err = self.handler.getNetwork().Terminators.Read(terminatorId)
if err != nil {
if boltz.IsErrNotFoundErr(err) {
self.err = invalidTerminator("invalid terminator: not found")
} else {
self.err = internalError(err)
}
log := logrus.
WithField("operation", self.handler.Label()).
WithField("terminatorId", terminatorId).
WithError(self.err)
if self.session != nil {
log = log.WithField("sessionId", self.session.Id)
}
log.Error("terminator not found")
return nil
}
if terminator != nil && terminator.Router != self.sourceRouter.Id {
self.err = invalidTerminator(fmt.Sprintf("%v request for terminator %v on router %v came from router %v",
self.handler.Label(), terminatorId, terminator.Router, self.sourceRouter.Id))
log := logrus.
WithField("operation", self.handler.Label()).
WithField("sourceRouter", self.sourceRouter.Id).
WithField("terminatorId", terminatorId).
WithField("terminatorRouter", terminator.Router).
WithError(self.err)
if self.session != nil {
log = log.WithField("sessionId", self.session.Id)
}
log.Error("not allowed to operate on terminators on other routers")
return nil
}
if terminator != nil && terminator.Binding != binding {
self.err = invalidTerminator(fmt.Sprintf("can't operate on terminator %v with wrong binding, expected binding %v, was %v ",
terminatorId, binding, terminator.Binding))
log := logrus.
WithField("operation", self.handler.Label()).
WithField("sourceRouter", self.sourceRouter.Id).
WithField("terminatorId", terminatorId).
WithField("terminatorRouter", terminator.Router).
WithField("binding", terminator.Binding).
WithField("expectedBinding", binding).
WithError(self.err)
if self.session != nil {
log = log.WithField("sessionId", self.session.Id)
}
log.Error("incorrect binding")
return nil
}
return terminator
}
return nil
}
func (self *baseSessionRequestContext) updateTerminator(terminator *network.Terminator, request UpdateTerminatorRequest) {
if self.err == nil {
checker := boltz.MapFieldChecker{}
if request.GetUpdateCost() {
if request.GetCost() > math.MaxUint16 {
self.err = invalidCost(fmt.Sprintf("invalid cost %v. cost must be between 0 and %v inclusive", request.GetCost(), math.MaxUint16))
return
}
terminator.Cost = uint16(request.GetCost())
checker[db.FieldTerminatorCost] = struct{}{}
}
if request.GetUpdatePrecedence() {
if request.GetPrecedence() == edge_ctrl_pb.TerminatorPrecedence_Default {
terminator.Precedence = xt.Precedences.Default
} else if request.GetPrecedence() == edge_ctrl_pb.TerminatorPrecedence_Required {
terminator.Precedence = xt.Precedences.Required
} else if request.GetPrecedence() == edge_ctrl_pb.TerminatorPrecedence_Failed {
terminator.Precedence = xt.Precedences.Failed
} else {
self.err = invalidPrecedence(fmt.Sprintf("invalid precedence: %v", request.GetPrecedence()))
return
}
checker[db.FieldTerminatorPrecedence] = struct{}{}
}
self.err = internalError(self.handler.getNetwork().Terminators.Patch(terminator, checker))
}
}
func (self *baseSessionRequestContext) createCircuit(terminatorIdentity string, peerData map[uint32][]byte) (*network.Circuit, map[uint32][]byte) {
var circuit *network.Circuit
returnPeerData := map[uint32][]byte{}
if self.err == nil {
if self.service.EncryptionRequired && peerData[edge.PublicKeyHeader] == nil {
self.err = encryptionDataMissing("encryption required on service, initiator did not send public header")
return nil, nil
}
serviceId := self.session.ServiceId
if terminatorIdentity != "" {
serviceId = terminatorIdentity + "@" + serviceId
}
clientId := &identity.TokenId{Token: self.session.Id, Data: peerData}
n := self.handler.getAppEnv().GetHostController().GetNetwork()
var err error
circuit, err = n.CreateCircuit(self.sourceRouter, clientId, serviceId, self.logContext)
if err != nil {
self.err = internalError(err)
}
if circuit != nil {
//static terminator peer data
for k, v := range circuit.Terminator.GetPeerData() {
returnPeerData[k] = v
}
//runtime peer data
for k, v := range circuit.PeerData {
returnPeerData[k] = v
}
if self.service.EncryptionRequired && returnPeerData[edge.PublicKeyHeader] == nil {
self.err = encryptionDataMissing("encryption required on service, terminator did not send public header")
if err := n.RemoveCircuit(circuit.Id, true); err != nil {
logrus.
WithField("operation", self.handler.Label()).
WithField("sourceRouter", self.sourceRouter.Id).
WithError(err).
Error("failed to remove session")
}
return nil, nil
}
}
}
return circuit, returnPeerData
}