mirror of
https://github.com/gravitl/netmaker.git
synced 2025-11-08 07:41:41 +08:00
Merge pull request #1016 from gravitl/feature_v0.13.0_mq_register
Feature v0.13.0 mq register
This commit is contained in:
commit
a3f44f152b
19 changed files with 727 additions and 18 deletions
|
|
@ -7,6 +7,7 @@ services:
|
|||
volumes:
|
||||
- dnsconfig:/root/config/dnsconfig
|
||||
- sqldata:/root/data
|
||||
- /root/certs:/etc/netmaker/
|
||||
cap_add:
|
||||
- NET_ADMIN
|
||||
- NET_RAW
|
||||
|
|
@ -36,7 +37,6 @@ services:
|
|||
MQ_HOST: "mq"
|
||||
HOST_NETWORK: "off"
|
||||
MANAGE_IPTABLES: "on"
|
||||
PORT_FORWARD_SERVICES: "mq"
|
||||
VERBOSITY: "1"
|
||||
ports:
|
||||
- "51821-51830:51821-51830/udp"
|
||||
|
|
@ -74,13 +74,17 @@ services:
|
|||
- caddy_data:/data
|
||||
- caddy_conf:/config
|
||||
mq:
|
||||
image: eclipse-mosquitto:2.0.14
|
||||
image: eclipse-mosquitto:2.0.11-openssl
|
||||
depends_on:
|
||||
- netmaker
|
||||
container_name: mq
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "1883:1883"
|
||||
- "127.0.0.1:1883:1883"
|
||||
- "8883:8883"
|
||||
volumes:
|
||||
- /root/mosquitto.conf:/mosquitto/config/mosquitto.conf
|
||||
- /root/certs/:/mosquitto/certs/
|
||||
- mosquitto_data:/mosquitto/data
|
||||
- mosquitto_logs:/mosquitto/log
|
||||
volumes:
|
||||
|
|
|
|||
|
|
@ -1,20 +1,28 @@
|
|||
package controller
|
||||
|
||||
import (
|
||||
"crypto/ed25519"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/gravitl/netmaker/logger"
|
||||
"github.com/gravitl/netmaker/logic"
|
||||
"github.com/gravitl/netmaker/models"
|
||||
"github.com/gravitl/netmaker/netclient/config"
|
||||
"github.com/gravitl/netmaker/servercfg"
|
||||
"github.com/gravitl/netmaker/tls"
|
||||
)
|
||||
|
||||
func serverHandlers(r *mux.Router) {
|
||||
// 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/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
|
||||
|
|
@ -102,3 +110,101 @@ func getConfig(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
// 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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,12 @@
|
|||
persistence true
|
||||
per_listener_settings true
|
||||
listener 1883
|
||||
|
||||
listener 8883
|
||||
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
|
||||
|
|
@ -7,6 +7,9 @@ import (
|
|||
"github.com/gravitl/netmaker/logic"
|
||||
)
|
||||
|
||||
// LINUX_APP_DATA_PATH - linux path
|
||||
const LINUX_APP_DATA_PATH = "/etc/netmaker"
|
||||
|
||||
// FileExists - checks if file exists
|
||||
func FileExists(f string) bool {
|
||||
info, err := os.Stat(f)
|
||||
|
|
@ -49,3 +52,8 @@ func SetDNSDir() error {
|
|||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetNetmakerPath - gets netmaker path locally
|
||||
func GetNetmakerPath() string {
|
||||
return LINUX_APP_DATA_PATH
|
||||
}
|
||||
|
|
|
|||
1
go.mod
1
go.mod
|
|
@ -32,6 +32,7 @@ require (
|
|||
)
|
||||
|
||||
require (
|
||||
filippo.io/edwards25519 v1.0.0-rc.1
|
||||
github.com/go-ping/ping v0.0.0-20211130115550-779d1e919534
|
||||
github.com/guumaster/hostctl v1.1.2
|
||||
github.com/kr/pretty v0.3.0
|
||||
|
|
|
|||
2
go.sum
2
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.34.0 h1:eOI3/cP2VTU6uZLDYAoic+eyzzB9YyGmJ7eIjl8rOPg=
|
||||
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/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=
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ func CreateAccessKey(accesskey models.AccessKey, network models.Network) (models
|
|||
GRPCConnString: s.GRPCConnString,
|
||||
GRPCSSL: s.GRPCSSL,
|
||||
Server: s.Server,
|
||||
APIConnString: s.APIConnString,
|
||||
}
|
||||
accessToken.ServerConfig = servervals
|
||||
accessToken.ClientConfig.Network = netID
|
||||
|
|
|
|||
77
main.go
77
main.go
|
|
@ -2,6 +2,9 @@ package main
|
|||
|
||||
import (
|
||||
"context"
|
||||
"crypto/ed25519"
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"net"
|
||||
|
|
@ -11,6 +14,7 @@ import (
|
|||
"strconv"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/gravitl/netmaker/auth"
|
||||
"github.com/gravitl/netmaker/config"
|
||||
|
|
@ -25,6 +29,7 @@ import (
|
|||
"github.com/gravitl/netmaker/netclient/ncutils"
|
||||
"github.com/gravitl/netmaker/servercfg"
|
||||
"github.com/gravitl/netmaker/serverctl"
|
||||
"github.com/gravitl/netmaker/tls"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
|
|
@ -103,9 +108,6 @@ func initialize() { // Client Mode Prereq Check
|
|||
if err := serverctl.InitServerNetclient(); err != nil {
|
||||
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
|
||||
if servercfg.ManageIPTables() != "off" {
|
||||
|
|
@ -120,6 +122,7 @@ func initialize() { // Client Mode Prereq Check
|
|||
logger.FatalLog(err.Error())
|
||||
}
|
||||
}
|
||||
genCerts()
|
||||
}
|
||||
|
||||
func startControllers() {
|
||||
|
|
@ -238,3 +241,71 @@ func setGarbageCollection() {
|
|||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,4 +15,5 @@ type ServerConfig struct {
|
|||
GRPCConnString string `json:"grpcconn"`
|
||||
GRPCSSL string `json:"grpcssl"`
|
||||
Server string `json:"server"`
|
||||
APIConnString string `json:"apiconnstring"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ func GetCommands(cliFlags []cli.Flag) []*cli.Command {
|
|||
err = errors.New("no server address provided")
|
||||
return err
|
||||
}
|
||||
err = command.Join(&cfg, pvtKey)
|
||||
err = command.Register(&cfg, pvtKey)
|
||||
return err
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -154,3 +154,7 @@ func Daemon() error {
|
|||
err := functions.Daemon()
|
||||
return err
|
||||
}
|
||||
|
||||
func Register(cfg *config.ClientConfig, key string) error {
|
||||
return functions.Register(cfg, key)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,10 @@ package config
|
|||
|
||||
import (
|
||||
//"github.com/davecgh/go-spew/spew"
|
||||
|
||||
"crypto/ed25519"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
|
@ -35,6 +39,21 @@ type ServerConfig struct {
|
|||
GRPCSSL string `yaml:"grpcssl"`
|
||||
CommsNetwork string `yaml:"commsnetwork"`
|
||||
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
|
||||
|
|
@ -190,6 +209,7 @@ func GetCLIConfig(c *cli.Context) (ClientConfig, string, error) {
|
|||
cfg.Node.LocalRange = accesstoken.ClientConfig.LocalRange
|
||||
cfg.Server.GRPCSSL = accesstoken.ServerConfig.GRPCSSL
|
||||
cfg.Server.Server = accesstoken.ServerConfig.Server
|
||||
cfg.Server.API = accesstoken.ServerConfig.APIConnString
|
||||
if 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") != "" {
|
||||
cfg.Server.CoreDNSAddr = c.String("corednsaddr")
|
||||
}
|
||||
if c.String("apiserver") != "" {
|
||||
cfg.Server.API = c.String("apiserver")
|
||||
}
|
||||
|
||||
} else {
|
||||
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.Server.GRPCSSL = c.String("grpcssl")
|
||||
cfg.Server.CoreDNSAddr = c.String("corednsaddr")
|
||||
cfg.Server.API = c.String("apiserver")
|
||||
}
|
||||
cfg.Node.Name = c.String("name")
|
||||
cfg.Node.Interface = c.String("interface")
|
||||
|
|
|
|||
|
|
@ -23,6 +23,9 @@ import (
|
|||
"google.golang.org/grpc/metadata"
|
||||
)
|
||||
|
||||
// LINUX_APP_DATA_PATH - linux path
|
||||
const LINUX_APP_DATA_PATH = "/etc/netmaker"
|
||||
|
||||
// ListPorts - lists ports of WireGuard devices
|
||||
func ListPorts() error {
|
||||
wgclient, err := wgctrl.New()
|
||||
|
|
@ -321,3 +324,8 @@ func WipeLocal(network string) error {
|
|||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// GetNetmakerPath - gets netmaker path locally
|
||||
func GetNetmakerPath() string {
|
||||
return LINUX_APP_DATA_PATH
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,8 +2,12 @@ package functions
|
|||
|
||||
import (
|
||||
"context"
|
||||
"crypto/ed25519"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
|
|
@ -20,6 +24,7 @@ import (
|
|||
"github.com/gravitl/netmaker/netclient/daemon"
|
||||
"github.com/gravitl/netmaker/netclient/ncutils"
|
||||
"github.com/gravitl/netmaker/netclient/wireguard"
|
||||
ssl "github.com/gravitl/netmaker/tls"
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
)
|
||||
|
||||
|
|
@ -40,7 +45,11 @@ func Daemon() error {
|
|||
serverSet := make(map[string]struct{})
|
||||
// == initial pull of all networks ==
|
||||
networks, _ := ncutils.GetSystemNetworks()
|
||||
if len(networks) == 0 {
|
||||
return errors.New("no networks")
|
||||
}
|
||||
for _, network := range networks {
|
||||
logger.Log(3, "initializing network", network)
|
||||
cfg := config.ClientConfig{}
|
||||
cfg.Network = network
|
||||
cfg.ReadConfig()
|
||||
|
|
@ -122,7 +131,7 @@ func PingServer(cfg *config.ClientConfig) error {
|
|||
// == Private ==
|
||||
|
||||
// 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) {
|
||||
if nodeCfg.DebugOn {
|
||||
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")
|
||||
}
|
||||
|
||||
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())
|
||||
return
|
||||
|
|
@ -176,8 +184,8 @@ func messageQueue(ctx context.Context, server string) {
|
|||
// utilizes comms client configs to setup connections
|
||||
func setupMQTTSub(server string) mqtt.Client {
|
||||
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.SetAutoReconnect(true)
|
||||
opts.SetConnectRetry(true)
|
||||
|
|
@ -261,13 +269,64 @@ func setupMQTTSub(server string) mqtt.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
|
||||
// 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 {
|
||||
opts := mqtt.NewClientOptions()
|
||||
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.SetAutoReconnect(true)
|
||||
opts.SetConnectRetry(true)
|
||||
|
|
@ -427,5 +486,3 @@ func read(network, which string) string {
|
|||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// == End Message Caches ==
|
||||
|
|
|
|||
|
|
@ -3,7 +3,9 @@ package functions
|
|||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
|
|
@ -11,6 +13,7 @@ import (
|
|||
"github.com/gravitl/netmaker/netclient/auth"
|
||||
"github.com/gravitl/netmaker/netclient/config"
|
||||
"github.com/gravitl/netmaker/netclient/ncutils"
|
||||
"github.com/gravitl/netmaker/tls"
|
||||
)
|
||||
|
||||
// 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 {
|
||||
Hello(&nodeCfg)
|
||||
}
|
||||
checkCertExpiry(&nodeCfg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -135,3 +139,19 @@ func publish(nodeCfg *config.ClientConfig, dest string, msg []byte, qos byte) er
|
|||
}
|
||||
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
netclient/functions/register.go
Normal file
96
netclient/functions/register.go
Normal file
|
|
@ -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
|
||||
}
|
||||
|
|
@ -317,6 +317,17 @@ func GetFileWithRetry(path string, retryCount int) ([]byte, error) {
|
|||
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
|
||||
func GetNetclientPathSpecific() string {
|
||||
if IsWindows() {
|
||||
|
|
|
|||
|
|
@ -139,6 +139,8 @@ wget -q -O /root/mosquitto.conf https://raw.githubusercontent.com/gravitl/netmak
|
|||
|
||||
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
|
||||
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
|
||||
|
|
|
|||
285
tls/tls.go
Normal file
285
tls/tls.go
Normal file
|
|
@ -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
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue