From 690d11194bb091baca5a075e5d8e7ac13e207e6c Mon Sep 17 00:00:00 2001 From: postmannen Date: Thu, 21 Apr 2022 13:21:36 +0200 Subject: [PATCH] persistent store of keys on node --- signatures.go => node_auth.go | 130 ++++++++++++++++++++++++++-------- process.go | 10 +-- processes.go | 6 +- requests.go | 22 +++--- server.go | 10 +-- 5 files changed, 123 insertions(+), 55 deletions(-) rename signatures.go => node_auth.go (60%) diff --git a/signatures.go b/node_auth.go similarity index 60% rename from signatures.go rename to node_auth.go index 51c11ce..c4747ad 100644 --- a/signatures.go +++ b/node_auth.go @@ -3,7 +3,9 @@ package steward import ( "crypto/ed25519" "encoding/base64" + "encoding/json" "fmt" + "io" "io/ioutil" "log" "os" @@ -17,7 +19,7 @@ type signature string // allowedSignatures is the structure for reading and writing from // the signatures map. It holds a mutex to use when interacting with // the map. -type signatures struct { +type nodeAuth struct { // All the allowed signatures a node is allowed to recive from. allowedSignatures *allowedSignatures @@ -41,26 +43,26 @@ type signatures struct { errorKernel *errorKernel } -func newSignatures(configuration *Configuration, errorKernel *errorKernel) *signatures { - s := signatures{ +func newNodeAuth(configuration *Configuration, errorKernel *errorKernel) *nodeAuth { + n := nodeAuth{ allowedSignatures: newAllowedSignatures(), - publicKeys: newPublicKeys(), + publicKeys: newPublicKeys(configuration), configuration: configuration, errorKernel: errorKernel, } // Set the signing key paths. - s.SignKeyFolder = filepath.Join(configuration.ConfigFolder, "signing") - s.SignKeyPrivateKeyPath = filepath.Join(s.SignKeyFolder, "private.key") - s.SignKeyPublicKeyPath = filepath.Join(s.SignKeyFolder, "public.key") + n.SignKeyFolder = filepath.Join(configuration.ConfigFolder, "signing") + n.SignKeyPrivateKeyPath = filepath.Join(n.SignKeyFolder, "private.key") + n.SignKeyPublicKeyPath = filepath.Join(n.SignKeyFolder, "public.key") - err := s.loadSigningKeys() + err := n.loadSigningKeys() if err != nil { log.Printf("%v\n", err) os.Exit(1) } - return &s + return &n } type allowedSignatures struct { @@ -79,24 +81,90 @@ func newAllowedSignatures() *allowedSignatures { type publicKeys struct { // nodesKey is a map who holds all the public keys for nodes. - nodeKeys map[Node][]byte + NodeKeys map[Node][]byte mu sync.Mutex + filePath string } -func newPublicKeys() *publicKeys { +func newPublicKeys(c *Configuration) *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 } +// 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 // 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. - if _, err := os.Stat(s.SignKeyFolder); os.IsNotExist(err) { - err := os.MkdirAll(s.SignKeyFolder, 0700) + if _, err := os.Stat(n.SignKeyFolder); os.IsNotExist(err) { + err := os.MkdirAll(n.SignKeyFolder, 0700) if err != nil { er := fmt.Errorf("error: failed to create directory for signing keys : %v", err) return er @@ -107,10 +175,10 @@ func (s *signatures) loadSigningKeys() error { // Check if there already are any keys in the etc folder. foundKey := false - if _, err := os.Stat(s.SignKeyPublicKeyPath); !os.IsNotExist(err) { + if _, err := os.Stat(n.SignKeyPublicKeyPath); !os.IsNotExist(err) { foundKey = true } - if _, err := os.Stat(s.SignKeyPrivateKeyPath); !os.IsNotExist(err) { + if _, err := os.Stat(n.SignKeyPrivateKeyPath); !os.IsNotExist(err) { foundKey = true } @@ -126,21 +194,21 @@ func (s *signatures) loadSigningKeys() error { privB64string := base64.RawStdEncoding.EncodeToString(priv) // Write public key to file. - err = s.writeSigningKey(s.SignKeyPublicKeyPath, pubB64string) + err = n.writeSigningKey(n.SignKeyPublicKeyPath, pubB64string) if err != nil { return err } // Write private key to file. - err = s.writeSigningKey(s.SignKeyPrivateKeyPath, privB64string) + err = n.writeSigningKey(n.SignKeyPrivateKeyPath, privB64string) if err != nil { return err } // Also store the keys in the processes structure so we can // reference them from there when we need them. - s.SignPublicKey = pub - s.SignPrivateKey = priv + n.SignPublicKey = pub + n.SignPrivateKey = priv er := fmt.Errorf("info: no signing keys found, generating new keys") log.Printf("%v\n", er) @@ -150,24 +218,24 @@ func (s *signatures) loadSigningKeys() error { } // 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 { return err } - s.SignPublicKey = pubKey + n.SignPublicKey = pubKey - privKey, _, err := s.readKeyFile(s.SignKeyPrivateKeyPath) + privKey, _, err := n.readKeyFile(n.SignKeyPrivateKeyPath) if err != nil { return err } - s.SignPublicKey = pubKey - s.SignPrivateKey = privKey + n.SignPublicKey = pubKey + n.SignPrivateKey = privKey return nil } // 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) if err != nil { 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 // encoded data, decode the data. It will return the raw data as []byte, // 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) if err != nil { 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 -func (s *signatures) verifySignature(m Message) bool { +func (n *nodeAuth) verifySignature(m Message) bool { // 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") return true } @@ -226,7 +294,7 @@ func (s *signatures) verifySignature(m Message) bool { // Verify if the signature matches. 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) diff --git a/process.go b/process.go index 8635bc9..c11050d 100644 --- a/process.go +++ b/process.go @@ -100,7 +100,7 @@ type process struct { // or subscriber processes startup *startup // Signatures - signatures *signatures + nodeAuth *nodeAuth // centralAuth centralAuth *centralAuth // errorKernel @@ -134,7 +134,7 @@ func newProcess(ctx context.Context, server *server, subject Subject, processKin ctx: ctx, ctxCancel: cancel, startup: newStartup(server), - signatures: server.signatures, + nodeAuth: server.nodeAuth, centralAuth: server.centralAuth, errorKernel: server.errorKernel, metrics: server.metrics, @@ -523,7 +523,7 @@ func (p process) messageSubscriberHandler(natsConn *nats.Conn, thisNode string, out := []byte{} var err error - if p.signatures.verifySignature(message) { + if p.nodeAuth.verifySignature(message) { // Call the method handler for the specified method. out, err = mh.handler(p, message, thisNode) if err != nil { @@ -543,7 +543,7 @@ func (p process) messageSubscriberHandler(natsConn *nats.Conn, thisNode string, p.errorKernel.errSend(p, message, er) } - if p.signatures.verifySignature(message) { + if p.nodeAuth.verifySignature(message) { _, err := mf.handler(p, message, thisNode) @@ -629,7 +629,7 @@ func (p process) publishMessages(natsConn *nats.Conn) { func (p process) addMethodArgSignature(m Message) []byte { argsString := argsToString(m.MethodArgs) - sign := ed25519.Sign(p.signatures.SignPrivateKey, []byte(argsString)) + sign := ed25519.Sign(p.nodeAuth.SignPrivateKey, []byte(argsString)) return sign } diff --git a/processes.go b/processes.go index 6d5ee87..4c18aca 100644 --- a/processes.go +++ b/processes.go @@ -36,7 +36,7 @@ type processes struct { configuration *Configuration // Signatures - Signatures *signatures + nodeAuth *nodeAuth } // newProcesses will prepare and return a *processes which @@ -48,7 +48,7 @@ func newProcesses(ctx context.Context, server *server) *processes { tui: server.tui, errorKernel: server.errorKernel, configuration: server.configuration, - Signatures: server.signatures, + nodeAuth: server.nodeAuth, metrics: server.metrics, } @@ -265,7 +265,7 @@ func (s startup) pubREQHello(p process) { // d := fmt.Sprintf("Hello from %v\n", p.node) // 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{ FileName: "hello.log", diff --git a/requests.go b/requests.go index 9928b35..b729ab7 100644 --- a/requests.go +++ b/requests.go @@ -1996,7 +1996,7 @@ func (m methodREQPublicKey) handler(proc process, message Message, node string) // structure is the same as the other handlers. select { 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 <-ctx.Done(): case <-outCh: - keys := make(map[Node]string) - err := json.Unmarshal(message.Data, &keys) + // keys := make(map[Node]string) + proc.nodeAuth.publicKeys.mu.Lock() + err := json.Unmarshal(message.Data, &proc.nodeAuth.publicKeys.NodeKeys) if err != nil { er := fmt.Errorf("error: REQPublicKeysToNode : json unmarshal failed: %v, message: %v", err, message) 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) - - // TODO: - // - We need to store the public keys in a map in signatures.go - // - 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. + err = proc.nodeAuth.publicKeys.saveToFile() + if err != nil { + er := fmt.Errorf("error: REQPublicKeysToNode : save to file failed: %v, message: %v", err, message) + proc.errorKernel.errSend(proc, message, er) + } // Prepare and queue for sending a new message with the output // of the action executed. diff --git a/server.go b/server.go index 1e75c9a..ccafab6 100644 --- a/server.go +++ b/server.go @@ -60,9 +60,9 @@ type server struct { // processInitial is the initial process that all other processes are tied to. processInitial process - // signatures holds all the signatures, - // and the public keys - signatures *signatures + // nodeAuth holds all the signatures, the public keys and other components + // related to authentication on an individual node. + nodeAuth *nodeAuth // helloRegister is a register of all the nodes that have sent hello messages // to the central server 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) s := server{ @@ -160,7 +160,7 @@ func NewServer(configuration *Configuration, version string) (*server, error) { version: version, tui: tuiClient, errorKernel: errorKernel, - signatures: signatures, + nodeAuth: nodeAuth, helloRegister: newHelloRegister(), centralAuth: newCentralAuth(configuration, errorKernel), }