Repository: letsencrypt/boulder

Issue:
Allow different lifetimes for validated authorizations
Right now, all validated authorizations stay in the "valid" state for 30 days, unless they are deactivated. We'd like to lower this for most users to something like 7 hours (in line with how long we treat a CAA check is valid). Some users probably depend on the 30-day lifetime for their workflows. To ease the transition, we should allow migration on a per-account basis. For instance, all new accounts might receive a flag that opts them into the shorter validation lifetime. We could then progressively apply this flag to older accounts that don't explicitly need the longer lifetime, and work with users who depend on the longer lifetime to migrate their workflows.

Discussion:
@beautifulentropy: Update: 

We began tracking order and authz age at creation and finalize time in #6841.

Currently, we're waiting for our SRE team drop some old rows from order and authz2 before we run any live experiments that may result in rapid table growth.@beautifulentropy: Chatted with SRE, freeing up enough database space (by dropping old data) is on the engineering priority list now. They've acknowledged that it's definitely not on the horizon at the moment so I'm going to drop this issue from my plate.@aarongable: We now support different authz lifetimes on a per-profile basis.

Solution patch:
diff --git a/ca/ca.go b/ca/ca.go
index 1f5b60aab..1101e4a8f 100644
--- a/ca/ca.go
+++ b/ca/ca.go
@@ -70,6 +70,7 @@ type certificateAuthorityImpl struct {
 	orphanCount        *prometheus.CounterVec
 	adoptedOrphanCount *prometheus.CounterVec
 	signErrorCount     *prometheus.CounterVec
+	lintErrorCount     prometheus.Counter
 }
 
 // makeIssuerMaps processes a list of issuers into a set of maps, mapping
@@ -149,6 +150,13 @@ func NewCertificateAuthorityImpl(
 		[]string{"type"})
 	stats.MustRegister(adoptedOrphanCount)
 
