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 }