79 lines
2.5 KiB
Go
79 lines
2.5 KiB
Go
package stream
|
||
|
||
import (
|
||
"crypto/ed25519"
|
||
"encoding/base64"
|
||
"encoding/json"
|
||
"errors"
|
||
"fmt"
|
||
)
|
||
|
||
var (
|
||
// ErrUnsigned: Das Event trägt keine Signatur.
|
||
ErrUnsigned = errors.New("stream: Event nicht signiert")
|
||
// ErrBadSignature: Die Signatur passt nicht (fremder Schlüssel oder verändertes Event).
|
||
ErrBadSignature = errors.New("stream: Event-Signatur ungültig")
|
||
)
|
||
|
||
const eventSignatureVersion = "SULTAN-EVENT-V1\n"
|
||
|
||
func eventMessage(ev Event) []byte {
|
||
ev.Signature = ""
|
||
b, _ := json.Marshal(ev)
|
||
return append([]byte(eventSignatureVersion), b...)
|
||
}
|
||
|
||
// SignEvent signiert ev (alle Felder außer der Signatur) mit dem Master-Key.
|
||
func SignEvent(ev Event, key ed25519.PrivateKey) Event {
|
||
ev.Signature = base64.StdEncoding.EncodeToString(ed25519.Sign(key, eventMessage(ev)))
|
||
return ev
|
||
}
|
||
|
||
// VerifyEvent prüft die Signatur mit dem Public Key des Master-Keys.
|
||
func VerifyEvent(ev Event, pub ed25519.PublicKey) error {
|
||
if ev.Signature == "" {
|
||
return ErrUnsigned
|
||
}
|
||
sig, err := base64.StdEncoding.DecodeString(ev.Signature)
|
||
if err != nil || len(sig) != ed25519.SignatureSize || len(pub) != ed25519.PublicKeySize {
|
||
return ErrBadSignature
|
||
}
|
||
if !ed25519.Verify(pub, eventMessage(ev), sig) {
|
||
return ErrBadSignature
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// Befehle in node_command – eine feste Liste, keine beliebigen Kommandos.
|
||
const (
|
||
CmdResync = "resync" // Agent sendet sofort seinen Zustand
|
||
CmdRotateKey = "rotate_key" // Agent erzeugt einen neuen node.key
|
||
CmdDrain = "drain" // Hinweis: Routen entfernt
|
||
CmdMaintenance = "maintenance" // Hinweis: Wartung angemeldet
|
||
CmdResume = "resume" // Hinweis: Wartung/drain beendet
|
||
CmdConfig = "config" // Server-Adressen (ServerURLs) für den Ausweichweg
|
||
)
|
||
|
||
// Commands sind alle bekannten Befehle.
|
||
var Commands = []string{CmdResync, CmdRotateKey, CmdDrain, CmdMaintenance, CmdResume, CmdConfig}
|
||
|
||
// NodeCommand ist die Payload eines node_command-Events.
|
||
type NodeCommand struct {
|
||
Command string `json:"command"`
|
||
Reason string `json:"reason,omitempty"`
|
||
// ServerURLs: bei config die Adressen, unter denen der Server erreichbar ist.
|
||
ServerURLs []string `json:"server_urls,omitempty"`
|
||
}
|
||
|
||
// ParseNodeCommand liest die Payload eines node_command-Events.
|
||
func ParseNodeCommand(ev Event) (NodeCommand, error) {
|
||
if ev.Type != EventNodeCommand {
|
||
return NodeCommand{}, fmt.Errorf("%w: kein node_command", ErrInvalidEvent)
|
||
}
|
||
var c NodeCommand
|
||
if err := json.Unmarshal(ev.Payload, &c); err != nil || c.Command == "" {
|
||
return NodeCommand{}, fmt.Errorf("%w: Befehl fehlt", ErrInvalidEvent)
|
||
}
|
||
return c, nil
|
||
}
|