Browse Source

Merge pull request #1016 from gravitl/feature_v0.13.0_mq_register

Feature v0.13.0 mq register
Matthew R Kasun 3 years ago
parent
commit
a3f44f152b

+ 7 - 3
compose/docker-compose.contained.yml

@@ -7,6 +7,7 @@ services:
     volumes:
     volumes:
       - dnsconfig:/root/config/dnsconfig
       - dnsconfig:/root/config/dnsconfig
       - sqldata:/root/data
       - sqldata:/root/data
+      - /root/certs:/etc/netmaker/
     cap_add: 
     cap_add: 
       - NET_ADMIN
       - NET_ADMIN
       - NET_RAW
       - NET_RAW
@@ -36,7 +37,6 @@ services:
       MQ_HOST: "mq"
       MQ_HOST: "mq"
       HOST_NETWORK: "off"
       HOST_NETWORK: "off"
       MANAGE_IPTABLES: "on"
       MANAGE_IPTABLES: "on"
-      PORT_FORWARD_SERVICES: "mq"
       VERBOSITY: "1"
       VERBOSITY: "1"
     ports:
     ports:
       - "51821-51830:51821-51830/udp"
       - "51821-51830:51821-51830/udp"
@@ -74,13 +74,17 @@ services:
       - caddy_data:/data
       - caddy_data:/data
       - caddy_conf:/config
       - caddy_conf:/config
   mq:
   mq:
-    image: eclipse-mosquitto:2.0.14
+    image: eclipse-mosquitto:2.0.11-openssl
+    depends_on:
+      - netmaker
     container_name: mq
     container_name: mq
     restart: unless-stopped
     restart: unless-stopped
     ports:
     ports:
-      - "1883:1883"
+      - "127.0.0.1:1883:1883"
+      - "8883:8883"
     volumes:
     volumes:
       - /root/mosquitto.conf:/mosquitto/config/mosquitto.conf
       - /root/mosquitto.conf:/mosquitto/config/mosquitto.conf
+      - /root/certs/:/mosquitto/certs/
       - mosquitto_data:/mosquitto/data
       - mosquitto_data:/mosquitto/data
       - mosquitto_logs:/mosquitto/log
       - mosquitto_logs:/mosquitto/log
 volumes:
 volumes:

+ 106 - 0
controllers/server.go

@@ -1,20 +1,28 @@
 package controller
 package controller
 
 
 import (
 import (
+	"crypto/ed25519"
+	"crypto/x509"
+	"crypto/x509/pkix"
 	"encoding/json"
 	"encoding/json"
+	"fmt"
 	"net/http"
 	"net/http"
 	"strings"
 	"strings"
 
 
 	"github.com/gorilla/mux"
 	"github.com/gorilla/mux"
+	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/logic"
 	"github.com/gravitl/netmaker/logic"
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/models"
+	"github.com/gravitl/netmaker/netclient/config"
 	"github.com/gravitl/netmaker/servercfg"
 	"github.com/gravitl/netmaker/servercfg"
+	"github.com/gravitl/netmaker/tls"
 )
 )
 
 
 func serverHandlers(r *mux.Router) {
 func serverHandlers(r *mux.Router) {
 	// r.HandleFunc("/api/server/addnetwork/{network}", securityCheckServer(true, http.HandlerFunc(addNetwork))).Methods("POST")
 	// r.HandleFunc("/api/server/addnetwork/{network}", securityCheckServer(true, http.HandlerFunc(addNetwork))).Methods("POST")
 	r.HandleFunc("/api/server/getconfig", securityCheckServer(false, http.HandlerFunc(getConfig))).Methods("GET")
 	r.HandleFunc("/api/server/getconfig", securityCheckServer(false, http.HandlerFunc(getConfig))).Methods("GET")
 	r.HandleFunc("/api/server/removenetwork/{network}", securityCheckServer(true, http.HandlerFunc(removeNetwork))).Methods("DELETE")
 	r.HandleFunc("/api/server/removenetwork/{network}", securityCheckServer(true, http.HandlerFunc(removeNetwork))).Methods("DELETE")
+	r.HandleFunc("/api/server/register", http.HandlerFunc(register)).Methods("POST")
 }
 }
 
 
 //Security check is middleware for every function and just checks to make sure that its the master calling
 //Security check is middleware for every function and just checks to make sure that its the master calling
