Skip to content

Commit 03244f7

Browse files
authored
Upgrade all audit log lines to structured json (#8595)
In the log package, remove all audit-level helper functions except for AuditErr and AuditObject, and rename the latter to AuditInfo to reflect its log level. Update both of those helpers to take arbitrary sets of key-value pairs (a map in the case of AuditErr, or any object in the case of AuditInfo to support our existing logEvent structs), which they then log as a JSON object. Throughout the rest of the code base, modify all audit log calls which are broken by the above change. In some cases, downgrade the log line from an audit log to a normal log, as it does not represent an event which the BRs require us to log and which we are not already logging elsewhere. In other cases, provide a map of key-value pairs to be included in the log message. Finally, update a small number of tests that were expecting very specific log message formats to be emitted. Fixes #8554
1 parent 0c321f7 commit 03244f7

30 files changed

Lines changed: 260 additions & 197 deletions

ca/ca.go

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,7 @@ func (ca *certificateAuthorityImpl) IssueCertificate(ctx context.Context, req *c
312312

313313
lintPrecertDER, issuanceToken, err := issuer.Prepare(profile, precertReq)
314314
if err != nil {
315-
ca.log.AuditErrf("Preparing precert failed: serial=[%s] err=[%v]", serialHex, err)
315+
ca.log.AuditErr("Preparing precert failed", err, map[string]any{"serial": serialHex})
316316
if errors.Is(err, linter.ErrLinting) {
317317
ca.metrics.lintErrorCount.Inc()
318318
}
@@ -334,7 +334,7 @@ func (ca *certificateAuthorityImpl) IssueCertificate(ctx context.Context, req *c
334334
return nil, fmt.Errorf("persisting linting precert to database: %w", err)
335335
}
336336

337-
ca.log.AuditObject("Signing precert", issuanceEvent{
337+
ca.log.AuditInfo("Signing precert", issuanceEvent{
338338
Requester: req.RegistrationID,
339339
OrderID: req.OrderID,
340340
Profile: req.CertProfileName,
@@ -346,12 +346,12 @@ func (ca *certificateAuthorityImpl) IssueCertificate(ctx context.Context, req *c
346346
precertDER, err := issuer.Issue(issuanceToken)
347347
if err != nil {
348348
ca.metrics.noteSignError(err)
349-
ca.log.AuditErrf("Signing precert failed: serial=[%s] err=[%v]", serialHex, err)
349+
ca.log.AuditErr("Signing precert failed", err, map[string]any{"serial": serialHex})
350350
return nil, fmt.Errorf("failed to sign precertificate: %w", err)
351351
}
352352
ca.metrics.signatureCount.With(prometheus.Labels{"purpose": string(precertType), "issuer": issuer.Name()}).Inc()
353353

354-
ca.log.AuditObject("Signing precert success", issuanceEvent{
354+
ca.log.AuditInfo("Signing precert success", issuanceEvent{
355355
Requester: req.RegistrationID,
356356
OrderID: req.OrderID,
357357
Profile: req.CertProfileName,
@@ -408,11 +408,11 @@ func (ca *certificateAuthorityImpl) IssueCertificate(ctx context.Context, req *c
408408

409409
lintCertDER, issuanceToken, err := issuer.Prepare(profile, certReq)
410410
if err != nil {
411-
ca.log.AuditErrf("Preparing cert failed: serial=[%s] err=[%v]", serialHex, err)
411+
ca.log.AuditErr("Preparing cert failed", err, map[string]any{"serial": serialHex})
412412
return nil, fmt.Errorf("failed to prepare certificate signing: %w", err)
413413
}
414414

415-
ca.log.AuditObject("Signing cert", issuanceEvent{
415+
ca.log.AuditInfo("Signing cert", issuanceEvent{
416416
Requester: req.RegistrationID,
417417
OrderID: req.OrderID,
418418
Profile: req.CertProfileName,
@@ -423,13 +423,13 @@ func (ca *certificateAuthorityImpl) IssueCertificate(ctx context.Context, req *c
423423
certDER, err := issuer.Issue(issuanceToken)
424424
if err != nil {
425425
ca.metrics.noteSignError(err)
426-
ca.log.AuditErrf("Signing cert failed: serial=[%s] err=[%v]", serialHex, err)
426+
ca.log.AuditErr("Signing cert failed", err, map[string]any{"serial": serialHex})
427427
return nil, fmt.Errorf("failed to sign certificate: %w", err)
428428
}
429429
ca.metrics.signatureCount.With(prometheus.Labels{"purpose": string(certType), "issuer": issuer.Name()}).Inc()
430430
ca.metrics.certificates.With(prometheus.Labels{"profile": req.CertProfileName}).Inc()
431431

432-
ca.log.AuditObject("Signing cert success", issuanceEvent{
432+
ca.log.AuditInfo("Signing cert success", issuanceEvent{
433433
Requester: req.RegistrationID,
434434
OrderID: req.OrderID,
435435
Profile: req.CertProfileName,
@@ -449,7 +449,7 @@ func (ca *certificateAuthorityImpl) IssueCertificate(ctx context.Context, req *c
449449
Issued: timestamppb.New(ca.clk.Now()),
450450
})
451451
if err != nil {
452-
ca.log.AuditErrf("Failed RPC to store at SA: serial=[%s] err=[%v]", serialHex, err)
452+
ca.log.AuditErr("Storing cert failed", err, map[string]any{"serial": serialHex})
453453
return nil, fmt.Errorf("persisting cert to database: %w", err)
454454
}
455455

@@ -491,7 +491,7 @@ func (ca *certificateAuthorityImpl) generateSerialNumber() (*big.Int, error) {
491491
_, err := rand.Read(serialBytes[1:])
492492
if err != nil {
493493
err = berrors.InternalServerError("failed to generate serial: %s", err)
494-
ca.log.AuditErrf("Serial randomness failed, err=[%v]", err)
494+
ca.log.AuditErr("Serial randomness failed", err, nil)
495495
return nil, err
496496
}
497497
serialBigInt := big.NewInt(0)

ca/crl.go

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"fmt"
88
"io"
99
"strings"
10+
"time"
1011

1112
"google.golang.org/grpc"
1213

@@ -117,28 +118,32 @@ func (ci *crlImpl) GenerateCRL(stream grpc.BidiStreamingServer[capb.GenerateCRLR
117118
// Compute a unique ID for this issuer-number-shard combo, to tie together all
118119
// the audit log lines related to its issuance.
119120
logID := blog.LogLineChecksum(fmt.Sprintf("%d", issuer.NameID()) + req.Number.String() + fmt.Sprintf("%d", req.Shard))
120-
ci.log.AuditInfof(
121-
"Signing CRL: logID=[%s] issuer=[%s] number=[%s] shard=[%d] thisUpdate=[%s] numEntries=[%d]",
122-
logID, issuer.Cert.Subject.CommonName, req.Number.String(), req.Shard, req.ThisUpdate, len(rcs),
123-
)
121+
ci.log.AuditInfo("Signing CRL", map[string]any{
122+
"logID": logID,
123+
"issuer": issuer.Cert.Subject.CommonName,
124+
"number": req.Number.String(),
125+
"shard": req.Shard,
126+
"thisUpdate": req.ThisUpdate.Format(time.RFC3339),
127+
"numEntries": len(rcs),
128+
})
124129

125130
if len(rcs) > 0 {
126131
builder := strings.Builder{}
127132
for i := range len(rcs) {
128-
if builder.Len() == 0 {
129-
fmt.Fprintf(&builder, "Signing CRL: logID=[%s] entries=[", logID)
130-
}
131-
132-
fmt.Fprintf(&builder, "%x:%d,", rcs[i].SerialNumber.Bytes(), rcs[i].ReasonCode)
133+
fmt.Fprintf(&builder, "\"%x:%d\",", rcs[i].SerialNumber.Bytes(), rcs[i].ReasonCode)
133134

134135
if builder.Len() >= ci.maxLogLen {
135-
fmt.Fprint(&builder, "]")
136-
ci.log.AuditInfo(builder.String())
136+
ci.log.AuditInfo("Signing CRL entries", map[string]string{
137+
"logID": logID,
138+
"entries": fmt.Sprintf("[%s]", strings.TrimSuffix(builder.String(), ",")),
139+
})
137140
builder = strings.Builder{}
138141
}
139142
}
140-
fmt.Fprint(&builder, "]")
141-
ci.log.AuditInfo(builder.String())
143+
ci.log.AuditInfo("Signing CRL entries", map[string]any{
144+
"logID": logID,
145+
"entries": fmt.Sprintf("[%s]", strings.TrimSuffix(builder.String(), ",")),
146+
})
142147
}
143148

144149
req.Entries = rcs
@@ -151,10 +156,11 @@ func (ci *crlImpl) GenerateCRL(stream grpc.BidiStreamingServer[capb.GenerateCRLR
151156
ci.metrics.signatureCount.With(prometheus.Labels{"purpose": "crl", "issuer": issuer.Name()}).Inc()
152157

153158
hash := sha256.Sum256(crlBytes)
154-
ci.log.AuditInfof(
155-
"Signing CRL success: logID=[%s] size=[%d] hash=[%x]",
156-
logID, len(crlBytes), hash,
157-
)
159+
ci.log.AuditInfo("Signing CRL success", map[string]any{
160+
"logID": logID,
161+
"size": len(crlBytes),
162+
"hash": fmt.Sprintf("%x", hash),
163+
})
158164

159165
for i := 0; i < len(crlBytes); i += 1000 {
160166
j := min(i+1000, len(crlBytes))

cmd/admin/key.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ func (a *admin) spkiHashFromCSRPEM(filename string, checkSignature bool, expecte
180180
return nil, fmt.Errorf("no PEM data found in %q", filename)
181181
}
182182

183-
a.log.AuditInfof("Parsing key to block from CSR PEM: %x", data)
183+
a.log.Debugf("Parsing key to block from CSR PEM: %x", data)
184184

185185
csr, err := x509.ParseCertificateRequest(data.Bytes)
186186
if err != nil {

cmd/admin/main.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -131,18 +131,18 @@ func main() {
131131

132132
// Finally, run the selected subcommand.
133133
if *dryRun {
134-
a.log.AuditInfof("admin tool executing a dry-run with the following arguments: %q", strings.Join(os.Args, " "))
134+
a.log.Infof("admin tool executing a dry-run with the following arguments: %q", strings.Join(os.Args, " "))
135135
} else {
136-
a.log.AuditInfof("admin tool executing with the following arguments: %q", strings.Join(os.Args, " "))
136+
a.log.AuditInfo("admin tool beginning execution", map[string]any{"cmd": strings.Join(os.Args, " ")})
137137
}
138138

139139
err = subcommand.Run(context.Background(), a)
140140
cmd.FailOnError(err, "executing subcommand")
141141

142142
if *dryRun {
143-
a.log.AuditInfof("admin tool has successfully completed executing a dry-run with the following arguments: %q", strings.Join(os.Args, " "))
143+
a.log.Infof("admin tool has successfully completed executing a dry-run with the following arguments: %q", strings.Join(os.Args, " "))
144144
a.log.Info("Dry run complete. Pass -dry-run=false to mutate the database.")
145145
} else {
146-
a.log.AuditInfof("admin tool has successfully completed executing with the following arguments: %q", strings.Join(os.Args, " "))
146+
a.log.AuditInfo("admin tool completed successfully", map[string]any{"cmd": strings.Join(os.Args, " ")})
147147
}
148148
}

cmd/admin/overrides_import.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@ import (
77
"fmt"
88
"sync"
99

10+
"google.golang.org/protobuf/types/known/durationpb"
11+
1012
"github.com/letsencrypt/boulder/ratelimits"
1113
sapb "github.com/letsencrypt/boulder/sa/proto"
12-
"google.golang.org/protobuf/types/known/durationpb"
1314
)
1415

1516
type subcommandImportOverrides struct {
@@ -70,7 +71,7 @@ func (c *subcommandImportOverrides) Run(ctx context.Context, a *admin) error {
7071
for range overrideCount {
7172
result := <-results
7273
if result.err != nil {
73-
a.log.AuditErrf("failed to add override: key=%q limit=%d: %s", result.ov.BucketKey, result.ov.LimitEnum, result.err)
74+
a.log.Errf("failed to add override: key=%q limit=%d: %s", result.ov.BucketKey, result.ov.LimitEnum, result.err)
7475
errorCount++
7576
}
7677
}

cmd/bad-key-revoker/main.go

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -194,21 +194,29 @@ func (bkr *badKeyRevoker) revokeCerts(certs []unrevokedCertificate) error {
194194

195195
// invoke exits early and returns true if there is no work to be done.
196196
// Otherwise, it processes a single key in the blockedKeys table and returns false.
197-
func (bkr *badKeyRevoker) invoke(ctx context.Context) (bool, error) {
197+
func (bkr *badKeyRevoker) invoke(ctx context.Context) (work bool, err error) {
198+
logEvent := make(map[string]any)
199+
defer func() {
200+
if err != nil {
201+
bkr.logger.AuditErr("Error while processing bad key", err, logEvent)
202+
} else {
203+
bkr.logger.AuditInfo("Processed bad key", logEvent)
204+
}
205+
}()
206+
198207
// Gather a count of rows to be processed.
199208
uncheckedCount, err := bkr.countUncheckedKeys(ctx)
200209
if err != nil {
201210
return false, err
202211
}
212+
logEvent["keysToProcess"] = uncheckedCount
203213

204214
// Set the gauge to the number of rows to be processed (max:
205215
// blockedKeysGaugeLimit).
206216
bkr.keysToProcess.Set(float64(uncheckedCount))
207217

208218
if uncheckedCount >= blockedKeysGaugeLimit {
209-
bkr.logger.AuditInfof("found >= %d unchecked blocked keys left to process", uncheckedCount)
210-
} else {
211-
bkr.logger.AuditInfof("found %d unchecked blocked keys left to process", uncheckedCount)
219+
logEvent["keysToProcessOverflow"] = true
212220
}
213221

214222
// select a row to process
@@ -219,17 +227,17 @@ func (bkr *badKeyRevoker) invoke(ctx context.Context) (bool, error) {
219227
}
220228
return false, err
221229
}
222-
bkr.logger.AuditInfof("found unchecked block key to work on: %s", unchecked)
230+
logEvent["keyHash"] = fmt.Sprintf("%x", unchecked.KeyHash)
231+
logEvent["revokedBy"] = unchecked.RevokedBy
223232

224233
// select all unrevoked, unexpired serials associated with the blocked key hash
225234
unrevokedCerts, err := bkr.findUnrevoked(ctx, unchecked)
226235
if err != nil {
227-
bkr.logger.AuditInfof("finding unrevoked certificates related to %s: %s", unchecked, err)
228236
return false, err
229237
}
238+
logEvent["certsToProcess"] = len(unrevokedCerts)
239+
230240
if len(unrevokedCerts) == 0 {
231-
bkr.logger.AuditInfof("found no certificates that need revoking related to %s, marking row as checked", unchecked)
232-
// mark row as checked
233241
err = bkr.markRowChecked(ctx, unchecked)
234242
if err != nil {
235243
return false, err
@@ -241,7 +249,7 @@ func (bkr *badKeyRevoker) invoke(ctx context.Context) (bool, error) {
241249
for _, cert := range unrevokedCerts {
242250
serials = append(serials, cert.Serial)
243251
}
244-
bkr.logger.AuditInfof("revoking serials %v for key with hash %x", serials, unchecked.KeyHash)
252+
logEvent["serials"] = serials
245253

246254
// revoke each certificate
247255
err = bkr.revokeCerts(unrevokedCerts)
@@ -384,13 +392,11 @@ func main() {
384392
noWork, err := bkr.invoke(context.Background())
385393
if err != nil {
386394
keysProcessed.WithLabelValues("error").Inc()
387-
logger.AuditErrf("failed to process blockedKeys row: %s", err)
388395
// Calculate and sleep for a backoff interval
389396
bkr.backoff()
390397
continue
391398
}
392399
if noWork {
393-
logger.Info("no work to do")
394400
// Calculate and sleep for a backoff interval
395401
bkr.backoff()
396402
} else {

cmd/cert-checker/main.go

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,11 @@ func (c *certChecker) findStartingID(ctx context.Context, begin, end time.Time)
172172
},
173173
)
174174
if err != nil {
175-
c.logger.AuditErrf("finding starting certificate: %s", err)
175+
c.logger.AuditErr("finding starting certificate", err, map[string]any{
176+
"begin": queryBegin.Format(time.RFC3339),
177+
"end": queryEnd.Format(time.RFC3339),
178+
"attempt": retries + 1,
179+
})
176180
retries++
177181
time.Sleep(core.RetryBackoff(retries, time.Second, time.Minute, 2))
178182
continue
@@ -234,7 +238,12 @@ func (c *certChecker) getCerts(ctx context.Context) error {
234238
},
235239
)
236240
if err != nil {
237-
c.logger.AuditErrf("selecting certificates: %s", err)
241+
c.logger.AuditErr("selecting certificates", err, map[string]any{
242+
"begin": c.issuedReport.begin.Format(time.RFC3339),
243+
"end": c.issuedReport.end.Format(time.RFC3339),
244+
"batchStartID": batchStartID,
245+
"attempt": retries + 1,
246+
})
238247
retries++
239248
time.Sleep(core.RetryBackoff(retries, time.Second, time.Minute, 2))
240249
continue
@@ -519,7 +528,7 @@ func (c *certChecker) checkCert(ctx context.Context, cert *corepb.Certificate) (
519528
for _, ident := range idents {
520529
identValues = append(identValues, ident.Value)
521530
}
522-
c.logger.AuditErrf("Certificate %s %s: %s", cert.Serial, identValues, err)
531+
c.logger.Warningf("Certificate %s %s: %s", cert.Serial, identValues, err)
523532
}
524533
}
525534
}

cmd/crl-checker/main.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -139,9 +139,12 @@ func main() {
139139
cmd.Fail(fmt.Sprintf("Encountered %d errors", errCount))
140140
}
141141

142-
logger.AuditInfof(
143-
"Validated %d CRLs, %d serials, %d bytes. Oldest CRL: %s",
144-
len(urls), len(seenSerials), totalBytes, oldestTimestamp.Format(time.RFC3339))
142+
logger.AuditInfo("CRL checking complete", map[string]string{
143+
"numCRLs": fmt.Sprintf("%d", len(urls)),
144+
"numSerials": fmt.Sprintf("%d", len(seenSerials)),
145+
"numBytes": fmt.Sprintf("%d", totalBytes),
146+
"oldestCRL": oldestTimestamp.Format(time.RFC3339),
147+
})
145148
}
146149

147150
func init() {

cmd/shell.go

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -304,10 +304,7 @@ func newStatsRegistry(addr string, logger blog.Logger) prometheus.Registerer {
304304
}
305305
go func() {
306306
err := server.ListenAndServe()
307-
if err != nil {
308-
logger.AuditErrf("Unable to boot debug server on %s: %v", addr, err)
309-
os.Exit(1)
310-
}
307+
FailOnError(err, "Unable to boot debug server")
311308
}()
312309
return registry
313310
}
@@ -362,7 +359,7 @@ func AuditPanic() {
362359
err := recover()
363360
// No panic, no problem
364361
if err == nil {
365-
blog.Get().AuditObject("Process exiting normally", info())
362+
blog.Get().AuditInfo("Process exiting normally", info())
366363
return
367364
}
368365
// Get the global logger if it's initialized, or create a default one if not.
@@ -372,12 +369,14 @@ func AuditPanic() {
372369
// For the special type `failure`, audit log the message and exit quietly
373370
fail, ok := err.(failure)
374371
if ok {
375-
log.AuditErr(fail.msg)
372+
log.AuditErr(fail.msg, nil, nil)
376373
} else {
377-
// For all other values passed to `panic`, log them and a stack trace
378-
log.AuditErrf("Panic caused by err: %s", err)
379-
380-
log.AuditErrf("Stack Trace (Current goroutine) %s", debug.Stack())
374+
// For all other values (which might not be an error) passed to `panic`, log
375+
// them and a stack trace
376+
log.AuditErr("Panic", nil, map[string]any{
377+
"panic": fmt.Sprintf("%#v", err),
378+
"stack": string(debug.Stack()),
379+
})
381380
}
382381
// Because this function is deferred as early as possible, there's no further defers to run after this one
383382
// So it is safe to os.Exit to set the exit code and exit without losing any defers we haven't executed.
@@ -550,7 +549,7 @@ func info() buildInfo {
550549
}
551550

552551
func LogStartup(logger blog.Logger) {
553-
logger.AuditObject("Process starting", info())
552+
logger.AuditInfo("Process starting", info())
554553
}
555554

556555
// CatchSignals blocks until a SIGTERM, SIGINT, or SIGHUP is received, then

cmd/shell_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,6 @@ func TestPanicStackTrace(t *testing.T) {
309309
output, err := cmd.CombinedOutput()
310310
test.AssertError(t, err, "running a failing program")
311311
test.AssertContains(t, string(output), "nil pointer dereference")
312-
test.AssertContains(t, string(output), "Stack Trace")
312+
test.AssertContains(t, string(output), "runtime/debug.Stack()")
313313
test.AssertContains(t, string(output), "cmd/shell_test.go:")
314314
}

0 commit comments

Comments
 (0)