Skip to content
File

Blob: acme/manager_test.go

go159 lines
1package acme
2 
3import (
4 "crypto/tls"
5 "fmt"
6 "net"
7 "net/http"
8 "net/http/httptest"
9 "os"
10 "path/filepath"
11 "runtime"
12 "testing"
13 "time"
14 
15 "go.miragespace.co/specter/kv/memory"
16 "go.miragespace.co/specter/spec/chord"
17 "go.miragespace.co/specter/spec/cipher"
18 "go.miragespace.co/specter/spec/protocol"
19 "go.miragespace.co/specter/spec/tun"
20 "go.miragespace.co/specter/util/testcond"
21 
22 "github.com/stretchr/testify/mock"
23 "github.com/stretchr/testify/require"
24 "go.uber.org/zap/zaptest"
25)
26 
27var (
28 _, b, _, _ = runtime.Caller(0)
29 basepath = filepath.Dir(b)
30)
31 
32type handshakeHook struct {
33 mock.Mock
34}
35 
36func (h *handshakeHook) onHandshake(sni string) {
37 h.Called(sni)
38}
39 
40func getTCPListener(as *require.Assertions) (net.Listener, int) {
41 l, err := net.Listen("tcp", "127.0.0.1:0")
42 as.NoError(err)
43 
44 return l, l.Addr().(*net.TCPAddr).Port
45}
46 
47func validateCert(as *require.Assertions, port int, serverName string) {
48 err := testcond.WaitForCondition(func() bool {
49 conn, err := tls.Dial("tcp", fmt.Sprintf("127.0.0.1:%d", port), &tls.Config{
50 ServerName: serverName,
51 InsecureSkipVerify: true,
52 })
53 if err != nil {
54 return false
55 }
56 defer conn.Close()
57 
58 if conn.Handshake() != nil {
59 return false
60 }
61 cs := conn.ConnectionState()
62 for _, cert := range cs.PeerCertificates {
63 if cert.VerifyHostname(serverName) == nil {
64 return true
65 }
66 }
67 return false
68 }, time.Second, time.Second*60)
69 as.NoError(err)
70}
71 
72func TestIntegrationACME(t *testing.T) {
73 if os.Getenv("GO_INTEGRATION_ACME") == "" {
74 t.Skip("skipping integration tests")
75 }
76 
77 as := require.New(t)
78 logger := zaptest.NewLogger(t)
79 ctx := t.Context()
80 
81 // Start Pebble ACME server in a container
82 pebbleEnv := StartPebble(t)
83 defer pebbleEnv.Stop(ctx)
84 
85 // test hook
86 hook := new(handshakeHook)
87 defer hook.AssertExpectations(t)
88 
89 hook.On("onHandshake", testManagedDomain)
90 hook.On("onHandshake", testDynamicDomain)
91 
92 // test manager
93 kv := memory.WithHashFn(chord.Hash)
94 
95 solver := &ChordSolver{
96 KV: kv,
97 ManagedDomains: []string{testManagedDomain},
98 }
99 
100 manager, err := NewManager(ManagerConfig{
101 Logger: logger,
102 KV: kv,
103 Email: testEmail,
104 DNSSolver: solver,
105 ManagedDomains: []string{testManagedDomain},
106 CA: pebbleEnv.ACMEURL,
107 testTrustedRoots: pebbleEnv.TrustedRoots,
108 })
109 as.NoError(err)
110 
111 manager.OnHandshake(hook.onHandshake)
112 
113 listener, port := getTCPListener(as)
114 gwConf := cipher.GetGatewayTLSConfig(manager.GetCertificate, []string{tun.ALPN(protocol.Link_UNKNOWN)})
115 tlsListener := tls.NewListener(listener, gwConf)
116 defer tlsListener.Close()
117 
118 go func() {
119 for {
120 conn, err := tlsListener.Accept()
121 if err != nil {
122 return
123 }
124 go func(conn net.Conn) {
125 b := make([]byte, 1)
126 conn.Read(b)
127 }(conn)
128 }
129 }()
130 
131 // managed domain
132 err = manager.Initialize(ctx)
133 as.NoError(err)
134 
135 validateCert(as, port, testManagedDomain)
136 
137 // dynamic domain
138 err = tun.SaveCustomHostname(ctx, kv, testDynamicDomain, &protocol.CustomHostname{
139 ClientIdentity: &protocol.Node{
140 Id: chord.Random(),
141 Address: "random",
142 },
143 ClientToken: &protocol.ClientToken{
144 Token: []byte("random"),
145 },
146 })
147 as.NoError(err)
148 
149 validateCert(as, port, testDynamicDomain)
150 
151 // test clean endpoint
152 handler := AcmeManagerHandler(manager)
153 req := httptest.NewRequest("POST", "/clean", nil)
154 w := httptest.NewRecorder()
155 handler.ServeHTTP(w, req)
156 
157 as.Equal(http.StatusNoContent, w.Result().StatusCode)
158}