@@ -102,3 +110,101 @@ func getConfig(w http.ResponseWriter, r *http.Request) {
 
 
 // 	json.NewEncoder(w).Encode("Server added to network " + params["network"])
 // 	json.NewEncoder(w).Encode("Server added to network " + params["network"])
 // }
 // }
+
+// register - registers a client with the server and return the CA and cert
+func register(w http.ResponseWriter, r *http.Request) {
+	logger.Log(2, "processing registration request")
+	w.Header().Set("Content-Type", "application/json")
+	bearerToken := r.Header.Get("Authorization")
+	var tokenSplit = strings.Split(bearerToken, " ")
+	var token = ""
+	if len(tokenSplit) < 2 {
+		errorResponse := models.ErrorResponse{
+			Code: http.StatusUnauthorized, Message: "W1R3: You are unauthorized to access this endpoint.",
+		}
+		returnErrorResponse(w, r, errorResponse)
+		return
+	} else {
+		token = tokenSplit[1]
+	}
+	//decode body
+	var request config.RegisterRequest
+	if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
+		logger.Log(0, "error decoding request", err.Error())
+		errorResponse := models.ErrorResponse{
+			Code: http.StatusBadRequest, Message: err.Error(),
+		}
+		returnErrorResponse(w, r, errorResponse)
+		return
+	}
+	found := false
+	networks, err := logic.GetNetworks()
+	if err != nil {
+		logger.Log(0, "no networks", err.Error())
+		errorResponse := models.ErrorResponse{
+			Code: http.StatusNotFound, Message: "no networks",
+		}
+		returnErrorResponse(w, r, errorResponse)
+		return
+	}
+	for _, network := range networks {
+		for _, key := range network.AccessKeys {
+			if key.Value == token {
+				found = true
+				break
+			}
+		}
+	}
+	if !found {
+		logger.Log(0, "valid access key not found")
+		errorResponse := models.ErrorResponse{
+			Code: http.StatusUnauthorized, Message: "You are unauthorized to access this endpoint.",
+		}
+		returnErrorResponse(w, r, errorResponse)
+		return
+	}
+	cert, ca, err := genCerts(&request.Key, &request.CommonName)
+	if err != nil {
+		logger.Log(0, "failed to generater certs ", err.Error())
+		errorResponse := models.ErrorResponse{
+			Code: http.StatusNotFound, Message: err.Error(),
+		}
+		returnErrorResponse(w, r, errorResponse)
+		return
+	}
+	//x509.Certificate.PublicKey is an interface therefore json encoding/decoding result in a string value rather than a []byte
+	//include the actual public key so the certificate can be properly reassembled on the other end.
+	response := config.RegisterResponse{
+		CA:         *ca,
+		CAPubKey:   (ca.PublicKey).(ed25519.PublicKey),
+		Cert:       *cert,
+		CertPubKey: (cert.PublicKey).(ed25519.PublicKey),
+	}
+	w.WriteHeader(http.StatusOK)
+	json.NewEncoder(w).Encode(response)
+}
+
+// genCerts generates a client certificate and returns the certificate and root CA
+func genCerts(clientKey *ed25519.PrivateKey, name *pkix.Name) (*x509.Certificate, *x509.Certificate, error) {
+	ca, err := tls.ReadCert("/etc/netmaker/root.pem")
+	if err != nil {
+		logger.Log(2, "root ca not found ", err.Error())
+		return nil, nil, fmt.Errorf("root ca not found %w", err)
+	}
+	key, err := tls.ReadKey("/etc/netmaker/root.key")
+	if err != nil {
+		logger.Log(2, "root key not found ", err.Error())
+		return nil, nil, fmt.Errorf("root key not found %w", err)
+	}
+	csr, err := tls.NewCSR(*clientKey, *name)
+	if err != nil {
+		logger.Log(2, "failed to generate client certificate requests", err.Error())
+		return nil, nil, fmt.Errorf("client certification request generation failed %w", err)
+	}
+	cert, err := tls.NewEndEntityCert(*key, csr, ca, tls.CERTIFICATE_VALIDITY)
+	if err != nil {
+		logger.Log(2, "unable to generate client certificate", err.Error())
+		return nil, nil, fmt.Errorf("client certification generation failed %w", err)
+	}
+	return cert, ca, nil
+}

+ 10 - 2
docker/mosquitto.conf

@@ -1,4 +1,12 @@
-persistence true
 per_listener_settings true
 per_listener_settings true
-listener 1883
+
+listener 8883
 allow_anonymous true
 allow_anonymous true
+require_certificate true
+use_identity_as_username true
+cafile /mosquitto/certs/root.pem
+certfile /mosquitto/certs/server.pem
+keyfile /mosquitto/certs/server.key
+
+listener 1883 
+allow_anonymous true

+ 8 - 0
functions/local.go

@@ -7,6 +7,9 @@ import (
 	"github.com/gravitl/netmaker/logic"
 	"github.com/gravitl/netmaker/logic"
 )
 )
 
 
+// LINUX_APP_DATA_PATH - linux path
+const LINUX_APP_DATA_PATH = "/etc/netmaker"
+
 // FileExists - checks if file exists
 // FileExists - checks if file exists
 func FileExists(f string) bool {
 func FileExists(f string) bool {
 	info, err := os.Stat(f)
 	info, err := os.Stat(f)
@@ -49,3 +52,8 @@ func SetDNSDir() error {
 	}
 	}
 	return nil
 	return nil
 }
 }
+
+// GetNetmakerPath - gets netmaker path locally
+func GetNetmakerPath() string {
+	return LINUX_APP_DATA_PATH
+}

+ 1 - 0
go.mod

@@ -32,6 +32,7 @@ require (
 )
 )
 
 
 require (
 require (
+	filippo.io/edwards25519 v1.0.0-rc.1
 	github.com/go-ping/ping v0.0.0-20211130115550-779d1e919534
 	github.com/go-ping/ping v0.0.0-20211130115550-779d1e919534
 	github.com/guumaster/hostctl v1.1.2
 	github.com/guumaster/hostctl v1.1.2
 	github.com/kr/pretty v0.3.0
 	github.com/kr/pretty v0.3.0

+ 2 - 0
go.sum

@@ -1,6 +1,8 @@
 cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
 cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
 cloud.google.com/go v0.34.0 h1:eOI3/cP2VTU6uZLDYAoic+eyzzB9YyGmJ7eIjl8rOPg=
 cloud.google.com/go v0.34.0 h1:eOI3/cP2VTU6uZLDYAoic+eyzzB9YyGmJ7eIjl8rOPg=
 cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
 cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+filippo.io/edwards25519 v1.0.0-rc.1 h1:m0VOOB23frXZvAOK44usCgLWvtsxIoMCTBGJZlpmGfU=
+filippo.io/edwards25519 v1.0.0-rc.1/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/Microsoft/go-winio v0.4.14 h1:+hMXMk01us9KgxGb7ftKQt2Xpf5hH/yky+TDA+qxleU=
 github.com/Microsoft/go-winio v0.4.14 h1:+hMXMk01us9KgxGb7ftKQt2Xpf5hH/yky+TDA+qxleU=
 github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
 github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=

+ 1 - 0
logic/accesskeys.go

@@ -57,6 +57,7 @@ func CreateAccessKey(accesskey models.AccessKey, network models.Network) (models
 		GRPCConnString: s.GRPCConnString,
 		GRPCConnString: s.GRPCConnString,
 		GRPCSSL:        s.GRPCSSL,
 		GRPCSSL:        s.GRPCSSL,
 		Server:         s.Server,
 		Server:         s.Server,
+		APIConnString:  s.APIConnString,
 	}
 	}
 	accessToken.ServerConfig = servervals
 	accessToken.ServerConfig = servervals
 	accessToken.ClientConfig.Network = netID
 	accessToken.ClientConfig.Network = netID

+ 74 - 3
main.go

@@ -2,6 +2,9 @@ package main
 
 
 import (
 import (
 	"context"
 	"context"
+	"crypto/ed25519"
+	"crypto/rand"
+	"errors"
 	"flag"
 	"flag"
 	"fmt"
 	"fmt"
 	"net"
 	"net"
@@ -11,6 +14,7 @@ import (
 	"strconv"
 	"strconv"
 	"sync"
 	"sync"
 	"syscall"
 	"syscall"
+	"time"
 
 
 	"github.com/gravitl/netmaker/auth"
 	"github.com/gravitl/netmaker/auth"
 	"github.com/gravitl/netmaker/config"
 	"github.com/gravitl/netmaker/config"
@@ -25,6 +29,7 @@ import (
 	"github.com/gravitl/netmaker/netclient/ncutils"
 	"github.com/gravitl/netmaker/netclient/ncutils"
 	"github.com/gravitl/netmaker/servercfg"
 	"github.com/gravitl/netmaker/servercfg"
 	"github.com/gravitl/netmaker/serverctl"
 	"github.com/gravitl/netmaker/serverctl"
+	"github.com/gravitl/netmaker/tls"
 	"google.golang.org/grpc"
 	"google.golang.org/grpc"
 )
 )
 
 
@@ -103,9 +108,6 @@ func initialize() { // Client Mode Prereq Check
 		if err := serverctl.InitServerNetclient(); err != nil {
 		if err := serverctl.InitServerNetclient(); err != nil {
 			logger.FatalLog("Did not find netclient to use CLIENT_MODE")
 			logger.FatalLog("Did not find netclient to use CLIENT_MODE")
 		}
 		}
-		if err := serverctl.InitializeCommsNetwork(); err != nil {
-			logger.FatalLog("could not inintialize comms network")
-		}
 	}
 	}
 	// initialize iptables to ensure gateways work correctly and mq is forwarded if containerized
 	// initialize iptables to ensure gateways work correctly and mq is forwarded if containerized
 	if servercfg.ManageIPTables() != "off" {
 	if servercfg.ManageIPTables() != "off" {
@@ -120,6 +122,7 @@ func initialize() { // Client Mode Prereq Check
 			logger.FatalLog(err.Error())
 			logger.FatalLog(err.Error())
 		}
 		}
 	}
 	}
+	genCerts()
 }
 }
 
 
 func startControllers() {
 func startControllers() {
@@ -238,3 +241,71 @@ func setGarbageCollection() {
 		debug.SetGCPercent(ncutils.DEFAULT_GC_PERCENT)
 		debug.SetGCPercent(ncutils.DEFAULT_GC_PERCENT)
 	}
 	}
 }
 }
