@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 738 lines 14.6 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 //go:build !windows
16
17 package logging
18
19 import (
20 "encoding/json"
21 "os"
22 "path"
23 "path/filepath"
24 "syscall"
25 "testing"
26 "time"
27
28 "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
29 )
30
31 func TestFileCreationMode(t *testing.T) {
32 on := true
33 off := false
34
35 tests := []struct {
36 name string
37 fw FileWriter
38 wantMode os.FileMode
39 }{
40 {
41 name: "default mode no roll",
42 fw: FileWriter{
43 Roll: &off,
44 },
45 wantMode: 0o600,
46 },
47 {
48 name: "default mode roll",
49 fw: FileWriter{
50 Roll: &on,
51 },
52 wantMode: 0o600,
53 },
54 {
55 name: "custom mode no roll",
56 fw: FileWriter{
57 Roll: &off,
58 Mode: 0o666,
59 },
60 wantMode: 0o666,
61 },
62 {
63 name: "custom mode roll",
64 fw: FileWriter{
65 Roll: &on,
66 Mode: 0o666,
67 },
68 wantMode: 0o666,
69 },
70 }
71
72 m := syscall.Umask(0o000)
73 defer syscall.Umask(m)
74
75 for _, tt := range tests {
76 t.Run(tt.name, func(t *testing.T) {
77 dir, err := os.MkdirTemp("", "caddytest")
78 if err != nil {
79 t.Fatalf("failed to create tempdir: %v", err)
80 }
81 defer os.RemoveAll(dir)
82 fpath := filepath.Join(dir, "test.log")
83 tt.fw.Filename = fpath
84
85 logger, err := tt.fw.OpenWriter()
86 if err != nil {
87 t.Fatalf("failed to create file: %v", err)
88 }
89 defer logger.Close()
90
91 st, err := os.Stat(fpath)
92 if err != nil {
93 t.Fatalf("failed to check file permissions: %v", err)
94 }
95
96 if st.Mode() != tt.wantMode {
97 t.Errorf("%s: file mode is %v, want %v", tt.name, st.Mode(), tt.wantMode)
98 }
99 })
100 }
101 }
102
103 func TestFileRotationPreserveMode(t *testing.T) {
104 m := syscall.Umask(0o000)
105 defer syscall.Umask(m)
106
107 dir, err := os.MkdirTemp("", "caddytest")
108 if err != nil {
109 t.Fatalf("failed to create tempdir: %v", err)
110 }
111 defer os.RemoveAll(dir)
112
113 fpath := path.Join(dir, "test.log")
114
115 roll := true
116 mode := fileMode(0o640)
117 fw := FileWriter{
118 Filename: fpath,
119 Mode: mode,
120 Roll: &roll,
121 RollSizeMB: 1,
122 }
123
124 logger, err := fw.OpenWriter()
125 if err != nil {
126 t.Fatalf("failed to create file: %v", err)
127 }
128 defer logger.Close()
129
130 b := make([]byte, 1024*1024-1000)
131 logger.Write(b)
132 logger.Write(b[0:2000])
133
134 files, err := os.ReadDir(dir)
135 if err != nil {
136 t.Fatalf("failed to read temporary log dir: %v", err)
137 }
138
139 // We might get 2 or 3 files depending
140 // on the race between compressed log file generation,
141 // removal of the non compressed file and reading the directory.
142 // Ordering of the files are [ test-*.log test-*.log.gz test.log ]
143 if len(files) < 2 || len(files) > 3 {
144 t.Log("got files: ", files)
145 t.Fatalf("got %v files want 2", len(files))
146 }
147
148 wantPattern := "test-*-*-*-*-*.*.log"
149 test_date_log := files[0]
150 if m, _ := path.Match(wantPattern, test_date_log.Name()); m != true {
151 t.Fatalf("got %v filename want %v", test_date_log.Name(), wantPattern)
152 }
153
154 st, err := os.Stat(path.Join(dir, test_date_log.Name()))
155 if err != nil {
156 t.Fatalf("failed to check file permissions: %v", err)
157 }
158
159 if st.Mode() != os.FileMode(mode) {
160 t.Errorf("file mode is %v, want %v", st.Mode(), mode)
161 }
162
163 test_dot_log := files[len(files)-1]
164 if test_dot_log.Name() != "test.log" {
165 t.Fatalf("got %v filename want test.log", test_dot_log.Name())
166 }
167
168 st, err = os.Stat(path.Join(dir, test_dot_log.Name()))
169 if err != nil {
170 t.Fatalf("failed to check file permissions: %v", err)
171 }
172
173 if st.Mode() != os.FileMode(mode) {
174 t.Errorf("file mode is %v, want %v", st.Mode(), mode)
175 }
176 }
177
178 func TestFileRotationPreserveModeWithUmask(t *testing.T) {
179 m := syscall.Umask(0o022)
180 defer syscall.Umask(m)
181
182 dir, err := os.MkdirTemp("", "caddytest")
183 if err != nil {
184 t.Fatalf("failed to create tempdir: %v", err)
185 }
186 defer os.RemoveAll(dir)
187
188 fpath := path.Join(dir, "test.log")
189
190 roll := true
191 mode := fileMode(0o660)
192 fw := FileWriter{
193 Filename: fpath,
194 Mode: mode,
195 Roll: &roll,
196 RollSizeMB: 1,
197 }
198
199 logger, err := fw.OpenWriter()
200 if err != nil {
201 t.Fatalf("failed to create file: %v", err)
202 }
203 defer logger.Close()
204
205 b := make([]byte, 1024*1024-1000)
206 logger.Write(b)
207 logger.Write(b[0:2000])
208
209 st, err := os.Stat(fpath)
210 if err != nil {
211 t.Fatalf("failed to check file permissions: %v", err)
212 }
213
214 if got := st.Mode().Perm(); got != os.FileMode(mode) {
215 t.Errorf("file mode after rotation is %v, want %v", got, mode)
216 }
217 }
218
219 func TestFileModeConfig(t *testing.T) {
220 tests := []struct {
221 name string
222 d *caddyfile.Dispenser
223 fw FileWriter
224 wantErr bool
225 }{
226 {
227 name: "set mode",
228 d: caddyfile.NewTestDispenser(`
229 file test.log {
230 mode 0666
231 }
232 `),
233 fw: FileWriter{
234 Mode: 0o666,
235 },
236 wantErr: false,
237 },
238 {
239 name: "set mode 3 digits",
240 d: caddyfile.NewTestDispenser(`
241 file test.log {
242 mode 666
243 }
244 `),
245 fw: FileWriter{
246 Mode: 0o666,
247 },
248 wantErr: false,
249 },
250 {
251 name: "set mode 2 digits",
252 d: caddyfile.NewTestDispenser(`
253 file test.log {
254 mode 66
255 }
256 `),
257 fw: FileWriter{
258 Mode: 0o066,
259 },
260 wantErr: false,
261 },
262 {
263 name: "set mode 1 digits",
264 d: caddyfile.NewTestDispenser(`
265 file test.log {
266 mode 6
267 }
268 `),
269 fw: FileWriter{
270 Mode: 0o006,
271 },
272 wantErr: false,
273 },
274 {
275 name: "invalid mode",
276 d: caddyfile.NewTestDispenser(`
277 file test.log {
278 mode foobar
279 }
280 `),
281 fw: FileWriter{},
282 wantErr: true,
283 },
284 }
285
286 for _, tt := range tests {
287 t.Run(tt.name, func(t *testing.T) {
288 fw := &FileWriter{}
289 if err := fw.UnmarshalCaddyfile(tt.d); (err != nil) != tt.wantErr {
290 t.Fatalf("UnmarshalCaddyfile() error = %v, want %v", err, tt.wantErr)
291 }
292 if fw.Mode != tt.fw.Mode {
293 t.Errorf("got mode %v, want %v", fw.Mode, tt.fw.Mode)
294 }
295 })
296 }
297 }
298
299 func TestFileModeJSON(t *testing.T) {
300 tests := []struct {
301 name string
302 config string
303 fw FileWriter
304 wantErr bool
305 }{
306 {
307 name: "set mode",
308 config: `
309 {
310 "mode": "0666"
311 }
312 `,
313 fw: FileWriter{
314 Mode: 0o666,
315 },
316 wantErr: false,
317 },
318 {
319 name: "set mode invalid value",
320 config: `
321 {
322 "mode": "0x666"
323 }
324 `,
325 fw: FileWriter{},
326 wantErr: true,
327 },
328 {
329 name: "set mode invalid string",
330 config: `
331 {
332 "mode": 777
333 }
334 `,
335 fw: FileWriter{},
336 wantErr: true,
337 },
338 }
339
340 for _, tt := range tests {
341 t.Run(tt.name, func(t *testing.T) {
342 fw := &FileWriter{}
343 if err := json.Unmarshal([]byte(tt.config), fw); (err != nil) != tt.wantErr {
344 t.Fatalf("UnmarshalJSON() error = %v, want %v", err, tt.wantErr)
345 }
346 if fw.Mode != tt.fw.Mode {
347 t.Errorf("got mode %v, want %v", fw.Mode, tt.fw.Mode)
348 }
349 })
350 }
351 }
352
353 func TestFileModeToJSON(t *testing.T) {
354 tests := []struct {
355 name string
356 mode fileMode
357 want string
358 wantErr bool
359 }{
360 {
361 name: "none zero",
362 mode: 0o644,
363 want: `"0644"`,
364 wantErr: false,
365 },
366 {
367 name: "zero mode",
368 mode: 0,
369 want: `"0000"`,
370 wantErr: false,
371 },
372 }
373
374 for _, tt := range tests {
375 t.Run(tt.name, func(t *testing.T) {
376 var b []byte
377 var err error
378
379 if b, err = json.Marshal(&tt.mode); (err != nil) != tt.wantErr {
380 t.Fatalf("MarshalJSON() error = %v, want %v", err, tt.wantErr)
381 }
382
383 got := string(b[:])
384
385 if got != tt.want {
386 t.Errorf("got mode %v, want %v", got, tt.want)
387 }
388 })
389 }
390 }
391
392 func TestFileModeModification(t *testing.T) {
393 m := syscall.Umask(0o000)
394 defer syscall.Umask(m)
395
396 dir, err := os.MkdirTemp("", "caddytest")
397 if err != nil {
398 t.Fatalf("failed to create tempdir: %v", err)
399 }
400 defer os.RemoveAll(dir)
401
402 fpath := path.Join(dir, "test.log")
403 f_tmp, err := os.OpenFile(fpath, os.O_WRONLY|os.O_APPEND|os.O_CREATE, os.FileMode(0o600))
404 if err != nil {
405 t.Fatalf("failed to create test file: %v", err)
406 }
407 f_tmp.Close()
408
409 fw := FileWriter{
410 Mode: 0o666,
411 Filename: fpath,
412 }
413
414 logger, err := fw.OpenWriter()
415 if err != nil {
416 t.Fatalf("failed to create file: %v", err)
417 }
418 defer logger.Close()
419
420 st, err := os.Stat(fpath)
421 if err != nil {
422 t.Fatalf("failed to check file permissions: %v", err)
423 }
424
425 want := os.FileMode(fw.Mode)
426 if st.Mode() != want {
427 t.Errorf("file mode is %v, want %v", st.Mode(), want)
428 }
429 }
430
431 func TestDirMode_Inherit(t *testing.T) {
432 m := syscall.Umask(0)
433 defer syscall.Umask(m)
434
435 parent := t.TempDir()
436 if err := os.Chmod(parent, 0o755); err != nil {
437 t.Fatal(err)
438 }
439
440 targetDir := filepath.Join(parent, "a", "b")
441 fw := &FileWriter{
442 Filename: filepath.Join(targetDir, "test.log"),
443 DirMode: "inherit",
444 Mode: 0o640,
445 Roll: func() *bool { f := false; return &f }(),
446 }
447 w, err := fw.OpenWriter()
448 if err != nil {
449 t.Fatal(err)
450 }
451 _ = w.Close()
452
453 st, err := os.Stat(targetDir)
454 if err != nil {
455 t.Fatal(err)
456 }
457 if got := st.Mode().Perm(); got != 0o755 {
458 t.Fatalf("dir perm = %o, want 0755", got)
459 }
460 }
461
462 func TestDirMode_FromFile(t *testing.T) {
463 m := syscall.Umask(0)
464 defer syscall.Umask(m)
465
466 base := t.TempDir()
467
468 dir1 := filepath.Join(base, "logs1")
469 fw1 := &FileWriter{
470 Filename: filepath.Join(dir1, "app.log"),
471 DirMode: "from_file",
472 Mode: 0o644, // => dir 0755
473 Roll: func() *bool { f := false; return &f }(),
474 }
475 w1, err := fw1.OpenWriter()
476 if err != nil {
477 t.Fatal(err)
478 }
479 _ = w1.Close()
480
481 st1, err := os.Stat(dir1)
482 if err != nil {
483 t.Fatal(err)
484 }
485 if got := st1.Mode().Perm(); got != 0o755 {
486 t.Fatalf("dir perm = %o, want 0755", got)
487 }
488
489 dir2 := filepath.Join(base, "logs2")
490 fw2 := &FileWriter{
491 Filename: filepath.Join(dir2, "app.log"),
492 DirMode: "from_file",
493 Mode: 0o600, // => dir 0700
494 Roll: func() *bool { f := false; return &f }(),
495 }
496 w2, err := fw2.OpenWriter()
497 if err != nil {
498 t.Fatal(err)
499 }
500 _ = w2.Close()
501
502 st2, err := os.Stat(dir2)
503 if err != nil {
504 t.Fatal(err)
505 }
506 if got := st2.Mode().Perm(); got != 0o700 {
507 t.Fatalf("dir perm = %o, want 0700", got)
508 }
509
510 dir3 := filepath.Join(base, "logs3")
511 fw3 := &FileWriter{
512 Filename: filepath.Join(dir3, "app.log"),
513 DirMode: "from_file",
514 // no Mode: the file is created 0600 => dir 0700
515 Roll: func() *bool { f := false; return &f }(),
516 }
517 w3, err := fw3.OpenWriter()
518 if err != nil {
519 t.Fatal(err)
520 }
521 _ = w3.Close()
522
523 st3, err := os.Stat(dir3)
524 if err != nil {
525 t.Fatal(err)
526 }
527 if got := st3.Mode().Perm(); got != 0o700 {
528 t.Fatalf("dir perm = %o, want 0700", got)
529 }
530 }
531
532 func TestDirMode_ExplicitOctal(t *testing.T) {
533 m := syscall.Umask(0)
534 defer syscall.Umask(m)
535
536 base := t.TempDir()
537 dest := filepath.Join(base, "logs3")
538 fw := &FileWriter{
539 Filename: filepath.Join(dest, "app.log"),
540 DirMode: "0750",
541 Mode: 0o640,
542 Roll: func() *bool { f := false; return &f }(),
543 }
544 w, err := fw.OpenWriter()
545 if err != nil {
546 t.Fatal(err)
547 }
548 _ = w.Close()
549
550 st, err := os.Stat(dest)
551 if err != nil {
552 t.Fatal(err)
553 }
554 if got := st.Mode().Perm(); got != 0o750 {
555 t.Fatalf("dir perm = %o, want 0750", got)
556 }
557 }
558
559 func TestDirMode_Default0700(t *testing.T) {
560 m := syscall.Umask(0)
561 defer syscall.Umask(m)
562
563 base := t.TempDir()
564 dest := filepath.Join(base, "logs4")
565 fw := &FileWriter{
566 Filename: filepath.Join(dest, "app.log"),
567 Mode: 0o640,
568 Roll: func() *bool { f := false; return &f }(),
569 }
570 w, err := fw.OpenWriter()
571 if err != nil {
572 t.Fatal(err)
573 }
574 _ = w.Close()
575
576 st, err := os.Stat(dest)
577 if err != nil {
578 t.Fatal(err)
579 }
580 if got := st.Mode().Perm(); got != 0o700 {
581 t.Fatalf("dir perm = %o, want 0700", got)
582 }
583 }
584
585 func TestDirMode_UmaskInteraction(t *testing.T) {
586 _ = syscall.Umask(0o022) // typical umask; restore after
587 defer syscall.Umask(0)
588
589 base := t.TempDir()
590 dest := filepath.Join(base, "logs5")
591 fw := &FileWriter{
592 Filename: filepath.Join(dest, "app.log"),
593 DirMode: "0755",
594 Mode: 0o644,
595 Roll: func() *bool { f := false; return &f }(),
596 }
597 w, err := fw.OpenWriter()
598 if err != nil {
599 t.Fatal(err)
600 }
601 _ = w.Close()
602
603 st, err := os.Stat(dest)
604 if err != nil {
605 t.Fatal(err)
606 }
607 // 0755 &^ 0022 still 0755 for dirs; this just sanity-checks we didn't get stricter unexpectedly
608 if got := st.Mode().Perm(); got != 0o755 {
609 t.Fatalf("dir perm = %o, want 0755 (considering umask)", got)
610 }
611 }
612
613 func TestCaddyfile_DirMode_Inherit(t *testing.T) {
614 d := caddyfile.NewTestDispenser(`
615 file /var/log/app.log {
616 dir_mode inherit
617 mode 0640
618 }`)
619 var fw FileWriter
620 if err := fw.UnmarshalCaddyfile(d); err != nil {
621 t.Fatal(err)
622 }
623 if fw.DirMode != "inherit" {
624 t.Fatalf("got %q", fw.DirMode)
625 }
626 if fw.Mode != 0o640 {
627 t.Fatalf("mode = %o", fw.Mode)
628 }
629 }
630
631 func TestCaddyfile_DirMode_FromFile(t *testing.T) {
632 d := caddyfile.NewTestDispenser(`
633 file /var/log/app.log {
634 dir_mode from_file
635 mode 0600
636 }`)
637 var fw FileWriter
638 if err := fw.UnmarshalCaddyfile(d); err != nil {
639 t.Fatal(err)
640 }
641 if fw.DirMode != "from_file" {
642 t.Fatalf("got %q", fw.DirMode)
643 }
644 if fw.Mode != 0o600 {
645 t.Fatalf("mode = %o", fw.Mode)
646 }
647 }
648
649 func TestCaddyfile_DirMode_Octal(t *testing.T) {
650 d := caddyfile.NewTestDispenser(`
651 file /var/log/app.log {
652 dir_mode 0755
653 }`)
654 var fw FileWriter
655 if err := fw.UnmarshalCaddyfile(d); err != nil {
656 t.Fatal(err)
657 }
658 if fw.DirMode != "0755" {
659 t.Fatalf("got %q", fw.DirMode)
660 }
661 }
662
663 func TestCaddyfile_DirMode_Invalid(t *testing.T) {
664 d := caddyfile.NewTestDispenser(`
665 file /var/log/app.log {
666 dir_mode nope
667 }`)
668 var fw FileWriter
669 if err := fw.UnmarshalCaddyfile(d); err == nil {
670 t.Fatal("expected error for invalid dir_mode")
671 }
672 }
673
674 func TestCaddyfile_RollInterval(t *testing.T) {
675 tests := []struct {
676 name string
677 input string
678 wantSecs float64
679 wantErr bool
680 }{
681 {
682 name: "day unit",
683 input: `file /var/log/app.log {
684 roll_interval 1d
685 }`,
686 wantSecs: 86400,
687 },
688 {
689 name: "hours unit",
690 input: `file /var/log/app.log {
691 roll_interval 24h
692 }`,
693 wantSecs: 86400,
694 },
695 {
696 name: "minutes unit",
697 input: `file /var/log/app.log {
698 roll_interval 30m
699 }`,
700 wantSecs: 1800,
701 },
702 {
703 name: "fractional days",
704 input: `file /var/log/app.log {
705 roll_interval 1.5d
706 }`,
707 wantSecs: 129600,
708 },
709 {
710 name: "invalid duration",
711 input: `file /var/log/app.log {
712 roll_interval bogus
713 }`,
714 wantErr: true,
715 },
716 }
717
718 for _, tt := range tests {
719 t.Run(tt.name, func(t *testing.T) {
720 d := caddyfile.NewTestDispenser(tt.input)
721 var fw FileWriter
722 err := fw.UnmarshalCaddyfile(d)
723 if tt.wantErr {
724 if err == nil {
725 t.Fatal("expected error but got none")
726 }
727 return
728 }
729 if err != nil {
730 t.Fatalf("unexpected error: %v", err)
731 }
732 gotSecs := time.Duration(fw.RollInterval).Seconds()
733 if gotSecs != tt.wantSecs {
734 t.Errorf("got %v seconds, want %v", gotSecs, tt.wantSecs)
735 }
736 })
737 }
738 }