A path beginning with the same letters is another path, and a read-only mount of a read-write access is still granted: the grant follows what mounts it.
135 lines
5.1 KiB
Go
135 lines
5.1 KiB
Go
package catalogue
|
|
|
|
import (
|
|
"fmt"
|
|
"path/filepath"
|
|
"sort"
|
|
"strings"
|
|
)
|
|
|
|
// A read-write access grants the path's own group to what runs the module's code (novox/hq ADR 0312, issue 537).
|
|
//
|
|
// **The controller says who; the node-engine reads which group.** Which group owns the operator's path is a fact
|
|
// of the machine the controller cannot know, and the path is never changed (§2): so each read-write access
|
|
// reaches a machine naming the identities its module's code runs as, and the node-engine reads the path's group
|
|
// at apply and grants it to them. Two kinds of identity run a module's code here:
|
|
//
|
|
// - each of its containers that mounts the path — the source of one of its volumes is the path, or below it —
|
|
// which gains the group as a supplementary group of its process and is recreated with it;
|
|
// - the account its tools run as, where it has a tools bundle the machine's runtime loads: the operator's
|
|
// account the runtime runs as, or the module's own account for a module served by a runtime of its own
|
|
// (ADR 0259 §8). That runtime names the access under its `restart-on`, so it is restarted when the access
|
|
// moved the account's groups, and its new process holds the group with no new login (§6).
|
|
//
|
|
// A read access grants nothing (§3): it is sent as before. A read-write access whose module runs nothing here is
|
|
// sent an empty grant, so the node-engine still states it unhealthy while its group cannot write (§2).
|
|
//
|
|
// **Only to an engine that reads it.** An older node-engine parses strictly and refuses a whole declaration for a
|
|
// field it does not know; the field and the restarts it brings are composed only where the engine's statement
|
|
// says link.GrantContract or later (Rendering.GrantsGroups).
|
|
|
|
// GrantField is an access's field naming who its path's group is granted to (novox/hq ADR 0312).
|
|
const GrantField = "grant"
|
|
|
|
// toolsAccount is the account module m's tools run as on this machine, or empty where they run nowhere as one:
|
|
// no runtime here, no bundle it loads, a module left out of an adopted machine's declaration, or a runtime
|
|
// running as root on a machine with no account.
|
|
func (r Resolution) toolsAccount(m Manifest, with Rendering) string {
|
|
if !r.runtimeHere() || (with.Adopted && m.Filtering != nil) {
|
|
return ""
|
|
}
|
|
loads := false
|
|
for _, b := range m.Bundles {
|
|
if len(b.Loads) > 0 {
|
|
loads = true
|
|
}
|
|
}
|
|
if !loads {
|
|
return ""
|
|
}
|
|
if m.RunsAs != "" {
|
|
return m.RunsAs
|
|
}
|
|
return r.Account
|
|
}
|
|
|
|
// grantAccesses writes the grant of every read-write access of module, among the resources composed so far: the
|
|
// module's containers that mount the access's path, and account when it is not empty. The ids of the accesses
|
|
// that grant to account, for the runtime that runs as it to be restarted on.
|
|
func grantAccesses(out []map[string]any, owner map[string]string, module, account string) []string {
|
|
var containers []map[string]any
|
|
for _, res := range out {
|
|
if res["type"] == "container" && owner[fmt.Sprint(res["id"])] == module {
|
|
containers = append(containers, res)
|
|
}
|
|
}
|
|
var toAccount []string
|
|
for _, res := range out {
|
|
if res["type"] != "access" || owner[fmt.Sprint(res["id"])] != module || res["mode"] != AccessReadWrite {
|
|
continue
|
|
}
|
|
path, _ := res["path"].(string)
|
|
grant := map[string]any{}
|
|
var mounting []any
|
|
for _, c := range containers {
|
|
if mounts(c, path) {
|
|
mounting = append(mounting, fmt.Sprint(c["id"]))
|
|
}
|
|
}
|
|
if len(mounting) > 0 {
|
|
grant["containers"] = mounting
|
|
}
|
|
if account != "" {
|
|
grant["account"] = account
|
|
toAccount = append(toAccount, fmt.Sprint(res["id"]))
|
|
}
|
|
res[GrantField] = grant
|
|
}
|
|
sort.Strings(toAccount)
|
|
return toAccount
|
|
}
|
|
|
|
// mounts says whether a container mounts path, or something below it, from the machine. A path that merely
|
|
// begins with the same letters — /storage/media2 beside /storage/media — is another path. A read-only mount of a
|
|
// read-write access still counts: the module declared the access read-write, the engine grants the group to what
|
|
// mounts it, and a read-only mount only means that container chose not to write through it.
|
|
func mounts(container map[string]any, path string) bool {
|
|
volumes, _ := container["volumes"].([]any)
|
|
path = filepath.Clean(path)
|
|
for _, v := range volumes {
|
|
s, ok := v.(string)
|
|
if !ok {
|
|
continue
|
|
}
|
|
source, _, _ := strings.Cut(s, ":")
|
|
if !strings.HasPrefix(source, "/") {
|
|
continue // a named volume: the runtime's, not the operator's path
|
|
}
|
|
source = filepath.Clean(source)
|
|
if source == path || strings.HasPrefix(source, path+"/") {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// restartOnGrants adds to a runtime process's restart-on the accesses that grant to the account it runs as, so it
|
|
// is restarted to hold a group the account was put in or taken out of (novox/hq ADR 0312 §6).
|
|
func restartOnGrants(process map[string]any, ids []string) {
|
|
if process == nil || len(ids) == 0 {
|
|
return
|
|
}
|
|
restarts, _ := process["restart-on"].([]any)
|
|
seen := map[string]bool{}
|
|
for _, id := range restarts {
|
|
seen[fmt.Sprint(id)] = true
|
|
}
|
|
for _, id := range ids {
|
|
if !seen[id] {
|
|
restarts = append(restarts, id)
|
|
seen[id] = true
|
|
}
|
|
}
|
|
process["restart-on"] = restarts
|
|
}
|