caddy/modules/logging/filewriter_test.go

609 lines
12 KiB
Go

// Copyright 2015 Matthew Holt and The Caddy Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build !windows
package logging
import (
"encoding/json"
"os"
"path"
"path/filepath"
"syscall"
"testing"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
)
func TestFileCreationMode(t *testing.T) {
on := true
off := false
tests := []struct {
name string
fw FileWriter
wantMode os.FileMode
}{
{
name: "default mode no roll",
fw: FileWriter{
Roll: &off,
},
wantMode: 0o600,
},
{
name: "default mode roll",
fw: FileWriter{
Roll: &on,
},
wantMode: 0o600,
},
{
name: "custom mode no roll",
fw: FileWriter{
Roll: &off,
Mode: 0o666,
},
wantMode: 0o666,
},
{
name: "custom mode roll",
fw: FileWriter{
Roll: &on,
Mode: 0o666,
},
wantMode: 0o666,
},
}
m := syscall.Umask(0o000)
defer syscall.Umask(m)
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
dir, err := os.MkdirTemp("", "caddytest")
if err != nil {
t.Fatalf("failed to create tempdir: %v", err)
}
defer os.RemoveAll(dir)
fpath := filepath.Join(dir, "test.log")
tt.fw.Filename = fpath
logger, err := tt.fw.OpenWriter()
if err != nil {
t.Fatalf("failed to create file: %v", err)
}
defer logger.Close()
st, err := os.Stat(fpath)
if err != nil {
t.Fatalf("failed to check file permissions: %v", err)
}
if st.Mode() != tt.wantMode {
t.Errorf("%s: file mode is %v, want %v", tt.name, st.Mode(), tt.wantMode)
}
})
}
}
func TestFileRotationPreserveMode(t *testing.T) {
m := syscall.Umask(0o000)
defer syscall.Umask(m)
dir, err := os.MkdirTemp("", "caddytest")
if err != nil {
t.Fatalf("failed to create tempdir: %v", err)
}
defer os.RemoveAll(dir)
fpath := path.Join(dir, "test.log")
roll := true
mode := fileMode(0o640)
fw := FileWriter{
Filename: fpath,
Mode: mode,
Roll: &roll,
RollSizeMB: 1,
}
logger, err := fw.OpenWriter()
if err != nil {
t.Fatalf("failed to create file: %v", err)
}
defer logger.Close()
b := make([]byte, 1024*1024-1000)
logger.Write(b)
logger.Write(b[0:2000])
files, err := os.ReadDir(dir)
if err != nil {
t.Fatalf("failed to read temporary log dir: %v", err)
}
// We might get 2 or 3 files depending
// on the race between compressed log file generation,
// removal of the non compressed file and reading the directory.
// Ordering of the files are [ test-*.log test-*.log.gz test.log ]
if len(files) < 2 || len(files) > 3 {
t.Log("got files: ", files)
t.Fatalf("got %v files want 2", len(files))
}
wantPattern := "test-*-*-*-*-*.*.log"
test_date_log := files[0]
if m, _ := path.Match(wantPattern, test_date_log.Name()); m != true {
t.Fatalf("got %v filename want %v", test_date_log.Name(), wantPattern)
}
st, err := os.Stat(path.Join(dir, test_date_log.Name()))
if err != nil {
t.Fatalf("failed to check file permissions: %v", err)
}
if st.Mode() != os.FileMode(mode) {
t.Errorf("file mode is %v, want %v", st.Mode(), mode)
}
test_dot_log := files[len(files)-1]
if test_dot_log.Name() != "test.log" {
t.Fatalf("got %v filename want test.log", test_dot_log.Name())
}
st, err = os.Stat(path.Join(dir, test_dot_log.Name()))
if err != nil {
t.Fatalf("failed to check file permissions: %v", err)
}
if st.Mode() != os.FileMode(mode) {
t.Errorf("file mode is %v, want %v", st.Mode(), mode)
}
}
func TestFileModeConfig(t *testing.T) {
tests := []struct {
name string
d *caddyfile.Dispenser
fw FileWriter
wantErr bool
}{
{
name: "set mode",
d: caddyfile.NewTestDispenser(`
file test.log {
mode 0666
}
`),
fw: FileWriter{
Mode: 0o666,
},
wantErr: false,
},
{
name: "set mode 3 digits",
d: caddyfile.NewTestDispenser(`
file test.log {
mode 666
}
`),
fw: FileWriter{
Mode: 0o666,
},
wantErr: false,
},
{
name: "set mode 2 digits",
d: caddyfile.NewTestDispenser(`
file test.log {
mode 66
}
`),
fw: FileWriter{
Mode: 0o066,
},
wantErr: false,
},
{
name: "set mode 1 digits",
d: caddyfile.NewTestDispenser(`
file test.log {
mode 6
}
`),
fw: FileWriter{
Mode: 0o006,
},
wantErr: false,
},
{
name: "invalid mode",
d: caddyfile.NewTestDispenser(`
file test.log {
mode foobar
}
`),
fw: FileWriter{},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
fw := &FileWriter{}
if err := fw.UnmarshalCaddyfile(tt.d); (err != nil) != tt.wantErr {
t.Fatalf("UnmarshalCaddyfile() error = %v, want %v", err, tt.wantErr)
}
if fw.Mode != tt.fw.Mode {
t.Errorf("got mode %v, want %v", fw.Mode, tt.fw.Mode)
}
})
}
}
func TestFileModeJSON(t *testing.T) {
tests := []struct {
name string
config string
fw FileWriter
wantErr bool
}{
{
name: "set mode",
config: `
{
"mode": "0666"
}
`,
fw: FileWriter{
Mode: 0o666,
},
wantErr: false,
},
{
name: "set mode invalid value",
config: `
{
"mode": "0x666"
}
`,
fw: FileWriter{},
wantErr: true,
},
{
name: "set mode invalid string",
config: `
{
"mode": 777
}
`,
fw: FileWriter{},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
fw := &FileWriter{}
if err := json.Unmarshal([]byte(tt.config), fw); (err != nil) != tt.wantErr {
t.Fatalf("UnmarshalJSON() error = %v, want %v", err, tt.wantErr)
}
if fw.Mode != tt.fw.Mode {
t.Errorf("got mode %v, want %v", fw.Mode, tt.fw.Mode)
}
})
}
}
func TestFileModeToJSON(t *testing.T) {
tests := []struct {
name string
mode fileMode
want string
wantErr bool
}{
{
name: "none zero",
mode: 0o644,
want: `"0644"`,
wantErr: false,
},
{
name: "zero mode",
mode: 0,
want: `"0000"`,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var b []byte
var err error
if b, err = json.Marshal(&tt.mode); (err != nil) != tt.wantErr {
t.Fatalf("MarshalJSON() error = %v, want %v", err, tt.wantErr)
}
got := string(b[:])
if got != tt.want {
t.Errorf("got mode %v, want %v", got, tt.want)
}
})
}
}
func TestFileModeModification(t *testing.T) {
m := syscall.Umask(0o000)
defer syscall.Umask(m)
dir, err := os.MkdirTemp("", "caddytest")
if err != nil {
t.Fatalf("failed to create tempdir: %v", err)
}
defer os.RemoveAll(dir)
fpath := path.Join(dir, "test.log")
f_tmp, err := os.OpenFile(fpath, os.O_WRONLY|os.O_APPEND|os.O_CREATE, os.FileMode(0o600))
if err != nil {
t.Fatalf("failed to create test file: %v", err)
}
f_tmp.Close()
fw := FileWriter{
Mode: 0o666,
Filename: fpath,
}
logger, err := fw.OpenWriter()
if err != nil {
t.Fatalf("failed to create file: %v", err)
}
defer logger.Close()
st, err := os.Stat(fpath)
if err != nil {
t.Fatalf("failed to check file permissions: %v", err)
}
want := os.FileMode(fw.Mode)
if st.Mode() != want {
t.Errorf("file mode is %v, want %v", st.Mode(), want)
}
}
func TestDirMode_Inherit(t *testing.T) {
m := syscall.Umask(0)
defer syscall.Umask(m)
parent := t.TempDir()
if err := os.Chmod(parent, 0o755); err != nil {
t.Fatal(err)
}
targetDir := filepath.Join(parent, "a", "b")
fw := &FileWriter{
Filename: filepath.Join(targetDir, "test.log"),
DirMode: "inherit",
Mode: 0o640,
Roll: func() *bool { f := false; return &f }(),
}
w, err := fw.OpenWriter()
if err != nil {
t.Fatal(err)
}
_ = w.Close()
st, err := os.Stat(targetDir)
if err != nil {
t.Fatal(err)
}
if got := st.Mode().Perm(); got != 0o755 {
t.Fatalf("dir perm = %o, want 0755", got)
}
}
func TestDirMode_FromFile(t *testing.T) {
m := syscall.Umask(0)
defer syscall.Umask(m)
base := t.TempDir()
dir1 := filepath.Join(base, "logs1")
fw1 := &FileWriter{
Filename: filepath.Join(dir1, "app.log"),
DirMode: "from_file",
Mode: 0o644, // => dir 0755
Roll: func() *bool { f := false; return &f }(),
}
w1, err := fw1.OpenWriter()
if err != nil {
t.Fatal(err)
}
_ = w1.Close()
st1, err := os.Stat(dir1)
if err != nil {
t.Fatal(err)
}
if got := st1.Mode().Perm(); got != 0o755 {
t.Fatalf("dir perm = %o, want 0755", got)
}
dir2 := filepath.Join(base, "logs2")
fw2 := &FileWriter{
Filename: filepath.Join(dir2, "app.log"),
DirMode: "from_file",
Mode: 0o600, // => dir 0700
Roll: func() *bool { f := false; return &f }(),
}
w2, err := fw2.OpenWriter()
if err != nil {
t.Fatal(err)
}
_ = w2.Close()
st2, err := os.Stat(dir2)
if err != nil {
t.Fatal(err)
}
if got := st2.Mode().Perm(); got != 0o700 {
t.Fatalf("dir perm = %o, want 0700", got)
}
}
func TestDirMode_ExplicitOctal(t *testing.T) {
m := syscall.Umask(0)
defer syscall.Umask(m)
base := t.TempDir()
dest := filepath.Join(base, "logs3")
fw := &FileWriter{
Filename: filepath.Join(dest, "app.log"),
DirMode: "0750",
Mode: 0o640,
Roll: func() *bool { f := false; return &f }(),
}
w, err := fw.OpenWriter()
if err != nil {
t.Fatal(err)
}
_ = w.Close()
st, err := os.Stat(dest)
if err != nil {
t.Fatal(err)
}
if got := st.Mode().Perm(); got != 0o750 {
t.Fatalf("dir perm = %o, want 0750", got)
}
}
func TestDirMode_Default0700(t *testing.T) {
m := syscall.Umask(0)
defer syscall.Umask(m)
base := t.TempDir()
dest := filepath.Join(base, "logs4")
fw := &FileWriter{
Filename: filepath.Join(dest, "app.log"),
Mode: 0o640,
Roll: func() *bool { f := false; return &f }(),
}
w, err := fw.OpenWriter()
if err != nil {
t.Fatal(err)
}
_ = w.Close()
st, err := os.Stat(dest)
if err != nil {
t.Fatal(err)
}
if got := st.Mode().Perm(); got != 0o700 {
t.Fatalf("dir perm = %o, want 0700", got)
}
}
func TestDirMode_UmaskInteraction(t *testing.T) {
_ = syscall.Umask(0o022) // typical umask; restore after
defer syscall.Umask(0)
base := t.TempDir()
dest := filepath.Join(base, "logs5")
fw := &FileWriter{
Filename: filepath.Join(dest, "app.log"),
DirMode: "0755",
Mode: 0o644,
Roll: func() *bool { f := false; return &f }(),
}
w, err := fw.OpenWriter()
if err != nil {
t.Fatal(err)
}
_ = w.Close()
st, err := os.Stat(dest)
if err != nil {
t.Fatal(err)
}
// 0755 &^ 0022 still 0755 for dirs; this just sanity-checks we didn't get stricter unexpectedly
if got := st.Mode().Perm(); got != 0o755 {
t.Fatalf("dir perm = %o, want 0755 (considering umask)", got)
}
}
func TestCaddyfile_DirMode_Inherit(t *testing.T) {
d := caddyfile.NewTestDispenser(`
file /var/log/app.log {
dir_mode inherit
mode 0640
}`)
var fw FileWriter
if err := fw.UnmarshalCaddyfile(d); err != nil {
t.Fatal(err)
}
if fw.DirMode != "inherit" {
t.Fatalf("got %q", fw.DirMode)
}
if fw.Mode != 0o640 {
t.Fatalf("mode = %o", fw.Mode)
}
}
func TestCaddyfile_DirMode_FromFile(t *testing.T) {
d := caddyfile.NewTestDispenser(`
file /var/log/app.log {
dir_mode from_file
mode 0600
}`)
var fw FileWriter
if err := fw.UnmarshalCaddyfile(d); err != nil {
t.Fatal(err)
}
if fw.DirMode != "from_file" {
t.Fatalf("got %q", fw.DirMode)
}
if fw.Mode != 0o600 {
t.Fatalf("mode = %o", fw.Mode)
}
}
func TestCaddyfile_DirMode_Octal(t *testing.T) {
d := caddyfile.NewTestDispenser(`
file /var/log/app.log {
dir_mode 0755
}`)
var fw FileWriter
if err := fw.UnmarshalCaddyfile(d); err != nil {
t.Fatal(err)
}
if fw.DirMode != "0755" {
t.Fatalf("got %q", fw.DirMode)
}
}
func TestCaddyfile_DirMode_Invalid(t *testing.T) {
d := caddyfile.NewTestDispenser(`
file /var/log/app.log {
dir_mode nope
}`)
var fw FileWriter
if err := fw.UnmarshalCaddyfile(d); err == nil {
t.Fatal("expected error for invalid dir_mode")
}
}