Skip to content

Commit

Permalink
feat: add json loggings
Browse files Browse the repository at this point in the history
  • Loading branch information
fritterhoff committed Jan 2, 2024
1 parent a0bdd2f commit aaa709f
Show file tree
Hide file tree
Showing 6 changed files with 78 additions and 2 deletions.
5 changes: 4 additions & 1 deletion cmd/certspotter/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ func main() {
startAtEnd bool
stateDir string
stdout bool
jsonLog bool
verbose bool
version bool
watchlist string
Expand All @@ -175,6 +176,7 @@ func main() {
flag.StringVar(&flags.script, "script", "", "Program to execute when a matching certificate is discovered")
flag.BoolVar(&flags.startAtEnd, "start_at_end", false, "Start monitoring logs from the end rather than the beginning (saves considerable bandwidth)")
flag.StringVar(&flags.stateDir, "state_dir", defaultStateDir(), "Directory for storing log position and discovered certificates")
flag.BoolVar(&flags.jsonLog, "jsonLog", false, "Write matching certificates to stdout in JSON format")
flag.BoolVar(&flags.stdout, "stdout", false, "Write matching certificates to stdout")
flag.BoolVar(&flags.verbose, "verbose", false, "Be verbose")
flag.BoolVar(&flags.version, "version", false, "Print version and exit")
Expand All @@ -200,6 +202,7 @@ func main() {
ScriptDir: defaultScriptDir(),
Email: flags.email,
Stdout: flags.stdout,
JsonLog: flags.jsonLog,
HealthCheckInterval: flags.healthcheck,
}

Expand All @@ -212,7 +215,7 @@ func main() {
os.Exit(1)
}

if len(config.Email) == 0 && !emailFileExists && config.Script == "" && !fileExists(config.ScriptDir) && config.Stdout == false {
if len(config.Email) == 0 && !emailFileExists && config.Script == "" && !fileExists(config.ScriptDir) && !config.Stdout {
fmt.Fprintf(os.Stderr, "%s: no notification methods were specified\n", programName)
fmt.Fprintf(os.Stderr, "Please specify at least one of the following notification methods:\n")
fmt.Fprintf(os.Stderr, " - Place one or more email addresses in %s (one address per line)\n", defaultEmailFile())
Expand Down
1 change: 1 addition & 0 deletions monitor/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,6 @@ type Config struct {
ScriptDir string
Email []string
Stdout bool
JsonLog bool
HealthCheckInterval time.Duration
}
37 changes: 37 additions & 0 deletions monitor/discoveredcert.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ package monitor
import (
"bytes"
"encoding/hex"
"encoding/json"
"encoding/pem"
"fmt"
"net"
"strings"
"time"

Expand Down Expand Up @@ -130,6 +132,41 @@ func (cert *discoveredCert) Environ() []string {
return env
}

func (cert *discoveredCert) Json() string {
log := struct {
Sha256 string
DNSNames []string
IPs []net.IP
Issuer string
Pubkey string
NotBefore string
NotAfter string
}{
Sha256: hex.EncodeToString(cert.SHA256[:]),
DNSNames: cert.Identifiers.DNSNames,
IPs: cert.Identifiers.IPAddrs,
Issuer: cert.Info.Issuer.String(),
Pubkey: hex.EncodeToString(cert.PubkeySHA256[:]),
}
if cert.Info.IssuerParseError == nil {
log.Issuer = cert.Info.Issuer.String()
} else {
log.Issuer = fmt.Sprintf("[unable to parse: %s]", cert.Info.IssuerParseError)
}
if cert.Info.ValidityParseError == nil {
log.NotBefore = cert.Info.Validity.NotBefore.String()
log.NotAfter = cert.Info.Validity.NotAfter.String()
} else {
log.NotBefore = fmt.Sprintf("[unable to parse: %s]", cert.Info.ValidityParseError)
log.NotAfter = fmt.Sprintf("[unable to parse: %s]", cert.Info.ValidityParseError)
}
json, err := json.Marshal(log)
if err != nil {
fmt.Printf("error marshaling log entry: %s\n", err)
}
return string(json)
}

func (cert *discoveredCert) Text() string {
// TODO-4: improve the output: include WatchItem, indicate hash algorithm used for fingerprints, ... (look at SSLMate email for inspiration)

Expand Down
10 changes: 10 additions & 0 deletions monitor/healthcheck.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,16 @@ func (e *staleLogListEvent) Summary() string {
return fmt.Sprintf("Unable to retrieve log list since %s", e.LastSuccess)
}

func (cert *staleLogListEvent) Json() string {
return ""
}
func (cert *backlogEvent) Json() string {
return ""
}
func (cert *staleSTHEvent) Json() string {
return ""
}

func (e *staleSTHEvent) Text() string {
text := new(strings.Builder)
fmt.Fprintf(text, "certspotter has been unable to contact %s since %s. Consequentially, certspotter may fail to notify you about certificates in this log.\n", e.Log.URL, e.LastSuccess)
Expand Down
17 changes: 17 additions & 0 deletions monitor/malformed.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
package monitor

import (
"encoding/json"
"fmt"
"strings"
)
Expand Down Expand Up @@ -55,6 +56,22 @@ func (malformed *malformedLogEntry) Environ() []string {
}
}

func (cert *malformedLogEntry) Json() string {
json, err := json.Marshal(struct {
LogEntry string
LeafHash string
Error string
}{
LogEntry: fmt.Sprintf("%d @ %s", cert.Entry.Index, cert.Entry.Log.URL),
LeafHash: cert.Entry.LeafHash.Base64String(),
Error: cert.Error,
})
if err != nil {
fmt.Printf("error marshaling malformed log entry: %s\n", err)
}
return string(json)
}

func (malformed *malformedLogEntry) Text() string {
text := new(strings.Builder)
writeField := func(name string, value any) { fmt.Fprintf(text, "\t%13s = %s\n", name, value) }
Expand Down
10 changes: 9 additions & 1 deletion monitor/notify.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,14 @@ type notification interface {
Environ() []string
Summary() string
Text() string
Json() string
}

func notify(ctx context.Context, config *Config, notif notification) error {
if config.Stdout {
if config.Stdout && !config.JsonLog {
writeToStdout(notif)
} else if config.JsonLog {
writeJsonToStdout(notif)
}

if len(config.Email) > 0 {
Expand All @@ -56,6 +59,11 @@ func notify(ctx context.Context, config *Config, notif notification) error {

return nil
}
func writeJsonToStdout(notif notification) {
stdoutMu.Lock()
defer stdoutMu.Unlock()
os.Stdout.WriteString(notif.Json() + "\n")
}

func writeToStdout(notif notification) {
stdoutMu.Lock()
Expand Down

0 comments on commit aaa709f

Please sign in to comment.