diff --git a/go/health/health.go b/go/health/health.go index 29a9fe6..b9ab26f 100644 --- a/go/health/health.go +++ b/go/health/health.go @@ -70,14 +70,31 @@ func Waiting(why string, waits ...Wait) Answer { if len(waits) == 0 { return Unhealthy(orSaid(why, "it says it waits") + " (and names nothing it waits for)") } + for _, w := range waits { + if err := w.Check(); err != nil { + return Unhealthy(orSaid(why, "it says it waits") + " (" + err.Error() + ")") + } + } return Answer{State: StateWaits, Why: why, Waits: waits} } // name is a secret's or a setting's name: what a manifest may declare. var name = regexp.MustCompile(`^[a-z0-9][a-z0-9-]*$`) -// looksLikeAddress finds an IPv4 address, a URL, or a path in words meant for the operator. -var looksLikeAddress = regexp.MustCompile(`\b[0-9]{1,3}(\.[0-9]{1,3}){3}\b|://|(^|\s)/[^\s]`) +// looksLikeAddress finds what the operator's words never carry: a URL, an IPv4 or IPv6 address, a host and port, a +// path (absolute, from a home or relative, on its own or after a bracket or a quote), a UNC path or a drive path. +// It cannot find a value typed in plain words ("the password hunter2"): a module never puts one there, and the +// words are its own, not the operator's. +var looksLikeAddress = regexp.MustCompile(strings.Join([]string{ + `://`, + `\b[0-9]{1,3}(\.[0-9]{1,3}){3}\b`, + `[0-9A-Fa-f]{0,4}::[0-9A-Fa-f]{0,4}`, + `\b[0-9A-Fa-f]{1,4}(:[0-9A-Fa-f]{1,4}){3,}\b`, + `\b[A-Za-z0-9.-]+:[0-9]{1,5}\b`, + `(^|[\s(\[{"'\x60])[~.]{0,2}/`, + `\\\\`, + `\b[A-Za-z]:\\`, +}, "|")) // Check says why a wait cannot be read, or nil. func (w Wait) Check() error { @@ -132,8 +149,12 @@ func Read(result []byte) (Answer, error) { a.State = StateUnhealthy } case StateHealthy: + if !a.Healthy { + return Unhealthy(fmt.Sprintf("it answered the state healthy and healthy false, which contradict each other (%s)", + orSaid(a.Why, "and said nothing more"))), nil + } // A healthy module waits for nothing: a wait beside healthy is a contradiction, and is dropped. - a.Healthy, a.Waits = true, nil + a.Waits = nil case StateUnhealthy: a.Healthy = false case StateWaits: diff --git a/go/health/health_test.go b/go/health/health_test.go index 8bcc0ef..b6a3531 100644 --- a/go/health/health_test.go +++ b/go/health/health_test.go @@ -116,3 +116,35 @@ func TestNotAnAnswerIsAnError(t *testing.T) { t.Fatal("a string read as an answer") } } + +func TestEveryFormOfPathOrAddressIsRefused(t *testing.T) { + for _, words := range []string{ + "the source (/mnt/games)", `the source "/mnt/games"`, "the folder ~/games", "the folder ./games", + "2001:db8::1", "fe80::1%eth0", "2001:0db8:0000:0000:0000:0000:0000:0001", "nas.lan:445", "192.0.2.7:445", + `\\nas\games`, `C:\Users\x`, "smb://nas/games", "198.51.100.4", + } { + w := Wait{Part: "the source games", Secret: "smb-password-games", What: "the password of " + words} + if err := w.Check(); err == nil { + t.Errorf("%q passed as words for the operator", words) + } + } + for _, words := range []string{"the password of the source games", "the username of the source games-2", "the API key, issued by the vendor"} { + w := Wait{Part: "the source games", Secret: "smb-password-games", What: words} + if err := w.Check(); err != nil { + t.Errorf("%q refused: %v", words, err) + } + } +} + +func TestHealthyStateWithHealthyFalseIsUnhealthy(t *testing.T) { + got, err := Read([]byte(`{"state":"healthy","healthy":false,"why":"x"}`)) + if err != nil || got.State != StateUnhealthy || !strings.Contains(got.Why, "contradict") { + t.Fatalf("read as %+v, %v", got, err) + } +} + +func TestWaitingChecksItsWaits(t *testing.T) { + if a := Waiting("it waits", Wait{Part: "p", What: "w"}); a.State != StateUnhealthy || !strings.Contains(a.Why, "neither") { + t.Fatalf("Waiting with a malformed wait is %+v", a) + } +}