// Command sshdir refuses any manifest but modules/ssh-client naming a path under ~/.ssh (novox/hq ADR 0415 §6, // issue 1020): ssh-client owns ~/.ssh and sweeps what it does not know, so a path another module placed there // would be moved out at the next sweep. A module that needs something there provides it to ssh-client instead. // // go run . // // Every string of a manifest is read, wherever it stands, except prose and a file's text (Prose): a path, a // place, a volume; a path inside a directory of the module's own (`${dir:}`) is not ~/.ssh. Exits 1 on a finding. package main import ( "encoding/json" "fmt" "os" "path/filepath" "regexp" "sort" "strings" ) // SSHModule is the one module allowed to name a path under ~/.ssh. const SSHModule = "ssh-client" // underSSH is a path under ~/.ssh, or ~/.ssh itself, however the home is spelled: ${machine:account-home}/.ssh, // ~/.ssh, .ssh/config, or a volume's host side (…/.ssh:/root/.ssh). var underSSH = regexp.MustCompile(`(^|[/~])\.ssh($|[/:])`) // Prose are the keys whose strings are words or a file's text, not a path: a template may say ~/.ssh in a comment. var Prose = map[string]bool{"description": true, "why": true, "does": true, "template": true, "content": true, "title": true} // Finding is one path under ~/.ssh in a manifest other than ssh-client's. type Finding struct { Module string Key string Value string } // Check reads every manifest under root/modules and answers which paths it refuses. func Check(root string) ([]Finding, error) { manifests, err := filepath.Glob(filepath.Join(root, "modules", "*", "module.json")) if err != nil { return nil, err } sort.Strings(manifests) var out []Finding for _, path := range manifests { raw, err := os.ReadFile(path) if err != nil { return nil, err } var m any if err := json.Unmarshal(raw, &m); err != nil { return nil, fmt.Errorf("%s: %w", path, err) } module := filepath.Base(filepath.Dir(path)) if obj, ok := m.(map[string]any); ok { if name, ok := obj["module"].(string); ok && name != "" { module = name } } if module == SSHModule { continue } walk(m, "", dirsOf(m), func(key, value string) { out = append(out, Finding{Module: module, Key: key, Value: value}) }) } return out, nil } // walk calls found for every string under v, outside Prose, that names a path under ~/.ssh. func walk(v any, key string, dirs map[string]string, found func(key, value string)) { switch x := v.(type) { case map[string]any: keys := make([]string, 0, len(x)) for k := range x { keys = append(keys, k) } sort.Strings(keys) for _, k := range keys { if !Prose[k] { walk(x[k], k, dirs, found) } } case []any: for _, e := range x { walk(e, key, dirs, found) } case string: // A path under a directory the mesh places for the module (${dir:} of a directory with a place and no path, // as a runs-as module's home) is that account's own .ssh, not the operator's ~/.ssh that ssh-client sweeps // (issue 1484, ADR 0466 §4). Any other ${dir:} is resolved to the path its resource declares and judged. if resolved, placed := resolveDir(x, dirs); !placed && underSSH.MatchString(resolved) { found(key, x) } } } var ofDir = regexp.MustCompile(`^\$\{dir:([a-z0-9][a-z0-9_-]*)\}`) // placedDir marks a directory the mesh places for the module: it has no path of its own. const placedDir = "\x00placed" // dirsOf are the module's directories by id: the path each declares, or placedDir for one with a place and no path. func dirsOf(m any) map[string]string { dirs := map[string]string{} obj, _ := m.(map[string]any) list, _ := obj["resources"].([]any) for _, r := range list { res, _ := r.(map[string]any) id, _ := res["id"].(string) path, _ := res["path"].(string) switch { case id == "": case path != "": dirs[id] = path case res["type"] == "directory": dirs[id] = placedDir } } return dirs } // resolveDir follows a leading ${dir:} through the module's directories. It answers the path with the references // resolved, and whether it ends under a placed directory. func resolveDir(x string, dirs map[string]string) (string, bool) { for i := 0; i < 8; i++ { m := ofDir.FindStringSubmatch(x) if m == nil { return x, false } to, ok := dirs[m[1]] if !ok { return x, false } if to == placedDir { return x, true } x = to + strings.TrimPrefix(x, m[0]) } return x, false } func main() { root := "." if len(os.Args) > 1 { root = os.Args[1] } found, err := Check(root) if err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } for _, f := range found { fmt.Printf("%s names %q (%s) under ~/.ssh: only %s may, and the sweep moves anything else out (novox/hq ADR 0415 §6)\n", f.Module, f.Value, f.Key, SSHModule) } if len(found) > 0 { os.Exit(1) } fmt.Println("~/.ssh: named only by ssh-client") }