Skip to content
File

Blob: tun/server/acme_rpc_test.go

go390 lines
1package server
2 
3import (
4 "bytes"
5 "crypto/ed25519"
6 "fmt"
7 "testing"
8 
9 "go.miragespace.co/specter/spec/acme"
10 mocks "go.miragespace.co/specter/spec/mocks"
11 "go.miragespace.co/specter/spec/pow"
12 "go.miragespace.co/specter/spec/protocol"
13 "go.miragespace.co/specter/spec/rpc"
14 "go.miragespace.co/specter/spec/transport"
15 "go.miragespace.co/specter/spec/tun"
16 
17 "github.com/stretchr/testify/mock"
18 "github.com/stretchr/testify/require"
19)
20 
21func TestAcmeInstruction(t *testing.T) {
22 as := require.New(t)
23 
24 logger, node, _, _, serv := getFixture(t, as)
25 cli, _, _ := getIdentities()
26 
27 ctx := t.Context()
28 
29 hostname := "external.example.com"
30 token := &protocol.ClientToken{
31 Token: mustGenerateToken(),
32 }
33 
34 clientBuf, err := cli.MarshalVT()
35 as.NoError(err)
36 
37 name, content := acme.GenerateCustomRecord(hostname, testAcmeZone, token.GetToken())
38 
39 node.On("Get",
40 mock.Anything,
41 mock.MatchedBy(func(key []byte) bool {
42 return bytes.Equal(key, []byte(tun.CustomHostnameKey(hostname)))
43 }),
44 ).Return(nil, tun.ErrHostnameNotFound).Once()
45 node.On("Get",
46 mock.Anything,
47 mock.MatchedBy(func(key []byte) bool {
48 return bytes.Equal(key, []byte(tun.ClientTokenKey(token)))
49 }),
50 ).Return(clientBuf, nil).Once()
51 
52 tp := mocks.SelfTransport()
53 streamRouter := transport.NewStreamRouter(logger, nil, tp)
54 go streamRouter.Accept(ctx)
55 
56 serv.AttachRouter(ctx, streamRouter)
57 
58 cRPC := rpc.DynamicTunnelClient(rpc.DisablePooling(ctx), tp)
59 
60 privKey := make(ed25519.PrivateKey, ed25519.PrivateKeySize)
61 tp.WithCertificate(toCertificate(as, logger, cli, token, withExtractPrivKey(privKey)))
62 
63 proof, err := pow.GenerateSolution(privKey, pow.Parameters{
64 Difficulty: acme.HashcashDifficulty,
65 Expires: acme.HashcashExpires,
66 GetSubject: func(pubKey ed25519.PublicKey) string {
67 return hostname
68 },
69 })
70 as.NoError(err)
71 
72 resp, err := cRPC.AcmeInstruction(rpc.WithNode(ctx, cli), &protocol.InstructionRequest{
73 Proof: proof,
74 Hostname: hostname,
75 })
76 
77 as.NoError(err)
78 as.NotNil(resp)
79 
80 as.Equal(name, resp.GetName())
81 as.Equal(content, resp.GetContent())
82 
83 node.AssertExpectations(t)
84}
85 
86func TestAcmeValidationSuccess(t *testing.T) {
87 as := require.New(t)
88 
89 resolver := new(mocks.Resolver)
90 
91 logger, node, _, _, serv := getFixture(t, as, withResolver(resolver))
92 cli, _, _ := getIdentities()
93 
94 ctx := t.Context()
95 
96 hostname := "external.example.com"
97 token := &protocol.ClientToken{
98 Token: mustGenerateToken(),
99 }
100 
101 clientBuf, err := cli.MarshalVT()
102 as.NoError(err)
103 
104 name, content := acme.GenerateCustomRecord(hostname, testAcmeZone, token.GetToken())
105 
106 node.On("Get",
107 mock.Anything,
108 mock.MatchedBy(func(key []byte) bool {
109 return bytes.Equal(key, []byte(tun.CustomHostnameKey(hostname)))
110 }),
111 ).Return(nil, tun.ErrHostnameNotFound).Once()
112 node.On("Get",
113 mock.Anything,
114 mock.MatchedBy(func(key []byte) bool {
115 return bytes.Equal(key, []byte(tun.ClientTokenKey(token)))
116 }),
117 ).Return(clientBuf, nil).Once()
118 node.On("Put",
119 mock.Anything,
120 mock.MatchedBy(func(key []byte) bool {
121 return bytes.Equal(key, []byte(tun.CustomHostnameKey(hostname)))
122 }),
123 mock.Anything,
124 ).Return(nil).Once()
125 node.On("PrefixAppend",
126 mock.Anything,
127 mock.MatchedBy(func(key []byte) bool {
128 return bytes.Equal(key, []byte(tun.ClientHostnamesPrefix(token)))
129 }),
130 mock.MatchedBy(func(key []byte) bool {
131 return bytes.Equal(key, []byte(hostname))
132 }),
133 ).Return(nil).Once()
134 
135 resolver.On("LookupCNAME", mock.Anything, name).Return(content, nil)
136 
137 tp := mocks.SelfTransport()
138 streamRouter := transport.NewStreamRouter(logger, nil, tp)
139 go streamRouter.Accept(ctx)
140 
141 serv.AttachRouter(ctx, streamRouter)
142 
143 cRPC := rpc.DynamicTunnelClient(rpc.DisablePooling(ctx), tp)
144 
145 privKey := make(ed25519.PrivateKey, ed25519.PrivateKeySize)
146 tp.WithCertificate(toCertificate(as, logger, cli, token, withExtractPrivKey(privKey)))
147 
148 proof, err := pow.GenerateSolution(privKey, pow.Parameters{
149 Difficulty: acme.HashcashDifficulty,
150 Expires: acme.HashcashExpires,
151 GetSubject: func(pubKey ed25519.PublicKey) string {
152 return hostname
153 },
154 })
155 as.NoError(err)
156 
157 resp, err := cRPC.AcmeValidate(rpc.WithNode(ctx, cli), &protocol.ValidateRequest{
158 Proof: proof,
159 Hostname: hostname,
160 })
161 
162 as.NoError(err)
163 as.NotNil(resp)
164 as.Equal(testRootDomain, resp.GetApex())
165 
166 node.AssertExpectations(t)
167 resolver.AssertExpectations(t)
168}
169 
170func TestAcmeValidationAlready(t *testing.T) {
171 as := require.New(t)
172 
173 resolver := new(mocks.Resolver)
174 
175 logger, node, _, _, serv := getFixture(t, as, withResolver(resolver))
176 cli, _, _ := getIdentities()
177 
178 ctx := t.Context()
179 
180 hostname := "external.example.com"
181 token := &protocol.ClientToken{
182 Token: mustGenerateToken(),
183 }
184 cli.Address = string(token.GetToken())
185 
186 clientBuf, err := cli.MarshalVT()
187 as.NoError(err)
188 
189 bundle := &protocol.CustomHostname{
190 ClientIdentity: cli,
191 ClientToken: token,
192 }
193 
194 bundleBuf, err := bundle.MarshalVT()
195 as.NoError(err)
196 
197 node.On("Get",
198 mock.Anything,
199 mock.MatchedBy(func(key []byte) bool {
200 return bytes.Equal(key, []byte(tun.CustomHostnameKey(hostname)))
201 }),
202 ).Return(bundleBuf, nil).Once()
203 node.On("Get",
204 mock.Anything,
205 mock.MatchedBy(func(key []byte) bool {
206 return bytes.Equal(key, []byte(tun.ClientTokenKey(token)))
207 }),
208 ).Return(clientBuf, nil).Once()
209 node.On("Put",
210 mock.Anything,
211 mock.MatchedBy(func(key []byte) bool {
212 return bytes.Equal(key, []byte(tun.CustomHostnameKey(hostname)))
213 }),
214 mock.Anything,
215 ).Return(nil).Once()
216 node.On("PrefixAppend",
217 mock.Anything,
218 mock.MatchedBy(func(key []byte) bool {
219 return bytes.Equal(key, []byte(tun.ClientHostnamesPrefix(token)))
220 }),
221 mock.MatchedBy(func(key []byte) bool {
222 return bytes.Equal(key, []byte(hostname))
223 }),
224 ).Return(nil).Once()
225 
226 tp := mocks.SelfTransport()
227 streamRouter := transport.NewStreamRouter(logger, nil, tp)
228 go streamRouter.Accept(ctx)
229 
230 serv.AttachRouter(ctx, streamRouter)
231 
232 cRPC := rpc.DynamicTunnelClient(rpc.DisablePooling(ctx), tp)
233 
234 privKey := make(ed25519.PrivateKey, ed25519.PrivateKeySize)
235 tp.WithCertificate(toCertificate(as, logger, cli, token, withExtractPrivKey(privKey)))
236 
237 proof, err := pow.GenerateSolution(privKey, pow.Parameters{
238 Difficulty: acme.HashcashDifficulty,
239 Expires: acme.HashcashExpires,
240 GetSubject: func(pubKey ed25519.PublicKey) string {
241 return hostname
242 },
243 })
244 as.NoError(err)
245 
246 resp, err := cRPC.AcmeValidate(rpc.WithNode(ctx, cli), &protocol.ValidateRequest{
247 Proof: proof,
248 Hostname: hostname,
249 })
250 
251 as.NoError(err)
252 as.NotNil(resp)
253 as.Equal(testRootDomain, resp.GetApex())
254 
255 node.AssertExpectations(t)
256 resolver.AssertExpectations(t)
257}
258 
259func TestAcmeValidationIncorrect(t *testing.T) {
260 as := require.New(t)
261 
262 resolver := new(mocks.Resolver)
263 
264 logger, node, _, _, serv := getFixture(t, as, withResolver(resolver))
265 cli, _, _ := getIdentities()
266 
267 ctx := t.Context()
268 
269 hostname := "external.example.com"
270 token := &protocol.ClientToken{
271 Token: mustGenerateToken(),
272 }
273 
274 clientBuf, err := cli.MarshalVT()
275 as.NoError(err)
276 
277 name, _ := acme.GenerateCustomRecord(hostname, testAcmeZone, token.GetToken())
278 
279 node.On("Get",
280 mock.Anything,
281 mock.MatchedBy(func(key []byte) bool {
282 return bytes.Equal(key, []byte(tun.CustomHostnameKey(hostname)))
283 }),
284 ).Return(nil, tun.ErrHostnameNotFound).Once()
285 node.On("Get",
286 mock.Anything,
287 mock.MatchedBy(func(key []byte) bool {
288 return bytes.Equal(key, []byte(tun.ClientTokenKey(token)))
289 }),
290 ).Return(clientBuf, nil).Once()
291 
292 resolver.On("LookupCNAME", mock.Anything, name).Return("random string", nil)
293 
294 tp := mocks.SelfTransport()
295 streamRouter := transport.NewStreamRouter(logger, nil, tp)
296 go streamRouter.Accept(ctx)
297 
298 serv.AttachRouter(ctx, streamRouter)
299 
300 cRPC := rpc.DynamicTunnelClient(rpc.DisablePooling(ctx), tp)
301 
302 privKey := make(ed25519.PrivateKey, ed25519.PrivateKeySize)
303 tp.WithCertificate(toCertificate(as, logger, cli, token, withExtractPrivKey(privKey)))
304 
305 proof, err := pow.GenerateSolution(privKey, pow.Parameters{
306 Difficulty: acme.HashcashDifficulty,
307 Expires: acme.HashcashExpires,
308 GetSubject: func(pubKey ed25519.PublicKey) string {
309 return hostname
310 },
311 })
312 as.NoError(err)
313 
314 _, err = cRPC.AcmeValidate(rpc.WithNode(ctx, cli), &protocol.ValidateRequest{
315 Proof: proof,
316 Hostname: hostname,
317 })
318 
319 as.Error(err)
320 
321 node.AssertExpectations(t)
322 resolver.AssertExpectations(t)
323}
324 
325func TestAcmeValidationError(t *testing.T) {
326 as := require.New(t)
327 
328 resolver := new(mocks.Resolver)
329 
330 logger, node, _, _, serv := getFixture(t, as, withResolver(resolver))
331 cli, _, _ := getIdentities()
332 
333 ctx := t.Context()
334 
335 hostname := "external.example.com"
336 token := &protocol.ClientToken{
337 Token: mustGenerateToken(),
338 }
339 
340 clientBuf, err := cli.MarshalVT()
341 as.NoError(err)
342 
343 name, _ := acme.GenerateCustomRecord(hostname, testAcmeZone, token.GetToken())
344 
345 node.On("Get",
346 mock.Anything,
347 mock.MatchedBy(func(key []byte) bool {
348 return bytes.Equal(key, []byte(tun.CustomHostnameKey(hostname)))
349 }),
350 ).Return(nil, tun.ErrHostnameNotFound).Once()
351 node.On("Get",
352 mock.Anything,
353 mock.MatchedBy(func(key []byte) bool {
354 return bytes.Equal(key, []byte(tun.ClientTokenKey(token)))
355 }),
356 ).Return(clientBuf, nil).Once()
357 
358 resolver.On("LookupCNAME", mock.Anything, name).Return("", fmt.Errorf("error"))
359 
360 tp := mocks.SelfTransport()
361 streamRouter := transport.NewStreamRouter(logger, nil, tp)
362 go streamRouter.Accept(ctx)
363 
364 serv.AttachRouter(ctx, streamRouter)
365 
366 cRPC := rpc.DynamicTunnelClient(rpc.DisablePooling(ctx), tp)
367 
368 privKey := make(ed25519.PrivateKey, ed25519.PrivateKeySize)
369 tp.WithCertificate(toCertificate(as, logger, cli, token, withExtractPrivKey(privKey)))
370 
371 proof, err := pow.GenerateSolution(privKey, pow.Parameters{
372 Difficulty: acme.HashcashDifficulty,
373 Expires: acme.HashcashExpires,
374 GetSubject: func(pubKey ed25519.PublicKey) string {
375 return hostname
376 },
377 })
378 as.NoError(err)
379 
380 _, err = cRPC.AcmeValidate(rpc.WithNode(ctx, cli), &protocol.ValidateRequest{
381 Proof: proof,
382 Hostname: hostname,
383 })
384 
385 as.Error(err)
386 
387 node.AssertExpectations(t)
388 resolver.AssertExpectations(t)
389}