150 lines
7.2 KiB
Go
150 lines
7.2 KiB
Go
package catalogue
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"path"
|
|
"strings"
|
|
)
|
|
|
|
// A node's files, read and managed (novox/hq ADR 0394, issue 900; superseding ADR 0310; hq to-be 41 WP9).
|
|
//
|
|
// **One seat for every file on a node.** The holder runs as the tool runner's account, the node's operator
|
|
// account (ADR 0394 §2), and reaches further through `sudo -n` where that account's rights do not. It refuses by
|
|
// name the places a secret is kept (reads only, ADR 0394 §7) and the paths a module owns, unless a write names a reason (`override`).
|
|
// node-reads stays beside it until mesh-files is assigned on every node (ADR 0394 §9); it goes in the next
|
|
// delivery.
|
|
|
|
// FilesSeat is the role that reads and manages a node's files and accounts (novox/hq ADR 0394).
|
|
const FilesSeat = "node-files"
|
|
|
|
// FilesOwnedPaths is the word the holder of node-files is given beside the secret places: a JSON object from
|
|
// each absolute path a module owns on its node to "module (tool to use)" (novox/hq ADR 0394 §4).
|
|
const FilesOwnedPaths = "MESH_OWNED_PATHS"
|
|
|
|
const (
|
|
filesAsAccount = "as the node's operator account, through sudo -n where its rights do not reach; a place a " +
|
|
"secret is kept, or a known credential file, is refused by name"
|
|
overrideWord = "a reason, to change a path a module owns anyway (optional)"
|
|
)
|
|
|
|
// filesVerbs is the contract every holder of node-files serves (novox/hq ADR 0394 §1): the reads of node-reads,
|
|
// with their descriptions restated for the operator's account, and the writes.
|
|
func filesVerbs() []Verb {
|
|
var verbs []Verb
|
|
for _, v := range readsVerbs() {
|
|
v.Description = strings.Replace(v.Description,
|
|
"Read as the holder's own account, which holds no secret: a component it may not reach says so.",
|
|
"", 1)
|
|
v.Description = strings.TrimSpace(v.Description) + " Read " + filesAsAccount + "."
|
|
verbs = append(verbs, v)
|
|
}
|
|
write := func(name, description string, in map[string]string, required []string, optional []string, replaces ...string) Verb {
|
|
in["reason"] = "why the file is changed, for the audit trail"
|
|
in["override"] = overrideWord
|
|
return Verb{Name: name, Description: description + " Refuses a path a module owns " +
|
|
"unless override names a reason; done as the node's operator account, through sudo -n where its rights do not reach.",
|
|
Input: schema(in, append(required, "reason"), append(optional, "override")...), Replaces: replaces}
|
|
}
|
|
return append(verbs,
|
|
write("write", "Write one text file whole, replacing what it holds; the old content goes to the node's trash.",
|
|
map[string]string{"path": "an absolute path", "content": "the text to write",
|
|
"mode": "the file's mode, such as 0644 (optional)"},
|
|
[]string{"path", "content"}, []string{"mode"}, "tee", ">"),
|
|
write("copy", "Copy a file or directory to a path on this node. A target on another node (<node>:/path) is "+
|
|
"refused: copying between nodes comes in step 2 (issue 903). An existing target is overwritten, the old one "+
|
|
"kept in the node's trash for 7 days.",
|
|
map[string]string{"from": "an absolute path", "to": "an absolute path on this node"},
|
|
[]string{"from", "to"}, nil, "cp", "cp -a", "ssh <node> cp"),
|
|
write("move", "Move or rename a file or directory. A target on another node is refused: copying between nodes "+
|
|
"comes in step 2 (issue 903). An existing target is overwritten, the old one kept in the node's trash for 7 days.",
|
|
map[string]string{"from": "an absolute path", "to": "an absolute path"},
|
|
[]string{"from", "to"}, nil, "mv"),
|
|
write("delete", "Delete a file or directory into the node's trash, where restore can bring it back.",
|
|
map[string]string{"path": "an absolute path", "recursive": "\"true\": delete a directory with what it holds"},
|
|
[]string{"path"}, []string{"recursive"}, "rm", "rm -r"),
|
|
write("mkdir", "Make a directory.",
|
|
map[string]string{"path": "an absolute path", "parents": "\"true\": make the missing parents too"},
|
|
[]string{"path"}, []string{"parents"}, "mkdir", "mkdir -p"),
|
|
write("chmod", "Change the mode of a file or directory.",
|
|
map[string]string{"path": "an absolute path", "mode": "the mode, such as 0644 or u+x",
|
|
"recursive": "\"true\": also everything under a directory"},
|
|
[]string{"path", "mode"}, []string{"recursive"}, "chmod"),
|
|
write("chown", "Change the owner and group of a file or directory.",
|
|
map[string]string{"path": "an absolute path", "owner": "an account's name or number",
|
|
"group": "a group's name or number (optional)", "recursive": "\"true\": also everything under a directory"},
|
|
[]string{"path", "owner"}, []string{"group", "recursive"}, "chown", "chgrp"),
|
|
Verb{Name: "trash", Description: "What the node's trash holds: each deleted or replaced item with its id, " +
|
|
"original path and when it went.",
|
|
Input: schema(map[string]string{}, nil), Replaces: []string{"ls ~/.local/share/Trash"}},
|
|
Verb{Name: "restore", Description: "Put an item of the node's trash back where it was; refuses when " +
|
|
"something is there now.",
|
|
Input: schema(map[string]string{"id": "an id from trash"}, []string{"id"}),
|
|
Replaces: []string{"trash-restore"}},
|
|
)
|
|
}
|
|
|
|
// FilesHolderProblems is what a holder of node-files is held to (novox/hq ADR 0394 §2): it runs as the tool
|
|
// runner's account, the node's operator account, so it declares no runs-as.
|
|
func FilesHolderProblems(m Manifest) []string {
|
|
if !m.ClaimsSeat(FilesSeat) || m.RunsAs == "" {
|
|
return nil
|
|
}
|
|
return []string{fmt.Sprintf("%s claims %s and declares runs-as %q: its holder runs as the tool runner's account, "+
|
|
"the node's operator account (novox/hq ADR 0394 §2)", m.Module, FilesSeat, m.RunsAs)}
|
|
}
|
|
|
|
// ownedPaths is every path a module owns on this node, as the holder is told (novox/hq ADR 0394 §4): the
|
|
// paths every seat declares (ADR 0287) with the module holding the seat here, and the paths and files each
|
|
// module's manifest places (declaredPaths). A seat's path wins where both name one.
|
|
func (r Resolution) ownedPaths() map[string]string {
|
|
owned := map[string]string{}
|
|
for _, m := range r.Modules {
|
|
for _, p := range sortedKeys(declaredPaths(m)) {
|
|
owned[p] = m.Module + " (its own tools)"
|
|
}
|
|
}
|
|
for _, s := range Seats() {
|
|
for _, sp := range s.Paths {
|
|
for _, m := range r.Modules {
|
|
if m.ClaimsSeat(s.Name) && declaresPath(m, path.Clean(sp.Path)) {
|
|
owned[sp.Path] = m.Module + " (" + s.Name + ")"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return owned
|
|
}
|
|
|
|
// filesWords is what the holder of node-files is given beside its own words: the node's secret places and the
|
|
// paths modules own here.
|
|
func (r Resolution) filesWords(m Manifest, words map[string]string, with Rendering) (map[string]string, error) {
|
|
if !m.ClaimsSeat(FilesSeat) {
|
|
return words, nil
|
|
}
|
|
places, err := r.secretPlaces(with)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
places2, err := json.Marshal(places)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
owned, err := json.Marshal(r.ownedPaths())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if words == nil {
|
|
words = map[string]string{}
|
|
}
|
|
for _, w := range []string{ReadsSecretPlaces, FilesOwnedPaths} {
|
|
if _, has := words[w]; has {
|
|
return nil, fmt.Errorf("%s gives its own %s; the node's secret places and owned paths are the mesh's to "+
|
|
"say (novox/hq ADR 0394 §4)", m.Module, w)
|
|
}
|
|
}
|
|
words[ReadsSecretPlaces] = string(places2)
|
|
words[FilesOwnedPaths] = string(owned)
|
|
return words, nil
|
|
}
|