package main import ( "context" "crypto/x509" "encoding/pem" "net/mail" "os" "time" log "github.com/sirupsen/logrus" cli "github.com/urfave/cli/v3" ) func main() { cmd := &cli.Command{ Name: "too-soon", Usage: "check if something expires too soon", Version: "0.1", Authors: []any{ &mail.Address{Name: "Vincent Batts", Address: "vbatts@hashbangbash.com"}, }, DefaultCommand: "pem", Flags: []cli.Flag{ &cli.IntFlag{ Name: "days", Value: 20, Usage: "days within range to alert about", }, &cli.BoolFlag{ Name: "debug", Aliases: []string{"D"}, Value: false, Usage: "output debug verbose info", }, }, Before: func(ctx context.Context, cmd *cli.Command) (context.Context, error) { if cmd.Bool("debug") { log.SetLevel(log.DebugLevel) } return ctx, nil }, Commands: []*cli.Command{ &cli.Command{ Name: "pem", Action: fPEMCheck, }, }, } if err := cmd.Run(context.Background(), os.Args); err != nil { log.Fatal(err) } } func fPEMCheck(ctx context.Context, cmd *cli.Command) error { retCode := 0 for i := 0; i <= cmd.Args().Len(); i++ { file := cmd.Args().Get(i) if file == "" { break } var certs []*x509.Certificate buf, err := os.ReadFile(file) if err != nil { log.Errorf("%q could not be read: %s", file, err) continue } more := true for more { block, rest := pem.Decode(buf) log.Debugf("%q : %s", file, block.Type) cert, err := x509.ParseCertificate(block.Bytes) if err != nil { log.Errorf("%q cert could not be parsed: %s", file, err) continue } certs = append(certs, cert) if len(rest) == 0 { more = false } // reset the buffer if there is more buf = rest } for _, cert := range certs { if len(cert.DNSNames) == 0 { continue } hours := time.Duration(cmd.Int("days") * -24) alertTime := cert.NotAfter.Add(hours * time.Hour) today := time.Now() if today.After(alertTime) { log.Warnf("%q : TIME TO RENEW CERTIFICATE (expires in less than %d days)", file, cmd.Int("days")) log.Infof("%q : %v", file, cert.NotAfter) log.Infof("%q : %v", file, cert.DNSNames) retCode++ } else { log.Debugf("%q : %v", file, cert.NotAfter) log.Debugf("%q : %v", file, cert.DNSNames) } } } if retCode != 0 { return cli.Exit("certificates need to be renewed", retCode) } return nil }