+
+func genCerts() error {
+	logger.Log(0, "checking keys and certificates")
+	var private *ed25519.PrivateKey
+	var err error
+	private, err = tls.ReadKey(functions.GetNetmakerPath() + "/root.key")
+	if errors.Is(err, os.ErrNotExist) {
+		logger.Log(0, "generating new root key")
+		_, newKey, err := ed25519.GenerateKey(rand.Reader)
+		if err != nil {
+			return err
+		}
+		if err := tls.SaveKey(functions.GetNetmakerPath(), "/root.key", newKey); err != nil {
+			return err
+		}
+		private = &newKey
+	} else if err != nil {
+		return err
+	}
+	ca, err := tls.ReadCert(functions.GetNetmakerPath() + "/root.pem")
+	//if cert doesn't exist or will expire within 10 days --- but can't do this as clients won't be able to connect
+	//if errors.Is(err, os.ErrNotExist) || cert.NotAfter.Before(time.Now().Add(time.Hour*24*10)) {
+	if errors.Is(err, os.ErrNotExist) {
+		logger.Log(0, "generating new root CA")
+		caName := tls.NewName("CA Root", "US", "Gravitl")
+		csr, err := tls.NewCSR(*private, caName)
+		if err != nil {
+			return err
+		}
+		rootCA, err := tls.SelfSignedCA(*private, csr, tls.CERTIFICATE_VALIDITY)
+		if err != nil {
+			return err
+		}
+		if err := tls.SaveCert(functions.GetNetmakerPath(), "/root.pem", rootCA); err != nil {
+			return err
+		}
+		ca = rootCA
+	} else if err != nil {
+		return err
+	}
+	cert, err := tls.ReadCert(functions.GetNetmakerPath() + "/server.pem")
+	if errors.Is(err, os.ErrNotExist) || cert.NotAfter.Before(time.Now().Add(time.Hour*24*10)) {
+		//gen new key
+		logger.Log(0, "generating new server key/certificate")
+		_, key, err := ed25519.GenerateKey(rand.Reader)
+		if err != nil {
+			return err
+		}
+		serverName := tls.NewCName(servercfg.GetServer())
+		csr, err := tls.NewCSR(key, serverName)
+		if err != nil {
+			return err
+		}
+		cert, err := tls.NewEndEntityCert(*private, csr, ca, tls.CERTIFICATE_VALIDITY)
+		if err != nil {
+			return err
+		}
+		if err := tls.SaveKey(functions.GetNetmakerPath(), "/server.key", key); err != nil {
+			return err
+		}
+		if err := tls.SaveCert(functions.GetNetmakerPath(), "/server.pem", cert); err != nil {
+			return err
+		}
+	} else if err != nil {
+		return err
+	}
+	return nil
+}

+ 1 - 0
models/accessToken.go

@@ -15,4 +15,5 @@ type ServerConfig struct {
 	GRPCConnString string `json:"grpcconn"`
 	GRPCConnString string `json:"grpcconn"`
 	GRPCSSL        string `json:"grpcssl"`
 	GRPCSSL        string `json:"grpcssl"`
 	Server         string `json:"server"`
 	Server         string `json:"server"`
+	APIConnString  string `json:"apiconnstring"`
 }
 }

+ 1 - 1
netclient/cli_options/cmds.go

@@ -30,7 +30,7 @@ func GetCommands(cliFlags []cli.Flag) []*cli.Command {
 					err = errors.New("no server address provided")
 					err = errors.New("no server address provided")
 					return err
 					return err
 				}
 				}
-				err = command.Join(&cfg, pvtKey)
+				err = command.Register(&cfg, pvtKey)
 				return err
 				return err
 			},
 			},
 		},
 		},

+ 4 - 0
netclient/command/commands.go

@@ -154,3 +154,7 @@ func Daemon() error {
 	err := functions.Daemon()
 	err := functions.Daemon()
 	return err
 	return err
 }
 }
