e2e/acme_test.go

e6cd75b5f28bacf51620bb531320c30fd4e66bfd
gitbay/e2e/acme_test.go history · blame · raw

92 lines · 2980 bytes

1 symbol in this file
 1package e2e
 2
 3import (
 4	"crypto/tls"
 5	"fmt"
 6	"net"
 7	"net/http"
 8	"os"
 9	"os/exec"
10	"path/filepath"
11	"testing"
12	"time"
13)
14
15// TestACMEServe verifies the acme wiring offline: the HTTPS listener is up
16// with autocert answering handshakes, and the port-80-style helper listener
17// serves redirects. Actual issuance needs a reachable CA and a public DNS
18// name, which a test cannot have; what matters here is that the plumbing is
19// correct and failure to issue does not kill the daemon.
20func TestACMEServe(t *testing.T) {
21	t.Parallel()
22	inst := startInstanceWith(t, "") // helper for binary + keys; killed below
23	inst.proc.Process.Kill()
24	inst.proc.Wait()
25
26	ports := freePorts(t, 2)
27	httpsPort, acmeHTTPPort := ports[0], ports[1]
28	cfg := fmt.Sprintf(`
29[server]
30root = %q
31site_url = "https://gitbay.example"
32secret_key_file = %q
33[ssh]
34port = %d
35[http]
36addr = "127.0.0.1:%d"
37tls = "acme"
38acme_email = "noreply@gitbay.example"
39acme_http_addr = "127.0.0.1:%d"
40`, inst.root, inst.keyFile, inst.port, httpsPort, acmeHTTPPort)
41	if err := os.WriteFile(inst.config, []byte(cfg), 0o600); err != nil {
42		t.Fatal(err)
43	}
44	inst.proc = exec.Command(inst.gitbayd, "--config", inst.config, "serve")
45	inst.proc.Stderr = os.Stderr
46	if err := inst.proc.Start(); err != nil {
47		t.Fatal(err)
48	}
49	t.Cleanup(func() { inst.proc.Process.Kill(); inst.proc.Wait() })
50
51	waitForPort(t, httpsPort)
52	waitForPort(t, acmeHTTPPort)
53
54	// The helper listener redirects everything to the canonical HTTPS host.
55	client := &http.Client{CheckRedirect: func(*http.Request, []*http.Request) error {
56		return http.ErrUseLastResponse
57	}}
58	resp, err := client.Get(fmt.Sprintf("http://127.0.0.1:%d/alice/repo/log?x=1", acmeHTTPPort))
59	if err != nil {
60		t.Fatal(err)
61	}
62	resp.Body.Close()
63	if resp.StatusCode != http.StatusMovedPermanently ||
64		resp.Header.Get("Location") != "https://gitbay.example/alice/repo/log?x=1" {
65		t.Fatalf("redirect: %d %q", resp.StatusCode, resp.Header.Get("Location"))
66	}
67
68	// A TLS handshake reaches autocert, which tries (and fails) to issue —
69	// the handshake errors, the daemon survives, the listener stays up.
70	conn, err := tls.DialWithDialer(&net.Dialer{Timeout: 3 * time.Second}, "tcp",
71		fmt.Sprintf("127.0.0.1:%d", httpsPort),
72		&tls.Config{ServerName: "gitbay.example", InsecureSkipVerify: true})
73	if err == nil {
74		conn.Close()
75		t.Fatal("handshake unexpectedly succeeded with no CA reachable")
76	}
77	waitForPort(t, httpsPort) // still listening after the failed handshake
78
79	// Certificates cache under the server root.
80	if _, err := os.Stat(filepath.Join(inst.root, "acme")); err != nil {
81		t.Fatalf("acme cache dir: %v", err)
82	}
83
84	// A host outside the whitelist is refused before any issuance attempt.
85	conn2, err := tls.DialWithDialer(&net.Dialer{Timeout: 3 * time.Second}, "tcp",
86		fmt.Sprintf("127.0.0.1:%d", httpsPort),
87		&tls.Config{ServerName: "evil.example", InsecureSkipVerify: true})
88	if err == nil {
89		conn2.Close()
90		t.Fatal("handshake for non-whitelisted host succeeded")
91	}
92}