Skip to content
Snippets Groups Projects
controller.go 9.97 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"
    
    	pb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/core"
    	cpb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/csbi"
    
    	dpb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/device"
    
    	ppb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/pnd"
    
    	apb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/rbac"
    
    	spb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/southbound"
    
    	tpb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/topology"
    
    	"code.fbi.h-da.de/danet/gosdn/controller/config"
    
    	eventservice "code.fbi.h-da.de/danet/gosdn/controller/eventService"
    
    	"code.fbi.h-da.de/danet/gosdn/controller/interfaces/device"
    	"code.fbi.h-da.de/danet/gosdn/controller/interfaces/networkdomain"
    
    	"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"
    
    	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
    
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    // Core is the representation of the controller's core
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    type Core struct {
    
    	pndStore        networkdomain.PndStore
    	userService     rbac.UserService
    	roleService     rbac.RoleService
    	topologyService topology.Service
    	nodeService     nodes.Service
    	portService     ports.Service
    	httpServer      *http.Server
    	grpcServer      *grpc.Server
    	nbi             *nbi.NorthboundInterface
    	eventService    eventInterfaces.Service
    
    	deviceWatcher   *nucleus.DeviceWatcher
    
    	stopChan        chan os.Signal
    
    Fabian Seidl's avatar
    Fabian Seidl committed
    	csbiClient cpb.CsbiServiceClient
    
    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
    func initialize() error {
    	err := config.InitializeConfig()
    	if err != nil {
    		return err
    	}
    
    
    	eventService, err := eventservice.NewEventService()
    	if err != nil {
    		return err
    	}
    
    
    	nodeService := nodes.NewNodeService(nodes.NewDatabaseNodeStore(), eventService)
    	portService := ports.NewPortService(ports.NewDatabasePortStore(), eventService)
    
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    	c = &Core{
    
    		pndStore:    nucleus.NewPndStore(),
    		userService: rbacImpl.NewUserService(rbacImpl.NewUserStore(), eventService),
    		roleService: rbacImpl.NewRoleService(rbacImpl.NewRoleStore(), eventService),
    		topologyService: topology.NewTopologyService(
    			topology.NewDatabaseTopologyStore(),
    			nodeService,
    			portService,
    			eventService,
    		),
    		nodeService:  nodeService,
    		portService:  portService,
    
    		eventService: eventService,
    		stopChan:     make(chan os.Signal, 1),
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    	}
    
    
    	// Setting up signal capturing
    
    	signal.Notify(c.stopChan, os.Interrupt, syscall.SIGTERM)
    
    	err = createPrincipalNetworkDomain()
    
    	c.deviceWatcher = nucleus.NewDeviceWatcher(c.pndStore)
    	// TODO: udpate with actual paths to subscribe to using template/config
    	c.deviceWatcher.SubToDevices([][]string{{"system", "config", "hostname"}}, nil)
    
    
    	err = ensureDefaultRoleExists()
    	if err != nil {
    		return err
    	}
    
    	err = ensureDefaultUserExists()
    	if err != nil {
    		return err
    	}
    
    
    	if err := startGrpc(); err != nil {
    		return err
    	}
    
    	coreLock.Lock()
    	startHttpServer()
    	coreLock.Unlock()
    
    
    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)
    
    	basePnd, err := c.pndStore.Get(store.Query{ID: config.BasePndUUID})
    	if err != nil {
    		panic(err)
    	}
    
    
    	c.nbi = nbi.NewNBI(
    		c.pndStore,
    		c.userService,
    		c.roleService,
    		*jwtManager,
    		c.topologyService,
    		c.nodeService,
    		c.portService,
    
    Fabian Seidl's avatar
    Fabian Seidl committed
    	pb.RegisterCoreServiceServer(c.grpcServer, c.nbi.Core)
    	ppb.RegisterPndServiceServer(c.grpcServer, c.nbi.Pnd)
    	cpb.RegisterCsbiServiceServer(c.grpcServer, c.nbi.Csbi)
    
    	spb.RegisterSbiServiceServer(c.grpcServer, c.nbi.Sbi)
    
    	apb.RegisterAuthServiceServer(c.grpcServer, c.nbi.Auth)
    
    	apb.RegisterUserServiceServer(c.grpcServer, c.nbi.User)
    	apb.RegisterRoleServiceServer(c.grpcServer, c.nbi.Role)
    
    	tpb.RegisterTopologyServiceServer(c.grpcServer, c.nbi.Topology)
    
    	dpb.RegisterDeviceServiceServer(c.grpcServer, c.nbi.Device)
    
    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)
    		}
    	}()
    
    
    	orchestrator := viper.GetString("csbi-orchestrator")
    
    Fabian Seidl's avatar
    Fabian Seidl committed
    	conn, err := grpc.Dial(orchestrator, grpc.WithTransportCredentials(insecure.NewCredentials()))
    
    	if err != nil {
    		log.Fatal(err)
    	}
    
    Fabian Seidl's avatar
    Fabian Seidl committed
    	c.csbiClient = cpb.NewCsbiServiceClient(conn)
    
    	return nil
    
    Manuel Kieweg's avatar
    Manuel Kieweg committed
    // createPrincipalNetworkDomain initializes the controller with an initial PND
    
    func createPrincipalNetworkDomain() error {
    
    	basePnd, err := c.pndStore.Get(store.Query{ID: config.BasePndUUID})
    
    Fabian Seidl's avatar
    Fabian Seidl committed
    	if err != nil {
    
    	if basePnd == nil {
    		pnd, err := nucleus.NewPND(
    			"base",
    			"gosdn base pnd",
    			config.BasePndUUID,
    
    			c.csbiClient,
    			callback,
    		)
    		if err != nil {
    			return err
    		}
    
    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.core.CoreService/GetPnd",
    			"/gosdn.core.CoreService/GetPndList",
    			"/gosdn.core.CoreService/CreatePndList",
    			"/gosdn.core.CoreService/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.pnd.PndService/GetOnd",
    			"/gosdn.pnd.PndService/GetOndList",
    			"/gosdn.pnd.PndService/GetSbi",
    			"/gosdn.pnd.PndService/GetSbiList",
    			"/gosdn.pnd.PndService/GetPath",
    			"/gosdn.pnd.PndService/GetChange",
    			"/gosdn.pnd.PndService/GetChangeList",
    			"/gosdn.pnd.PndService/SetOndList",
    			"/gosdn.pnd.PndService/SetChangeList",
    			"/gosdn.pnd.PndService/SetPathList",
    			"/gosdn.pnd.PndService/SetSbiList",
    			"/gosdn.pnd.PndService/DeleteOnd",
    
    			"/gosdn.pnd.PndService/SubscribePath",
    
    			"/gosdn.southbound.SbiService/GetSchema",
    		}))
    		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 {
    		// 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)
    		}
    
    
    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(generatedPassword), []byte(salt), 1, 64*1024, 4, 32))
    
    Fabian Seidl's avatar
    Fabian Seidl committed
    		err = c.userService.Add(rbacImpl.NewUser(uuid.New(), defaultUserName, map[string]string{config.BasePndUUID.String(): "admin"}, string(hashedPassword), "", salt))
    
    Fabian Seidl's avatar
    Fabian Seidl committed
    
    		fmt.Printf("########\n Generated admin password: %s\n########\n", generatedPassword)
    
    // 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 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 device.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")
    	}
    }