+	lintErrorCount := prometheus.NewCounter(
+		prometheus.CounterOpts{
+			Name: "lint_errors",
+			Help: "Number of issuances that were halted by linting errors",
+		})
+	stats.MustRegister(lintErrorCount)
+
 	ca = &certificateAuthorityImpl{
 		sa:                 sa,
 		pa:                 pa,
@@ -164,6 +172,7 @@ func NewCertificateAuthorityImpl(
 		orphanCount:        orphanCount,
 		adoptedOrphanCount: adoptedOrphanCount,
 		signErrorCount:     signErrorCount,
+		lintErrorCount:     lintErrorCount,
 		clk:                clk,
 		ecdsaAllowList:     ecdsaAllowList,
 	}
@@ -437,6 +446,9 @@ func (ca *certificateAuthorityImpl) issuePrecertificateInner(ctx context.Context
 	if err != nil {
 		ca.log.AuditErrf("Preparing precert failed: serial=[%s] regID=[%d] names=[%s] err=[%v]",
 			serialHex, issueReq.RegistrationID, strings.Join(csr.DNSNames, ", "), err)
+		if errors.Is(err, issuance.ErrLinting) {
+			ca.lintErrorCount.Inc()
+		}
 		return nil, nil, berrors.InternalServerError("failed to prepare precertificate signing: %s", err)
 	}
 
diff --git a/cmd/admin-revoker/main.go b/cmd/admin-revoker/main.go
index 94a053bbb..375f779f0 100644
--- a/cmd/admin-revoker/main.go
+++ b/cmd/admin-revoker/main.go
@@ -100,7 +100,8 @@ type revoker struct {
 func newRevoker(c Config) *revoker {
 	logger := cmd.NewLogger(c.Syslog)
 
-	tlsConfig, err := c.Revoker.TLS.Load()
+	// TODO(#6840) Rework admin-revoker to export prometheus metrics.
+	tlsConfig, err := c.Revoker.TLS.Load(metrics.NoopRegisterer)
 	cmd.FailOnError(err, "TLS config")
 
 	clk := cmd.Clock()
diff --git a/cmd/akamai-purger/main.go b/cmd/akamai-purger/main.go
index 9f81c8262..f0bc3788c 100644
--- a/cmd/akamai-purger/main.go
+++ b/cmd/akamai-purger/main.go
@@ -366,7 +366,7 @@ func manualPurge(purgeClient *akamai.CachePurgeClient, tag, tagFile string) {
 func daemon(c Config, ap *akamaiPurger, logger blog.Logger, scope prometheus.Registerer) {
 	clk := cmd.Clock()
 
-	tlsConfig, err := c.AkamaiPurger.TLS.Load()
+	tlsConfig, err := c.AkamaiPurger.TLS.Load(scope)
 	cmd.FailOnError(err, "tlsConfig config")
 
 	stop, stopped := make(chan bool, 1), make(chan bool, 1)
diff --git a/cmd/bad-key-revoker/main.go b/cmd/bad-key-revoker/main.go
index 527d4d36a..8fc67e0c7 100644
--- a/cmd/bad-key-revoker/main.go
+++ b/cmd/bad-key-revoker/main.go
@@ -450,7 +450,7 @@ func main() {
 	dbMap, err := sa.InitWrappedDb(config.BadKeyRevoker.DB, scope, logger)
 	cmd.FailOnError(err, "While initializing dbMap")
 
-	tlsConfig, err := config.BadKeyRevoker.TLS.Load()
+	tlsConfig, err := config.BadKeyRevoker.TLS.Load(scope)
 	cmd.FailOnError(err, "TLS config")
 
 	conn, err := bgrpc.ClientSetup(config.BadKeyRevoker.RAService, tlsConfig, scope, clk)
diff --git a/cmd/boulder-ca/main.go b/cmd/boulder-ca/main.go
index 60ce83f20..b9e7125ec 100644
--- a/cmd/boulder-ca/main.go
+++ b/cmd/boulder-ca/main.go
@@ -214,7 +214,7 @@ func main() {
 	boulderIssuers, err = loadBoulderIssuers(c.CA.Issuance.Profile, c.CA.Issuance.Issuers, c.CA.Issuance.IgnoredLints)
 	cmd.FailOnError(err, "Couldn't load issuers")
 
-	tlsConfig, err := c.CA.TLS.Load()
+	tlsConfig, err := c.CA.TLS.Load(scope)
 	cmd.FailOnError(err, "TLS config")
 
 	clk := cmd.Clock()
diff --git a/cmd/boulder-publisher/main.go b/cmd/boulder-publisher/main.go
index 17ddf5964..435c0b7f0 100644
--- a/cmd/boulder-publisher/main.go
+++ b/cmd/boulder-publisher/main.go
@@ -65,7 +65,6 @@ func main() {
 	if c.Publisher.UserAgent == "" {
 		c.Publisher.UserAgent = "certificate-transparency-go/1.0"
 	}
-
 	scope, logger, oTelShutdown := cmd.StatsAndLogging(c.Syslog, c.OpenTelemetry, c.Publisher.DebugAddr)
 	defer oTelShutdown(context.Background())
 	defer logger.AuditPanic()
@@ -88,7 +87,7 @@ func main() {
 		bundles[id] = publisher.GetCTBundleForChain(chain)
 	}
 
-	tlsConfig, err := c.Publisher.TLS.Load()
+	tlsConfig, err := c.Publisher.TLS.Load(scope)
 	cmd.FailOnError(err, "TLS config")
 
 	clk := cmd.Clock()
diff --git a/cmd/boulder-ra/main.go b/cmd/boulder-ra/main.go
index f6f85d31e..e0018519b 100644
--- a/cmd/boulder-ra/main.go
+++ b/cmd/boulder-ra/main.go
@@ -140,7 +140,7 @@ func main() {
 	err = pa.SetHostnamePolicyFile(c.RA.HostnamePolicyFile)
 	cmd.FailOnError(err, "Couldn't load hostname policy file")
 
-	tlsConfig, err := c.RA.TLS.Load()
+	tlsConfig, err := c.RA.TLS.Load(scope)
 	cmd.FailOnError(err, "TLS config")
 
 	clk := cmd.Clock()
diff --git a/cmd/boulder-sa/main.go b/cmd/boulder-sa/main.go
index 3938e3fbf..51d5d5efa 100644
--- a/cmd/boulder-sa/main.go
+++ b/cmd/boulder-sa/main.go
@@ -84,7 +84,7 @@ func main() {
 		parallel = 1
 	}
 
-	tls, err := c.SA.TLS.Load()
+	tls, err := c.SA.TLS.Load(scope)
 	cmd.FailOnError(err, "TLS config")
 
 	saroi, err := sa.NewSQLStorageAuthorityRO(
diff --git a/cmd/boulder-va/main.go b/cmd/boulder-va/main.go
index ebc83fa14..d67b7823d 100644
--- a/cmd/boulder-va/main.go
+++ b/cmd/boulder-va/main.go
@@ -120,7 +120,7 @@ func main() {
 			logger)
 	}
 
-	tlsConfig, err := c.VA.TLS.Load()
+	tlsConfig, err := c.VA.TLS.Load(scope)
 	cmd.FailOnError(err, "tlsConfig config")
 
 	var remotes []va.RemoteVA
diff --git a/cmd/boulder-wfe2/main.go b/cmd/boulder-wfe2/main.go
index 615005a15..d9d4b3dae 100644
--- a/cmd/boulder-wfe2/main.go
+++ b/cmd/boulder-wfe2/main.go
@@ -311,7 +311,7 @@ func loadChain(certFiles []string) (*issuance.Certificate, []byte, error) {
 }
 
 func setupWFE(c Config, scope prometheus.Registerer, clk clock.Clock) (rapb.RegistrationAuthorityClient, sapb.StorageAuthorityReadOnlyClient, nonce.Getter, map[string]nonce.Redeemer, nonce.Redeemer, string) {
-	tlsConfig, err := c.WFE.TLS.Load()
+	tlsConfig, err := c.WFE.TLS.Load(scope)
 	cmd.FailOnError(err, "TLS config")
 
 	raConn, err := bgrpc.ClientSetup(c.WFE.RAService, tlsConfig, scope, clk)
diff --git a/cmd/boulder/main.go b/cmd/boulder/main.go
index 4fd877627..12754701f 100644
--- a/cmd/boulder/main.go
+++ b/cmd/boulder/main.go
@@ -4,7 +4,6 @@ import (
 	"flag"
 	"fmt"
 	"os"
-	"path"
 
 	_ "github.com/letsencrypt/boulder/cmd/admin-revoker"
 	_ "github.com/letsencrypt/boulder/cmd/akamai-purger"
@@ -32,6 +31,7 @@ import (
 	_ "github.com/letsencrypt/boulder/cmd/orphan-finder"
 	_ "github.com/letsencrypt/boulder/cmd/reversed-hostname-checker"
 	_ "github.com/letsencrypt/boulder/cmd/rocsp-tool"
+	"github.com/letsencrypt/boulder/core"
 
 	"github.com/letsencrypt/boulder/cmd"
 )
@@ -61,7 +61,7 @@ func readAndValidateConfigFile(name, filename string) error {
 }
 
 func main() {
-	cmd.LookupCommand(path.Base(os.Args[0]))()
+	cmd.LookupCommand(core.Command())()
 }
 
 func init() {
diff --git a/cmd/config.go b/cmd/config.go
index 73a111178..2446e7b75 100644
--- a/cmd/config.go
+++ b/cmd/config.go
@@ -10,6 +10,7 @@ import (
 	"strings"
 
 	"github.com/go-sql-driver/mysql"
+	"github.com/prometheus/client_golang/prometheus"
 	"google.golang.org/grpc/resolver"
 
 	"github.com/letsencrypt/boulder/config"
@@ -139,8 +140,9 @@ type TLSConfig struct {
 }
 
 // Load reads and parses the certificates and key listed in the TLSConfig, and
-// returns a *tls.Config suitable for either client or server use.
-func (t *TLSConfig) Load() (*tls.Config, error) {
+// returns a *tls.Config suitable for either client or server use. Prometheus
+// metrics for various certificate fields will be exported.
+func (t *TLSConfig) Load(scope prometheus.Registerer) (*tls.Config, error) {
 	if t == nil {
 		return nil, fmt.Errorf("nil TLS section in config")
 	}
@@ -166,6 +168,48 @@ func (t *TLSConfig) Load() (*tls.Config, error) {
 		return nil, fmt.Errorf("loading key pair from %q and %q: %s",
 			*t.CertFile, *t.KeyFile, err)
 	}
+
+	tlsNotBefore := prometheus.NewGaugeVec(
+		prometheus.GaugeOpts{
+			Name: "tlsconfig_notbefore_seconds",
+			Help: "TLS certificate NotBefore field expressed as Unix epoch time",
+		},
+		[]string{"serial"})
+	err = scope.Register(tlsNotBefore)
+	if err != nil {
+		are := prometheus.AlreadyRegisteredError{}
+		if errors.As(err, &are) {
+			tlsNotBefore = are.ExistingCollector.(*prometheus.GaugeVec)
+		} else {
+			return nil, err
+		}
+	}
+
+	tlsNotAfter := prometheus.NewGaugeVec(
+		prometheus.GaugeOpts{
+			Name: "tlsconfig_notafter_seconds",
+			Help: "TLS certificate NotAfter field expressed as Unix epoch time",
+		},
+		[]string{"serial"})
+	err = scope.Register(tlsNotAfter)
+	if err != nil {
+		are := prometheus.AlreadyRegisteredError{}
+		if errors.As(err, &are) {
+			tlsNotAfter = are.ExistingCollector.(*prometheus.GaugeVec)
+		} else {
+			return nil, err
+		}
+	}
+
+	leaf, err := x509.ParseCertificate(cert.Certificate[0])
+	if err != nil {
+		return nil, err
+	}
+
+	serial := leaf.SerialNumber.String()
+	tlsNotBefore.WithLabelValues(serial).Set(float64(leaf.NotBefore.Unix()))
+	tlsNotAfter.WithLabelValues(serial).Set(float64(leaf.NotAfter.Unix()))
+
 	return &tls.Config{
 		RootCAs:      rootCAs,
 		ClientCAs:    rootCAs,
diff --git a/cmd/crl-storer/main.go b/cmd/crl-storer/main.go
index 1f75fc305..c7af47697 100644
--- a/cmd/crl-storer/main.go
+++ b/cmd/crl-storer/main.go
@@ -82,15 +82,15 @@ func main() {
 	err = features.Set(c.CRLStorer.Features)
 	cmd.FailOnError(err, "Failed to set feature flags")
 
-	tlsConfig, err := c.CRLStorer.TLS.Load()
-	cmd.FailOnError(err, "TLS config")
-
 	scope, logger, oTelShutdown := cmd.StatsAndLogging(c.Syslog, c.OpenTelemetry, c.CRLStorer.DebugAddr)
 	defer oTelShutdown(context.Background())
 	defer logger.AuditPanic()
 	logger.Info(cmd.VersionString())
 	clk := cmd.Clock()
 
+	tlsConfig, err := c.CRLStorer.TLS.Load(scope)
+	cmd.FailOnError(err, "TLS config")
+
 	issuers := make([]*issuance.Certificate, 0, len(c.CRLStorer.IssuerCerts))
 	for _, filepath := range c.CRLStorer.IssuerCerts {
 		cert, err := issuance.LoadCertificate(filepath)
diff --git a/cmd/crl-updater/main.go b/cmd/crl-updater/main.go
index bb08c80d9..9471931ce 100644
--- a/cmd/crl-updater/main.go
+++ b/cmd/crl-updater/main.go
@@ -115,15 +115,15 @@ func main() {
 	err = features.Set(c.CRLUpdater.Features)
 	cmd.FailOnError(err, "Failed to set feature flags")
 
-	tlsConfig, err := c.CRLUpdater.TLS.Load()
-	cmd.FailOnError(err, "TLS config")
-
 	scope, logger, oTelShutdown := cmd.StatsAndLogging(c.Syslog, c.OpenTelemetry, c.CRLUpdater.DebugAddr)
 	defer oTelShutdown(context.Background())
 	defer logger.AuditPanic()
 	logger.Info(cmd.VersionString())
 	clk := cmd.Clock()
 
+	tlsConfig, err := c.CRLUpdater.TLS.Load(scope)
+	cmd.FailOnError(err, "TLS config")
+
 	issuers := make([]*issuance.Certificate, 0, len(c.CRLUpdater.IssuerCerts))
 	for _, filepath := range c.CRLUpdater.IssuerCerts {
 		cert, err := issuance.LoadCertificate(filepath)
diff --git a/cmd/expiration-mailer/main.go b/cmd/expiration-mailer/main.go
index c1d239343..1cc24dd94 100644
--- a/cmd/expiration-mailer/main.go
+++ b/cmd/expiration-mailer/main.go
@@ -825,7 +825,7 @@ func main() {
 	dbMap, err := sa.InitWrappedDb(c.Mailer.DB, scope, logger)
 	cmd.FailOnError(err, "While initializing dbMap")
 
-	tlsConfig, err := c.Mailer.TLS.Load()
+	tlsConfig, err := c.Mailer.TLS.Load(scope)
 	cmd.FailOnError(err, "TLS config")
 
 	clk := cmd.Clock()
diff --git a/cmd/nonce-service/main.go b/cmd/nonce-service/main.go
index 0323362a2..92fef9816 100644
--- a/cmd/nonce-service/main.go
+++ b/cmd/nonce-service/main.go
@@ -108,7 +108,7 @@ func main() {
 	ns, err := nonce.NewNonceService(scope, c.NonceService.MaxUsed, c.NonceService.NoncePrefix)
 	cmd.FailOnError(err, "Failed to initialize nonce service")
 
-	tlsConfig, err := c.NonceService.TLS.Load()
+	tlsConfig, err := c.NonceService.TLS.Load(scope)
 	cmd.FailOnError(err, "tlsConfig config")
 
 	nonceServer := nonce.NewServer(ns)
diff --git a/cmd/ocsp-responder/main.go b/cmd/ocsp-responder/main.go
index c76f87eae..2254dc26f 100644
--- a/cmd/ocsp-responder/main.go
+++ b/cmd/ocsp-responder/main.go
@@ -163,7 +163,7 @@ as generated by Boulder's ceremony command.
 			liveSigningPeriod = 60 * time.Hour
 		}
 
-		tlsConfig, err := c.OCSPResponder.TLS.Load()
+		tlsConfig, err := c.OCSPResponder.TLS.Load(scope)
 		cmd.FailOnError(err, "TLS config")
 
 		raConn, err := bgrpc.ClientSetup(c.OCSPResponder.RAService, tlsConfig, scope, clk)
diff --git a/cmd/orphan-finder/main.go b/cmd/orphan-finder/main.go
index 512512bd0..2823d8fb7 100644
--- a/cmd/orphan-finder/main.go
+++ b/cmd/orphan-finder/main.go
@@ -174,7 +174,8 @@ func newOrphanFinder(configFile string) *orphanFinder {
 	cmd.FailOnError(err, "Failed to set feature flags")
 	logger := cmd.NewLogger(conf.Syslog)
 
-	tlsConfig, err := conf.TLS.Load()
+	// TODO(#6840) Rework orphan-finder to export prometheus metrics.
+	tlsConfig, err := conf.TLS.Load(metrics.NoopRegisterer)
 	cmd.FailOnError(err, "TLS config")
 
 	saConn, err := bgrpc.ClientSetup(conf.SAService, tlsConfig, metrics.NoopRegisterer, cmd.Clock())
diff --git a/cmd/rocsp-tool/main.go b/cmd/rocsp-tool/main.go
index ac4f01721..6f537121a 100644
--- a/cmd/rocsp-tool/main.go
+++ b/cmd/rocsp-tool/main.go
@@ -249,7 +249,7 @@ func helpExit() {
 }
 
 func configureOCSPGenerator(tlsConf cmd.TLSConfig, grpcConf cmd.GRPCClientConfig, clk clock.Clock, scope prometheus.Registerer) (capb.OCSPGeneratorClient, error) {
-	tlsConfig, err := tlsConf.Load()
+	tlsConfig, err := tlsConf.Load(scope)
 	if err != nil {
 		return nil, fmt.Errorf("loading TLS config: %w", err)
 	}
diff --git a/cmd/shell.go b/cmd/shell.go
index ca45d78a9..2400becf9 100644
--- a/cmd/shell.go
+++ b/cmd/shell.go
@@ -1,4 +1,4 @@
-// This package provides utilities that underlie the specific commands.
+// Package cmd provides utilities that underlie the specific commands.
 package cmd
 
 import (
@@ -14,7 +14,6 @@ import (
 	"net/http/pprof"
 	"os"
 	"os/signal"
-	"path"
 	"runtime"
 	"strings"
 	"syscall"
@@ -40,10 +39,6 @@ import (
 	"github.com/letsencrypt/validator/v10"
 )
 
-func command() string {
-	return path.Base(os.Args[0])
-}
-
 // Because we don't know when this init will be called with respect to
 // flag.Parse() and other flag definitions, we can't rely on the regular
 // flag mechanism. But this one is fine.
@@ -198,7 +193,7 @@ func NewLogger(logConf SyslogConfig) blog.Logger {
 			"",
 			"",
 			syslog.LOG_INFO, // default, not actually used
-			command())
+			core.Command())
 		FailOnError(err, "Could not connect to Syslog")
 		syslogLevel := int(syslog.LOG_INFO)
 		if logConf.SyslogLevel != 0 {
@@ -245,7 +240,7 @@ func newVersionCollector() prometheus.Collector {
 			Name: "version",
 			Help: fmt.Sprintf(
 				"A metric with a constant value of '1' labeled by the short commit-id (buildId), build timestamp in RFC3339 format (buildTime), and Go release tag like 'go1.3' (goVersion) from which %s was built.",
-				command(),
+				core.Command(),
 			),
 			ConstLabels: prometheus.Labels{
 				"buildId":   core.GetBuildID(),
@@ -310,7 +305,7 @@ func newOpenTelemetry(config OpenTelemetryConfig, logger blog.Logger) func(ctx c
 		resource.Default(),
 		resource.NewWithAttributes(
 			semconv.SchemaURL,
-			semconv.ServiceNameKey.String(command()),
+			semconv.ServiceNameKey.String(core.Command()),
 			semconv.ServiceVersionKey.String(core.GetBuildID()),
 		),
 	)
@@ -493,7 +488,7 @@ func ValidateYAMLConfig(cv *ConfigValidator, in io.Reader) error {
 
 // VersionString produces a friendly Application version string.
 func VersionString() string {
-	return fmt.Sprintf("Versions: %s=(%s %s) Golang=(%s) BuildHost=(%s)", command(), core.GetBuildID(), core.GetBuildTime(), runtime.Version(), core.GetBuildHost())
+	return fmt.Sprintf("Versions: %s=(%s %s) Golang=(%s) BuildHost=(%s)", core.Command(), core.GetBuildID(), core.GetBuildTime(), runtime.Version(), core.GetBuildHost())
 }
 
 // CatchSignals blocks until a SIGTERM, SIGINT, or SIGHUP is received, then
diff --git a/core/util.go b/core/util.go
index 6949e4563..5ecebb45b 100644
--- a/core/util.go
+++ b/core/util.go
@@ -16,6 +16,7 @@ import (
 	"math/big"
 	mrand "math/rand"
 	"os"
+	"path"
 	"reflect"
 	"regexp"
 	"sort"
@@ -298,3 +299,7 @@ func IsASCII(str string) bool {
 	}
 	return true
 }
+
+func Command() string {
+	return path.Base(os.Args[0])
+}
diff --git a/issuance/issuance.go b/issuance/issuance.go
index 101ac303d..2e8b4c652 100644
--- a/issuance/issuance.go
+++ b/issuance/issuance.go
@@ -36,6 +36,8 @@ import (
 	"github.com/letsencrypt/pkcs11key/v4"
 )
 
+var ErrLinting = errors.New("tbsCertificate linting failed")
+
 // ProfileConfig describes the certificate issuance constraints for all issuers.
 type ProfileConfig struct {
 	AllowMustStaple bool
@@ -666,7 +668,7 @@ func (i *Issuer) Prepare(req *IssuanceRequest) ([]byte, *issuanceToken, error) {
 	// with a throwaway key and then linting it using zlint
 	lintCertBytes, err := i.Linter.Check(template, req.PublicKey)
 	if err != nil {
-		return nil, nil, fmt.Errorf("tbsCertificate linting failed: %w", err)
+		return nil, nil, fmt.Errorf("%w: %w", ErrLinting, err)
 	}
 
 	token := &issuanceToken{sync.Mutex{}, template, req.PublicKey, i}
diff --git a/log/log.go b/log/log.go
index 4cb12c65b..75262337d 100644
--- a/log/log.go
+++ b/log/log.go
@@ -10,13 +10,14 @@ import (
 	"io"
 	"log/syslog"
 	"os"
-	"path"
 	"runtime"
 	"strings"
 	"sync"
 
 	"github.com/jmhodges/clock"
 	"golang.org/x/term"
+
+	"github.com/letsencrypt/boulder/core"
 )
 
 // A Logger logs messages with explicit priority levels. It is
@@ -92,7 +93,7 @@ func newStdoutWriter(level int) *stdoutWriter {
 		}
 	}
 
-	prefix := fmt.Sprintf("%s %s %s[%d]:", shortHostname, datacenter, path.Base(os.Args[0]), os.Getpid())
+	prefix := fmt.Sprintf("%s %s %s[%d]:", shortHostname, datacenter, core.Command(), os.Getpid())
 
 	return &stdoutWriter{
 		prefix: prefix,
@@ -269,7 +270,7 @@ func (w *stdoutWriter) logAtLevel(level syslog.Priority, msg string, a ...interf
 			w.clk.Now().UTC().Format("2006-01-02T15:04:05.000000+00:00Z"),
 			w.prefix,
 			int(level),
-			path.Base(os.Args[0]),
+			core.Command(),
 			checkSummed(msg),
 			reset); err != nil {
 			panic(fmt.Sprintf("failed to write to stdout: %v\n", err))
diff --git a/ra/ra.go b/ra/ra.go
index 40ad76011..a3ec42916 100644
--- a/ra/ra.go
+++ b/ra/ra.go
@@ -111,12 +111,11 @@ type RegistrationAuthorityImpl struct {
 	revocationReasonCounter     *prometheus.CounterVec
 	namesPerCert                *prometheus.HistogramVec
 	newRegCounter               prometheus.Counter
-	reusedValidAuthzCounter     prometheus.Counter
 	recheckCAACounter           prometheus.Counter
 	newCertCounter              prometheus.Counter
 	recheckCAAUsedAuthzLifetime prometheus.Counter
-	authzAges                   prometheus.Histogram
-	orderAges                   prometheus.Histogram
+	authzAges                   *prometheus.HistogramVec
+	orderAges                   *prometheus.HistogramVec
 	inflightFinalizes           prometheus.Gauge
 }
 
@@ -173,12 +172,6 @@ func NewRegistrationAuthorityImpl(
 	})
 	stats.MustRegister(newRegCounter)
 
-	reusedValidAuthzCounter := prometheus.NewCounter(prometheus.CounterOpts{
-		Name: "reused_valid_authz",
-		Help: "A counter of reused valid authorizations",
-	})
-	stats.MustRegister(reusedValidAuthzCounter)
-
 	recheckCAACounter := prometheus.NewCounter(prometheus.CounterOpts{
 		Name: "recheck_caa",
 		Help: "A counter of CAA rechecks",
@@ -203,27 +196,28 @@ func NewRegistrationAuthorityImpl(
 	}, []string{"reason"})
 	stats.MustRegister(revocationReasonCounter)
 
-	authzAges := prometheus.NewHistogram(prometheus.HistogramOpts{
+	authzAges := prometheus.NewHistogramVec(prometheus.HistogramOpts{
 		Name: "authz_ages",
-		Help: "Histogram of ages of Authorization objects when they're attached to a new Order",
-		// authzAges keeps track of how old, in seconds, authorizations were at
-		// the time that we attached them to an order. We give it a non-standard
-		// bucket distribution so that the leftmost (closest to zero) bucket can be
-		// used exclusively for brand-new (i.e. not reused) authzs. Our buckets are:
-		// one nanosecond, one second, one minute, one hour, 7 hours (our CAA reuse
-		// time), 1 day, 2 days, 7 days, 30 days, +inf (should be empty).
+		Help: "Histogram of ages, in seconds, of Authorization objects, labelled by method and type",
+		// authzAges keeps track of how old, in seconds, authorizations are when
+		// we attach them to a new order and again when we finalize that order.
+		// We give it a non-standard bucket distribution so that the leftmost
+		// (closest to zero) bucket can be used exclusively for brand-new (i.e.
+		// not reused) authzs. Our buckets are: one nanosecond, one second, one
+		// minute, one hour, 7 hours (our CAA reuse time), 1 day, 2 days, 7
+		// days, 30 days, +inf (should be empty).
 		Buckets: []float64{0.000000001, 1, 60, 3600, 25200, 86400, 172800, 604800, 2592000, 7776000},
-	})
+	}, []string{"method", "type"})
 	stats.MustRegister(authzAges)
 
-	orderAges := prometheus.NewHistogram(prometheus.HistogramOpts{
+	orderAges := prometheus.NewHistogramVec(prometheus.HistogramOpts{
 		Name: "order_ages",
-		Help: "Histogram of ages of Order objects when they're Finalized",
-		// Orders currently have a max age of 7 days (168hrs), so our buckets are:
-		// 1 second, 10 seconds, 1 minute, 10 minutes, 1 hour, 10 hours, 1 day,
-		// 7 days, +inf.
-		Buckets: []float64{1, 10, 60, 600, 3600, 36000, 86400, 172800},
-	})
+		Help: "Histogram of ages, in seconds, of Order objects when they're reused and finalized, labelled by method",
+		// Orders currently have a max age of 7 days (168hrs), so our buckets
+		// are: one nanosecond (new), 1 second, 10 seconds, 1 minute, 10
+		// minutes, 1 hour, 7 hours (our CAA reuse time), 1 day, 7 days, +inf.
+		Buckets: []float64{0.000000001, 1, 10, 60, 600, 3600, 25200, 86400, 172800},
+	}, []string{"method"})
 	stats.MustRegister(orderAges)
 
 	inflightFinalizes := prometheus.NewGauge(prometheus.GaugeOpts{
@@ -260,7 +254,6 @@ func NewRegistrationAuthorityImpl(
 		namesPerCert:                 namesPerCert,
 		rateLimitCounter:             rateLimitCounter,
 		newRegCounter:                newRegCounter,
-		reusedValidAuthzCounter:      reusedValidAuthzCounter,
 		recheckCAACounter:            recheckCAACounter,
 		newCertCounter:               newCertCounter,
 		revocationReasonCounter:      revocationReasonCounter,
@@ -1023,7 +1016,7 @@ func (ra *RegistrationAuthorityImpl) FinalizeOrder(ctx context.Context, req *rap
 
 	// Observe the age of this order, so we know how quickly most clients complete
 	// issuance flows.
-	ra.orderAges.Observe(ra.clk.Since(time.Unix(0, req.Order.Created)).Seconds())
+	ra.orderAges.WithLabelValues("FinalizeOrder").Observe(ra.clk.Since(time.Unix(0, req.Order.Created)).Seconds())
 
 	// Step 2: Set the Order to Processing status
 	//
@@ -1169,6 +1162,8 @@ func (ra *RegistrationAuthorityImpl) validateFinalizeRequest(
 			ID:            authz.ID,
 			ChallengeType: solvedByChallengeType,
 		}
+		authzAge := (ra.authorizationLifetime - authz.Expires.Sub(ra.clk.Now())).Seconds()
+		ra.authzAges.WithLabelValues("FinalizeOrder", string(authz.Status)).Observe(authzAge)
 	}
 	logEvent.Authorizations = logEventAuthzs
 
@@ -2360,6 +2355,8 @@ func (ra *RegistrationAuthorityImpl) NewOrder(ctx context.Context, req *rapb.New
 		if existingOrder.Id == 0 || existingOrder.Created == 0 || existingOrder.Status == "" || existingOrder.RegistrationID == 0 || existingOrder.Expires == 0 || len(existingOrder.Names) == 0 {
 			return nil, errIncompleteGRPCResponse
 		}
+		// Track how often we reuse an existing order and how old that order is.
+		ra.orderAges.WithLabelValues("NewOrder").Observe(ra.clk.Since(time.Unix(0, existingOrder.Created)).Seconds())
 		return existingOrder, nil
 	}
 
@@ -2406,6 +2403,7 @@ func (ra *RegistrationAuthorityImpl) NewOrder(ctx context.Context, req *rapb.New
 			continue
 		}
 		authz := nameToExistingAuthz[name]
+		authzAge := (ra.authorizationLifetime - time.Unix(0, authz.Expires).Sub(ra.clk.Now())).Seconds()
 		// If the identifier is a wildcard and the existing authz only has one
 		// DNS-01 type challenge we can reuse it. In theory we will
 		// never get back an authorization for a domain with a wildcard prefix
@@ -2418,7 +2416,7 @@ func (ra *RegistrationAuthorityImpl) NewOrder(ctx context.Context, req *rapb.New
 				return nil, err
 			}
 			newOrder.V2Authorizations = append(newOrder.V2Authorizations, authzID)
-			ra.authzAges.Observe((ra.authorizationLifetime - time.Unix(0, authz.Expires).Sub(ra.clk.Now())).Seconds())
+			ra.authzAges.WithLabelValues("NewOrder", authz.Status).Observe(authzAge)
 			continue
 		} else if !strings.HasPrefix(name, "*.") {
 			// If the identifier isn't a wildcard, we can reuse any authz
@@ -2427,7 +2425,7 @@ func (ra *RegistrationAuthorityImpl) NewOrder(ctx context.Context, req *rapb.New
 				return nil, err
 			}
 			newOrder.V2Authorizations = append(newOrder.V2Authorizations, authzID)
-			ra.authzAges.Observe((ra.authorizationLifetime - time.Unix(0, authz.Expires).Sub(ra.clk.Now())).Seconds())
+			ra.authzAges.WithLabelValues("NewOrder", authz.Status).Observe(authzAge)
 			continue
 		}
 
@@ -2437,7 +2435,6 @@ func (ra *RegistrationAuthorityImpl) NewOrder(ctx context.Context, req *rapb.New
 		// reuse and we need to mark the name as requiring a new pending authz
 		missingAuthzNames = append(missingAuthzNames, name)
 	}
-	ra.reusedValidAuthzCounter.Add(float64(len(newOrder.V2Authorizations)))
 
 	// If the order isn't fully authorized we need to check that the client has
 	// rate limit room for more pending authorizations
@@ -2460,7 +2457,7 @@ func (ra *RegistrationAuthorityImpl) NewOrder(ctx context.Context, req *rapb.New
 			return nil, err
 		}
 		newAuthzs = append(newAuthzs, pb)
-		ra.authzAges.Observe(0)
+		ra.authzAges.WithLabelValues("NewOrder", pb.Status).Observe(0)
 	}
 
 	// Start with the order's own expiry as the minExpiry. We only care
@@ -2506,6 +2503,7 @@ func (ra *RegistrationAuthorityImpl) NewOrder(ctx context.Context, req *rapb.New
 	if storedOrder.Id == 0 || storedOrder.Created == 0 || storedOrder.Status == "" || storedOrder.RegistrationID == 0 || storedOrder.Expires == 0 || len(storedOrder.Names) == 0 {
 		return nil, errIncompleteGRPCResponse
 	}
+	ra.orderAges.WithLabelValues("NewOrder").Observe(0)
 
 	// Note how many names are being requested in this certificate order.
 	ra.namesPerCert.With(prometheus.Labels{"type": "requested"}).Observe(float64(len(storedOrder.Names)))
diff --git a/rocsp/config/rocsp_config.go b/rocsp/config/rocsp_config.go
index 93edff0ca..0492175c7 100644
--- a/rocsp/config/rocsp_config.go
+++ b/rocsp/config/rocsp_config.go
@@ -102,7 +102,7 @@ func MakeClient(c *RedisConfig, clk clock.Clock, stats prometheus.Registerer) (*
 		return nil, fmt.Errorf("loading password: %w", err)
 	}
 
-	tlsConfig, err := c.TLS.Load()
+	tlsConfig, err := c.TLS.Load(stats)
 	if err != nil {
 		return nil, fmt.Errorf("loading TLS config: %w", err)
 	}
@@ -141,7 +141,7 @@ func MakeReadClient(c *RedisConfig, clk clock.Clock, stats prometheus.Registerer
 		return nil, fmt.Errorf("loading password: %w", err)
 	}
 
-	tlsConfig, err := c.TLS.Load()
+	tlsConfig, err := c.TLS.Load(stats)
 	if err != nil {
 		return nil, fmt.Errorf("loading TLS config: %w", err)
 	}



Test patch:
diff --git a/cmd/config_test.go b/cmd/config_test.go
index 6988e1e77..4a18fda4d 100644
--- a/cmd/config_test.go
+++ b/cmd/config_test.go
@@ -5,6 +5,7 @@ import (
 	"strings"
 	"testing"
 
+	"github.com/letsencrypt/boulder/metrics"
 	"github.com/letsencrypt/boulder/test"
 )
 
@@ -54,6 +55,7 @@ func TestTLSConfigLoad(t *testing.T) {
 	cert := "testdata/cert.pem"
 	key := "testdata/key.pem"
 	caCert := "testdata/minica.pem"
+
 	testCases := []struct {
 		TLSConfig
 		want string
@@ -86,7 +88,7 @@ func TestTLSConfigLoad(t *testing.T) {
 			title[2] = *tc.CACertFile
 		}
 		t.Run(strings.Join(title[:], "_"), func(t *testing.T) {
-			_, err := tc.TLSConfig.Load()
+			_, err := tc.TLSConfig.Load(metrics.NoopRegisterer)
 			if err == nil {
 				t.Errorf("got no error")
 			}
diff --git a/cmd/rocsp-tool/client_test.go b/cmd/rocsp-tool/client_test.go
index ea80daf9e..8365e44b6 100644
--- a/cmd/rocsp-tool/client_test.go
+++ b/cmd/rocsp-tool/client_test.go
@@ -31,7 +31,7 @@ func makeClient() (*rocsp.RWClient, clock.Clock) {
 		CertFile:   &CertFile,
 		KeyFile:    &KeyFile,
 	}
-	tlsConfig2, err := tlsConfig.Load()
+	tlsConfig2, err := tlsConfig.Load(metrics.NoopRegisterer)
 	if err != nil {
 		panic(err)
 	}
diff --git a/issuance/issuance_test.go b/issuance/issuance_test.go
index eafea35f4..2002ea0eb 100644
--- a/issuance/issuance_test.go
+++ b/issuance/issuance_test.go
@@ -814,6 +814,7 @@ func TestIssueBadLint(t *testing.T) {
 		NotAfter:  fc.Now().Add(time.Hour - time.Second),
 	})
 	test.AssertError(t, err, "Prepare didn't fail")
+	test.AssertErrorIs(t, err, ErrLinting)
 	test.AssertContains(t, err.Error(), "tbsCertificate linting failed: failed lints")
 }
 
diff --git a/rocsp/rocsp_test.go b/rocsp/rocsp_test.go
index c5b3f2f11..ba2e2aef5 100644
--- a/rocsp/rocsp_test.go
+++ b/rocsp/rocsp_test.go
@@ -24,7 +24,7 @@ func makeClient() (*RWClient, clock.Clock) {
 		CertFile:   &CertFile,
 		KeyFile:    &KeyFile,
 	}
-	tlsConfig2, err := tlsConfig.Load()
+	tlsConfig2, err := tlsConfig.Load(metrics.NoopRegisterer)
 	if err != nil {
 		panic(err)
 	}
diff --git a/test/health-checker/main.go b/test/health-checker/main.go
index 7ae41cfd6..74bb884c0 100644
--- a/test/health-checker/main.go
+++ b/test/health-checker/main.go
@@ -42,7 +42,7 @@ func main() {
 		c.GRPC.ServerAddress = *serverAddr
 	}
 
-	tlsConfig, err := c.TLS.Load()
+	tlsConfig, err := c.TLS.Load(metrics.NoopRegisterer)
 	cmd.FailOnError(err, "failed to load TLS credentials")
 
 	// GRPC connection prerequisites.
@@ -75,7 +75,6 @@ func main() {
 				Service: "",
 			}
 			resp, err := client.Check(ctx2, req)
-
 			if err != nil {
 				fmt.Fprintf(os.Stderr, "got error connecting to health service %s: %s\n", *serverAddr, err)
 			} else if resp.Status == healthpb.HealthCheckResponse_SERVING {
diff --git a/test/integration/akamai_purger_drain_queue_test.go b/test/integration/akamai_purger_drain_queue_test.go
index 8a764ae00..5689496a9 100644
--- a/test/integration/akamai_purger_drain_queue_test.go
+++ b/test/integration/akamai_purger_drain_queue_test.go
@@ -15,6 +15,7 @@ import (
 	akamaipb "github.com/letsencrypt/boulder/akamai/proto"
 	"github.com/letsencrypt/boulder/cmd"
 	bcreds "github.com/letsencrypt/boulder/grpc/creds"
+	"github.com/letsencrypt/boulder/metrics"
 	"github.com/letsencrypt/boulder/test"
 	"google.golang.org/grpc"
 	"google.golang.org/grpc/balancer/roundrobin"
@@ -42,7 +43,7 @@ func setup() (*exec.Cmd, *bytes.Buffer, akamaipb.AkamaiPurgerClient, error) {
 		CACertFile: s("test/grpc-creds/minica.pem"),
 		CertFile:   s("test/grpc-creds/ra.boulder/cert.pem"),
 		KeyFile:    s("test/grpc-creds/ra.boulder/key.pem"),
-	}).Load()
+	}).Load(metrics.NoopRegisterer)
 	if err != nil {
 		sigterm()
 		return nil, nil, nil, err