package main import ( "crypto/tls" "crypto/x509" "os" "path/filepath" "strings" "testing" ) // novox/hq 04-ISSUES/146. The bootstrap could not make the bus a certificate: it asked an image for // `openssl` and the image it asks has none. What replaces it is this command, so what is checked is // what the bootstrap needs from it — a pair a TLS server can actually load, made once and only once. func TestTheBusCertificateLoadsAsAServersWould(t *testing.T) { into := t.TempDir() if err := busCertificate([]string{"--into", into}); err != nil { t.Fatalf("the bus could not be given a certificate: %v", err) } // The check a cheaper test would not make. The key was present and valid and the server could // not start, once, because nothing loaded the pair the way a server loads it // (novox/hq 04-ISSUES/014). pair, err := tls.LoadX509KeyPair(filepath.Join(into, "tls.crt"), filepath.Join(into, "tls.key")) if err != nil { t.Fatalf("a TLS server cannot load what was written: %v", err) } leaf := pair.Leaf if leaf == nil { if leaf, err = x509.ParseCertificate(pair.Certificate[0]); err != nil { t.Fatal(err) } } if err := leaf.VerifyHostname("mesh-broker"); err != nil { t.Errorf("the certificate is not for the name the bus is reached by: %v", err) } if len(leaf.IPAddresses) == 0 || leaf.IPAddresses[0].String() != "127.0.0.1" { t.Errorf("the certificate does not cover the loopback address the foundation dials: %v", leaf.IPAddresses) } // The key is not readable by anything else on the machine; the certificate is public and is. key, err := os.Stat(filepath.Join(into, "tls.key")) if err != nil { t.Fatal(err) } if key.Mode().Perm() != 0o600 { t.Errorf("the key is %v", key.Mode().Perm()) } crt, err := os.Stat(filepath.Join(into, "tls.crt")) if err != nil { t.Fatal(err) } if crt.Mode().Perm() != 0o644 { t.Errorf("the certificate is %v, which the server runs as another user cannot read", crt.Mode().Perm()) } } // **Made once.** The step is applied again on every reconcile, and a second certificate is one the // hosts that pinned the first no longer believe (novox/hq ADR 0004). func TestTheBusCertificateIsMadeOnce(t *testing.T) { into := t.TempDir() if err := busCertificate([]string{"--into", into}); err != nil { t.Fatal(err) } first, err := os.ReadFile(filepath.Join(into, "tls.crt")) if err != nil { t.Fatal(err) } if err := busCertificate([]string{"--into", into}); err != nil { t.Fatal(err) } again, err := os.ReadFile(filepath.Join(into, "tls.crt")) if err != nil { t.Fatal(err) } if string(first) != string(again) { t.Fatal("running it twice replaced the certificate every host had pinned") } } // The verify half: false before, true after, which is what makes the bootstrap run the step at all. func TestTheCheckIsFalseUntilThereIsAPair(t *testing.T) { into := t.TempDir() if err := busCertificate([]string{"--check", "--into", into}); err == nil { t.Fatal("an empty directory reported a usable certificate") } if err := busCertificate([]string{"--into", into}); err != nil { t.Fatal(err) } if err := busCertificate([]string{"--check", "--into", into}); err != nil { t.Fatalf("the certificate it just made does not satisfy its own check: %v", err) } } // A half-written pair is not a pair. An interrupted bootstrap leaves exactly this, and a step that // called it done would hand the server a certificate with no key and report success. func TestACertificateWithoutItsKeyIsNotUsable(t *testing.T) { into := t.TempDir() if err := busCertificate([]string{"--into", into}); err != nil { t.Fatal(err) } if err := os.Remove(filepath.Join(into, "tls.key")); err != nil { t.Fatal(err) } if err := busCertificate([]string{"--check", "--into", into}); err == nil { t.Fatal("a certificate with no key passed the check") } if err := busCertificate([]string{"--into", into}); err != nil { t.Fatal(err) } if _, err := tls.LoadX509KeyPair(filepath.Join(into, "tls.crt"), filepath.Join(into, "tls.key")); err != nil { t.Fatalf("it did not replace the unusable pair: %v", err) } } func TestWhereToWriteIsRequired(t *testing.T) { if err := busCertificate(nil); err == nil || !strings.Contains(err.Error(), "--into") { t.Fatalf("it did not ask where to write: %v", err) } }