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 }