+
+func Register(cfg *config.ClientConfig, key string) error {
+	return functions.Register(cfg, key)
+}

+ 24 - 0
netclient/config/config.go

@@ -2,6 +2,10 @@ package config
 
 
 import (
 import (
 	//"github.com/davecgh/go-spew/spew"
 	//"github.com/davecgh/go-spew/spew"
+
+	"crypto/ed25519"
+	"crypto/x509"
+	"crypto/x509/pkix"
 	"encoding/base64"
 	"encoding/base64"
 	"encoding/json"
 	"encoding/json"
 	"errors"
 	"errors"
@@ -35,6 +39,21 @@ type ServerConfig struct {
 	GRPCSSL      string `yaml:"grpcssl"`
 	GRPCSSL      string `yaml:"grpcssl"`
 	CommsNetwork string `yaml:"commsnetwork"`
 	CommsNetwork string `yaml:"commsnetwork"`
 	Server       string `yaml:"server"`
 	Server       string `yaml:"server"`
+	API          string `yaml:"api"`
+}
+
+// RegisterRequest - struct for registation with netmaker server
+type RegisterRequest struct {
+	Key        ed25519.PrivateKey
+	CommonName pkix.Name
+}
+
+// RegisterResponse - the response to register function
+type RegisterResponse struct {
+	CA         x509.Certificate
+	CAPubKey   ed25519.PublicKey
+	Cert       x509.Certificate
+	CertPubKey ed25519.PublicKey
 }
 }
 
 
 // Write - writes the config of a client to disk
 // Write - writes the config of a client to disk
@@ -190,6 +209,7 @@ func GetCLIConfig(c *cli.Context) (ClientConfig, string, error) {
 		cfg.Node.LocalRange = accesstoken.ClientConfig.LocalRange
 		cfg.Node.LocalRange = accesstoken.ClientConfig.LocalRange
 		cfg.Server.GRPCSSL = accesstoken.ServerConfig.GRPCSSL
 		cfg.Server.GRPCSSL = accesstoken.ServerConfig.GRPCSSL
 		cfg.Server.Server = accesstoken.ServerConfig.Server
 		cfg.Server.Server = accesstoken.ServerConfig.Server
+		cfg.Server.API = accesstoken.ServerConfig.APIConnString
 		if c.String("grpcserver") != "" {
 		if c.String("grpcserver") != "" {
 			cfg.Server.GRPCAddress = c.String("grpcserver")
 			cfg.Server.GRPCAddress = c.String("grpcserver")
 		}
 		}
@@ -209,6 +229,9 @@ func GetCLIConfig(c *cli.Context) (ClientConfig, string, error) {
 		if c.String("corednsaddr") != "" {
 		if c.String("corednsaddr") != "" {
 			cfg.Server.CoreDNSAddr = c.String("corednsaddr")
 			cfg.Server.CoreDNSAddr = c.String("corednsaddr")
 		}
 		}
+		if c.String("apiserver") != "" {
+			cfg.Server.API = c.String("apiserver")
+		}
 
 
 	} else {
 	} else {
 		cfg.Server.GRPCAddress = c.String("grpcserver")
 		cfg.Server.GRPCAddress = c.String("grpcserver")
@@ -218,6 +241,7 @@ func GetCLIConfig(c *cli.Context) (ClientConfig, string, error) {
 		cfg.Node.LocalRange = c.String("localrange")
 		cfg.Node.LocalRange = c.String("localrange")
 		cfg.Server.GRPCSSL = c.String("grpcssl")
 		cfg.Server.GRPCSSL = c.String("grpcssl")
 		cfg.Server.CoreDNSAddr = c.String("corednsaddr")
 		cfg.Server.CoreDNSAddr = c.String("corednsaddr")
+		cfg.Server.API = c.String("apiserver")
 	}
 	}
 	cfg.Node.Name = c.String("name")
 	cfg.Node.Name = c.String("name")
 	cfg.Node.Interface = c.String("interface")
 	cfg.Node.Interface = c.String("interface")

+ 8 - 0
netclient/functions/common.go

@@ -23,6 +23,9 @@ import (
 	"google.golang.org/grpc/metadata"
 	"google.golang.org/grpc/metadata"
 )
 )
 
 
+// LINUX_APP_DATA_PATH - linux path
+const LINUX_APP_DATA_PATH = "/etc/netmaker"
+
 // ListPorts - lists ports of WireGuard devices
 // ListPorts - lists ports of WireGuard devices
 func ListPorts() error {
 func ListPorts() error {
 	wgclient, err := wgctrl.New()
 	wgclient, err := wgctrl.New()
@@ -321,3 +324,8 @@ func WipeLocal(network string) error {
 	}
 	}
 	return err
 	return err
 }
 }
+
+// GetNetmakerPath - gets netmaker path locally
+func GetNetmakerPath() string {
+	return LINUX_APP_DATA_PATH
+}

+ 66 - 9
netclient/functions/daemon.go

