Skip to content

Commit

Permalink
allow to set the log file permissions
Browse files Browse the repository at this point in the history
Adding a "mode" option to overwrite the default logfile permissions.

Default remains "0600" which is the one currently used by lumberjack.
  • Loading branch information
ririsoft committed May 12, 2024
1 parent 4356635 commit 8da6f83
Show file tree
Hide file tree
Showing 2 changed files with 237 additions and 2 deletions.
27 changes: 25 additions & 2 deletions modules/logging/filewriter.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ type FileWriter struct {
// Filename is the name of the file to write.
Filename string `json:"filename,omitempty"`

// The file permissions mode.
// 0600 by default.
Mode os.FileMode `json:"mode,omitempty"`

// Roll toggles log rolling or rotation, which is
// enabled by default.
Roll *bool `json:"roll,omitempty"`
Expand Down Expand Up @@ -100,6 +104,10 @@ func (fw FileWriter) WriterKey() string {

// OpenWriter opens a new file writer.
func (fw FileWriter) OpenWriter() (io.WriteCloser, error) {
if fw.Mode == 0 {
fw.Mode = 0o600
}

// roll log files by default
if fw.Roll == nil || *fw.Roll {
if fw.RollSizeMB == 0 {
Expand All @@ -116,6 +124,9 @@ func (fw FileWriter) OpenWriter() (io.WriteCloser, error) {
fw.RollKeepDays = 90
}

f_tmp, _ := os.OpenFile(fw.Filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, fw.Mode)
f_tmp.Close()

return &lumberjack.Logger{
Filename: fw.Filename,
MaxSize: fw.RollSizeMB,
Expand All @@ -127,12 +138,13 @@ func (fw FileWriter) OpenWriter() (io.WriteCloser, error) {
}

// otherwise just open a regular file
return os.OpenFile(fw.Filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0o666)
return os.OpenFile(fw.Filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, fw.Mode)
}

// UnmarshalCaddyfile sets up the module from Caddyfile tokens. Syntax:
//
// file <filename> {
// mode <mode>
// roll_disabled
// roll_size <size>
// roll_uncompressed
Expand All @@ -150,7 +162,7 @@ func (fw FileWriter) OpenWriter() (io.WriteCloser, error) {
// The roll_keep_for duration has day resolution.
// Fractional values are rounded up to the next whole number of days.
//
// If any of the roll_size, roll_keep, or roll_keep_for subdirectives are
// If any of the mode, roll_size, roll_keep, or roll_keep_for subdirectives are
// omitted or set to a zero value, then Caddy's default value for that
// subdirective is used.
func (fw *FileWriter) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
Expand All @@ -165,6 +177,17 @@ func (fw *FileWriter) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {

for d.NextBlock(0) {
switch d.Val() {
case "mode":
var modeStr string
if !d.AllArgs(&modeStr) {
return d.ArgErr()
}
mode, err := strconv.ParseUint(modeStr, 0, 32)
if err != nil {
return d.Errf("parsing mode: %v", err)
}
fw.Mode = os.FileMode(mode)

case "roll_disabled":
var f bool
fw.Roll = &f
Expand Down
212 changes: 212 additions & 0 deletions modules/logging/filewriter_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
// 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.

package logging

import (
"os"
"path"
"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)

Check failure on line 67 in modules/logging/filewriter_test.go

View workflow job for this annotation

GitHub Actions / test (windows, 1.21)

undefined: syscall.Umask

Check failure on line 67 in modules/logging/filewriter_test.go

View workflow job for this annotation

GitHub Actions / test (windows, 1.22)

undefined: syscall.Umask
defer func() {
syscall.Umask(m)

Check failure on line 69 in modules/logging/filewriter_test.go

View workflow job for this annotation

GitHub Actions / test (windows, 1.21)

undefined: syscall.Umask

Check failure on line 69 in modules/logging/filewriter_test.go

View workflow job for this annotation

GitHub Actions / test (windows, 1.22)

undefined: syscall.Umask
}()

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 := path.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("file mode is %v, want %v", st.Mode(), tt.wantMode)
}
})
}
}

func TestFileRotationPreserveMode(t *testing.T) {
m := syscall.Umask(0o000)

Check failure on line 101 in modules/logging/filewriter_test.go

View workflow job for this annotation

GitHub Actions / test (windows, 1.21)

undefined: syscall.Umask

Check failure on line 101 in modules/logging/filewriter_test.go

View workflow job for this annotation

GitHub Actions / test (windows, 1.22)

undefined: syscall.Umask
defer func() {
syscall.Umask(m)

Check failure on line 103 in modules/logging/filewriter_test.go

View workflow job for this annotation

GitHub Actions / test (windows, 1.21)

undefined: syscall.Umask

Check failure on line 103 in modules/logging/filewriter_test.go

View workflow job for this annotation

GitHub Actions / test (windows, 1.22)

undefined: syscall.Umask
}()

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 := os.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)
}

if len(files) != 2 {
t.Fatalf("got %v files want 2", len(files))
}

wantPattern := "test-*-*-*-*-*.*.log"
if m, _ := path.Match(wantPattern, files[0].Name()); m != true {
t.Fatalf("got %v filename want %v", files[0].Name(), wantPattern)
}

st, err := os.Stat(path.Join(dir, files[0].Name()))
if err != nil {
t.Fatalf("failed to check file permissions: %v", err)
}

if st.Mode() != mode {
t.Errorf("file mode is %v, want %v", st.Mode(), mode)
}

if files[1].Name() != "test.log" {
t.Fatalf("got %v filename want test.log", files[1].Name())
}

st, err = os.Stat(path.Join(dir, files[1].Name()))
if err != nil {
t.Fatalf("failed to check file permissions: %v", err)
}

if st.Mode() != 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: "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)
}
})
}
}

0 comments on commit 8da6f83

Please sign in to comment.