diff --git a/log/log.go b/log/log.go index 6abceed2c43..c3e1feb5083 100644 --- a/log/log.go +++ b/log/log.go @@ -160,12 +160,13 @@ type stdoutWriter struct { isatty bool } +// LogLineChecksum computes a CRC32 over the log line, which can be checked by +// log-validator to ensure no unexpected log corruption has occurred. func LogLineChecksum(line string) string { crc := crc32.ChecksumIEEE([]byte(line)) - // Using the hash.Hash32 doesn't make this any easier - // as it also returns a uint32 rather than []byte - buf := make([]byte, binary.MaxVarintLen32) - binary.PutUvarint(buf, uint64(crc)) + buf := make([]byte, crc32.Size) + // Error is unreachable because we provide a supported type and buffer size + _, _ = binary.Encode(buf, binary.LittleEndian, crc) return base64.RawURLEncoding.EncodeToString(buf) } diff --git a/log/log_test.go b/log/log_test.go index fad3fcf3d80..4d8f23c985e 100644 --- a/log/log_test.go +++ b/log/log_test.go @@ -12,6 +12,7 @@ import ( "time" "github.com/jmhodges/clock" + "github.com/letsencrypt/boulder/test" ) @@ -101,8 +102,8 @@ func TestStdoutLogger(t *testing.T) { logger.Warning("Warning log") logger.Info("Info log") - test.AssertEquals(t, stdout.String(), "1970-01-01 prefix 6 log.test pcbo7wk Info log\n") - test.AssertEquals(t, stderr.String(), "1970-01-01 prefix 3 log.test 46_ghQg [AUDIT] Error Audit\n1970-01-01 prefix 4 log.test 97r2xAw Warning log\n") + test.AssertEquals(t, stdout.String(), "1970-01-01 prefix 6 log.test JSP6nQ Info log\n") + test.AssertEquals(t, stderr.String(), "1970-01-01 prefix 3 log.test 4xe4gA [AUDIT] Error Audit\n1970-01-01 prefix 4 log.test d52dyA Warning log\n") } func TestSyslogMethods(t *testing.T) { @@ -342,3 +343,28 @@ func TestLogAtLevelEscapesNewlines(t *testing.T) { test.Assert(t, strings.Contains(buf.String(), "foo\\nbar"), "failed to escape newline") } + +func TestLogLineChecksum(t *testing.T) { + testCases := []struct { + name string + function func(string) string + input string + expected string + }{ + { + name: "LogLineChecksum with Hello, World!", + function: LogLineChecksum, + input: "Hello, World!", + expected: "0MNK7A", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + checksum := tc.function(tc.input) + if checksum != tc.expected { + t.Fatalf("got %q, want %q", checksum, tc.expected) + } + }) + } +} diff --git a/log/validator/validator.go b/log/validator/validator.go index a73330cb3f3..6b02f83ae67 100644 --- a/log/validator/validator.go +++ b/log/validator/validator.go @@ -186,9 +186,9 @@ func lineValid(text string) error { } checksum := fields[5] _, err := base64.RawURLEncoding.DecodeString(checksum) - if err != nil || len(checksum) != 7 { + if err != nil || len(checksum) != 6 { return fmt.Errorf( - "%s expected a 7 character base64 raw URL decodable string, got %q: %w", + "%s expected a 6 character base64 raw URL decodable string, got %q: %w", errorPrefix, checksum, errInvalidChecksum, @@ -204,7 +204,8 @@ func lineValid(text string) error { return nil } // Check the extracted checksum against the computed checksum - if computedChecksum := log.LogLineChecksum(line); checksum != computedChecksum { + computedChecksum := log.LogLineChecksum(line) + if checksum != computedChecksum { return fmt.Errorf("%s invalid checksum (expected %q, got %q)", errorPrefix, computedChecksum, checksum) } return nil diff --git a/log/validator/validator_test.go b/log/validator/validator_test.go index fc543b6529f..72c8de08b65 100644 --- a/log/validator/validator_test.go +++ b/log/validator/validator_test.go @@ -6,13 +6,18 @@ import ( "github.com/letsencrypt/boulder/test" ) -func TestLineValidAccepts(t *testing.T) { - err := lineValid("2020-07-06T18:07:43.109389+00:00 70877f679c72 datacenter 6 boulder-wfe[1595]: kKG6cwA Caught SIGTERM") +func TestLineValidAcceptsNew(t *testing.T) { + err := lineValid("2020-07-06T18:07:43.109389+00:00 70877f679c72 datacenter 6 boulder-wfe[1595]: kJBuDg Caught SIGTERM") test.AssertNotError(t, err, "errored on valid checksum") } +func TestLineValidRejectsOld(t *testing.T) { + err := lineValid("2020-07-06T18:07:43.109389+00:00 70877f679c72 datacenter 6 boulder-wfe[1595]: kKG6cwA Caught SIGTERM") + test.AssertError(t, err, "didn't error on old checksum format") +} + func TestLineValidRejects(t *testing.T) { - err := lineValid("2020-07-06T18:07:43.109389+00:00 70877f679c72 datacenter 6 boulder-wfe[1595]: xxxxxxx Caught SIGTERM") + err := lineValid("2020-07-06T18:07:43.109389+00:00 70877f679c72 datacenter 6 boulder-wfe[1595]: xxxxxx Caught SIGTERM") test.AssertError(t, err, "didn't error on invalid checksum") } @@ -23,10 +28,10 @@ func TestLineValidRejectsNotAChecksum(t *testing.T) { } func TestLineValidNonOurobouros(t *testing.T) { - err := lineValid("2020-07-06T18:07:43.109389+00:00 70877f679c72 datacenter 6 boulder-wfe[1595]: xxxxxxx Caught SIGTERM") + err := lineValid("2020-07-06T18:07:43.109389+00:00 70877f679c72 datacenter 6 boulder-wfe[1595]: xxxxxx Caught SIGTERM") test.AssertError(t, err, "didn't error on invalid checksum") - selfOutput := "2020-07-06T18:07:43.109389+00:00 70877f679c72 datacenter 6 log-validator[1337]: xxxxxxx " + err.Error() + selfOutput := "2020-07-06T18:07:43.109389+00:00 70877f679c72 datacenter 6 log-validator[1337]: xxxxxx " + err.Error() err2 := lineValid(selfOutput) test.AssertNotError(t, err2, "expected no error when feeding lineValid's error output into itself") }