@@ -2,8 +2,12 @@ package functions
 
 
 import (
 import (
 	"context"
 	"context"
+	"crypto/ed25519"
+	"crypto/tls"
+	"crypto/x509"
 	"errors"
 	"errors"
 	"fmt"
 	"fmt"
+	"log"
 	"os"
 	"os"
 	"os/signal"
 	"os/signal"
 	"strings"
 	"strings"
@@ -20,6 +24,7 @@ import (
 	"github.com/gravitl/netmaker/netclient/daemon"
 	"github.com/gravitl/netmaker/netclient/daemon"
 	"github.com/gravitl/netmaker/netclient/ncutils"
 	"github.com/gravitl/netmaker/netclient/ncutils"
 	"github.com/gravitl/netmaker/netclient/wireguard"
 	"github.com/gravitl/netmaker/netclient/wireguard"
+	ssl "github.com/gravitl/netmaker/tls"
 	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
 	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
 )
 )
 
 
@@ -40,7 +45,11 @@ func Daemon() error {
 	serverSet := make(map[string]struct{})
 	serverSet := make(map[string]struct{})
 	// == initial pull of all networks ==
 	// == initial pull of all networks ==
 	networks, _ := ncutils.GetSystemNetworks()
 	networks, _ := ncutils.GetSystemNetworks()
+	if len(networks) == 0 {
+		return errors.New("no networks")
+	}
 	for _, network := range networks {
 	for _, network := range networks {
+		logger.Log(3, "initializing network", network)
 		cfg := config.ClientConfig{}
 		cfg := config.ClientConfig{}
 		cfg.Network = network
 		cfg.Network = network
 		cfg.ReadConfig()
 		cfg.ReadConfig()
@@ -122,7 +131,7 @@ func PingServer(cfg *config.ClientConfig) error {
 // == Private ==
 // == Private ==
 
 
 // sets MQ client subscriptions for a specific node config
 // sets MQ client subscriptions for a specific node config
-// should be called for each node belonging to a given comms network
+// should be called for each node belonging to a given server
 func setSubscriptions(client mqtt.Client, nodeCfg *config.ClientConfig) {
 func setSubscriptions(client mqtt.Client, nodeCfg *config.ClientConfig) {
 	if nodeCfg.DebugOn {
 	if nodeCfg.DebugOn {
 		if token := client.Subscribe("#", 0, nil); token.Wait() && token.Error() != nil {
 		if token := client.Subscribe("#", 0, nil); token.Wait() && token.Error() != nil {
@@ -131,7 +140,6 @@ func setSubscriptions(client mqtt.Client, nodeCfg *config.ClientConfig) {
 		}
 		}
 		logger.Log(0, "subscribed to all topics for debugging purposes")
 		logger.Log(0, "subscribed to all topics for debugging purposes")
 	}
 	}
-
 	if token := client.Subscribe(fmt.Sprintf("update/%s/%s", nodeCfg.Node.Network, nodeCfg.Node.ID), 0, mqtt.MessageHandler(NodeUpdate)); token.Wait() && token.Error() != nil {
 	if token := client.Subscribe(fmt.Sprintf("update/%s/%s", nodeCfg.Node.Network, nodeCfg.Node.ID), 0, mqtt.MessageHandler(NodeUpdate)); token.Wait() && token.Error() != nil {
 		logger.Log(0, token.Error().Error())
 		logger.Log(0, token.Error().Error())
 		return
 		return
@@ -176,8 +184,8 @@ func messageQueue(ctx context.Context, server string) {
 // utilizes comms client configs to setup connections
 // utilizes comms client configs to setup connections
 func setupMQTTSub(server string) mqtt.Client {
 func setupMQTTSub(server string) mqtt.Client {
 	opts := mqtt.NewClientOptions()
 	opts := mqtt.NewClientOptions()
-	opts.AddBroker(server + ":1883")             // TODO get the appropriate port of the comms mq server
-	opts.ClientID = ncutils.MakeRandomString(23) // helps avoid id duplication on broker
+	opts.AddBroker("ssl://" + server + ":8883") // TODO get the appropriate port of the comms mq server
+	opts.TLSConfig = NewTLSConfig(nil, server)
 	opts.SetDefaultPublishHandler(All)
 	opts.SetDefaultPublishHandler(All)
 	opts.SetAutoReconnect(true)
 	opts.SetAutoReconnect(true)
 	opts.SetConnectRetry(true)
 	opts.SetConnectRetry(true)
@@ -261,13 +269,64 @@ func setupMQTTSub(server string) mqtt.Client {
 	return client
 	return client
 }
 }
 
 
+// NewTLSConf sets up tls configuration to connect to broker securely
+func NewTLSConfig(cfg *config.ClientConfig, server string) *tls.Config {
+	var file string
+	if cfg != nil {
+		server = cfg.Server.Server
+	}
+	file = ncutils.GetNetclientServerPath(server) + "/root.pem"
+	certpool := x509.NewCertPool()
+	ca, err := os.ReadFile(file)
+	if err != nil {
+		logger.Log(0, "could not read CA file ", err.Error())
+	}
+	ok := certpool.AppendCertsFromPEM(ca)
+	if !ok {
+		logger.Log(0, "failed to append cert")
+	}
+	clientKeyPair, err := tls.LoadX509KeyPair(ncutils.GetNetclientServerPath(server)+"/client.pem", ncutils.GetNetclientPath()+"/client.key")
+	if err != nil {
+		log.Fatalf("could not read client cert/key %v \n", err)
+	}
+	certs := []tls.Certificate{clientKeyPair}
+	return &tls.Config{
+		RootCAs:      certpool,
+		ClientAuth:   tls.NoClientCert,
+		ClientCAs:    nil,
+		Certificates: certs,
+		//InsecureSkipVerify: false  fails ---- so need to use VerifyConnection
+		InsecureSkipVerify: true,
+		VerifyConnection: func(cs tls.ConnectionState) error {
+			if cs.ServerName != server {
+				logger.Log(0, "VerifyConnection - certifiate mismatch")
+				return errors.New("certificate doesn't match server")
+			}
+			ca, err := ssl.ReadCert(ncutils.GetNetclientServerPath(cs.ServerName) + "/root.pem")
+			if err != nil {
+				logger.Log(0, "VerifyConnection - unable to read ca", err.Error())
+				return errors.New("unable to read ca")
+			}
+			for _, cert := range cs.PeerCertificates {
+				if cert.IsCA {
+					if string(cert.PublicKey.(ed25519.PublicKey)) != string(ca.PublicKey.(ed25519.PublicKey)) {
+						logger.Log(0, "VerifyConnection - public key mismatch")
+						return errors.New("cert public key does not match ca public key")
+					}
+				}
+			}
+			return nil
+		},
+	}
+}
+
 // setupMQTT creates a connection to broker and return client
 // setupMQTT creates a connection to broker and return client
-// utilizes comms client configs to setup connections
+// this function is primarily used to create a connection to publish to the broker
 func setupMQTT(cfg *config.ClientConfig, publish bool) mqtt.Client {
 func setupMQTT(cfg *config.ClientConfig, publish bool) mqtt.Client {
 	opts := mqtt.NewClientOptions()
 	opts := mqtt.NewClientOptions()
 	server := cfg.Server.Server
 	server := cfg.Server.Server
-	opts.AddBroker(server + ":1883")             // TODO get the appropriate port of the comms mq server
-	opts.ClientID = ncutils.MakeRandomString(23) // helps avoid id duplication on broker
+	opts.AddBroker("ssl://" + server + ":8883") // TODO get the appropriate port of the comms mq server
+	opts.TLSConfig = NewTLSConfig(cfg, "")
 	opts.SetDefaultPublishHandler(All)
 	opts.SetDefaultPublishHandler(All)
 	opts.SetAutoReconnect(true)
 	opts.SetAutoReconnect(true)
 	opts.SetConnectRetry(true)
 	opts.SetConnectRetry(true)
@@ -427,5 +486,3 @@ func read(network, which string) string {
 	}
 	}
 	return ""
 	return ""
 }
 }
-
-// == End Message Caches ==

+ 20 - 0
netclient/functions/mqpublish.go

@@ -3,7 +3,9 @@ package functions
 import (
 import (
 	"context"
 	"context"
 	"encoding/json"
 	"encoding/json"
+	"errors"
 	"fmt"
 	"fmt"
+	"os"
 	"sync"
 	"sync"
 	"time"
 	"time"
 
 
@@ -11,6 +13,7 @@ import (
 	"github.com/gravitl/netmaker/netclient/auth"
 	"github.com/gravitl/netmaker/netclient/auth"
 	"github.com/gravitl/netmaker/netclient/config"
 	"github.com/gravitl/netmaker/netclient/config"
 	"github.com/gravitl/netmaker/netclient/ncutils"
 	"github.com/gravitl/netmaker/netclient/ncutils"
+	"github.com/gravitl/netmaker/tls"
 )
 )
 
 
 // Checkin  -- go routine that checks for public or local ip changes, publishes changes
 // Checkin  -- go routine that checks for public or local ip changes, publishes changes
@@ -75,6 +78,7 @@ func Checkin(ctx context.Context, wg *sync.WaitGroup, currentComms map[string]st
 				} else {
 				} else {
 					Hello(&nodeCfg)
 					Hello(&nodeCfg)
 				}
 				}
+				checkCertExpiry(&nodeCfg)
 			}
 			}
 		}
 		}
 	}
 	}
@@ -135,3 +139,19 @@ func publish(nodeCfg *config.ClientConfig, dest string, msg []byte, qos byte) er
 	}
 	}
 	return nil
 	return nil
 }
 }
+
+func checkCertExpiry(cfg *config.ClientConfig) error {
+	cert, err := tls.ReadCert(ncutils.GetNetclientServerPath(cfg.Server.Server) + "/client.pem")
+	//if cert doesn't exist or will expire within 10 days
+	if errors.Is(err, os.ErrNotExist) || cert.NotAfter.Before(time.Now().Add(time.Hour*24*10)) {
+		key, err := tls.ReadKey(ncutils.GetNetclientPath() + "/client.key")
+		if err != nil {
+			return err
+		}
+		return RegisterWithServer(key, cfg)
+	}
+	if err != nil {
+		return err
+	}
+	return nil
+}

+ 96 - 0
netclient/functions/register.go

@@ -0,0 +1,96 @@
+package functions
+
+import (
+	"bytes"
+	"crypto/ed25519"
+	"crypto/rand"
+	"encoding/json"
+	"errors"
+	"log"
+	"net/http"
+	"os"
+
+	"github.com/gravitl/netmaker/logger"
+	"github.com/gravitl/netmaker/netclient/config"
+	"github.com/gravitl/netmaker/netclient/ncutils"
+	"github.com/gravitl/netmaker/tls"
+)
+
+// Register - the function responsible for registering with the server and acquiring certs
+func Register(cfg *config.ClientConfig, key string) error {
+	if cfg.Server.Server == "" {
+		return errors.New("no server provided")
+	}
+	if cfg.Server.AccessKey == "" {
+		return errors.New("no access key provided")
+	}
+	//generate new key if one doesn' exist
+	var private *ed25519.PrivateKey
+	var err error
+	private, err = tls.ReadKey(ncutils.GetNetclientPath() + "/client.key")
+	if err != nil {
+		_, newKey, err := ed25519.GenerateKey(rand.Reader)
+		if err != nil {
+			return err
+		}
+		if err := tls.SaveKey(ncutils.GetNetclientPath(), "/client.key", newKey); err != nil {
+			return err
+		}
+		private = &newKey
+	}
+	//check if cert exists
+	_, err = tls.ReadCert(ncutils.GetNetclientServerPath(cfg.Server.Server) + "/client.pem")
+	if errors.Is(err, os.ErrNotExist) {
+		if err := RegisterWithServer(private, cfg); err != nil {
+			return err
+		}
+	} else if err != nil {
+		return err
+	}
+	return JoinNetwork(cfg, key, false)
+}
+
+// RegisterWithServer calls the register endpoint with privatekey and commonname - api returns ca and client certificate
+func RegisterWithServer(private *ed25519.PrivateKey, cfg *config.ClientConfig) error {
+	data := config.RegisterRequest{
+		Key:        *private,
+		CommonName: tls.NewCName(os.Getenv("HOSTNAME")),
+	}
+	payload, err := json.Marshal(data)
+	if err != nil {
+		return err
+	}
+	url := "https://" + cfg.Server.API + "/api/server/register"
+	log.Println("register at ", url)
+	request, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(payload))
+	if err != nil {
+		return err
+	}
+	request.Header.Set("Content-Type", "application/json")
+	request.Header.Set("authorization", "Bearer "+cfg.Server.AccessKey)
+	client := http.Client{}
+	response, err := client.Do(request)
+	if err != nil {
+		return err
+	}
+	if response.StatusCode != http.StatusOK {
+		return errors.New(response.Status)
+	}
+	var resp config.RegisterResponse
+	if err := json.NewDecoder(response.Body).Decode(&resp); err != nil {
+		return errors.New("unmarshal cert error " + err.Error())
+	}
+	//x509.Certificate.PublicKey is an interface so json encoding/decoding results in a string rather that []byte
+	//the pubkeys are included in the response so the values in the certificate can be updated appropriately
+	resp.CA.PublicKey = resp.CAPubKey
+	resp.Cert.PublicKey = resp.CertPubKey
+	if err := tls.SaveCert(ncutils.GetNetclientServerPath(cfg.Server.Server)+"/", "root.pem", &resp.CA); err != nil {
+		return err
+	}
+	if err := tls.SaveCert(ncutils.GetNetclientServerPath(cfg.Server.Server)+"/", "client.pem", &resp.Cert); err != nil {
+		return err
+	}
+	logger.Log(0, "certificates/key saved ")
+	//join the network defined in the token
+	return nil
+}

