github.com/letsencrypt/boulder@v0.20251208.0/test/integration/observer_test.go (about) 1 //go:build integration 2 3 package integration 4 5 import ( 6 "bufio" 7 "context" 8 "crypto/ecdsa" 9 "crypto/elliptic" 10 "crypto/rand" 11 "crypto/x509" 12 "encoding/pem" 13 "fmt" 14 "net/http" 15 "os" 16 "os/exec" 17 "path" 18 "path/filepath" 19 "strings" 20 "testing" 21 "time" 22 23 "github.com/eggsampler/acme/v3" 24 ) 25 26 func streamOutput(t *testing.T, c *exec.Cmd) (<-chan string, func()) { 27 t.Helper() 28 outChan := make(chan string) 29 30 stdout, err := c.StdoutPipe() 31 if err != nil { 32 t.Fatalf("getting stdout handle: %s", err) 33 } 34 35 outScanner := bufio.NewScanner(stdout) 36 go func() { 37 for outScanner.Scan() { 38 outChan <- outScanner.Text() 39 } 40 }() 41 42 stderr, err := c.StderrPipe() 43 if err != nil { 44 t.Fatalf("getting stderr handle: %s", err) 45 } 46 47 errScanner := bufio.NewScanner(stderr) 48 go func() { 49 for errScanner.Scan() { 50 outChan <- errScanner.Text() 51 } 52 }() 53 54 err = c.Start() 55 if err != nil { 56 t.Fatalf("starting cmd: %s", err) 57 } 58 59 return outChan, func() { 60 c.Cancel() 61 c.Wait() 62 } 63 } 64 65 func TestTLSProbe(t *testing.T) { 66 t.Parallel() 67 68 // We can't use random_domain(), because the observer needs to be able to 69 // resolve this hostname within the docker-compose environment. 70 hostname := "integration.trust" 71 tempdir := t.TempDir() 72 73 // Create the certificate that the prober will inspect. 74 client, err := makeClient() 75 if err != nil { 76 t.Fatalf("creating test acme client: %s", err) 77 } 78 79 key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) 80 if err != nil { 81 t.Fatalf("generating test key: %s", err) 82 } 83 84 res, err := authAndIssue(client, key, []acme.Identifier{{Type: "dns", Value: hostname}}, true, "") 85 if err != nil { 86 t.Fatalf("issuing test cert: %s", err) 87 } 88 89 // Set up the HTTP server that the prober will be pointed at. 90 certFile, err := os.Create(path.Join(tempdir, "fullchain.pem")) 91 if err != nil { 92 t.Fatalf("creating cert file: %s", err) 93 } 94 95 err = pem.Encode(certFile, &pem.Block{Type: "CERTIFICATE", Bytes: res.certs[0].Raw}) 96 if err != nil { 97 t.Fatalf("writing test cert to file: %s", err) 98 } 99 100 err = pem.Encode(certFile, &pem.Block{Type: "CERTIFICATE", Bytes: res.certs[1].Raw}) 101 if err != nil { 102 t.Fatalf("writing test issuer cert to file: %s", err) 103 } 104 105 err = certFile.Close() 106 if err != nil { 107 t.Errorf("closing cert file: %s", err) 108 } 109 110 keyFile, err := os.Create(path.Join(tempdir, "privkey.pem")) 111 if err != nil { 112 t.Fatalf("creating key file: %s", err) 113 } 114 115 keyDER, err := x509.MarshalECPrivateKey(key) 116 if err != nil { 117 t.Fatalf("marshalling test key: %s", err) 118 } 119 120 err = pem.Encode(keyFile, &pem.Block{Type: "EC PRIVATE KEY", Bytes: keyDER}) 121 if err != nil { 122 t.Fatalf("writing test key to file: %s", err) 123 } 124 125 err = keyFile.Close() 126 if err != nil { 127 t.Errorf("closing key file: %s", err) 128 } 129 130 go http.ListenAndServeTLS(":8675", certFile.Name(), keyFile.Name(), http.DefaultServeMux) 131 132 // Kick off the prober, pointed at the server presenting our test cert. 133 configFile, err := os.Create(path.Join(tempdir, "observer.yml")) 134 if err != nil { 135 t.Fatalf("creating config file: %s", err) 136 } 137 138 _, err = configFile.WriteString(fmt.Sprintf(`--- 139 buckets: [.001, .002, .005, .01, .02, .05, .1, .2, .5, 1, 2, 5, 10] 140 syslog: 141 stdoutlevel: 6 142 sysloglevel: 0 143 monitors: 144 - 145 period: 1s 146 kind: TLS 147 settings: 148 response: valid 149 hostname: "%s:8675"`, hostname)) 150 if err != nil { 151 t.Fatalf("writing test config: %s", err) 152 } 153 154 binPath, err := filepath.Abs("bin/boulder") 155 if err != nil { 156 t.Fatalf("computing boulder binary path: %s", err) 157 } 158 159 c := exec.CommandContext(context.Background(), binPath, "boulder-observer", "-config", configFile.Name(), "-debug-addr", ":8024") 160 output, cancel := streamOutput(t, c) 161 defer cancel() 162 163 timeout := time.NewTimer(5 * time.Second) 164 165 for { 166 select { 167 case <-timeout.C: 168 t.Fatalf("timed out before getting desired log line from boulder-observer") 169 case line := <-output: 170 t.Log(line) 171 if strings.Contains(line, "name=[integration.trust:8675]") && strings.Contains(line, "success=[true]") { 172 return 173 } 174 } 175 } 176 }