package controller import ( "context" "encoding/base64" "fmt" "net" "net/http" "os" "os/signal" "sync" "syscall" "github.com/google/uuid" "github.com/sethvargo/go-password/password" log "github.com/sirupsen/logrus" "github.com/spf13/viper" "golang.org/x/crypto/argon2" "google.golang.org/grpc" "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" 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" ) var coreLock sync.RWMutex var coreOnce sync.Once // Core is the representation of the controller's core 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 stopChan chan os.Signal csbiClient cpb.CsbiServiceClient } 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) 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), } // Setting up signal capturing signal.Notify(c.stopChan, os.Interrupt, syscall.SIGTERM) if err := startGrpc(); err != nil { return err } coreLock.Lock() startHttpServer() coreLock.Unlock() err = createPrincipalNetworkDomain() if err != nil { return err } err = ensureDefaultRoleExists() if err != nil { return err } err = ensureDefaultUserExists() if err != nil { return err } return nil } func startGrpc() error { socket := viper.GetString("socket") lislisten, err := net.Listen("tcp", socket) if err != nil { return err } log.Infof("listening to %v", lislisten.Addr()) jwtManager := rbacImpl.NewJWTManager(config.JWTSecret, config.JWTDuration) setupGRPCServerWithCorrectSecurityLevel(jwtManager, c.userService, c.roleService) c.nbi = nbi.NewNBI( c.pndStore, c.userService, c.roleService, *jwtManager, c.topologyService, c.nodeService, c.portService, ) 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) go func() { if err := c.grpcServer.Serve(lislisten); err != nil { log.Fatal(err) } }() orchestrator := viper.GetString("csbi-orchestrator") conn, err := grpc.Dial(orchestrator, grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil { log.Fatal(err) } c.csbiClient = cpb.NewCsbiServiceClient(conn) return nil } // createPrincipalNetworkDomain initializes the controller with an initial PND func createPrincipalNetworkDomain() error { basePnd, err := c.pndStore.Get(store.Query{ID: config.BasePndUUID}) if err != nil { log.Info(err) } if basePnd == nil { pnd, err := nucleus.NewPND( "base", "gosdn base pnd", config.BasePndUUID, c.csbiClient, callback, ) if err != nil { return err } err = c.pndStore.Add(pnd) if err != nil { return err } return nil } return nil } 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.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) } 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)) err = c.userService.Add(rbacImpl.NewUser(uuid.New(), defaultUserName, map[string]string{config.BasePndUUID.String(): "admin"}, string(hashedPassword), "", salt)) if err != nil { return err } fmt.Printf("########\n Generated admin password: %s\n########\n", generatedPassword) } return nil } // Run calls initialize to start the controller func Run(ctx context.Context) error { var initError error coreOnce.Do(func() { initError = initialize() }) if initError != nil { log.WithFields(log.Fields{}).Error(initError) return initError } log.WithFields(log.Fields{}).Info("initialisation finished") select { case <-c.stopChan: return shutdown() case <-ctx.Done(): return shutdown() } } func shutdown() error { log.Info("shutting down controller") coreLock.Lock() defer coreLock.Unlock() c.grpcServer.GracefulStop() c.eventService.CloseConnection() return stopHttpServer() } 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") } }