diff --git a/main.go b/main.go index 289f9cdf..01dc0b9c 100644 --- a/main.go +++ b/main.go @@ -139,6 +139,12 @@ func startControllers() { logger.Log(0, "error occurred initializing DNS: ", err.Error()) } } + if servercfg.IsMessageQueueBackend() { + if err := mq.Configure(); err != nil { + logger.FatalLog("failed to configure MQ: ", err.Error()) + } + } + //Run Rest Server if servercfg.IsRestBackend() { if !servercfg.DisableRemoteIPCheck() && servercfg.GetAPIHost() == "127.0.0.1" { @@ -150,7 +156,6 @@ func startControllers() { waitnetwork.Add(1) go controller.HandleRESTRequests(&waitnetwork) } - //Run MessageQueue if servercfg.IsMessageQueueBackend() { waitnetwork.Add(1) @@ -169,8 +174,7 @@ func runMessageQueue(wg *sync.WaitGroup) { defer wg.Done() brokerHost, secure := servercfg.GetMessageQueueEndpoint() logger.Log(0, "connecting to mq broker at", brokerHost, "with TLS?", fmt.Sprintf("%v", secure)) - // update admin password and re-create client - mq.Configure() + mq.SetUpAdminClient() mq.SetupMQTT() ctx, cancel := context.WithCancel(context.Background()) go mq.DynamicSecManager(ctx) diff --git a/mq/dynsec.go b/mq/dynsec.go index 4f9d75ab..bc21d003 100644 --- a/mq/dynsec.go +++ b/mq/dynsec.go @@ -2,11 +2,18 @@ package mq import ( "context" + "crypto/sha512" + "encoding/base64" "encoding/json" + "errors" "fmt" + "os" mqtt "github.com/eclipse/paho.mqtt.golang" "github.com/gravitl/netmaker/logger" + "github.com/gravitl/netmaker/logic" + "github.com/gravitl/netmaker/servercfg" + "golang.org/x/crypto/pbkdf2" ) const DynamicSecSubTopic = "$CONTROL/dynamic-security/#" @@ -14,14 +21,14 @@ const DynamicSecPubTopic = "$CONTROL/dynamic-security/v1" type DynSecActionType string +var mqAdminClient mqtt.Client + var ( - CreateClient DynSecActionType = "CREATE_CLIENT" - DisableClient DynSecActionType = "DISABLE_CLIENT" - EnableClient DynSecActionType = "ENABLE_CLIENT" - DeleteClient DynSecActionType = "DELETE_CLIENT" - CreateAdminClient DynSecActionType = "CREATE_ADMIN_CLIENT" - ModifyClient DynSecActionType = "MODIFY_CLIENT" - DISABLE_EXISTING_ADMINS DynSecActionType = "DISABLE_EXISTING_ADMINS" + CreateClient DynSecActionType = "CREATE_CLIENT" + DisableClient DynSecActionType = "DISABLE_CLIENT" + EnableClient DynSecActionType = "ENABLE_CLIENT" + DeleteClient DynSecActionType = "DELETE_CLIENT" + ModifyClient DynSecActionType = "MODIFY_CLIENT" ) var ( @@ -32,10 +39,43 @@ var ( ) var ( - mqDynSecAdmin string = "Netmaker-Admin" - adminPassword string = "Netmaker-Admin" + mqAdminUserName string = "Netmaker-Admin" + mqNetmakerServerUserName string = "Netmaker-Server" ) +type client struct { + Username string `json:"username"` + TextName string `json:"textName"` + Password string `json:"password"` + Salt string `json:"salt"` + Iterations int `json:"iterations"` + Roles []struct { + Rolename string `json:"rolename"` + } `json:"roles"` +} + +type role struct { + Rolename string `json:"rolename"` + Acls []struct { + Acltype string `json:"acltype"` + Topic string `json:"topic"` + Allow bool `json:"allow"` + } `json:"acls"` +} + +type defaultAccessAcl struct { + PublishClientSend bool `json:"publishClientSend"` + PublishClientReceive bool `json:"publishClientReceive"` + Subscribe bool `json:"subscribe"` + Unsubscribe bool `json:"unsubscribe"` +} + +type dynCnf struct { + Clients []client `json:"clients"` + Roles []role `json:"roles"` + DefaultACLAccess defaultAccessAcl `json:"defaultACLAccess"` +} + type MqDynSecGroup struct { Groupname string `json:"groupname"` Priority int `json:"priority"` @@ -77,6 +117,39 @@ type MqDynsecPayload struct { var DynSecChan = make(chan DynSecAction, 100) +func encodePasswordToPBKDF2(password string, salt string, iterations int, keyLength int) string { + binaryEncoded := pbkdf2.Key([]byte(password), []byte(salt), iterations, keyLength, sha512.New) + return base64.StdEncoding.EncodeToString(binaryEncoded) +} + +func Configure() error { + file := "/root/dynamic-security.json" + b, err := os.ReadFile(file) + if err != nil { + return err + } + c := dynCnf{} + json.Unmarshal(b, &c) + password := servercfg.GetMqAdminPassword() + if password == "" { + return errors.New("MQ admin password not provided") + } + for i, cI := range c.Clients { + if cI.Username == mqAdminUserName || cI.Username == mqNetmakerServerUserName { + salt := logic.RandomString(12) + hashed := encodePasswordToPBKDF2(password, salt, 101, 64) + cI.Password = hashed + cI.Salt = base64.StdEncoding.EncodeToString([]byte(salt)) + c.Clients[i] = cI + } + } + data, err := json.MarshalIndent(c, "", " ") + if err != nil { + return err + } + return os.WriteFile(file, data, 0755) +} + func DynamicSecManager(ctx context.Context) { defer close(DynSecChan) for { diff --git a/mq/mq.go b/mq/mq.go index 51fde63d..e2884438 100644 --- a/mq/mq.go +++ b/mq/mq.go @@ -2,12 +2,10 @@ package mq import ( "context" - "encoding/json" "time" mqtt "github.com/eclipse/paho.mqtt.golang" "github.com/gravitl/netmaker/logger" - "github.com/gravitl/netmaker/logic" "github.com/gravitl/netmaker/netclient/ncutils" "github.com/gravitl/netmaker/servercfg" ) @@ -24,29 +22,28 @@ var peer_force_send = 0 var mqclient mqtt.Client -func Configure() { +func SetUpAdminClient() { opts := mqtt.NewClientOptions() - broker, _ := servercfg.GetMessageQueueEndpoint() - opts.AddBroker(broker) - id := ncutils.MakeRandomString(23) - opts.ClientID = id - opts.SetUsername(mqDynSecAdmin) - opts.SetPassword(adminPassword) - opts.SetAutoReconnect(true) - opts.SetConnectRetry(true) - opts.SetConnectRetryInterval(time.Second << 2) - opts.SetKeepAlive(time.Minute) - opts.SetWriteTimeout(time.Minute) - mqclient := mqtt.NewClient(opts) + setMqOptions(mqAdminUserName, servercfg.GetMqAdminPassword(), opts) + mqAdminClient = mqtt.NewClient(opts) + opts.SetOnConnectHandler(func(client mqtt.Client) { + if token := client.Subscribe(DynamicSecSubTopic, 0, mqtt.MessageHandler(watchDynSecTopic)); token.WaitTimeout(MQ_TIMEOUT*time.Second) && token.Error() != nil { + client.Disconnect(240) + logger.Log(0, "Dynamic security client subscription failed") + } + + opts.SetOrderMatters(true) + opts.SetResumeSubs(true) + }) tperiod := time.Now().Add(10 * time.Second) for { - if token := mqclient.Connect(); !token.WaitTimeout(MQ_TIMEOUT*time.Second) || token.Error() != nil { - logger.Log(2, "unable to connect to broker, retrying ...") + if token := mqAdminClient.Connect(); !token.WaitTimeout(MQ_TIMEOUT*time.Second) || token.Error() != nil { + logger.Log(2, "Admin: unable to connect to broker, retrying ...") if time.Now().After(tperiod) { if token.Error() == nil { - logger.FatalLog("could not connect to broker, token timeout, exiting ...") + logger.FatalLog("Admin: could not connect to broker, token timeout, exiting ...") } else { - logger.FatalLog("could not connect to broker, exiting ...", token.Error().Error()) + logger.FatalLog("Admin: could not connect to broker, exiting ...", token.Error().Error()) } } } else { @@ -54,38 +51,27 @@ func Configure() { } time.Sleep(2 * time.Second) } - newAdminPassword := logic.GenKey() - payload := MqDynsecPayload{ - Commands: []MqDynSecCmd{ - { - Command: ModifyClientCmd, - Username: mqDynSecAdmin, - Password: newAdminPassword, - }, - }, - } - d, _ := json.Marshal(payload) - if token := mqclient.Publish(DynamicSecPubTopic, 0, true, d); token.Error() != nil { - logger.FatalLog("failed to modify admin password: ", token.Error().Error()) - } - mqclient.Disconnect(2) - adminPassword = newAdminPassword + } -// SetupMQTT creates a connection to broker and return client -func SetupMQTT() { - opts := mqtt.NewClientOptions() +func setMqOptions(user, password string, opts *mqtt.ClientOptions) { broker, _ := servercfg.GetMessageQueueEndpoint() opts.AddBroker(broker) id := ncutils.MakeRandomString(23) opts.ClientID = id - opts.SetUsername(mqDynSecAdmin) - opts.SetPassword(adminPassword) + opts.SetUsername(user) + opts.SetPassword(password) opts.SetAutoReconnect(true) opts.SetConnectRetry(true) opts.SetConnectRetryInterval(time.Second << 2) opts.SetKeepAlive(time.Minute) opts.SetWriteTimeout(time.Minute) +} + +// SetupMQTT creates a connection to broker and return client +func SetupMQTT() { + opts := mqtt.NewClientOptions() + setMqOptions(mqNetmakerServerUserName, servercfg.GetMqAdminPassword(), opts) opts.SetOnConnectHandler(func(client mqtt.Client) { if token := client.Subscribe("ping/#", 2, mqtt.MessageHandler(Ping)); token.WaitTimeout(MQ_TIMEOUT*time.Second) && token.Error() != nil { client.Disconnect(240)