mirror of
https://github.com/openziti/ziti.git
synced 2026-09-10 16:55:41 +00:00
421 lines
12 KiB
Go
421 lines
12 KiB
Go
/*
|
|
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 server
|
|
|
|
import (
|
|
"fmt"
|
|
"github.com/michaelquigley/pfxlog"
|
|
"github.com/openziti/channel/v2"
|
|
"github.com/openziti/storage/boltz"
|
|
"github.com/openziti/ziti/common/config"
|
|
"github.com/openziti/ziti/common/pb/edge_ctrl_pb"
|
|
runner2 "github.com/openziti/ziti/common/runner"
|
|
"github.com/openziti/ziti/controller/api_impl"
|
|
edgeconfig "github.com/openziti/ziti/controller/config"
|
|
"github.com/openziti/ziti/controller/env"
|
|
"github.com/openziti/ziti/controller/handler_edge_ctrl"
|
|
"github.com/openziti/ziti/controller/handler_edge_mgmt"
|
|
"github.com/openziti/ziti/controller/internal/policy"
|
|
_ "github.com/openziti/ziti/controller/internal/routes"
|
|
"github.com/openziti/ziti/controller/model"
|
|
sync2 "github.com/openziti/ziti/controller/sync_strats"
|
|
"os"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
type Controller struct {
|
|
config *edgeconfig.Config
|
|
AppEnv *env.AppEnv
|
|
xmgmt *submgmt
|
|
xctrl *subctrl
|
|
policyEngine runner2.Runner
|
|
isLoaded bool
|
|
initModulesOnce sync.Once
|
|
initialized bool
|
|
}
|
|
|
|
const (
|
|
policyMinFreq = 1 * time.Second
|
|
policyMaxFreq = 1 * time.Hour
|
|
policyAppWanFreq = 1 * time.Second
|
|
policySessionFreq = 5 * time.Second
|
|
|
|
ZitiInstanceId = "ziti-instance-id"
|
|
)
|
|
|
|
func NewController(cfg config.Configurable, host env.HostController) (*Controller, error) {
|
|
c := &Controller{}
|
|
|
|
if err := cfg.Configure(c); err != nil {
|
|
return nil, fmt.Errorf("failed to load configuration: %s", err)
|
|
}
|
|
|
|
if !c.IsEnabled() {
|
|
return c, nil
|
|
}
|
|
|
|
c.AppEnv = env.NewAppEnv(c.config, host)
|
|
|
|
c.AppEnv.TraceManager = env.NewTraceManager(host.GetCloseNotifyChannel())
|
|
c.AppEnv.HostController.GetNetwork().AddCapability("ziti.edge")
|
|
|
|
pfxlog.Logger().Infof("edge controller instance id: %s", c.AppEnv.InstanceId)
|
|
|
|
pe, err := runner2.NewRunner(policyMinFreq, policyMaxFreq, func(e error, enforcer runner2.Operation) {
|
|
pfxlog.Logger().
|
|
WithField("cause", e).
|
|
WithField("enforcerName", enforcer.GetName()).
|
|
WithField("enforcerId", enforcer.GetId()).
|
|
Errorf("error running policy enforcer")
|
|
})
|
|
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create policy runner: %s", err)
|
|
}
|
|
|
|
c.policyEngine = pe
|
|
|
|
c.xmgmt = &submgmt{
|
|
parent: c,
|
|
}
|
|
|
|
c.xctrl = &subctrl{
|
|
parent: c,
|
|
}
|
|
|
|
// Add the root host controller's identity's CAs to the ca's served by well-known urls
|
|
if caCerts, err := os.ReadFile(c.AppEnv.HostController.Identity().GetConfig().CA); err == nil {
|
|
c.config.AddCaPems(caCerts)
|
|
} else {
|
|
pfxlog.Logger().Fatalf("could not read controller identity CA file: %s: %v", c.AppEnv.HostController.Identity().GetConfig().CA, err)
|
|
}
|
|
|
|
if err := host.RegisterXctrl(c.xctrl); err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
if err := host.RegisterXmgmt(c.xmgmt); err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
host.RegisterAgentBindHandler(channel.BindHandlerF(func(binding channel.Binding) error {
|
|
binding.AddTypedReceiveHandler(handler_edge_mgmt.NewInitEdgeHandler(c.AppEnv))
|
|
return nil
|
|
}))
|
|
|
|
api_impl.OverrideRequestWrapper(&fabricWrapper{ae: c.AppEnv})
|
|
|
|
return c, nil
|
|
}
|
|
|
|
func (c *Controller) IsEnabled() bool {
|
|
return c.config != nil && c.config.Enabled
|
|
}
|
|
|
|
func (c *Controller) SetHostController(h env.HostController) {
|
|
if !c.IsEnabled() {
|
|
return
|
|
}
|
|
|
|
c.AppEnv.HostController = h
|
|
c.AppEnv.TraceManager = env.NewTraceManager(h.GetCloseNotifyChannel())
|
|
c.AppEnv.HostController.GetNetwork().AddCapability("ziti.edge")
|
|
if err := h.RegisterXctrl(c.xctrl); err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
if err := h.RegisterXmgmt(c.xmgmt); err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
func (c *Controller) GetCtrlHandlers(binding channel.Binding) []channel.TypedReceiveHandler {
|
|
ch := binding.GetChannel()
|
|
tunnelState := handler_edge_ctrl.NewTunnelState()
|
|
|
|
result := []channel.TypedReceiveHandler{
|
|
handler_edge_ctrl.NewSessionHeartbeatHandler(c.AppEnv),
|
|
handler_edge_ctrl.NewCreateCircuitHandler(c.AppEnv, ch),
|
|
handler_edge_ctrl.NewCreateCircuitV2Handler(c.AppEnv, ch),
|
|
handler_edge_ctrl.NewCreateTerminatorHandler(c.AppEnv, ch),
|
|
handler_edge_ctrl.NewCreateTerminatorV2Handler(c.AppEnv, ch),
|
|
handler_edge_ctrl.NewUpdateTerminatorHandler(c.AppEnv, ch),
|
|
handler_edge_ctrl.NewRemoveTerminatorHandler(c.AppEnv, ch),
|
|
handler_edge_ctrl.NewValidateSessionsHandler(c.AppEnv, ch),
|
|
handler_edge_ctrl.NewHealthEventHandler(c.AppEnv, ch),
|
|
|
|
handler_edge_ctrl.NewCreateApiSessionHandler(c.AppEnv, ch, tunnelState),
|
|
handler_edge_ctrl.NewCreateCircuitForTunnelHandler(c.AppEnv, ch, tunnelState),
|
|
handler_edge_ctrl.NewCreateTunnelTerminatorHandler(c.AppEnv, ch, tunnelState),
|
|
handler_edge_ctrl.NewUpdateTunnelTerminatorHandler(c.AppEnv, ch),
|
|
handler_edge_ctrl.NewRemoveTunnelTerminatorHandler(c.AppEnv, ch),
|
|
handler_edge_ctrl.NewListTunnelServicesHandler(c.AppEnv, ch, tunnelState),
|
|
handler_edge_ctrl.NewTunnelHealthEventHandler(c.AppEnv, ch),
|
|
handler_edge_ctrl.NewExtendEnrollmentHandler(c.AppEnv),
|
|
handler_edge_ctrl.NewExtendEnrollmentVerifyHandler(c.AppEnv),
|
|
}
|
|
|
|
result = append(result, c.AppEnv.Broker.GetReceiveHandlers()...)
|
|
|
|
return result
|
|
}
|
|
|
|
func (c *Controller) GetMgmtHandlers() []channel.TypedReceiveHandler {
|
|
return []channel.TypedReceiveHandler{
|
|
handler_edge_mgmt.NewInitEdgeHandler(c.AppEnv),
|
|
}
|
|
}
|
|
|
|
func (c *Controller) LoadConfig(cfgmap map[interface{}]interface{}) error {
|
|
if c.isLoaded {
|
|
return nil
|
|
}
|
|
|
|
parsedConfig, err := edgeconfig.LoadFromMap(cfgmap)
|
|
if err != nil {
|
|
return fmt.Errorf("error loading edge controller configuration: %s", err.Error())
|
|
}
|
|
|
|
c.config = parsedConfig
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *Controller) Enabled() bool {
|
|
return c.config.Enabled
|
|
}
|
|
|
|
func (c *Controller) initializeAuthModules() {
|
|
c.initModulesOnce.Do(func() {
|
|
c.AppEnv.AuthRegistry.Add(model.NewAuthModuleUpdb(c.AppEnv))
|
|
c.AppEnv.AuthRegistry.Add(model.NewAuthModuleCert(c.AppEnv, c.AppEnv.GetConfig().CaPems()))
|
|
c.AppEnv.AuthRegistry.Add(model.NewAuthModuleExtJwt(c.AppEnv))
|
|
|
|
c.AppEnv.EnrollRegistry.Add(model.NewEnrollModuleCa(c.AppEnv))
|
|
c.AppEnv.EnrollRegistry.Add(model.NewEnrollModuleOttCa(c.AppEnv))
|
|
c.AppEnv.EnrollRegistry.Add(model.NewEnrollModuleOtt(c.AppEnv))
|
|
c.AppEnv.EnrollRegistry.Add(model.NewEnrollModuleEdgeRouterOtt(c.AppEnv))
|
|
c.AppEnv.EnrollRegistry.Add(model.NewEnrollModuleTransitRouterOtt(c.AppEnv))
|
|
c.AppEnv.EnrollRegistry.Add(model.NewEnrollModuleUpdb(c.AppEnv))
|
|
})
|
|
}
|
|
|
|
func (c *Controller) Initialize() {
|
|
if !c.config.Enabled {
|
|
return
|
|
}
|
|
|
|
log := pfxlog.Logger()
|
|
|
|
log.Info("initializing edge")
|
|
|
|
//should be done after all modules that add migrations have been added (i.e. AuthRegistry)
|
|
if err := c.AppEnv.InitPersistence(); err != nil {
|
|
log.Fatalf("error initializing persistence: %+v", err)
|
|
}
|
|
|
|
c.initializeAuthModules()
|
|
|
|
//after InitPersistence
|
|
c.AppEnv.Broker = env.NewBroker(c.AppEnv, sync2.NewInstantStrategy(c.AppEnv, sync2.InstantStrategyOptions{
|
|
MaxQueuedRouterConnects: 100,
|
|
MaxQueuedClientHellos: 1000,
|
|
RouterConnectWorkerCount: 10,
|
|
SyncWorkerCount: 10,
|
|
RouterTxBufferSize: 100,
|
|
HelloSendTimeout: 10 * time.Second,
|
|
SessionChunkSize: 100,
|
|
}))
|
|
|
|
servicePolicyEnforcer := policy.NewServicePolicyEnforcer(c.AppEnv, policyAppWanFreq)
|
|
if err := c.policyEngine.AddOperation(servicePolicyEnforcer); err != nil {
|
|
log.WithField("cause", err).
|
|
WithField("enforcerName", servicePolicyEnforcer.GetName()).
|
|
WithField("enforcerId", servicePolicyEnforcer.GetId()).
|
|
Fatalf("could not add service policy enforcer")
|
|
}
|
|
|
|
sessionEnforcer := policy.NewSessionEnforcer(c.AppEnv, policySessionFreq, c.config.SessionTimeoutDuration())
|
|
if err := c.policyEngine.AddOperation(sessionEnforcer); err != nil {
|
|
log.WithField("cause", err).
|
|
WithField("enforcerName", sessionEnforcer.GetName()).
|
|
WithField("enforcerId", sessionEnforcer.GetId()).
|
|
Errorf("could not add session enforcer")
|
|
|
|
}
|
|
|
|
if err := c.AppEnv.GetStores().EventualEventer.Start(c.AppEnv.GetHostController().GetCloseNotifyChannel()); err != nil {
|
|
log.WithError(err).Panic("could not start EventualEventer")
|
|
}
|
|
|
|
c.initialized = true
|
|
}
|
|
|
|
func (c *Controller) Run() {
|
|
if !c.config.Enabled {
|
|
return
|
|
}
|
|
log := pfxlog.Logger()
|
|
|
|
if !c.initialized {
|
|
log.Panic("edge not initialized")
|
|
}
|
|
|
|
log.Info("starting edge")
|
|
|
|
//after InitPersistence
|
|
for _, rf := range env.GetRouters() {
|
|
rf(c.AppEnv)
|
|
}
|
|
|
|
go c.checkEdgeInitialized()
|
|
|
|
managementApiFactory := NewManagementApiFactory(c.AppEnv)
|
|
clientApiFactory := NewClientApiFactory(c.AppEnv)
|
|
oidcApiFactory := NewOidcApiFactory(c.AppEnv)
|
|
|
|
if err := c.AppEnv.HostController.GetXWebInstance().GetRegistry().Add(managementApiFactory); err != nil {
|
|
pfxlog.Logger().Fatalf("failed to create Edge Management API factory: %v", err)
|
|
}
|
|
|
|
if err := c.AppEnv.HostController.GetXWebInstance().GetRegistry().Add(clientApiFactory); err != nil {
|
|
pfxlog.Logger().Fatalf("failed to create Edge Client API factory: %v", err)
|
|
}
|
|
|
|
if err := c.AppEnv.HostController.GetXWebInstance().GetRegistry().Add(oidcApiFactory); err != nil {
|
|
pfxlog.Logger().Fatalf("failed to create OIDC API factory: %v", err)
|
|
}
|
|
|
|
if err := c.policyEngine.Start(c.AppEnv.HostController.GetCloseNotifyChannel()); err != nil {
|
|
log.WithError(err).Fatalf("error starting policy engine")
|
|
}
|
|
}
|
|
|
|
func (c *Controller) checkEdgeInitialized() {
|
|
log := pfxlog.Logger()
|
|
defaultAdminFound := false
|
|
lastWarn := time.Time{}
|
|
first := true
|
|
|
|
for !defaultAdminFound {
|
|
admin, err := c.AppEnv.Managers.Identity.ReadDefaultAdmin()
|
|
|
|
if err != nil {
|
|
log.WithError(err).Panic("could not check if a default admin exists")
|
|
}
|
|
|
|
if admin == nil {
|
|
if !c.AppEnv.GetHostController().IsRaftEnabled() {
|
|
log.Fatal("the Ziti Edge has not been initialized via 'ziti-controller edge init', no default admin exists")
|
|
}
|
|
|
|
if first {
|
|
time.Sleep(3 * time.Second)
|
|
first = false
|
|
}
|
|
|
|
now := time.Now()
|
|
if now.Sub(lastWarn) > time.Minute {
|
|
log.Warn("the Ziti Edge has not been initialized, no default admin exists. Please run 'ziti agent controller init' to configure the default admin'")
|
|
lastWarn = now
|
|
}
|
|
time.Sleep(time.Second)
|
|
} else {
|
|
defaultAdminFound = true
|
|
}
|
|
}
|
|
log.Info("edge initialized")
|
|
}
|
|
|
|
func (c *Controller) Shutdown() {
|
|
if c.config.Enabled {
|
|
log := pfxlog.Logger()
|
|
|
|
pfxlog.Logger().Info("edge controller: shutting down...")
|
|
|
|
c.AppEnv.Broker.Stop()
|
|
|
|
c.AppEnv.GetManagers().ApiSession.HeartbeatCollector.Stop()
|
|
|
|
pfxlog.Logger().Info("edge controller: stopped")
|
|
|
|
pfxlog.Logger().Info("fabric controller: shutting down...")
|
|
|
|
c.AppEnv.GetHostController().Shutdown()
|
|
|
|
pfxlog.Logger().Info("fabric controller: stopped")
|
|
|
|
log.Info("shutdown complete")
|
|
}
|
|
}
|
|
|
|
type subctrl struct {
|
|
parent *Controller
|
|
}
|
|
|
|
func (c *subctrl) GetTraceDecoders() []channel.TraceMessageDecoder {
|
|
return []channel.TraceMessageDecoder{
|
|
edge_ctrl_pb.Decoder{},
|
|
}
|
|
}
|
|
|
|
func (c *subctrl) NotifyOfReconnect(channel.Channel) {
|
|
}
|
|
|
|
func (c *subctrl) LoadConfig(map[interface{}]interface{}) error {
|
|
return nil
|
|
}
|
|
|
|
func (c *subctrl) Enabled() bool {
|
|
return c.parent.Enabled()
|
|
}
|
|
|
|
func (c *subctrl) BindChannel(binding channel.Binding) error {
|
|
for _, h := range c.parent.GetCtrlHandlers(binding) {
|
|
binding.AddTypedReceiveHandler(h)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *subctrl) Run(channel.Channel, boltz.Db, chan struct{}) error {
|
|
return nil
|
|
}
|
|
|
|
type submgmt struct {
|
|
parent *Controller
|
|
}
|
|
|
|
func (m *submgmt) LoadConfig(map[interface{}]interface{}) error {
|
|
return nil
|
|
}
|
|
|
|
func (m *submgmt) Enabled() bool {
|
|
return m.parent.Enabled()
|
|
}
|
|
|
|
func (m *submgmt) BindChannel(binding channel.Binding) error {
|
|
for _, h := range m.parent.GetMgmtHandlers() {
|
|
binding.AddTypedReceiveHandler(h)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (m *submgmt) Run(channel.Channel, chan struct{}) error {
|
|
return nil
|
|
}
|