// Package envfile reads a setting that may be a secret from the environment or, preferably, from // a file the environment names. // // **A secret reaches a process as a file** (novox/hq ADR 0086). An environment variable is // readable in `docker inspect`, in the process's /proc entry and in whatever composed it; a file // the mesh sealed to the machine and the host wrote at 0600 is readable where it is used and // nowhere else. So every variable of the control plane's that carries a credential has a `_FILE` // twin naming such a file, and the plain form remains only for a control plane a person starts by // hand and for the bundle that raises the first one. The store's connections had this shape // already (internal/store); this is the same rule for the rest. package envfile import ( "fmt" "os" "strings" ) // Value is the setting named by `name`: the content of the file `name_FILE` points at when that // is set, else the variable itself. Both set is refused — two sources that could disagree is how // a setting silently stops meaning what it says. Neither set is "", nil. func Value(name string) (string, error) { plain, hasPlain := os.LookupEnv(name) path, hasFile := os.LookupEnv(name + "_FILE") switch { case hasFile && hasPlain && strings.TrimSpace(plain) != "" && strings.TrimSpace(path) != "": return "", fmt.Errorf("both %s and %s_FILE are set; one of them, not both", name, name) case hasFile && strings.TrimSpace(path) != "": raw, err := os.ReadFile(strings.TrimSpace(path)) if err != nil { return "", fmt.Errorf("%s_FILE names %s, which cannot be read: %w", name, path, err) } // A file has a line ending and a value does not — trimmed, and only the ending, because a // value may begin or end with a space and still be the value. return strings.TrimRight(string(raw), "\r\n"), nil default: return strings.TrimSpace(plain), nil } }