@marcel91200 / caddy Mirror · read-only
Fork 0

Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS

Mirrored from https://github.com/Slazee/caddy.git

Synced Nov 11, 01:00 UTC

master
go 569 lines 13.9 KB
Raw
1 // Copyright 2015 Matthew Holt and The Caddy Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 package caddytls
16
17 import (
18 "context"
19 "encoding/json"
20 "io/fs"
21 "sync"
22 "testing"
23 "time"
24
25 "github.com/caddyserver/caddy/v2"
26 "github.com/caddyserver/certmagic"
27 "github.com/cloudflare/circl/hpke"
28 "go.uber.org/zap"
29 )
30
31 var (
32 testStorageMu sync.Mutex
33 activeTestStorage certmagic.Storage
34 )
35
36 type testStorageModule struct{}
37
38 func (testStorageModule) CaddyModule() caddy.ModuleInfo {
39 return caddy.ModuleInfo{
40 ID: "caddy.storage.test_blocking",
41 New: func() caddy.Module { return new(testStorageModule) },
42 }
43 }
44
45 func (testStorageModule) CertMagicStorage() (certmagic.Storage, error) {
46 testStorageMu.Lock()
47 defer testStorageMu.Unlock()
48 return activeTestStorage, nil
49 }
50
51 func init() {
52 caddy.RegisterModule(testStorageModule{})
53 }
54
55 func newTestContextWithStorage(t *testing.T, storage certmagic.Storage) (caddy.Context, context.CancelFunc) {
56 testStorageMu.Lock()
57 activeTestStorage = storage
58 testStorageMu.Unlock()
59
60 cfg := &caddy.Config{
61 StorageRaw: []byte(`{"module": "test_blocking"}`),
62 }
63 ctx, err := caddy.ProvisionContext(cfg)
64 if err != nil {
65 t.Fatalf("caddy.ProvisionContext failed: %v", err)
66 }
67 return ctx, func() {
68 testStorageMu.Lock()
69 activeTestStorage = nil
70 testStorageMu.Unlock()
71 }
72 }
73
74 type blockingStorage struct {
75 lockFunc func(ctx context.Context, key string) error
76 unlockFunc func(ctx context.Context, key string) error
77 storeFunc func(ctx context.Context, key string, value []byte) error
78 loadFunc func(ctx context.Context, key string) ([]byte, error)
79 deleteFunc func(ctx context.Context, key string) error
80 existsFunc func(ctx context.Context, key string) bool
81 listFunc func(ctx context.Context, path string, recursive bool) ([]string, error)
82 statFunc func(ctx context.Context, key string) (certmagic.KeyInfo, error)
83 }
84
85 func (s *blockingStorage) Lock(ctx context.Context, key string) error {
86 if s.lockFunc != nil {
87 return s.lockFunc(ctx, key)
88 }
89 return nil
90 }
91
92 func (s *blockingStorage) Unlock(ctx context.Context, key string) error {
93 if s.unlockFunc != nil {
94 return s.unlockFunc(ctx, key)
95 }
96 return nil
97 }
98
99 func (s *blockingStorage) Store(ctx context.Context, key string, value []byte) error {
100 if s.storeFunc != nil {
101 return s.storeFunc(ctx, key, value)
102 }
103 return nil
104 }
105
106 func (s *blockingStorage) Load(ctx context.Context, key string) ([]byte, error) {
107 if s.loadFunc != nil {
108 return s.loadFunc(ctx, key)
109 }
110 return nil, fs.ErrNotExist
111 }
112
113 func (s *blockingStorage) Delete(ctx context.Context, key string) error {
114 if s.deleteFunc != nil {
115 return s.deleteFunc(ctx, key)
116 }
117 return nil
118 }
119
120 func (s *blockingStorage) Exists(ctx context.Context, key string) bool {
121 if s.existsFunc != nil {
122 return s.existsFunc(ctx, key)
123 }
124 return false
125 }
126
127 func (s *blockingStorage) List(ctx context.Context, path string, recursive bool) ([]string, error) {
128 if s.listFunc != nil {
129 return s.listFunc(ctx, path, recursive)
130 }
131 return nil, nil
132 }
133
134 func (s *blockingStorage) Stat(ctx context.Context, key string) (certmagic.KeyInfo, error) {
135 if s.statFunc != nil {
136 return s.statFunc(ctx, key)
137 }
138 return certmagic.KeyInfo{}, fs.ErrNotExist
139 }
140
141 type blockingPublisher struct {
142 key string
143 publishFunc func(ctx context.Context, innerNames []string, echConfigList []byte) error
144 }
145
146 func (p *blockingPublisher) PublisherKey() string {
147 if p.key != "" {
148 return p.key
149 }
150 return "blocking_test_publisher"
151 }
152
153 func (p *blockingPublisher) PublishECHConfigList(ctx context.Context, innerNames []string, echConfigList []byte) error {
154 if p.publishFunc != nil {
155 return p.publishFunc(ctx, innerNames, echConfigList)
156 }
157 return nil
158 }
159
160 func TestTLSECHPublishTracksMultiplePublishers(t *testing.T) {
161 storedMetadata := make(map[string][]byte)
162 storage := &blockingStorage{
163 storeFunc: func(_ context.Context, key string, value []byte) error {
164 storedMetadata[key] = append([]byte(nil), value...)
165 return nil
166 },
167 }
168 ctx, cleanup := newTestContextWithStorage(t, storage)
169 defer cleanup()
170
171 publishCalls := make(map[string]int)
172 publisher1 := &blockingPublisher{
173 key: "publisher1",
174 publishFunc: func(context.Context, []string, []byte) error {
175 publishCalls["publisher1"]++
176 return nil
177 },
178 }
179 publisher2 := &blockingPublisher{
180 key: "publisher2",
181 publishFunc: func(context.Context, []string, []byte) error {
182 publishCalls["publisher2"]++
183 return nil
184 },
185 }
186
187 publicKey, _, err := hpke.KEM_X25519_HKDF_SHA256.Scheme().GenerateKeyPair()
188 if err != nil {
189 t.Fatalf("GenerateKeyPair failed: %v", err)
190 }
191
192 ech := &ECH{
193 configsMu: new(sync.RWMutex),
194 Publication: []*ECHPublication{
195 {
196 Domains: []string{"example.com"},
197 publishers: []ECHPublisher{publisher1, publisher2},
198 },
199 },
200 configs: map[string][]echConfig{
201 "example.com": {
202 {
203 PublicKey: publicKey,
204 Version: draftTLSESNI25,
205 ConfigID: 1,
206 RawPublicName: "example.com",
207 KEMID: hpke.KEM_X25519_HKDF_SHA256,
208 CipherSuites: []hpkeSymmetricCipherSuite{
209 {
210 KDFID: hpke.KDF_HKDF_SHA256,
211 AEADID: hpke.AEAD_AES128GCM,
212 },
213 },
214 },
215 },
216 },
217 }
218 tlsApp := &TLS{
219 ctx: ctx,
220 logger: zap.NewNop(),
221 EncryptedClientHello: ech,
222 serverNames: map[string]serverNameRegistration{
223 "example.com": {},
224 },
225 }
226
227 for i := 0; i < 2; i++ {
228 if err := tlsApp.publishECHConfigs(ctx.Context, tlsApp.logger); err != nil {
229 t.Fatalf("publishECHConfigs call %d failed: %v", i+1, err)
230 }
231 }
232
233 for _, publisherKey := range []string{"publisher1", "publisher2"} {
234 if publishCalls[publisherKey] != 1 {
235 t.Errorf("%s was called %d times; want once", publisherKey, publishCalls[publisherKey])
236 }
237 }
238
239 assertPublicationHistory := func(t *testing.T, history publicationHistory) {
240 t.Helper()
241 for _, publisherKey := range []string{"publisher1", "publisher2"} {
242 if publishedAt := history[publisherKey]["example.com"]; publishedAt.IsZero() {
243 t.Errorf("missing publication history for %s", publisherKey)
244 }
245 }
246 }
247
248 assertPublicationHistory(t, ech.configs["example.com"][0].meta.Publications)
249
250 var storedMeta echConfigMeta
251 if err := json.Unmarshal(storedMetadata[echMetaKey(1)], &storedMeta); err != nil {
252 t.Fatalf("unmarshaling stored ECH metadata: %v", err)
253 }
254 assertPublicationHistory(t, storedMeta.Publications)
255 }
256
257 func TestTLSStorageCleanStopSynchronization(t *testing.T) {
258 ctx, cancel := caddy.NewContext(caddy.Context{Context: context.Background()})
259 defer cancel()
260
261 tlsApp := &TLS{
262 ctx: ctx,
263 logger: zap.NewNop(),
264 Automation: &AutomationConfig{
265 StorageCleanInterval: caddy.Duration(1 * time.Hour),
266 },
267 }
268
269 // Start storage cleaner
270 tlsApp.keepStorageClean()
271
272 // Stop must cancel cleaner context and wait for completion cleanly
273 err := tlsApp.Stop()
274 if err != nil {
275 t.Fatalf("TLS.Stop failed: %v", err)
276 }
277 }
278
279 func TestTLSStorageCleanUnitsCanceledContext(t *testing.T) {
280 ctx, cancel := caddy.NewContext(caddy.Context{Context: context.Background()})
281 cancel() // canceled immediately
282
283 tlsApp := &TLS{
284 ctx: ctx,
285 logger: zap.NewNop(),
286 }
287
288 // cleanStorageUnits with canceled context should return immediately
289 tlsApp.cleanStorageUnits(ctx.Context)
290 }
291
292 func TestTLSECHStopSynchronization(t *testing.T) {
293 ctx, cancel := caddy.NewContext(caddy.Context{Context: context.Background()})
294 defer cancel()
295
296 tlsApp := &TLS{
297 ctx: ctx,
298 logger: zap.NewNop(),
299 Automation: &AutomationConfig{},
300 EncryptedClientHello: &ECH{},
301 DisableStorageClean: true,
302 }
303
304 err := tlsApp.Start()
305 if err != nil {
306 t.Fatalf("TLS.Start failed: %v", err)
307 }
308
309 // Stop must cancel ECH context and wait for background ECH worker to exit
310 err = tlsApp.Stop()
311 if err != nil {
312 t.Fatalf("TLS.Stop failed: %v", err)
313 }
314 }
315
316 func TestTLSStorageCleanBlockingStorageStopSynchronization(t *testing.T) {
317 startedLock := make(chan struct{})
318 storage := &blockingStorage{
319 lockFunc: func(ctx context.Context, key string) error {
320 if key == "storage_clean" {
321 close(startedLock)
322 <-ctx.Done()
323 return ctx.Err()
324 }
325 return nil
326 },
327 }
328
329 ctx, cleanup := newTestContextWithStorage(t, storage)
330 defer cleanup()
331
332 tlsApp := &TLS{
333 ctx: ctx,
334 logger: zap.NewNop(),
335 Automation: &AutomationConfig{
336 StorageCleanInterval: caddy.Duration(1 * time.Hour),
337 },
338 }
339
340 // Reset storageClean timestamp so cleaning runs
341 storageCleanMu.Lock()
342 storageClean = time.Time{}
343 storageCleanMu.Unlock()
344
345 tlsApp.keepStorageClean()
346
347 select {
348 case <-startedLock:
349 case <-time.After(5 * time.Second):
350 t.Fatal("timed out waiting for storage lock to be attempted")
351 }
352
353 stopped := make(chan error, 1)
354 go func() {
355 stopped <- tlsApp.Stop()
356 }()
357
358 select {
359 case err := <-stopped:
360 if err != nil {
361 t.Fatalf("TLS.Stop failed: %v", err)
362 }
363 case <-time.After(5 * time.Second):
364 t.Fatal("TLS.Stop hung while storage cleaner was blocked on storage lock")
365 }
366 }
367
368 func TestTLSECHPublishBlockingStorageStopSynchronization(t *testing.T) {
369 startedLock := make(chan struct{})
370 storage := &blockingStorage{
371 lockFunc: func(ctx context.Context, key string) error {
372 if key == "ech_publish" {
373 close(startedLock)
374 <-ctx.Done()
375 return ctx.Err()
376 }
377 return nil
378 },
379 }
380
381 ctx, cleanup := newTestContextWithStorage(t, storage)
382 defer cleanup()
383
384 ech := &ECH{
385 configsMu: new(sync.RWMutex),
386 Configs: []ECHConfiguration{{PublicName: "example.com"}},
387 }
388 tlsApp := &TLS{
389 ctx: ctx,
390 logger: zap.NewNop(),
391 Automation: &AutomationConfig{},
392 EncryptedClientHello: ech,
393 DisableStorageClean: true,
394 }
395
396 err := tlsApp.Start()
397 if err != nil {
398 t.Fatalf("TLS.Start failed: %v", err)
399 }
400
401 select {
402 case <-startedLock:
403 case <-time.After(5 * time.Second):
404 t.Fatal("timed out waiting for ech_publish storage lock to be attempted")
405 }
406
407 stopped := make(chan error, 1)
408 go func() {
409 stopped <- tlsApp.Stop()
410 }()
411
412 select {
413 case err := <-stopped:
414 if err != nil {
415 t.Fatalf("TLS.Stop failed: %v", err)
416 }
417 case <-time.After(5 * time.Second):
418 t.Fatal("TLS.Stop hung while ECH publisher was blocked on ech_publish storage lock")
419 }
420 }
421
422 func TestTLSECHPublishBlockingPublisherStopSynchronization(t *testing.T) {
423 startedPublish := make(chan struct{})
424 publisher := &blockingPublisher{
425 publishFunc: func(ctx context.Context, innerNames []string, echConfigList []byte) error {
426 close(startedPublish)
427 <-ctx.Done()
428 return ctx.Err()
429 },
430 }
431
432 storage := &blockingStorage{}
433 ctx, cleanup := newTestContextWithStorage(t, storage)
434 defer cleanup()
435
436 publicKey, _, err := hpke.KEM_X25519_HKDF_SHA256.Scheme().GenerateKeyPair()
437 if err != nil {
438 t.Fatalf("GenerateKeyPair failed: %v", err)
439 }
440
441 ech := &ECH{
442 configsMu: new(sync.RWMutex),
443 Configs: []ECHConfiguration{{PublicName: "example.com"}},
444 Publication: []*ECHPublication{
445 {
446 Domains: []string{"example.com"},
447 publishers: []ECHPublisher{publisher},
448 },
449 },
450 configs: map[string][]echConfig{
451 "example.com": {
452 {
453 PublicKey: publicKey,
454 Version: draftTLSESNI25,
455 ConfigID: 1,
456 RawPublicName: "example.com",
457 KEMID: hpke.KEM_X25519_HKDF_SHA256,
458 CipherSuites: []hpkeSymmetricCipherSuite{
459 {
460 KDFID: hpke.KDF_HKDF_SHA256,
461 AEADID: hpke.AEAD_AES128GCM,
462 },
463 },
464 },
465 },
466 },
467 }
468 tlsApp := &TLS{
469 ctx: ctx,
470 logger: zap.NewNop(),
471 Automation: &AutomationConfig{},
472 EncryptedClientHello: ech,
473 DisableStorageClean: true,
474 serverNames: map[string]serverNameRegistration{
475 "example.com": {},
476 },
477 }
478
479 err = tlsApp.Start()
480 if err != nil {
481 t.Fatalf("TLS.Start failed: %v", err)
482 }
483
484 select {
485 case <-startedPublish:
486 case <-time.After(5 * time.Second):
487 t.Fatal("timed out waiting for ECH publication to be attempted")
488 }
489
490 stopped := make(chan error, 1)
491 go func() {
492 stopped <- tlsApp.Stop()
493 }()
494
495 select {
496 case err := <-stopped:
497 if err != nil {
498 t.Fatalf("TLS.Stop failed: %v", err)
499 }
500 case <-time.After(5 * time.Second):
501 t.Fatal("TLS.Stop hung while ECH publisher was blocked inside PublishECHConfigList")
502 }
503 }
504
505 func TestTLSECHRotateBlockingStorageStopSynchronization(t *testing.T) {
506 startedLock := make(chan struct{})
507 storage := &blockingStorage{
508 lockFunc: func(ctx context.Context, key string) error {
509 if key == echStorageLockName {
510 close(startedLock)
511 <-ctx.Done()
512 return ctx.Err()
513 }
514 return nil
515 },
516 }
517
518 ctx, cleanup := newTestContextWithStorage(t, storage)
519 defer cleanup()
520
521 ech := &ECH{
522 configsMu: new(sync.RWMutex),
523 Configs: []ECHConfiguration{{PublicName: "example.com"}},
524 configs: map[string][]echConfig{
525 "example.com": {
526 {
527 ConfigID: 1,
528 RawPublicName: "example.com",
529 meta: echConfigMeta{
530 Created: time.Now().Add(-24 * time.Hour * 31), // > 30 days old so rotationNeeded returns true
531 },
532 },
533 },
534 },
535 }
536 tlsApp := &TLS{
537 ctx: ctx,
538 logger: zap.NewNop(),
539 Automation: &AutomationConfig{},
540 EncryptedClientHello: ech,
541 DisableStorageClean: true,
542 echRotateInterval: 5 * time.Millisecond,
543 }
544
545 err := tlsApp.Start()
546 if err != nil {
547 t.Fatalf("TLS.Start failed: %v", err)
548 }
549
550 select {
551 case <-startedLock:
552 case <-time.After(5 * time.Second):
553 t.Fatal("timed out waiting for echStorageLockName storage lock to be attempted during rotation")
554 }
555
556 stopped := make(chan error, 1)
557 go func() {
558 stopped <- tlsApp.Stop()
559 }()
560
561 select {
562 case err := <-stopped:
563 if err != nil {
564 t.Fatalf("TLS.Stop failed: %v", err)
565 }
566 case <-time.After(5 * time.Second):
567 t.Fatal("TLS.Stop hung while ECH key rotation was blocked on storage lock (context was likely ignored)")
568 }
569 }