Bound the certificate check to 3 s a handshake and 7 s, and answer waits while printer is not given (issue 1510)
This commit is contained in:
@@ -34,6 +34,13 @@ var keyKinds = []struct {
|
||||
}},
|
||||
}
|
||||
|
||||
// certDial bounds one handshake and certTotal the whole check (two handshakes), so the node-engine's look, which has
|
||||
// ten seconds, is always answered (issue 1510).
|
||||
var (
|
||||
certDial = 3 * time.Second
|
||||
certTotal = 7 * time.Second
|
||||
)
|
||||
|
||||
// Served is a certificate the printer served to a handshake of one key type.
|
||||
type Served struct {
|
||||
Key string `json:"key"`
|
||||
@@ -45,9 +52,30 @@ type Served struct {
|
||||
// CertHealth is brother_health's answer: healthy, why when it is not (or what could not be compared), and what the
|
||||
// printer served.
|
||||
type CertHealth struct {
|
||||
Healthy bool `json:"healthy"`
|
||||
Why string `json:"why"`
|
||||
Served []Served `json:"served"`
|
||||
Healthy bool `json:"healthy"`
|
||||
State string `json:"state"` // healthy, unhealthy or waits (ADR 0283)
|
||||
Why string `json:"why"`
|
||||
// Waits names the config values the operator has yet to give, when State is waits.
|
||||
Waits []map[string]string `json:"waits,omitempty"`
|
||||
Served []Served `json:"served"`
|
||||
}
|
||||
|
||||
// CertWaits is the answer while printer or printer-certificate is not given: the module waits for the operator, which
|
||||
// is not a fault (ADR 0283), and names the setting.
|
||||
func CertWaits(s Settings) (CertHealth, bool) {
|
||||
var waits []map[string]string
|
||||
var names []string
|
||||
for _, c := range []struct{ name, value string }{{"printer", s.Printer}, {"printer-certificate", s.PrinterCertificate}} {
|
||||
if c.value == "" {
|
||||
waits = append(waits, map[string]string{"config": c.name})
|
||||
names = append(names, c.name)
|
||||
}
|
||||
}
|
||||
if len(waits) == 0 {
|
||||
return CertHealth{}, false
|
||||
}
|
||||
return CertHealth{Healthy: true, State: "waits", Waits: waits, Served: []Served{},
|
||||
Why: "waits for the operator to give the config value " + strings.Join(names, " and ")}, true
|
||||
}
|
||||
|
||||
// serveAddr is the host with the HTTPS port when it names none.
|
||||
@@ -60,7 +88,9 @@ func serveAddr(host string) string {
|
||||
|
||||
// served is the leaf's fingerprint the printer gives a handshake that offers only these suites, or why it gave none.
|
||||
func served(ctx context.Context, addr string, suites []uint16) (string, error) {
|
||||
d := tls.Dialer{NetDialer: &net.Dialer{Timeout: 5 * time.Second}, Config: &tls.Config{
|
||||
ctx, cancel := context.WithTimeout(ctx, certDial)
|
||||
defer cancel()
|
||||
d := tls.Dialer{NetDialer: &net.Dialer{Timeout: certDial}, Config: &tls.Config{
|
||||
MinVersion: tls.VersionTLS12, MaxVersion: tls.VersionTLS12, CipherSuites: suites,
|
||||
// Nothing is sent over this connection, and the certificate is only read, to be compared with the pins.
|
||||
InsecureSkipVerify: true,
|
||||
@@ -79,9 +109,11 @@ func served(ctx context.Context, addr string, suites []uint16) (string, error) {
|
||||
|
||||
// CheckCertificates compares what the printer serves with the pins. The login is never used.
|
||||
func CheckCertificates(ctx context.Context, host, pins string) CertHealth {
|
||||
ctx, cancel := context.WithTimeout(ctx, certTotal)
|
||||
defer cancel()
|
||||
want := pinList(pins)
|
||||
addr := serveAddr(host)
|
||||
h := CertHealth{Healthy: true}
|
||||
h := CertHealth{Healthy: true, State: "healthy"}
|
||||
var changed, unreachable []string
|
||||
for _, k := range keyKinds {
|
||||
s := Served{Key: k.Name}
|
||||
@@ -99,7 +131,7 @@ func CheckCertificates(ctx context.Context, host, pins string) CertHealth {
|
||||
}
|
||||
switch {
|
||||
case len(changed) > 0:
|
||||
h.Healthy = false
|
||||
h.Healthy, h.State = false, "unhealthy"
|
||||
h.Why = fmt.Sprintf("the printer serves a certificate that is not pinned: %s; the pinned printer-certificate is %s. If the printer was reset or given new certificates, set printer-certificate to the new fingerprint(s) (with the old ones kept if they still serve)",
|
||||
strings.Join(changed, "; "), strings.Join(want, ", "))
|
||||
case len(unreachable) == len(keyKinds):
|
||||
|
||||
@@ -103,7 +103,48 @@ func TestBrotherHealthIsAToolThatReadsTheSettings(t *testing.T) {
|
||||
if h := got.(CertHealth); h.Healthy {
|
||||
t.Errorf("a wrong pin is healthy: %+v", h)
|
||||
}
|
||||
if _, err := tool(t, newRig(t, `{}`, "pw").tl, "brother_health").Run(map[string]any{}); err == nil {
|
||||
t.Error("an unset printer is healthy")
|
||||
}
|
||||
|
||||
// 1510: with the printer not given the answer is waits, naming the setting (ADR 0283), not an error; and so is
|
||||
// the pin.
|
||||
func TestHealthWaitsForTheSettingsTheOperatorHasYetToGive(t *testing.T) {
|
||||
got, err := tool(t, newRig(t, `{}`, "pw").tl, "brother_health").Run(map[string]any{})
|
||||
h, ok := got.(CertHealth)
|
||||
if err != nil || !ok || !h.Healthy || h.State != "waits" || len(h.Waits) != 2 || h.Waits[0]["config"] != "printer" || h.Waits[1]["config"] != "printer-certificate" {
|
||||
t.Errorf("answer = %+v, %v", got, err)
|
||||
}
|
||||
got, _ = tool(t, newRig(t, `{"printer":"192.0.2.10"}`, "pw").tl, "brother_health").Run(map[string]any{})
|
||||
if h := got.(CertHealth); h.State != "waits" || len(h.Waits) != 1 || h.Waits[0]["config"] != "printer-certificate" {
|
||||
t.Errorf("no pin: %+v", h)
|
||||
}
|
||||
}
|
||||
|
||||
// 1510: each handshake is bounded by 3 s and the whole check by 7 s, and a printer that accepts and never answers
|
||||
// ends the check at the bound with the module healthy (nothing compared).
|
||||
func TestTheCheckIsBoundedAndAHungPrinterEndsItAtTheBound(t *testing.T) {
|
||||
if certDial != 3*time.Second || certTotal != 7*time.Second {
|
||||
t.Fatalf("bounds = %s and %s, want 3 s and 7 s", certDial, certTotal)
|
||||
}
|
||||
ln, _ := net.Listen("tcp", "127.0.0.1:0")
|
||||
defer ln.Close()
|
||||
go func() {
|
||||
for {
|
||||
c, err := ln.Accept()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer c.Close() // held open, never answered
|
||||
}
|
||||
}()
|
||||
oldD, oldT := certDial, certTotal
|
||||
certDial, certTotal = 200*time.Millisecond, 500*time.Millisecond
|
||||
t.Cleanup(func() { certDial, certTotal = oldD, oldT })
|
||||
start := time.Now()
|
||||
h := CheckCertificates(context.Background(), ln.Addr().String(), strings.Repeat("AB:", 31)+"AB")
|
||||
if took := time.Since(start); took > 700*time.Millisecond {
|
||||
t.Errorf("the check took %s", took)
|
||||
}
|
||||
if !h.Healthy || !strings.Contains(h.Why, "none was compared") {
|
||||
t.Errorf("%+v", h)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -339,10 +339,10 @@ func tools(settingsFile string, now func() time.Time) []stdio.Tool {
|
||||
}
|
||||
}()
|
||||
}
|
||||
if err := s.Settings.MissingPrinter(); err != nil {
|
||||
return nil, err
|
||||
if w, waits := CertWaits(s.Settings); waits {
|
||||
return w, nil
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 9*time.Second)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), certTotal+time.Second)
|
||||
defer cancel()
|
||||
return CheckCertificates(ctx, s.Settings.Printer, s.Settings.PrinterCertificate), nil
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user