1
0
Fork 0
mirror of https://github.com/postmannen/ctrl.git synced 2024-12-14 12:37:31 +00:00

persistent store of keys on node

This commit is contained in:
postmannen 2022-04-21 13:21:36 +02:00
parent c4f8979f48
commit 690d11194b
5 changed files with 123 additions and 55 deletions

View file

@ -3,7 +3,9 @@ package steward
import ( import (
"crypto/ed25519" "crypto/ed25519"
"encoding/base64" "encoding/base64"
"encoding/json"
"fmt" "fmt"
"io"
"io/ioutil" "io/ioutil"
"log" "log"
"os" "os"
@ -17,7 +19,7 @@ type signature string
// allowedSignatures is the structure for reading and writing from // allowedSignatures is the structure for reading and writing from
// the signatures map. It holds a mutex to use when interacting with // the signatures map. It holds a mutex to use when interacting with
// the map. // the map.
type signatures struct { type nodeAuth struct {
// All the allowed signatures a node is allowed to recive from. // All the allowed signatures a node is allowed to recive from.
allowedSignatures *allowedSignatures allowedSignatures *allowedSignatures
@ -41,26 +43,26 @@ type signatures struct {
errorKernel *errorKernel errorKernel *errorKernel
} }
func newSignatures(configuration *Configuration, errorKernel *errorKernel) *signatures { func newNodeAuth(configuration *Configuration, errorKernel *errorKernel) *nodeAuth {
s := signatures{ n := nodeAuth{
allowedSignatures: newAllowedSignatures(), allowedSignatures: newAllowedSignatures(),
publicKeys: newPublicKeys(), publicKeys: newPublicKeys(configuration),
configuration: configuration, configuration: configuration,
errorKernel: errorKernel, errorKernel: errorKernel,
} }
// Set the signing key paths. // Set the signing key paths.
s.SignKeyFolder = filepath.Join(configuration.ConfigFolder, "signing") n.SignKeyFolder = filepath.Join(configuration.ConfigFolder, "signing")
s.SignKeyPrivateKeyPath = filepath.Join(s.SignKeyFolder, "private.key") n.SignKeyPrivateKeyPath = filepath.Join(n.SignKeyFolder, "private.key")
s.SignKeyPublicKeyPath = filepath.Join(s.SignKeyFolder, "public.key") n.SignKeyPublicKeyPath = filepath.Join(n.SignKeyFolder, "public.key")
err := s.loadSigningKeys() err := n.loadSigningKeys()
if err != nil { if err != nil {
log.Printf("%v\n", err) log.Printf("%v\n", err)
os.Exit(1) os.Exit(1)
} }
return &s return &n
} }
type allowedSignatures struct { type allowedSignatures struct {
@ -79,24 +81,90 @@ func newAllowedSignatures() *allowedSignatures {
type publicKeys struct { type publicKeys struct {
// nodesKey is a map who holds all the public keys for nodes. // nodesKey is a map who holds all the public keys for nodes.
nodeKeys map[Node][]byte NodeKeys map[Node][]byte
mu sync.Mutex mu sync.Mutex
filePath string
} }
func newPublicKeys() *publicKeys { func newPublicKeys(c *Configuration) *publicKeys {
p := publicKeys{ p := publicKeys{
nodeKeys: make(map[Node][]byte), NodeKeys: make(map[Node][]byte),
filePath: filepath.Join(c.DatabaseFolder, "publickeys.txt"),
}
err := p.loadFromFile()
if err != nil {
log.Printf("error: loading public keys from file: %v\n", err)
// os.Exit(1)
} }
return &p return &p
} }
// loadFromFile will try to load all the currently stored public keys from file,
// and return the error if it fails.
// If no file is found a nil error is returned.
func (p *publicKeys) loadFromFile() error {
if _, err := os.Stat(p.filePath); os.IsNotExist(err) {
// Just logging the error since it is not crucial that a key file is missing,
// since a new one will be created on the next update.
log.Printf("no public keys file found at %v\n", p.filePath)
return nil
}
fh, err := os.OpenFile(p.filePath, os.O_RDONLY, 0600)
if err != nil {
return fmt.Errorf("error: failed to open public keys file: %v", err)
}
defer fh.Close()
b, err := io.ReadAll(fh)
if err != nil {
return err
}
p.mu.Lock()
defer p.mu.Unlock()
err = json.Unmarshal(b, &p.NodeKeys)
if err != nil {
return err
}
fmt.Printf("\n ***** DEBUG: Loaded existing keys from file: %v\n\n", p.NodeKeys)
return nil
}
// saveToFile will save all the public kets to file for persistent storage.
// An error is returned if it fails.
func (p *publicKeys) saveToFile() error {
fh, err := os.OpenFile(p.filePath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
return fmt.Errorf("error: failed to open public keys file: %v", err)
}
defer fh.Close()
p.mu.Lock()
defer p.mu.Unlock()
b, err := json.Marshal(p.NodeKeys)
if err != nil {
return err
}
_, err = fh.Write(b)
if err != nil {
return err
}
return nil
}
// loadSigningKeys will try to load the ed25519 signing keys. If the // loadSigningKeys will try to load the ed25519 signing keys. If the
// files are not found new keys will be generated and written to disk. // files are not found new keys will be generated and written to disk.
func (s *signatures) loadSigningKeys() error { func (n *nodeAuth) loadSigningKeys() error {
// Check if folder structure exist, if not create it. // Check if folder structure exist, if not create it.
if _, err := os.Stat(s.SignKeyFolder); os.IsNotExist(err) { if _, err := os.Stat(n.SignKeyFolder); os.IsNotExist(err) {
err := os.MkdirAll(s.SignKeyFolder, 0700) err := os.MkdirAll(n.SignKeyFolder, 0700)
if err != nil { if err != nil {
er := fmt.Errorf("error: failed to create directory for signing keys : %v", err) er := fmt.Errorf("error: failed to create directory for signing keys : %v", err)
return er return er
@ -107,10 +175,10 @@ func (s *signatures) loadSigningKeys() error {
// Check if there already are any keys in the etc folder. // Check if there already are any keys in the etc folder.
foundKey := false foundKey := false
if _, err := os.Stat(s.SignKeyPublicKeyPath); !os.IsNotExist(err) { if _, err := os.Stat(n.SignKeyPublicKeyPath); !os.IsNotExist(err) {
foundKey = true foundKey = true
} }
if _, err := os.Stat(s.SignKeyPrivateKeyPath); !os.IsNotExist(err) { if _, err := os.Stat(n.SignKeyPrivateKeyPath); !os.IsNotExist(err) {
foundKey = true foundKey = true
} }
@ -126,21 +194,21 @@ func (s *signatures) loadSigningKeys() error {
privB64string := base64.RawStdEncoding.EncodeToString(priv) privB64string := base64.RawStdEncoding.EncodeToString(priv)
// Write public key to file. // Write public key to file.
err = s.writeSigningKey(s.SignKeyPublicKeyPath, pubB64string) err = n.writeSigningKey(n.SignKeyPublicKeyPath, pubB64string)
if err != nil { if err != nil {
return err return err
} }
// Write private key to file. // Write private key to file.
err = s.writeSigningKey(s.SignKeyPrivateKeyPath, privB64string) err = n.writeSigningKey(n.SignKeyPrivateKeyPath, privB64string)
if err != nil { if err != nil {
return err return err
} }
// Also store the keys in the processes structure so we can // Also store the keys in the processes structure so we can
// reference them from there when we need them. // reference them from there when we need them.
s.SignPublicKey = pub n.SignPublicKey = pub
s.SignPrivateKey = priv n.SignPrivateKey = priv
er := fmt.Errorf("info: no signing keys found, generating new keys") er := fmt.Errorf("info: no signing keys found, generating new keys")
log.Printf("%v\n", er) log.Printf("%v\n", er)
@ -150,24 +218,24 @@ func (s *signatures) loadSigningKeys() error {
} }
// Key files found, load them into the processes struct fields. // Key files found, load them into the processes struct fields.
pubKey, _, err := s.readKeyFile(s.SignKeyPublicKeyPath) pubKey, _, err := n.readKeyFile(n.SignKeyPublicKeyPath)
if err != nil { if err != nil {
return err return err
} }
s.SignPublicKey = pubKey n.SignPublicKey = pubKey
privKey, _, err := s.readKeyFile(s.SignKeyPrivateKeyPath) privKey, _, err := n.readKeyFile(n.SignKeyPrivateKeyPath)
if err != nil { if err != nil {
return err return err
} }
s.SignPublicKey = pubKey n.SignPublicKey = pubKey
s.SignPrivateKey = privKey n.SignPrivateKey = privKey
return nil return nil
} }
// writeSigningKey will write the base64 encoded signing key to file. // writeSigningKey will write the base64 encoded signing key to file.
func (s *signatures) writeSigningKey(realPath string, keyB64 string) error { func (n *nodeAuth) writeSigningKey(realPath string, keyB64 string) error {
fh, err := os.OpenFile(realPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600) fh, err := os.OpenFile(realPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil { if err != nil {
er := fmt.Errorf("error: failed to open key file for writing: %v", err) er := fmt.Errorf("error: failed to open key file for writing: %v", err)
@ -187,7 +255,7 @@ func (s *signatures) writeSigningKey(realPath string, keyB64 string) error {
// readKeyFile will take the path of a key file as input, read the base64 // readKeyFile will take the path of a key file as input, read the base64
// encoded data, decode the data. It will return the raw data as []byte, // encoded data, decode the data. It will return the raw data as []byte,
// the base64 encoded data, and any eventual error. // the base64 encoded data, and any eventual error.
func (s *signatures) readKeyFile(keyFile string) (ed2519key []byte, b64Key []byte, err error) { func (n *nodeAuth) readKeyFile(keyFile string) (ed2519key []byte, b64Key []byte, err error) {
fh, err := os.Open(keyFile) fh, err := os.Open(keyFile)
if err != nil { if err != nil {
er := fmt.Errorf("error: failed to open key file: %v", err) er := fmt.Errorf("error: failed to open key file: %v", err)
@ -211,9 +279,9 @@ func (s *signatures) readKeyFile(keyFile string) (ed2519key []byte, b64Key []byt
} }
// verifySignature // verifySignature
func (s *signatures) verifySignature(m Message) bool { func (n *nodeAuth) verifySignature(m Message) bool {
// fmt.Printf(" * DEBUG: verifySignature, method: %v\n", m.Method) // fmt.Printf(" * DEBUG: verifySignature, method: %v\n", m.Method)
if !s.configuration.EnableSignatureCheck { if !n.configuration.EnableSignatureCheck {
// fmt.Printf(" * DEBUG: verifySignature: AllowEmptySignature set to TRUE\n") // fmt.Printf(" * DEBUG: verifySignature: AllowEmptySignature set to TRUE\n")
return true return true
} }
@ -226,7 +294,7 @@ func (s *signatures) verifySignature(m Message) bool {
// Verify if the signature matches. // Verify if the signature matches.
argsStringified := argsToString(m.MethodArgs) argsStringified := argsToString(m.MethodArgs)
ok := ed25519.Verify(s.SignPublicKey, []byte(argsStringified), m.ArgSignature) ok := ed25519.Verify(n.SignPublicKey, []byte(argsStringified), m.ArgSignature)
// fmt.Printf(" * DEBUG: verifySignature, result: %v, fromNode: %v, method: %v\n", ok, m.FromNode, m.Method) // fmt.Printf(" * DEBUG: verifySignature, result: %v, fromNode: %v, method: %v\n", ok, m.FromNode, m.Method)

View file

@ -100,7 +100,7 @@ type process struct {
// or subscriber processes // or subscriber processes
startup *startup startup *startup
// Signatures // Signatures
signatures *signatures nodeAuth *nodeAuth
// centralAuth // centralAuth
centralAuth *centralAuth centralAuth *centralAuth
// errorKernel // errorKernel
@ -134,7 +134,7 @@ func newProcess(ctx context.Context, server *server, subject Subject, processKin
ctx: ctx, ctx: ctx,
ctxCancel: cancel, ctxCancel: cancel,
startup: newStartup(server), startup: newStartup(server),
signatures: server.signatures, nodeAuth: server.nodeAuth,
centralAuth: server.centralAuth, centralAuth: server.centralAuth,
errorKernel: server.errorKernel, errorKernel: server.errorKernel,
metrics: server.metrics, metrics: server.metrics,
@ -523,7 +523,7 @@ func (p process) messageSubscriberHandler(natsConn *nats.Conn, thisNode string,
out := []byte{} out := []byte{}
var err error var err error
if p.signatures.verifySignature(message) { if p.nodeAuth.verifySignature(message) {
// Call the method handler for the specified method. // Call the method handler for the specified method.
out, err = mh.handler(p, message, thisNode) out, err = mh.handler(p, message, thisNode)
if err != nil { if err != nil {
@ -543,7 +543,7 @@ func (p process) messageSubscriberHandler(natsConn *nats.Conn, thisNode string,
p.errorKernel.errSend(p, message, er) p.errorKernel.errSend(p, message, er)
} }
if p.signatures.verifySignature(message) { if p.nodeAuth.verifySignature(message) {
_, err := mf.handler(p, message, thisNode) _, err := mf.handler(p, message, thisNode)
@ -629,7 +629,7 @@ func (p process) publishMessages(natsConn *nats.Conn) {
func (p process) addMethodArgSignature(m Message) []byte { func (p process) addMethodArgSignature(m Message) []byte {
argsString := argsToString(m.MethodArgs) argsString := argsToString(m.MethodArgs)
sign := ed25519.Sign(p.signatures.SignPrivateKey, []byte(argsString)) sign := ed25519.Sign(p.nodeAuth.SignPrivateKey, []byte(argsString))
return sign return sign
} }

View file

@ -36,7 +36,7 @@ type processes struct {
configuration *Configuration configuration *Configuration
// Signatures // Signatures
Signatures *signatures nodeAuth *nodeAuth
} }
// newProcesses will prepare and return a *processes which // newProcesses will prepare and return a *processes which
@ -48,7 +48,7 @@ func newProcesses(ctx context.Context, server *server) *processes {
tui: server.tui, tui: server.tui,
errorKernel: server.errorKernel, errorKernel: server.errorKernel,
configuration: server.configuration, configuration: server.configuration,
Signatures: server.signatures, nodeAuth: server.nodeAuth,
metrics: server.metrics, metrics: server.metrics,
} }
@ -265,7 +265,7 @@ func (s startup) pubREQHello(p process) {
// d := fmt.Sprintf("Hello from %v\n", p.node) // d := fmt.Sprintf("Hello from %v\n", p.node)
// Send the ed25519 public key used for signing as the payload of the message. // Send the ed25519 public key used for signing as the payload of the message.
d := s.server.signatures.SignPublicKey d := s.server.nodeAuth.SignPublicKey
m := Message{ m := Message{
FileName: "hello.log", FileName: "hello.log",

View file

@ -1996,7 +1996,7 @@ func (m methodREQPublicKey) handler(proc process, message Message, node string)
// structure is the same as the other handlers. // structure is the same as the other handlers.
select { select {
case <-ctx.Done(): case <-ctx.Done():
case outCh <- proc.signatures.SignPublicKey: case outCh <- proc.nodeAuth.SignPublicKey:
} }
}() }()
@ -2117,21 +2117,21 @@ func (m methodREQPublicKeysToNode) handler(proc process, message Message, node s
// case proc.toRingbufferCh <- []subjectAndMessage{sam}: // case proc.toRingbufferCh <- []subjectAndMessage{sam}:
case <-ctx.Done(): case <-ctx.Done():
case <-outCh: case <-outCh:
keys := make(map[Node]string) // keys := make(map[Node]string)
err := json.Unmarshal(message.Data, &keys) proc.nodeAuth.publicKeys.mu.Lock()
err := json.Unmarshal(message.Data, &proc.nodeAuth.publicKeys.NodeKeys)
if err != nil { if err != nil {
er := fmt.Errorf("error: REQPublicKeysToNode : json unmarshal failed: %v, message: %v", err, message) er := fmt.Errorf("error: REQPublicKeysToNode : json unmarshal failed: %v, message: %v", err, message)
proc.errorKernel.errSend(proc, message, er) proc.errorKernel.errSend(proc, message, er)
} }
fmt.Printf(" *** RECEIVED KEYS: %v\n", proc.nodeAuth.publicKeys.NodeKeys)
proc.nodeAuth.publicKeys.mu.Unlock()
fmt.Printf(" *** RECEIVED KEYS: %v\n", keys) err = proc.nodeAuth.publicKeys.saveToFile()
if err != nil {
// TODO: er := fmt.Errorf("error: REQPublicKeysToNode : save to file failed: %v, message: %v", err, message)
// - We need to store the public keys in a map in signatures.go proc.errorKernel.errSend(proc, message, er)
// - We should also persist that public keys map to file, so we can read }
// that file on startup to get all the previosly received keys.
// - The store to map and also that map to file should happen with a method
// on signatures.publicKeys, so we do both in one go.
// Prepare and queue for sending a new message with the output // Prepare and queue for sending a new message with the output
// of the action executed. // of the action executed.

View file

@ -60,9 +60,9 @@ type server struct {
// processInitial is the initial process that all other processes are tied to. // processInitial is the initial process that all other processes are tied to.
processInitial process processInitial process
// signatures holds all the signatures, // nodeAuth holds all the signatures, the public keys and other components
// and the public keys // related to authentication on an individual node.
signatures *signatures nodeAuth *nodeAuth
// helloRegister is a register of all the nodes that have sent hello messages // helloRegister is a register of all the nodes that have sent hello messages
// to the central server // to the central server
helloRegister *helloRegister helloRegister *helloRegister
@ -145,7 +145,7 @@ func NewServer(configuration *Configuration, version string) (*server, error) {
} }
signatures := newSignatures(configuration, errorKernel) nodeAuth := newNodeAuth(configuration, errorKernel)
// fmt.Printf(" * DEBUG: newServer: signatures contains: %+v\n", signatures) // fmt.Printf(" * DEBUG: newServer: signatures contains: %+v\n", signatures)
s := server{ s := server{
@ -160,7 +160,7 @@ func NewServer(configuration *Configuration, version string) (*server, error) {
version: version, version: version,
tui: tuiClient, tui: tuiClient,
errorKernel: errorKernel, errorKernel: errorKernel,
signatures: signatures, nodeAuth: nodeAuth,
helloRegister: newHelloRegister(), helloRegister: newHelloRegister(),
centralAuth: newCentralAuth(configuration, errorKernel), centralAuth: newCentralAuth(configuration, errorKernel),
} }