The grants part writes its list as an object of its own bucket, sealed to the bus node's key, and names it in user-list.sealed; the node-engine of the node holding mesh-broker is granted that one key and an object by name, the grants part its bucket (the controller's copy carries the same grants, byte for byte, golden regenerated there). A state may name its readers (announce, NamedReaders): mesh-vault.bus-accounts is read by mesh-grants alone.
150 lines
6.3 KiB
Go
150 lines
6.3 KiB
Go
package delivery
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/seal"
|
|
)
|
|
|
|
// **The grants part's user list, sealed to the bus node** (novox/hq ADR 0547 clauses 7 and 8, issue 1945): the contract
|
|
// between the grants part (mesh-delivery modules/mesh-grants), which writes it, and the node-engine of the node holding
|
|
// mesh-broker (mesh-host), which follows it once the operator's take-over says so.
|
|
//
|
|
// - The grants part composes the bus's user list (the accounts block, buscompose.ComposeAccounts, with the vault's
|
|
// hashes), seals it to the bus node's sealing key (seal.Seal, the key the node says in its have of the bus module,
|
|
// UserListWritten.Key), and puts the box as one object of its object bucket GrantsUserListObjects, named by the
|
|
// list's digest (UserListObjectName).
|
|
// - It writes the key GrantsUserListKey of its state `user-list` (bucket GrantsUserListBucket): the GrantsUserList
|
|
// below, which names the object, the list's digest, its revision, its user count and the users left out, and which
|
|
// source the bus node follows (Follow: FollowGrants from take-over, FollowController in shadow and after hand-back).
|
|
// - The node-engine of the bus node watches that key alone. While it says FollowGrants it opens the object, checks
|
|
// the digest, writes the bus's user-list file (mode 0600) only when the file's digest differs, and says what it
|
|
// loaded in its have of the bus module (UserListWritten, Source GrantsUserListSource); otherwise it follows the
|
|
// controller's compositions as before (clause 8). Never both.
|
|
|
|
// The names of the grants part's user list on the bus.
|
|
const (
|
|
// GrantsUserListBucket is the grants part's state `user-list`.
|
|
GrantsUserListBucket = "mesh-grants_user-list"
|
|
// GrantsUserListKey is the one key of it the bus node reads.
|
|
GrantsUserListKey = "sealed"
|
|
// GrantsUserListObjects is the grants part's object bucket, where each sealed list is one object.
|
|
GrantsUserListObjects = "mesh-grants-user-list"
|
|
// GrantsUserListObjectStream is the bucket's stream.
|
|
GrantsUserListObjectStream = "OBJ_" + GrantsUserListObjects
|
|
// GrantsUserListSource is the source a have names for a list taken from the grants part.
|
|
GrantsUserListSource = GrantsUserListBucket + "." + GrantsUserListKey
|
|
)
|
|
|
|
// Which list the bus node follows.
|
|
const (
|
|
FollowController = "controller"
|
|
FollowGrants = "grants"
|
|
)
|
|
|
|
// GrantsUserList is the value of the grants part's key `user-list.sealed`.
|
|
type GrantsUserList struct {
|
|
V int `json:"v"`
|
|
// Follow is which list the bus node follows: FollowGrants from take-over, FollowController otherwise.
|
|
Follow string `json:"follow"`
|
|
// Digest is the list's digest, `sha256:<hex>` of the file's bytes as the bus loads them.
|
|
Digest string `json:"digest"`
|
|
// Object is the object's name in GrantsUserListObjects (UserListObjectName of Digest).
|
|
Object string `json:"object"`
|
|
// SealedTo is the fingerprint of the key the object is sealed to (SealingKeyID), and Node the node it holds.
|
|
SealedTo string `json:"sealed_to"`
|
|
Node string `json:"node"`
|
|
// Revision counts the lists the grants part wrote: one more for each new digest.
|
|
Revision uint64 `json:"revision"`
|
|
Users int `json:"users"`
|
|
// LeftOut are the users the list leaves out for want of a hash in the vault.
|
|
LeftOut []string `json:"left_out"`
|
|
At time.Time `json:"at"`
|
|
}
|
|
|
|
// EncodeGrantsUserList is g as JSON at Version; it refuses a value the bus node could not follow.
|
|
func EncodeGrantsUserList(g GrantsUserList) ([]byte, error) {
|
|
g.V = Version
|
|
if g.LeftOut == nil {
|
|
g.LeftOut = []string{}
|
|
}
|
|
if err := g.check(); err != nil {
|
|
return nil, err
|
|
}
|
|
return json.Marshal(g)
|
|
}
|
|
|
|
// DecodeGrantsUserList reads the key's value; it refuses another version and what Encode refuses.
|
|
func DecodeGrantsUserList(data []byte) (GrantsUserList, error) {
|
|
var g GrantsUserList
|
|
if err := decode(data, &g); err != nil {
|
|
return GrantsUserList{}, err
|
|
}
|
|
if err := g.check(); err != nil {
|
|
return GrantsUserList{}, err
|
|
}
|
|
return g, nil
|
|
}
|
|
|
|
func (g GrantsUserList) check() error {
|
|
if g.Follow != FollowGrants && g.Follow != FollowController {
|
|
return fmt.Errorf("delivery: the user list's follow %q is not %s or %s", g.Follow, FollowGrants, FollowController)
|
|
}
|
|
if g.Digest == "" {
|
|
return nil // a switch with no list written yet: the bus node keeps what it follows
|
|
}
|
|
if !strings.HasPrefix(g.Digest, "sha256:") || g.Object != UserListObjectName(g.Digest) {
|
|
return fmt.Errorf("delivery: the user list names object %q for digest %q; the object is the digest's hex", g.Object, g.Digest)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// UserListDigest is a list's digest as the have and the key say it, `sha256:<hex>` of its bytes.
|
|
func UserListDigest(content []byte) string {
|
|
sum := sha256.Sum256(content)
|
|
return "sha256:" + hex.EncodeToString(sum[:])
|
|
}
|
|
|
|
// UserListObjectName is the object a list of that digest is put under: the digest's hex.
|
|
func UserListObjectName(digest string) string { return strings.TrimPrefix(digest, "sha256:") }
|
|
|
|
// SealingKeyID is a short fingerprint of a public key in PEM, `sha256:<16 hex>`, to say which key a box is sealed to
|
|
// without carrying the key.
|
|
func SealingKeyID(publicPEM string) string {
|
|
sum := sha256.Sum256([]byte(strings.TrimSpace(publicPEM)))
|
|
return "sha256:" + hex.EncodeToString(sum[:8])
|
|
}
|
|
|
|
// SealUserList is the object a list is put as: the sealed box (seal.Seal) of its bytes to the bus node's key, as JSON.
|
|
func SealUserList(list []byte, nodePEM string) ([]byte, error) {
|
|
if !strings.Contains(nodePEM, "PUBLIC KEY") {
|
|
return nil, fmt.Errorf("delivery: the bus node's key is not a public key in PEM")
|
|
}
|
|
box, err := seal.Seal(string(list), nodePEM)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("delivery: the user list could not be sealed: %w", err)
|
|
}
|
|
return json.Marshal(box)
|
|
}
|
|
|
|
// OpenUserList opens an object with open (the node's own key) and checks it is the list of digest.
|
|
func OpenUserList(object []byte, digest string, open func(seal.Box) ([]byte, error)) ([]byte, error) {
|
|
var box seal.Box
|
|
if err := json.Unmarshal(object, &box); err != nil {
|
|
return nil, fmt.Errorf("delivery: the object is no sealed box: %w", err)
|
|
}
|
|
list, err := open(box)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if got := UserListDigest(list); got != digest {
|
|
return nil, fmt.Errorf("delivery: the opened list's digest is %s, and the key names %s", got, digest)
|
|
}
|
|
return list, nil
|
|
}
|