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

Feature v0.13.0 mq register
This commit is contained in:
Matthew R Kasun 2022-04-20 09:33:33 -04:00 committed by GitHub
commit a3f44f152b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 727 additions and 18 deletions

View file

@ -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:

View file

@ -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
}

View file

@ -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

View file

@ -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
View file

@ -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
View file

@ -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=

View file

@ -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
View file

@ -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
}

View file

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

View file

@ -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
},
},

View file

@ -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)
}

View file

@ -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")

View file

@ -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
}

View file

@ -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 ==

View file

@ -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
}

View 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
}

View file

@ -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() {

View file

@ -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
View 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
}