-
Fabian Seidl authored
Resolve "Running the controller via VSCode uses different paths for configs/stores/etc. which leads to some problems" See merge request !360
Fabian Seidl authoredResolve "Running the controller via VSCode uses different paths for configs/stores/etc. which leads to some problems" See merge request !360
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
controller.go 10.04 KiB
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"
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"
)
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
deviceWatcher *nucleus.DeviceWatcher
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
}
err = config.ReadGnmiSubscriptionPaths()
if err != nil {
log.Error("Error reading in gNMI subscription paths, can not watch devices automatically: ", 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)
err = createPrincipalNetworkDomain()
if err != nil {
return err
}
c.deviceWatcher = nucleus.NewDeviceWatcher(c.pndStore)
c.deviceWatcher.SubToDevices(config.GetGnmiSubscriptionPaths(), 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()
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)
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,
basePnd,
)
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)
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.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)
}
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")
}
}