Skip to content
Snippets Groups Projects
controller.go 14.1 KiB
Newer Older
  • Learn to ignore specific revisions
  • Manuel Kieweg's avatar
    Manuel Kieweg committed
    	"context"
    
    Fabian Seidl's avatar
    Fabian Seidl committed
    	"encoding/base64"
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    	"net"
    
    	"net/http"
    	"os"
    	"os/signal"
    
    	"syscall"
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    	"github.com/google/uuid"
    
    	"github.com/sethvargo/go-password/password"
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    	log "github.com/sirupsen/logrus"
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    	"github.com/spf13/viper"
    
    Fabian Seidl's avatar
    Fabian Seidl committed
    	"golang.org/x/crypto/argon2"
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    	"google.golang.org/grpc"
    
    Fabian Seidl's avatar
    Fabian Seidl committed
    	"google.golang.org/grpc/credentials/insecure"
    
    	apppb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/app"
    
    	cmpb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/configurationmanagement"
    
    	cpb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/csbi"
    
    	mnepb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/networkelement"
    
    	pipb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/plugin-internal"
    	rpb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/plugin-registry"
    
    	ppb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/pnd"
    
    	apb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/rbac"
    
    	tpb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/topology"
    
    	"code.fbi.h-da.de/danet/gosdn/controller/nucleus/database"
    
    	"code.fbi.h-da.de/danet/gosdn/controller/config"
    
    	"code.fbi.h-da.de/danet/gosdn/controller/conflict"
    
    	eventservice "code.fbi.h-da.de/danet/gosdn/controller/eventService"
    
    	"code.fbi.h-da.de/danet/gosdn/controller/interfaces/networkdomain"
    
    	"code.fbi.h-da.de/danet/gosdn/controller/interfaces/networkelement"
    
    	"code.fbi.h-da.de/danet/gosdn/controller/interfaces/plugin"
    
    	"code.fbi.h-da.de/danet/gosdn/controller/interfaces/rbac"
    
    	"code.fbi.h-da.de/danet/gosdn/controller/northbound/server"
    
    	nbi "code.fbi.h-da.de/danet/gosdn/controller/northbound/server"
    
    	rbacImpl "code.fbi.h-da.de/danet/gosdn/controller/rbac"
    
    	"code.fbi.h-da.de/danet/gosdn/controller/store"
    
    	"code.fbi.h-da.de/danet/gosdn/controller/topology"
    	"code.fbi.h-da.de/danet/gosdn/controller/topology/nodes"
    	"code.fbi.h-da.de/danet/gosdn/controller/topology/ports"
    
    	routingtables "code.fbi.h-da.de/danet/gosdn/controller/topology/routing-tables"
    
    	eventInterfaces "code.fbi.h-da.de/danet/gosdn/controller/interfaces/event"
    
    
    	"code.fbi.h-da.de/danet/gosdn/controller/nucleus"
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    var coreLock sync.RWMutex
    var coreOnce sync.Once
    
    
    // Core is the representation of the controller's core.
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    type Core struct {
    
    	pndStore              networkdomain.PndStore
    
    	pndService            networkdomain.Service
    	mneService            networkelement.Service
    	changeStore           store.ChangeStore
    
    	userService           rbac.UserService
    	roleService           rbac.RoleService
    	topologyService       topology.Service
    	nodeService           nodes.Service
    	portService           ports.Service
    	routeService          routingtables.Service
    
    	httpServer            *http.Server
    	grpcServer            *grpc.Server
    	nbi                   *nbi.NorthboundInterface
    	eventService          eventInterfaces.Service
    	appService            app.ManagementService
    	networkElementWatcher *nucleus.NetworkElementWatcher
    	stopChan              chan os.Signal
    
    	csbiClient           cpb.CsbiServiceClient
    	pluginRegistryClient rpb.PluginRegistryServiceClient
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    }
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    var c *Core
    
    
    // initialize does start-up housekeeping like reading controller config files.
    
    	if err := config.InitializeConfig(); err != nil {
    
    	if err := config.ReadGnmiSubscriptionPaths(); err != nil {
    
    		log.Error("Error reading in gNMI subscription paths, can not watch network elements automatically: ", err)
    
    	eventService, err := eventservice.NewEventService()
    	if err != nil {
    		return err
    	}
    
    
    	db := database.GetDatabaseConnection()
    
    	nodeService := nodes.NewNodeService(nodes.NewNodeStore(db), eventService)
    	portService := ports.NewPortService(ports.NewPortStore(db), eventService)
    
    	routeService := routingtables.NewRoutingTableService(
    
    		routingtables.NewRoutingTableStore(db),
    
    
    	pluginRegistryClient := setupPluginRegistryClient()
    
    	pluginService := nucleus.NewPluginService(
    		nucleus.NewPluginStore(db),
    		eventService,
    		nucleus.NewPluginThroughReattachConfig,
    		pluginRegistryClient,
    	)
    
    	pndStore := nucleus.NewPndStore(db, pluginService)
    
    
    	changeStore := store.NewChangeStore()
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    	c = &Core{
    
    		pndStore:   pndStore,
    		pndService: nucleus.NewPndService(pndStore),
    		mneService: nucleus.NewNetworkElementService(
    			nucleus.NewNetworkElementStore(db, config.BasePndUUID),
    			pluginService,
    			eventService,
    		),
    
    		changeStore: *changeStore,
    
    		userService: rbacImpl.NewUserService(rbacImpl.NewUserStore(db), eventService),
    		roleService: rbacImpl.NewRoleService(rbacImpl.NewRoleStore(db), eventService),
    
    		topologyService: topology.NewTopologyService(
    
    			topology.NewTopologyStore(db),
    
    			nodeService,
    			portService,
    			eventService,
    		),
    
    		nodeService:          nodeService,
    		portService:          portService,
    		routeService:         routeService,
    		eventService:         eventService,
    		pluginService:        pluginService,
    
    		appService:           app.NewAppService(app.NewAppStore(db)),
    
    		stopChan:             make(chan os.Signal, 1),
    		pluginRegistryClient: pluginRegistryClient,
    
    
    	// Setting up signal capturing
    
    	signal.Notify(c.stopChan, os.Interrupt, syscall.SIGTERM)
    
    	setupOrchestratorClient()
    
    
    	if err := createPrincipalNetworkDomain(); err != nil {
    
    	c.networkElementWatcher = nucleus.NewNetworkElementWatcher(c.mneService, c.eventService)
    
    	c.networkElementWatcher.SubscribeToNetworkElements(nil)
    
    	if err := ensureDefaultRoleExists(); err != nil {
    
    	if err := ensureDefaultUserExists(); err != nil {
    
    	if err := startGrpc(); err != nil {
    		return err
    	}
    
    	coreLock.Lock()
    	startHttpServer()
    	coreLock.Unlock()
    
    
    func setupOrchestratorClient() {
    
    	orchestrator := viper.GetString("csbi-orchestrator")
    
    	conn, err := grpc.NewClient(orchestrator, grpc.WithTransportCredentials(insecure.NewCredentials()))
    
    	if err != nil {
    		log.Fatal(err)
    	}
    	c.csbiClient = cpb.NewCsbiServiceClient(conn)
    
    func setupPluginRegistryClient() rpb.PluginRegistryServiceClient {
    
    	pluginRegistry := viper.GetString("plugin-registry")
    
    	conn, err := grpc.NewClient(pluginRegistry, grpc.WithTransportCredentials(insecure.NewCredentials()))
    
    	return rpb.NewPluginRegistryServiceClient(conn)
    
    func startGrpc() error {
    
    	socket := viper.GetString("socket")
    	lislisten, err := net.Listen("tcp", socket)
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    	if err != nil {
    		return err
    	}
    
    	log.Infof("listening to %v", lislisten.Addr())
    
    Fabian Seidl's avatar
    Fabian Seidl committed
    	jwtManager := rbacImpl.NewJWTManager(config.JWTSecret, config.JWTDuration)
    
    	setupGRPCServerWithCorrectSecurityLevel(jwtManager, c.userService, c.roleService)
    
    	c.nbi = nbi.NewNBI(
    		c.pndStore,
    
    		c.pndService,
    		c.mneService,
    		c.changeStore,
    
    		c.userService,
    		c.roleService,
    		*jwtManager,
    		c.topologyService,
    		c.nodeService,
    		c.portService,
    
    		c.pluginService,
    		c.pluginRegistryClient,
    		c.csbiClient,
    		callback,
    
    Fabian Seidl's avatar
    Fabian Seidl committed
    	ppb.RegisterPndServiceServer(c.grpcServer, c.nbi.Pnd)
    	cpb.RegisterCsbiServiceServer(c.grpcServer, c.nbi.Csbi)
    
    	apb.RegisterAuthServiceServer(c.grpcServer, c.nbi.Auth)
    
    	apb.RegisterUserServiceServer(c.grpcServer, c.nbi.User)
    	apb.RegisterRoleServiceServer(c.grpcServer, c.nbi.Role)
    
    	apppb.RegisterAppServiceServer(c.grpcServer, c.nbi.App)
    
    	tpb.RegisterTopologyServiceServer(c.grpcServer, c.nbi.Topology)
    
    	mnepb.RegisterNetworkElementServiceServer(c.grpcServer, c.nbi.NetworkElement)
    
    	tpb.RegisterRoutingTableServiceServer(c.grpcServer, c.nbi.Routes)
    
    	pipb.RegisterPluginInternalServiceServer(c.grpcServer, c.nbi.Plugin)
    
    	cmpb.RegisterConfigurationManagementServiceServer(c.grpcServer, c.nbi.ConfigurationManagement)
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    	go func() {
    
    		if err := c.grpcServer.Serve(lislisten); err != nil {
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    			log.Fatal(err)
    		}
    	}()
    
    	return nil
    
    // createPrincipalNetworkDomain initializes the controller with an initial PND.
    
    func createPrincipalNetworkDomain() error {
    
    	basePnd, err := c.pndService.Get(store.Query{ID: config.BasePndUUID})
    
    Fabian Seidl's avatar
    Fabian Seidl committed
    	if err != nil {
    
    		pnd := nucleus.NewPND(
    			config.BasePndUUID,
    
    		err = c.pndStore.Add(context.Background(), pnd)
    
    func ensureDefaultRoleExists() error {
    	defaultAdminRoleName := "admin"
    	adminRole, err := c.roleService.Get(store.Query{ID: uuid.Nil, Name: defaultAdminRoleName})
    	if err != nil {
    		log.Info(err)
    	}
    
    	if adminRole == nil {
    		err := c.roleService.Add(rbacImpl.NewRole(uuid.New(), defaultAdminRoleName, "admin role", []string{
    
    			"/gosdn.pnd.PndService/GetPnd",
    			"/gosdn.pnd.PndService/GetPndList",
    			"/gosdn.pnd.PndService/CreatePndList",
    			"/gosdn.pnd.PndService/DeletePnd",
    
    			"/gosdn.rbac.UserService/CreateUsers",
    			"/gosdn.rbac.UserService/GetUser",
    			"/gosdn.rbac.UserService/GetUsers",
    			"/gosdn.rbac.UserService/UpdateUsers",
    			"/gosdn.rbac.UserService/DeleteUsers",
    			"/gosdn.rbac.RoleService/CreateRoles",
    			"/gosdn.rbac.RoleService/GetRole",
    			"/gosdn.rbac.RoleService/GetRoles",
    			"/gosdn.rbac.RoleService/UpdateRoles",
    			"/gosdn.rbac.RoleService/DeletePermissionsForRole",
    			"/gosdn.rbac.RoleService/DeleteRoles",
    
    			"/gosdn.networkelement.NetworkElementService/Get",
    			"/gosdn.networkelement.NetworkElementService/GetFlattened",
    			"/gosdn.networkelement.NetworkElementService/GetAll",
    			"/gosdn.networkelement.NetworkElementService/GetAllFlattened",
    
    			"/gosdn.networkelement.NetworkElementService/GetPath",
    
    			"/gosdn.networkelement.NetworkElementService/GetIntendedPath",
    
    			"/gosdn.networkelement.NetworkElementService/GetChange",
    			"/gosdn.networkelement.NetworkElementService/GetChangeList",
    
    			"/gosdn.networkelement.NetworkElementService/AddList",
    
    			"/gosdn.networkelement.NetworkElementService/SetChangeList",
    			"/gosdn.networkelement.NetworkElementService/SetPathList",
    			"/gosdn.networkelement.NetworkElementService/DeviceSchema",
    
    			"/gosdn.networkelement.NetworkElementService/Delete",
    
    			"/gosdn.networkelement.NetworkElementService/SubscribePath",
    
    			"/gosdn.plugin_internal.PluginInternalService/AvailablePlugins",
    			"/gosdn.plugin_internal.PluginInternalService/GetPluginSchema",
    
    			"/gosdn.app.AppService/Register",
    			"/gosdn.app.AppService/Deregister",
    			"/gosdn.configurationmanagement.ConfigurationManagementService/ExportSDNConfig",
    			"/gosdn.configurationmanagement.ConfigurationManagementService/ImportSDNConfig",
    			"/gosdn.topology.RoutingTableService/AddRoutingTable",
    			"/gosdn.topology.RoutingTableService/GetRoutes",
    			"/gosdn.topology.RoutingTableService/DeleteRoute",
    			"/gosdn.topology.TopologyService/AddLink",
    			"/gosdn.topology.TopologyService/GetTopology",
    			"/gosdn.topology.TopologyService/UpdateLink",
    			"/gosdn.topology.TopologyService/DeleteLink",
    
    		}))
    		if err != nil {
    			return err
    		}
    	}
    
    	return nil
    }
    
    func ensureDefaultUserExists() error {
    	defaultUserName := "admin"
    	adminUser, err := c.userService.Get(store.Query{ID: uuid.Nil, Name: defaultUserName})
    	if err != nil {
    		log.Info(err)
    	}
    	if adminUser == nil {
    
    		// Getting the password from the environment variable which is set in gosdn.clab.yaml.
    		var preDefinedPassword = os.Getenv("GOSDN_ADMIN_PASSWORD")
    		var usedPassword string
    
    		// If environment variable is set and password is not 0, the password from the environment variable will be used.
    		if len(preDefinedPassword) == 0 {
    			// Generate a password that is 16 characters long with 3 digits, 0 symbols,
    			// allowing upper and lower case letters, disallowing repeat characters.
    			generatedPassword, err := password.Generate(16, 3, 0, true, false)
    			if err != nil {
    				log.Fatal(err)
    			}
    			usedPassword = generatedPassword
    		} else {
    			usedPassword = preDefinedPassword
    
    Fabian Seidl's avatar
    Fabian Seidl committed
    		salt, err := password.Generate(16, 3, 0, true, false)
    		if err != nil {
    			log.Fatal(err)
    		}
    
    
    		hashedPassword := base64.RawStdEncoding.EncodeToString(argon2.IDKey([]byte(usedPassword), []byte(salt), 1, 64*1024, 4, 32))
    
    		err = c.userService.Add(rbacImpl.NewUser(uuid.New(), defaultUserName, map[string]string{config.BasePndUUID.String(): "admin"}, string(hashedPassword), "", salt, conflict.Metadata{ResourceVersion: 0}))
    
    		fmt.Printf("########\n Generated admin password: %s\n########\n", usedPassword)
    
    // Run calls initialize to start the controller.
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    func Run(ctx context.Context) error {
    
    	var initError error
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    	coreOnce.Do(func() {
    
    		initError = initialize()
    	})
    	if initError != nil {
    		log.WithFields(log.Fields{}).Error(initError)
    		return initError
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    	log.WithFields(log.Fields{}).Info("initialisation finished")
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    	select {
    	case <-c.stopChan:
    		return shutdown()
    	case <-ctx.Done():
    		return shutdown()
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    	}
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    func shutdown() error {
    
    	log.Info("shutting down controller")
    
    	coreLock.Lock()
    
    	defer func() {
    		plugins, err := c.pluginService.GetAll()
    		if err != nil {
    			log.Error(err)
    		}
    		for _, plugin := range plugins {
    			log.Info("Defer: ", plugin.Manifest().Name)
    
    			log.Info("Defer - exited: ", plugin.GetClient().Exited())
    		}
    		coreLock.Unlock()
    	}()
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    	c.grpcServer.GracefulStop()
    
    	return stopHttpServer()
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    }
    
    func callback(id uuid.UUID, ch chan networkelement.Details) {
    
    	if ch != nil {
    
    		c.pndStore.AddPendingChannel(id, ch)
    
    		log.Infof("pending channel %v added", id)
    	} else {
    
    		c.pndStore.RemovePendingChannel(id)
    
    		log.Infof("pending channel %v removed", id)
    	}
    }
    
    
    // setupGRPCServerWithCorrectSecurityLevel sets up a gRPC server with desired security level
    //
    // Only two options for now: insecure or secure, add 'else if' if required.
    // Secure is the recommended mode and is set as default.
    // Insecure starts the controller without the gRPC interceptor which is supposed to handle authz.
    // This allows users to operate on the controller without any authentication/authorization,
    // but they could still login if they want to.
    // Use insecure only for testing purposes and with caution.
    
    func setupGRPCServerWithCorrectSecurityLevel(jwt *rbacImpl.JWTManager, userService rbac.UserService, roleService rbac.RoleService) {
    
    	securityLevel := viper.GetString("security")
    	if securityLevel == "insecure" {
    		c.grpcServer = grpc.NewServer()
    		log.Info("set up grpc server in insecure mode")
    	} else {
    
    		interceptor := server.NewAuthInterceptor(jwt, userService, roleService)
    
    		c.grpcServer = grpc.NewServer(grpc.UnaryInterceptor(interceptor.Unary()), grpc.StreamInterceptor(interceptor.Stream()))
    
    		log.Info("set up grpc server in secure mode")
    	}
    }