+ 11 - 0
netclient/ncutils/netclientutils.go

@@ -317,6 +317,17 @@ func GetFileWithRetry(path string, retryCount int) ([]byte, error) {
 	return data, err
 	return data, err
 }
 }
 
 
+// GetNetclientServerPath - gets netclient server path
+func GetNetclientServerPath(server string) string {
+	if IsWindows() {
+		return WINDOWS_APP_DATA_PATH + "\\" + server + "\\"
+	} else if IsMac() {
+		return "/etc/netclient/" + server + "/"
+	} else {
+		return LINUX_APP_DATA_PATH + "/" + server
+	}
+}
+
 // GetNetclientPathSpecific - gets specific netclient config path
 // GetNetclientPathSpecific - gets specific netclient config path
 func GetNetclientPathSpecific() string {
 func GetNetclientPathSpecific() string {
 	if IsWindows() {
 	if IsWindows() {

+ 2 - 0
scripts/nm-quick.sh

@@ -139,6 +139,8 @@ wget -q -O /root/mosquitto.conf https://raw.githubusercontent.com/gravitl/netmak
 
 
 echo "setting docker-compose..."
 echo "setting docker-compose..."
 
 
+mkdir -p /etc/netmaker
+
 wget -q -O /root/docker-compose.yml https://raw.githubusercontent.com/gravitl/netmaker/master/compose/docker-compose.contained.yml
 wget -q -O /root/docker-compose.yml https://raw.githubusercontent.com/gravitl/netmaker/master/compose/docker-compose.contained.yml
 sed -i "s/NETMAKER_BASE_DOMAIN/$NETMAKER_BASE_DOMAIN/g" /root/docker-compose.yml
 sed -i "s/NETMAKER_BASE_DOMAIN/$NETMAKER_BASE_DOMAIN/g" /root/docker-compose.yml
 sed -i "s/SERVER_PUBLIC_IP/$SERVER_PUBLIC_IP/g" /root/docker-compose.yml
 sed -i "s/SERVER_PUBLIC_IP/$SERVER_PUBLIC_IP/g" /root/docker-compose.yml

+ 285 - 0
tls/tls.go

@@ -0,0 +1,285 @@
+package tls
+
+import (
+	"crypto/ed25519"
+	"crypto/rand"
+	"crypto/x509"
+	"crypto/x509/pkix"
+	"encoding/base64"
+	"encoding/pem"
+	"errors"
+	"fmt"
+	"math/big"
+	"os"
+	"time"
+
+	"filippo.io/edwards25519"
+	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
+)
+
+// CERTTIFICAT_VALIDITY duration of certificate validity in days
+const CERTIFICATE_VALIDITY = 365
+
+type (
+	// Key is the struct for an edwards representation point
+	Key struct {
+		point *edwards25519.Point
+	}
+)
+
+// NewKey generates a new key.
+func NewKey() *Key {
+	seed := make([]byte, 64)
+	rand.Reader.Read(seed)
+	s, _ := (&edwards25519.Scalar{}).SetUniformBytes(seed)
+	return &Key{(&edwards25519.Point{}).ScalarBaseMult(s)}
+}
+
+// Key.Ed25519PrivateKey returns the private key in Edwards form used for EdDSA.
+func (n *Key) Ed25519PrivateKey() (ed25519.PrivateKey, error) {
+	if n.point == nil {
+		return ed25519.PrivateKey{}, errors.New("nil point")
+	}
+	if len(n.point.Bytes()) != ed25519.SeedSize {
+		return ed25519.PrivateKey{}, errors.New("incorrect seed size")
+	}
+	return ed25519.NewKeyFromSeed(n.point.Bytes()), nil
+}
+
+// Key.Curve25519PrivateKey returns the private key in Montogomery form used for ECDH.
+func (n *Key) Curve25519PrivateKey() (wgtypes.Key, error) {
+	if n.point == nil {
+		return wgtypes.Key{}, errors.New("nil point")
+	}
+	if len(n.point.Bytes()) != ed25519.SeedSize {
+		return wgtypes.Key{}, errors.New("incorrect seed size")
+	}
+	return wgtypes.ParseKey(base64.StdEncoding.EncodeToString(n.point.BytesMontgomery()))
+}
+
+// Key.Save : saves the private key to path.
+func (n *Key) Save(path string) error {
+	f, err := os.Create(path)
+	if err != nil {
+		return err
+	}
+	defer f.Close()
+	f.Write(n.point.Bytes())
+	return nil
+}
+
+// ReadFrom reads a private key from path.
+func ReadFrom(path string) (*Key, error) {
+	key, err := os.ReadFile(path)
+	if err != nil {
+		return nil, err
+	}
+	point, err := (&edwards25519.Point{}).SetBytes(key)
+	if err != nil {
+		return nil, err
+	}
+	return &Key{point}, nil
+}
+
+// NewName creates a new pkix.Name with common name, country, and organization
+func NewName(commonName, country, org string) pkix.Name {
+	res := NewCName(commonName)
+	res.Country = []string{country}
+	res.Organization = []string{org}
+	return res
+}
+
+// NewCName creates a new pkix.Name with only a common name
+func NewCName(commonName string) pkix.Name {
+	return pkix.Name{
+		CommonName: commonName,
+	}
+}
+
+// NewCSR creates a new certificate signing request for a
+func NewCSR(key ed25519.PrivateKey, name pkix.Name) (*x509.CertificateRequest, error) {
+	dnsnames := []string{}
+	dnsnames = append(dnsnames, name.CommonName)
+	derCertRequest, err := x509.CreateCertificateRequest(rand.Reader, &x509.CertificateRequest{
+		Subject:            name,
+		PublicKey:          key.Public(),
+		DNSNames:           dnsnames,
+		PublicKeyAlgorithm: x509.Ed25519,
+		Version:            3,
+	}, key)
+	if err != nil {
+		return nil, err
+	}
+	csr, err := x509.ParseCertificateRequest(derCertRequest)
+	if err != nil {
+		return nil, err
+	}
+	return csr, nil
+}
+
+// SelfSignedCA returns a new self-signed certificate
+func SelfSignedCA(key ed25519.PrivateKey, req *x509.CertificateRequest, days int) (*x509.Certificate, error) {
+
+	template := &x509.Certificate{
+		BasicConstraintsValid: true,
+		IsCA:                  true,
+		Version:               req.Version,
+		KeyUsage:              x509.KeyUsageCertSign | x509.KeyUsageCRLSign | x509.KeyUsageDataEncipherment,
+		NotAfter:              time.Now().Add(duration(days)),
+		NotBefore:             time.Now(),
+		SerialNumber:          serialNumber(),
+		PublicKey:             key.Public(),
+		Subject: pkix.Name{
+			CommonName:   req.Subject.CommonName,
+			Organization: req.Subject.Organization,
+			Country:      req.Subject.Country,
+		},
+	}
+	rootCa, err := x509.CreateCertificate(rand.Reader, template, template, req.PublicKey, key)
+	if err != nil {
+		return nil, err
+	}
+	result, err := x509.ParseCertificate(rootCa)
+	if err != nil {
+		return nil, err
+	}
+	return result, nil
+}
+
+// NewEndEntityCert issues a new certificate from a parent certificate authority
+func NewEndEntityCert(key ed25519.PrivateKey, req *x509.CertificateRequest, parent *x509.Certificate, days int) (*x509.Certificate, error) {
+	template := &x509.Certificate{
+		Version:               req.Version,
+		NotBefore:             time.Now(),
+		NotAfter:              time.Now().Add(duration(days)),
+		SerialNumber:          serialNumber(),
+		Subject:               req.Subject,
+		Issuer:                parent.Subject,
+		KeyUsage:              x509.KeyUsageDigitalSignature,
+		BasicConstraintsValid: true,
+	}
+	rootCa, err := x509.CreateCertificate(rand.Reader, template, parent, req.PublicKey, key)
+	if err != nil {
+		return nil, err
+	}
+	result, err := x509.ParseCertificate(rootCa)
+	if err != nil {
+		return nil, err
+	}
+	return result, nil
+}
+
+// SaveRequest saves a certificate request to the specified path
+func SaveRequest(path, name string, csr *x509.CertificateRequest) error {
+	if err := os.MkdirAll(path, 0600); err != nil {
+		return err
+	}
+	requestOut, err := os.Create(path + name)
+	if err != nil {
+		return err
+	}
+	defer requestOut.Close()
+	if err := pem.Encode(requestOut, &pem.Block{
+		Type:  "CERTIFICATE REQUEST",
+		Bytes: csr.Raw,
+	}); err != nil {
+		return err
+	}
+	return nil
+}
+
+// SaveCert save a certificate to the specified path
+func SaveCert(path, name string, cert *x509.Certificate) error {
+	//certbytes, err := x509.ParseCertificate(cert)
+	if err := os.MkdirAll(path, 0600); err != nil {
+		return fmt.Errorf("failed to create dir %s %w", path, err)
+	}
+	certOut, err := os.Create(path + name)
+	if err != nil {
+		return fmt.Errorf("failed to open certficate file for writing: %v", err)
+	}
+	defer certOut.Close()
+	if err := pem.Encode(certOut, &pem.Block{
+		Type:  "CERTIFICATE",
+		Bytes: cert.Raw,
+	}); err != nil {
+		return fmt.Errorf("failed to write certificate to file %v", err)
+	}
+	return nil
+}
+
+// SaveKey save a private key (ed25519) to the specified path
+func SaveKey(path, name string, key ed25519.PrivateKey) error {
+	//func SaveKey(name string, key *ecdsa.PrivateKey) error {
+	if err := os.MkdirAll(path, 0600); err != nil {
+		return fmt.Errorf("failed to create dir %s %w", path, err)
+	}
+	keyOut, err := os.Create(path + name)
+	if err != nil {
+		return fmt.Errorf("failed open key file for writing: %v", err)
+	}
+	defer keyOut.Close()
+	privBytes, err := x509.MarshalPKCS8PrivateKey(key)
+	if err != nil {
+		return fmt.Errorf("failedto marshal key %v ", err)
+	}
+	if err := pem.Encode(keyOut, &pem.Block{
+		Type:  "PRIVATE KEY",
+		Bytes: privBytes,
+	}); err != nil {
+		return fmt.Errorf("failed to write key to file %v", err)
+	}
+	return nil
+}
+
+// ReadCert reads a certificate from disk
+func ReadCert(name string) (*x509.Certificate, error) {
+	contents, err := os.ReadFile(name)
+	if err != nil {
+		return nil, fmt.Errorf("unable to read file %w", err)
+	}
+	block, _ := pem.Decode(contents)
+	if block == nil || block.Type != "CERTIFICATE" {
+		return nil, errors.New("not a cert " + block.Type)
+	}
+	cert, err := x509.ParseCertificate(block.Bytes)
+	if err != nil {
+		return nil, fmt.Errorf("unable to parse cert %w", err)
+	}
+	return cert, nil
+}
+
+// ReadKey reads a private key (ed25519) from disk
+func ReadKey(name string) (*ed25519.PrivateKey, error) {
+	bytes, err := os.ReadFile(name)
+	if err != nil {
+		return nil, fmt.Errorf("unable to read file %w", err)
+	}
+	keyBytes, _ := pem.Decode(bytes)
+	key, err := x509.ParsePKCS8PrivateKey(keyBytes.Bytes)
+	if err != nil {
+		return nil, fmt.Errorf("unable to parse file %w", err)
+	}
+	private := key.(ed25519.PrivateKey)
+	return &private, nil
+}
+
+// serialNumber generates a serial number for a certificate
+func serialNumber() *big.Int {
+	serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
+	serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
+	if err != nil {
+		return nil
+	}
+	return serialNumber
+}
+
+// duration coverts the number of days to time.duration
+func duration(days int) time.Duration {
+	hours := days * 24
+	duration, err := time.ParseDuration(fmt.Sprintf("%dh", hours))
+	if err != nil {
+		duration = time.Until(time.Now().Add(time.Hour * 24))
+	}
+	return duration
+}