boulder/cmd/crl-updater/main.go

233 lines
9.0 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package notmain
import (
"context"
"errors"
"flag"
"os"
"time"
capb "github.com/letsencrypt/boulder/ca/proto"
"github.com/letsencrypt/boulder/cmd"
"github.com/letsencrypt/boulder/config"
cspb "github.com/letsencrypt/boulder/crl/storer/proto"
"github.com/letsencrypt/boulder/crl/updater"
"github.com/letsencrypt/boulder/features"
bgrpc "github.com/letsencrypt/boulder/grpc"
"github.com/letsencrypt/boulder/issuance"
sapb "github.com/letsencrypt/boulder/sa/proto"
)
type Config struct {
CRLUpdater struct {
DebugAddr string `validate:"omitempty,hostname_port"`
// TLS client certificate, private key, and trusted root bundle.
TLS cmd.TLSConfig
SAService *cmd.GRPCClientConfig
CRLGeneratorService *cmd.GRPCClientConfig
CRLStorerService *cmd.GRPCClientConfig
// IssuerCerts is a list of paths to issuer certificates on disk. This
// controls the set of CRLs which will be published by this updater: it will
// publish one set of NumShards CRL shards for each issuer in this list.
IssuerCerts []string `validate:"min=1,dive,required"`
// NumShards is the number of shards into which each issuer's "full and
// complete" CRL will be split.
// WARNING: When this number is changed, the "JSON Array of CRL URLs" field
// in CCADB MUST be updated.
NumShards int `validate:"min=1"`
// ShardWidth is the amount of time (width on a timeline) that a single
// shard should cover. Ideally, NumShards*ShardWidth should be an amount of
// time noticeably larger than the current longest certificate lifetime,
// but the updater will continue to work if this is not the case (albeit
// with more confusing mappings of serials to shards).
// WARNING: When this number is changed, revocation entries will move
// between shards.
ShardWidth config.Duration `validate:"-"`
// LookbackPeriod is how far back the updater should look for revoked expired
// certificates. We are required to include every revoked cert in at least
// one CRL, even if it is revoked seconds before it expires, so this must
// always be greater than the UpdatePeriod, and should be increased when
// recovering from an outage to ensure continuity of coverage.
LookbackPeriod config.Duration `validate:"-"`
// UpdatePeriod controls how frequently the crl-updater runs and publishes
// new versions of every CRL shard. The Baseline Requirements, Section 4.9.7:
// "MUST update and publish a new CRL within twentyfour (24) hours after
// recording a Certificate as revoked."
UpdatePeriod config.Duration
// UpdateTimeout controls how long a single CRL shard is allowed to attempt
// to update before being timed out. The total CRL updating process may take
// significantly longer, since a full update cycle may consist of updating
// many shards with varying degrees of parallelism. This value must be
// strictly less than the UpdatePeriod. Defaults to 10 minutes, one order
// of magnitude greater than our p99 update latency.
UpdateTimeout config.Duration `validate:"-"`
// TemporallyShardedSerialPrefixes is a list of prefixes that were used to
// issue certificates with no CRLDistributionPoints extension, and which are
// therefore temporally sharded. If it's non-empty, the CRL Updater will
// require matching serials when querying by temporal shard. When querying
// by explicit shard, any prefix is allowed.
//
// This should be set to the current set of serial prefixes in production.
// When deploying explicit sharding (i.e. the CRLDistributionPoints extension),
// the CAs should be configured with a new set of serial prefixes that haven't
// been used before (and the OCSP Responder config should be updated to
// recognize the new prefixes as well as the old ones).
TemporallyShardedSerialPrefixes []string
// MaxParallelism controls how many workers may be running in parallel.
// A higher value reduces the total time necessary to update all CRL shards
// that this updater is responsible for, but also increases the memory used
// by this updater. Only relevant in -runOnce mode.
MaxParallelism int `validate:"min=0"`
// MaxAttempts control how many times the updater will attempt to generate
// a single CRL shard. A higher number increases the likelihood of a fully
// successful run, but also increases the worst-case runtime and db/network
// load of said run. The default is 1.
MaxAttempts int `validate:"omitempty,min=1"`
// ExpiresMargin adds a small increment to the CRL's HTTP Expires time.
//
// When uploading a CRL, its Expires field in S3 is set to the expected time
// the next CRL will be uploaded (by this instance). That allows our CDN
// instances to cache for that long. However, since the next update might be
// slow or delayed, we add a margin of error.
//
// Tradeoffs: A large ExpiresMargin reduces the chance that a CRL becomes
// uncacheable and floods S3 with traffic (which might result in 503s while
// S3 scales out).
//
// A small ExpiresMargin means revocations become visible sooner, including
// admin-invoked revocations that may have a time requirement.
ExpiresMargin config.Duration
// CacheControl is a string passed verbatim to the crl-storer to store on
// the S3 object.
//
// Note: if this header contains max-age, it will override
// Expires. https://www.rfc-editor.org/rfc/rfc9111.html#name-calculating-freshness-lifet
// Cache-Control: max-age has the disadvantage that it caches for a fixed
// amount of time, regardless of how close the CRL is to replacement. So
// if max-age is used, the worst-case time for a revocation to become visible
// is UpdatePeriod + the value of max age.
//
// The stale-if-error and stale-while-revalidate headers may be useful here:
// https://aws.amazon.com/about-aws/whats-new/2023/05/amazon-cloudfront-stale-while-revalidate-stale-if-error-cache-control-directives/
//
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control
CacheControl string
Features features.Config
}
Syslog cmd.SyslogConfig
OpenTelemetry cmd.OpenTelemetryConfig
}
func main() {
configFile := flag.String("config", "", "File path to the configuration file for this service")
debugAddr := flag.String("debug-addr", "", "Debug server address override")
runOnce := flag.Bool("runOnce", false, "If true, run once immediately and then exit")
flag.Parse()
if *configFile == "" {
flag.Usage()
os.Exit(1)
}
var c Config
err := cmd.ReadConfigFile(*configFile, &c)
cmd.FailOnError(err, "Reading JSON config file into config structure")
if *debugAddr != "" {
c.CRLUpdater.DebugAddr = *debugAddr
}
features.Set(c.CRLUpdater.Features)
scope, logger, oTelShutdown := cmd.StatsAndLogging(c.Syslog, c.OpenTelemetry, c.CRLUpdater.DebugAddr)
defer oTelShutdown(context.Background())
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)
cmd.FailOnError(err, "Failed to load issuer cert")
issuers = append(issuers, cert)
}
if c.CRLUpdater.ShardWidth.Duration == 0 {
c.CRLUpdater.ShardWidth.Duration = 16 * time.Hour
}
if c.CRLUpdater.LookbackPeriod.Duration == 0 {
c.CRLUpdater.LookbackPeriod.Duration = 24 * time.Hour
}
if c.CRLUpdater.UpdateTimeout.Duration == 0 {
c.CRLUpdater.UpdateTimeout.Duration = 10 * time.Minute
}
saConn, err := bgrpc.ClientSetup(c.CRLUpdater.SAService, tlsConfig, scope, clk)
cmd.FailOnError(err, "Failed to load credentials and create gRPC connection to SA")
sac := sapb.NewStorageAuthorityClient(saConn)
caConn, err := bgrpc.ClientSetup(c.CRLUpdater.CRLGeneratorService, tlsConfig, scope, clk)
cmd.FailOnError(err, "Failed to load credentials and create gRPC connection to CRLGenerator")
cac := capb.NewCRLGeneratorClient(caConn)
csConn, err := bgrpc.ClientSetup(c.CRLUpdater.CRLStorerService, tlsConfig, scope, clk)
cmd.FailOnError(err, "Failed to load credentials and create gRPC connection to CRLStorer")
csc := cspb.NewCRLStorerClient(csConn)
u, err := updater.NewUpdater(
issuers,
c.CRLUpdater.NumShards,
c.CRLUpdater.ShardWidth.Duration,
c.CRLUpdater.LookbackPeriod.Duration,
c.CRLUpdater.UpdatePeriod.Duration,
c.CRLUpdater.UpdateTimeout.Duration,
c.CRLUpdater.MaxParallelism,
c.CRLUpdater.MaxAttempts,
c.CRLUpdater.CacheControl,
c.CRLUpdater.ExpiresMargin.Duration,
c.CRLUpdater.TemporallyShardedSerialPrefixes,
sac,
cac,
csc,
scope,
logger,
clk,
)
cmd.FailOnError(err, "Failed to create crl-updater")
ctx, cancel := context.WithCancel(context.Background())
go cmd.CatchSignals(cancel)
if *runOnce {
err = u.RunOnce(ctx)
if err != nil && !errors.Is(err, context.Canceled) {
cmd.FailOnError(err, "")
}
} else {
err = u.Run(ctx)
if err != nil && !errors.Is(err, context.Canceled) {
cmd.FailOnError(err, "")
}
}
}
func init() {
cmd.RegisterCommand("crl-updater", main, &cmd.ConfigValidator{Config: &Config{}})
}