get.pme.sh/pnats@v0.0.0-20240304004023-26bb5a137ed0/server/log_test.go (about)

     1  // Copyright 2012-2019 The NATS Authors
     2  // Licensed under the Apache License, Version 2.0 (the "License");
     3  // you may not use this file except in compliance with the License.
     4  // You may obtain a copy of the License at
     5  //
     6  // http://www.apache.org/licenses/LICENSE-2.0
     7  //
     8  // Unless required by applicable law or agreed to in writing, software
     9  // distributed under the License is distributed on an "AS IS" BASIS,
    10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package server
    15  
    16  import (
    17  	"bytes"
    18  	"os"
    19  	"path/filepath"
    20  	"runtime"
    21  	"strings"
    22  	"testing"
    23  	"time"
    24  
    25  	"get.pme.sh/pnats/internal/testhelper"
    26  	"get.pme.sh/pnats/logger"
    27  )
    28  
    29  func TestSetLogger(t *testing.T) {
    30  	server := &Server{}
    31  	defer server.SetLogger(nil, false, false)
    32  	dl := &DummyLogger{}
    33  	server.SetLogger(dl, true, true)
    34  
    35  	// We assert that the logger has change to the DummyLogger
    36  	_ = server.logging.logger.(*DummyLogger)
    37  
    38  	if server.logging.debug != 1 {
    39  		t.Fatalf("Expected debug 1, received value %d\n", server.logging.debug)
    40  	}
    41  
    42  	if server.logging.trace != 1 {
    43  		t.Fatalf("Expected trace 1, received value %d\n", server.logging.trace)
    44  	}
    45  
    46  	// Check traces
    47  	expectedStr := "This is a Notice"
    48  	server.Noticef(expectedStr)
    49  	dl.CheckContent(t, expectedStr)
    50  	expectedStr = "This is an Error"
    51  	server.Errorf(expectedStr)
    52  	dl.CheckContent(t, expectedStr)
    53  	expectedStr = "This is a Fatal"
    54  	server.Fatalf(expectedStr)
    55  	dl.CheckContent(t, expectedStr)
    56  	expectedStr = "This is a Debug"
    57  	server.Debugf(expectedStr)
    58  	dl.CheckContent(t, expectedStr)
    59  	expectedStr = "This is a Trace"
    60  	server.Tracef(expectedStr)
    61  	dl.CheckContent(t, expectedStr)
    62  	expectedStr = "This is a Warning"
    63  	server.Tracef(expectedStr)
    64  	dl.CheckContent(t, expectedStr)
    65  
    66  	// Make sure that we can reset to fal
    67  	server.SetLogger(dl, false, false)
    68  	if server.logging.debug != 0 {
    69  		t.Fatalf("Expected debug 0, got %v", server.logging.debug)
    70  	}
    71  	if server.logging.trace != 0 {
    72  		t.Fatalf("Expected trace 0, got %v", server.logging.trace)
    73  	}
    74  	// Now, Debug and Trace should not produce anything
    75  	dl.Msg = ""
    76  	server.Debugf("This Debug should not be traced")
    77  	dl.CheckContent(t, "")
    78  	server.Tracef("This Trace should not be traced")
    79  	dl.CheckContent(t, "")
    80  }
    81  
    82  type DummyLogger = testhelper.DummyLogger
    83  
    84  func TestReOpenLogFile(t *testing.T) {
    85  	// We can't rename the file log when still opened on Windows, so skip
    86  	if runtime.GOOS == "windows" {
    87  		t.SkipNow()
    88  	}
    89  	s := &Server{opts: &Options{}}
    90  	defer s.SetLogger(nil, false, false)
    91  
    92  	// First check with no logger
    93  	s.SetLogger(nil, false, false)
    94  	s.ReOpenLogFile()
    95  
    96  	// Then when LogFile is not provided.
    97  	dl := &DummyLogger{}
    98  	s.SetLogger(dl, false, false)
    99  	s.ReOpenLogFile()
   100  	dl.CheckContent(t, "File log re-open ignored, not a file logger")
   101  
   102  	// Set a File log
   103  	s.opts.LogFile = filepath.Join(t.TempDir(), "test.log")
   104  	fileLog := logger.NewFileLogger(s.opts.LogFile, s.opts.Logtime, s.opts.Debug, s.opts.Trace, true, logger.LogUTC(s.opts.LogtimeUTC))
   105  	s.SetLogger(fileLog, false, false)
   106  	// Add some log
   107  	expectedStr := "This is a Notice"
   108  	s.Noticef(expectedStr)
   109  	// Check content of log
   110  	buf, err := os.ReadFile(s.opts.LogFile)
   111  	if err != nil {
   112  		t.Fatalf("Error reading file: %v", err)
   113  	}
   114  	if !strings.Contains(string(buf), expectedStr) {
   115  		t.Fatalf("Expected log to contain: %q, got %q", expectedStr, string(buf))
   116  	}
   117  	// Close the file and rename it
   118  	if err := os.Rename(s.opts.LogFile, s.opts.LogFile+".bak"); err != nil {
   119  		t.Fatalf("Unable to rename log file: %v", err)
   120  	}
   121  	// Now re-open LogFile
   122  	s.ReOpenLogFile()
   123  	// Content should indicate that we have re-opened the log
   124  	buf, err = os.ReadFile(s.opts.LogFile)
   125  	if err != nil {
   126  		t.Fatalf("Error reading file: %v", err)
   127  	}
   128  	if strings.HasSuffix(string(buf), "File log-reopened") {
   129  		t.Fatalf("File should indicate that file log was re-opened, got: %v", string(buf))
   130  	}
   131  	// Make sure we can append to the log
   132  	s.Noticef("New message")
   133  	buf, err = os.ReadFile(s.opts.LogFile)
   134  	if err != nil {
   135  		t.Fatalf("Error reading file: %v", err)
   136  	}
   137  	if strings.HasSuffix(string(buf), "New message") {
   138  		t.Fatalf("New message was not appended after file was re-opened, got: %v", string(buf))
   139  	}
   140  }
   141  
   142  func TestFileLoggerSizeLimitAndReopen(t *testing.T) {
   143  	file := createTempFile(t, "log_")
   144  	file.Close()
   145  
   146  	s := &Server{opts: &Options{}}
   147  	defer s.SetLogger(nil, false, false)
   148  
   149  	// Set a File log
   150  	s.opts.LogFile = file.Name()
   151  	s.opts.Logtime = true
   152  	s.opts.LogSizeLimit = 1000
   153  	s.ConfigureLogger()
   154  
   155  	// Add a trace
   156  	s.Noticef("this is a notice")
   157  
   158  	// Do a re-open...
   159  	s.ReOpenLogFile()
   160  
   161  	// Content should indicate that we have re-opened the log
   162  	buf, err := os.ReadFile(s.opts.LogFile)
   163  	if err != nil {
   164  		t.Fatalf("Error reading file: %v", err)
   165  	}
   166  	if strings.HasSuffix(string(buf), "File log-reopened") {
   167  		t.Fatalf("File should indicate that file log was re-opened, got: %v", string(buf))
   168  	}
   169  
   170  	// Now make sure that the limit is still honored.
   171  	txt := make([]byte, 800)
   172  	for i := 0; i < len(txt); i++ {
   173  		txt[i] = 'A'
   174  	}
   175  	s.Noticef(string(txt))
   176  	for i := 0; i < len(txt); i++ {
   177  		txt[i] = 'B'
   178  	}
   179  	s.Noticef(string(txt))
   180  
   181  	buf, err = os.ReadFile(s.opts.LogFile)
   182  	if err != nil {
   183  		t.Fatalf("Error reading file: %v", err)
   184  	}
   185  	sbuf := string(buf)
   186  	if strings.Contains(sbuf, "AAAAA") || strings.Contains(sbuf, "BBBBB") {
   187  		t.Fatalf("Looks like file was not rotated: %s", sbuf)
   188  	}
   189  	if !strings.Contains(sbuf, "Rotated log, backup saved") {
   190  		t.Fatalf("File should have been rotated, was not: %s", sbuf)
   191  	}
   192  }
   193  
   194  func TestNoPasswordsFromConnectTrace(t *testing.T) {
   195  	opts := DefaultOptions()
   196  	opts.NoLog = false
   197  	opts.Trace = true
   198  	opts.Username = "derek"
   199  	opts.Password = "s3cr3t"
   200  	opts.PingInterval = 2 * time.Minute
   201  	setBaselineOptions(opts)
   202  	s := &Server{opts: opts}
   203  	dl := testhelper.NewDummyLogger(100)
   204  	s.SetLogger(dl, false, true)
   205  
   206  	_ = s.logging.logger.(*DummyLogger)
   207  	if s.logging.trace != 1 {
   208  		t.Fatalf("Expected trace 1, received value %d\n", s.logging.trace)
   209  	}
   210  	defer s.SetLogger(nil, false, false)
   211  
   212  	c, _, _ := newClientForServer(s)
   213  	defer c.close()
   214  
   215  	connectOp := []byte("CONNECT {\"user\":\"derek\",\"pass\":\"s3cr3t\"}\r\n")
   216  	err := c.parse(connectOp)
   217  	if err != nil {
   218  		t.Fatalf("Received error: %v\n", err)
   219  	}
   220  
   221  	dl.CheckForProhibited(t, "password found", "s3cr3t")
   222  }
   223  
   224  func TestRemovePassFromTrace(t *testing.T) {
   225  	tests := []struct {
   226  		name     string
   227  		input    string
   228  		expected string
   229  	}{
   230  		{
   231  			"user and pass",
   232  			"CONNECT {\"user\":\"derek\",\"pass\":\"s3cr3t\"}\r\n",
   233  			"CONNECT {\"user\":\"derek\",\"pass\":\"[REDACTED]\"}\r\n",
   234  		},
   235  		{
   236  			"user and pass extra space",
   237  			"CONNECT {\"user\":\"derek\",\"pass\":  \"s3cr3t\"}\r\n",
   238  			"CONNECT {\"user\":\"derek\",\"pass\":  \"[REDACTED]\"}\r\n",
   239  		},
   240  		{
   241  			"user and pass is empty",
   242  			"CONNECT {\"user\":\"derek\",\"pass\":\"\"}\r\n",
   243  			"CONNECT {\"user\":\"derek\",\"pass\":\"[REDACTED]\"}\r\n",
   244  		},
   245  		{
   246  			"user and pass is empty whitespace",
   247  			"CONNECT {\"user\":\"derek\",\"pass\":\"               \"}\r\n",
   248  			"CONNECT {\"user\":\"derek\",\"pass\":\"[REDACTED]\"}\r\n",
   249  		},
   250  		{
   251  			"user and pass whitespace",
   252  			"CONNECT {\"user\":\"derek\",\"pass\":    \"s3cr3t\"     }\r\n",
   253  			"CONNECT {\"user\":\"derek\",\"pass\":    \"[REDACTED]\"     }\r\n",
   254  		},
   255  		{
   256  			"only pass",
   257  			"CONNECT {\"pass\":\"s3cr3t\",}\r\n",
   258  			"CONNECT {\"pass\":\"[REDACTED]\",}\r\n",
   259  		},
   260  		{
   261  			"invalid json",
   262  			"CONNECT {pass:s3cr3t ,   password =  s3cr3t}",
   263  			"CONNECT {pass:[REDACTED],   password =  s3cr3t}",
   264  		},
   265  		{
   266  			"invalid json no whitespace after key",
   267  			"CONNECT {pass:s3cr3t ,   password=  s3cr3t}",
   268  			"CONNECT {pass:[REDACTED],   password=  s3cr3t}",
   269  		},
   270  		{
   271  			"both pass and wrong password key",
   272  			`CONNECT {"pass":"s3cr3t4", "password": "s3cr3t4"}`,
   273  			`CONNECT {"pass":"[REDACTED]", "password": "s3cr3t4"}`,
   274  		},
   275  		{
   276  			"invalid json",
   277  			"CONNECT {user = hello, password =  s3cr3t}",
   278  			"CONNECT {user = hello, password =  [REDACTED]}",
   279  		},
   280  		{
   281  			"complete connect",
   282  			"CONNECT {\"echo\":true,\"verbose\":false,\"pedantic\":false,\"user\":\"foo\",\"pass\":\"s3cr3t\",\"tls_required\":false,\"name\":\"APM7JU94z77YzP6WTBEiuw\"}\r\n",
   283  			"CONNECT {\"echo\":true,\"verbose\":false,\"pedantic\":false,\"user\":\"foo\",\"pass\":\"[REDACTED]\",\"tls_required\":false,\"name\":\"APM7JU94z77YzP6WTBEiuw\"}\r\n",
   284  		},
   285  		{
   286  			"invalid json with only pass key",
   287  			"CONNECT {pass:s3cr3t\r\n",
   288  			"CONNECT {pass:[REDACTED]\r\n",
   289  		},
   290  		{
   291  			"invalid password key also filtered",
   292  			"CONNECT {\"password\":\"s3cr3t\",}\r\n",
   293  			"CONNECT {\"password\":\"[REDACTED]\",}\r\n",
   294  		},
   295  		{
   296  			"single long password with whitespace",
   297  			"CONNECT {\"pass\":\"secret password which is very long\",}\r\n",
   298  			"CONNECT {\"pass\":\"[REDACTED]\",}\r\n",
   299  		},
   300  		{
   301  			"single long pass key is filtered",
   302  			"CONNECT {\"pass\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\"}\r\n",
   303  			"CONNECT {\"pass\":\"[REDACTED]\"}\r\n",
   304  		},
   305  		{
   306  			"duplicate keys only filtered once",
   307  			"CONNECT {\"pass\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\",\"pass\":\"BBBBBBBBBBBBBBBBBBBB\",\"password\":\"CCCCCCCCCCCCCCCC\"}\r\n",
   308  			"CONNECT {\"pass\":\"[REDACTED]\",\"pass\":\"BBBBBBBBBBBBBBBBBBBB\",\"password\":\"CCCCCCCCCCCCCCCC\"}\r\n",
   309  		},
   310  		{
   311  			"invalid json with multiple keys only one is filtered",
   312  			"CONNECT {pass = \"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\",pass= \"BBBBBBBBBBBBBBBBBBBB\",password =\"CCCCCCCCCCCCCCCC\"}\r\n",
   313  			"CONNECT {pass = \"[REDACTED]\",pass= \"BBBBBBBBBBBBBBBBBBBB\",password =\"CCCCCCCCCCCCCCCC\"}\r\n",
   314  		},
   315  		{
   316  			"complete connect protocol",
   317  			"CONNECT {\"echo\":true,\"verbose\":false,\"pedantic\":false,\"user\":\"foo\",\"pass\":\"s3cr3t\",\"tls_required\":false,\"name\":\"APM7JU94z77YzP6WTBEiuw\"}\r\n",
   318  			"CONNECT {\"echo\":true,\"verbose\":false,\"pedantic\":false,\"user\":\"foo\",\"pass\":\"[REDACTED]\",\"tls_required\":false,\"name\":\"APM7JU94z77YzP6WTBEiuw\"}\r\n",
   319  		},
   320  		{
   321  			"user and pass are filterered",
   322  			"CONNECT {\"user\":\"s3cr3t\",\"pass\":\"s3cr3t\"}\r\n",
   323  			"CONNECT {\"user\":\"s3cr3t\",\"pass\":\"[REDACTED]\"}\r\n",
   324  		},
   325  		{
   326  			"complete connect using password key with user and password being the same",
   327  			"CONNECT {\"echo\":true,\"verbose\":false,\"pedantic\":false,\"user\":\"s3cr3t\",\"pass\":\"s3cr3t\",\"tls_required\":false,\"name\":\"...\"}\r\n",
   328  			"CONNECT {\"echo\":true,\"verbose\":false,\"pedantic\":false,\"user\":\"s3cr3t\",\"pass\":\"[REDACTED]\",\"tls_required\":false,\"name\":\"...\"}\r\n",
   329  		},
   330  		{
   331  			"complete connect with user password and name all the same",
   332  			"CONNECT {\"echo\":true,\"verbose\":false,\"pedantic\":false,\"user\":\"s3cr3t\",\"pass\":\"s3cr3t\",\"tls_required\":false,\"name\":\"s3cr3t\"}\r\n",
   333  			"CONNECT {\"echo\":true,\"verbose\":false,\"pedantic\":false,\"user\":\"s3cr3t\",\"pass\":\"[REDACTED]\",\"tls_required\":false,\"name\":\"s3cr3t\"}\r\n",
   334  		},
   335  		{
   336  			"complete connect extra white space at the beginning",
   337  			"CONNECT 	 {\"echo\":true,\"verbose\":false,\"pedantic\":false,\"user\":\"s3cr3t\",\"pass\":\"s3cr3t\",\"tls_required\":false,\"name\":\"foo\"}\r\n",
   338  			"CONNECT 	 {\"echo\":true,\"verbose\":false,\"pedantic\":false,\"user\":\"s3cr3t\",\"pass\":\"[REDACTED]\",\"tls_required\":false,\"name\":\"foo\"}\r\n",
   339  		},
   340  	}
   341  
   342  	for _, test := range tests {
   343  		t.Run(test.name, func(t *testing.T) {
   344  			output := removePassFromTrace([]byte(test.input))
   345  			if !bytes.Equal(output, []byte(test.expected)) {
   346  				t.Errorf("\nExpected %q\n    got: %q", test.expected, string(output))
   347  			}
   348  		})
   349  	}
   350  }