// StatsAndLogging constructs a Statter and an AuditLogger based on its config // parameters, and return them both. Crashes if any setup fails. // Also sets the constructed AuditLogger as the default logger. func StatsAndLogging(statConf StatsdConfig, logConf SyslogConfig) (metrics.Statter, blog.Logger) { stats, err := metrics.NewStatter(statConf.Server, statConf.Prefix) FailOnError(err, "Couldn't connect to statsd") tag := path.Base(os.Args[0]) syslogger, err := syslog.Dial( "", "", syslog.LOG_INFO|syslog.LOG_LOCAL0, // default, overridden by log calls tag) FailOnError(err, "Could not connect to Syslog") stdoutLoglevel := int(syslog.LOG_DEBUG) if logConf.StdoutLevel != nil { stdoutLoglevel = *logConf.StdoutLevel } syslogLogLevel := int(syslog.LOG_DEBUG) if logConf.SyslogLevel != nil { syslogLogLevel = *logConf.SyslogLevel } logger, err := blog.New(syslogger, stdoutLoglevel, syslogLogLevel) FailOnError(err, "Could not connect to Syslog") _ = blog.Set(logger) cfsslLog.SetLogger(cfsslLogger{logger}) _ = mysql.SetLogger(mysqlLogger{logger}) return stats, logger }
// StatsAndLogging constructs a Statter and an AuditLogger based on its config // parameters, and return them both. Crashes if any setup fails. // Also sets the constructed AuditLogger as the default logger, and configures // the cfssl, mysql, and grpc packages to use our logger. // This must be called before any gRPC code is called, because gRPC's SetLogger // doesn't use any locking. func StatsAndLogging(statConf StatsdConfig, logConf SyslogConfig) (metrics.Statter, blog.Logger) { stats, err := metrics.NewStatter(statConf.Server, statConf.Prefix) FailOnError(err, "Couldn't connect to statsd") tag := path.Base(os.Args[0]) syslogger, err := syslog.Dial( "", "", syslog.LOG_INFO, // default, not actually used tag) FailOnError(err, "Could not connect to Syslog") syslogLevel := int(syslog.LOG_INFO) if logConf.SyslogLevel != 0 { syslogLevel = logConf.SyslogLevel } logger, err := blog.New(syslogger, logConf.StdoutLevel, syslogLevel) FailOnError(err, "Could not connect to Syslog") _ = blog.Set(logger) cfsslLog.SetLogger(cfsslLogger{logger}) _ = mysql.SetLogger(mysqlLogger{logger}) grpclog.SetLogger(grpcLogger{logger}) return stats, logger }
func main() { app := cli.NewApp() app.Name = "cert-checker" app.Usage = "Checks validity of issued certificates stored in the database" app.Version = cmd.Version() app.Author = "Boulder contributors" app.Email = "*****@*****.**" app.Flags = []cli.Flag{ cli.IntFlag{ Name: "workers", Value: runtime.NumCPU(), Usage: "The number of concurrent workers used to process certificates", }, cli.BoolFlag{ Name: "unexpired-only", Usage: "Only check currently unexpired certificates", }, cli.BoolFlag{ Name: "bad-results-only", Usage: "Only collect and display bad results", }, cli.StringFlag{ Name: "db-connect", Usage: "SQL URI if not provided in the configuration file", }, cli.StringFlag{ Name: "check-period", Value: "2160h", Usage: "How far back to check", }, cli.StringFlag{ Name: "config", Value: "config.json", Usage: "Path to configuration file", }, } app.Action = func(c *cli.Context) { configPath := c.GlobalString("config") if configPath == "" { fmt.Fprintln(os.Stderr, "--config is required") os.Exit(1) } configBytes, err := ioutil.ReadFile(configPath) cmd.FailOnError(err, "Failed to read config file") var config cmd.Config err = json.Unmarshal(configBytes, &config) cmd.FailOnError(err, "Failed to parse config file") stats, err := metrics.NewStatter(config.Statsd.Server, config.Statsd.Prefix) cmd.FailOnError(err, "Failed to create StatsD client") syslogger, err := syslog.Dial("", "", syslog.LOG_INFO|syslog.LOG_LOCAL0, "") cmd.FailOnError(err, "Failed to dial syslog") logger, err := blog.New(syslogger, 0, 0) cmd.FailOnError(err, "Failed to construct logger") err = blog.Set(logger) cmd.FailOnError(err, "Failed to set audit logger") if connect := c.GlobalString("db-connect"); connect != "" { config.CertChecker.DBConnect = connect } if workers := c.GlobalInt("workers"); workers != 0 { config.CertChecker.Workers = workers } config.CertChecker.UnexpiredOnly = c.GlobalBool("valid-only") config.CertChecker.BadResultsOnly = c.GlobalBool("bad-results-only") if cp := c.GlobalString("check-period"); cp != "" { config.CertChecker.CheckPeriod.Duration, err = time.ParseDuration(cp) cmd.FailOnError(err, "Failed to parse check period") } // Validate PA config and set defaults if needed cmd.FailOnError(config.PA.CheckChallenges(), "Invalid PA configuration") saDbURL, err := config.CertChecker.DBConfig.URL() cmd.FailOnError(err, "Couldn't load DB URL") saDbMap, err := sa.NewDbMap(saDbURL, config.CertChecker.DBConfig.MaxDBConns) cmd.FailOnError(err, "Could not connect to database") go sa.ReportDbConnCount(saDbMap, metrics.NewStatsdScope(stats, "CertChecker")) pa, err := policy.New(config.PA.Challenges) cmd.FailOnError(err, "Failed to create PA") err = pa.SetHostnamePolicyFile(config.CertChecker.HostnamePolicyFile) cmd.FailOnError(err, "Failed to load HostnamePolicyFile") checker := newChecker( saDbMap, clock.Default(), pa, config.CertChecker.CheckPeriod.Duration, ) fmt.Fprintf(os.Stderr, "# Getting certificates issued in the last %s\n", config.CertChecker.CheckPeriod) // Since we grab certificates in batches we don't want this to block, when it // is finished it will close the certificate channel which allows the range // loops in checker.processCerts to break go func() { err = checker.getCerts(config.CertChecker.UnexpiredOnly) cmd.FailOnError(err, "Batch retrieval of certificates failed") }() fmt.Fprintf(os.Stderr, "# Processing certificates using %d workers\n", config.CertChecker.Workers) wg := new(sync.WaitGroup) for i := 0; i < config.CertChecker.Workers; i++ { wg.Add(1) go func() { s := checker.clock.Now() checker.processCerts(wg, config.CertChecker.BadResultsOnly) stats.TimingDuration("certChecker.processingLatency", time.Since(s), 1.0) }() } wg.Wait() fmt.Fprintf( os.Stderr, "# Finished processing certificates, sample: %d, good: %d, bad: %d\n", len(checker.issuedReport.Entries), checker.issuedReport.GoodCerts, checker.issuedReport.BadCerts, ) err = checker.issuedReport.dump() cmd.FailOnError(err, "Failed to dump results: %s\n") } err := app.Run(os.Args) cmd.FailOnError(err, "Failed to run application") }
func main() { configFile := flag.String("config", "", "File path to the configuration file for this service") workers := flag.Int("workers", runtime.NumCPU(), "The number of concurrent workers used to process certificates") badResultsOnly := flag.Bool("bad-results-only", false, "Only collect and display bad results") connect := flag.String("db-connect", "", "SQL URI if not provided in the configuration file") cp := flag.Duration("check-period", time.Hour*2160, "How far back to check") unexpiredOnly := flag.Bool("unexpired-only", false, "Only check currently unexpired certificates") flag.Parse() if *configFile == "" { flag.Usage() os.Exit(1) } var config config err := cmd.ReadConfigFile(*configFile, &config) cmd.FailOnError(err, "Reading JSON config file into config structure") err = features.Set(config.CertChecker.Features) cmd.FailOnError(err, "Failed to set feature flags") stats, err := metrics.NewStatter(config.Statsd.Server, config.Statsd.Prefix) cmd.FailOnError(err, "Failed to create StatsD client") syslogger, err := syslog.Dial("", "", syslog.LOG_INFO|syslog.LOG_LOCAL0, "") cmd.FailOnError(err, "Failed to dial syslog") logger, err := blog.New(syslogger, 0, 0) cmd.FailOnError(err, "Failed to construct logger") err = blog.Set(logger) cmd.FailOnError(err, "Failed to set audit logger") if *connect != "" { config.CertChecker.DBConnect = *connect } if *workers != 0 { config.CertChecker.Workers = *workers } config.CertChecker.UnexpiredOnly = *unexpiredOnly config.CertChecker.BadResultsOnly = *badResultsOnly config.CertChecker.CheckPeriod.Duration = *cp // Validate PA config and set defaults if needed cmd.FailOnError(config.PA.CheckChallenges(), "Invalid PA configuration") saDbURL, err := config.CertChecker.DBConfig.URL() cmd.FailOnError(err, "Couldn't load DB URL") saDbMap, err := sa.NewDbMap(saDbURL, config.CertChecker.DBConfig.MaxDBConns) cmd.FailOnError(err, "Could not connect to database") go sa.ReportDbConnCount(saDbMap, metrics.NewStatsdScope(stats, "CertChecker")) pa, err := policy.New(config.PA.Challenges) cmd.FailOnError(err, "Failed to create PA") err = pa.SetHostnamePolicyFile(config.CertChecker.HostnamePolicyFile) cmd.FailOnError(err, "Failed to load HostnamePolicyFile") checker := newChecker( saDbMap, clock.Default(), pa, config.CertChecker.CheckPeriod.Duration, ) fmt.Fprintf(os.Stderr, "# Getting certificates issued in the last %s\n", config.CertChecker.CheckPeriod) // Since we grab certificates in batches we don't want this to block, when it // is finished it will close the certificate channel which allows the range // loops in checker.processCerts to break go func() { err = checker.getCerts(config.CertChecker.UnexpiredOnly) cmd.FailOnError(err, "Batch retrieval of certificates failed") }() fmt.Fprintf(os.Stderr, "# Processing certificates using %d workers\n", config.CertChecker.Workers) wg := new(sync.WaitGroup) for i := 0; i < config.CertChecker.Workers; i++ { wg.Add(1) go func() { s := checker.clock.Now() checker.processCerts(wg, config.CertChecker.BadResultsOnly) stats.TimingDuration("certChecker.processingLatency", time.Since(s), 1.0) }() } wg.Wait() fmt.Fprintf( os.Stderr, "# Finished processing certificates, sample: %d, good: %d, bad: %d\n", len(checker.issuedReport.Entries), checker.issuedReport.GoodCerts, checker.issuedReport.BadCerts, ) err = checker.issuedReport.dump() cmd.FailOnError(err, "Failed to dump results: %s\n") }