github.com/letsencrypt/boulder@v0.20251208.0/cmd/crl-updater/main.go (about) 1 package notmain 2 3 import ( 4 "context" 5 "errors" 6 "flag" 7 "os" 8 "time" 9 10 "github.com/jmhodges/clock" 11 12 capb "github.com/letsencrypt/boulder/ca/proto" 13 "github.com/letsencrypt/boulder/cmd" 14 "github.com/letsencrypt/boulder/config" 15 cspb "github.com/letsencrypt/boulder/crl/storer/proto" 16 "github.com/letsencrypt/boulder/crl/updater" 17 "github.com/letsencrypt/boulder/features" 18 bgrpc "github.com/letsencrypt/boulder/grpc" 19 "github.com/letsencrypt/boulder/issuance" 20 sapb "github.com/letsencrypt/boulder/sa/proto" 21 ) 22 23 type Config struct { 24 CRLUpdater struct { 25 DebugAddr string `validate:"omitempty,hostname_port"` 26 27 // TLS client certificate, private key, and trusted root bundle. 28 TLS cmd.TLSConfig 29 30 SAService *cmd.GRPCClientConfig 31 CRLGeneratorService *cmd.GRPCClientConfig 32 CRLStorerService *cmd.GRPCClientConfig 33 34 // IssuerCerts is a list of paths to issuer certificates on disk. This 35 // controls the set of CRLs which will be published by this updater: it will 36 // publish one set of NumShards CRL shards for each issuer in this list. 37 IssuerCerts []string `validate:"min=1,dive,required"` 38 39 // NumShards is the number of shards into which each issuer's "full and 40 // complete" CRL will be split. 41 // WARNING: When this number is changed, the "JSON Array of CRL URLs" field 42 // in CCADB MUST be updated. 43 NumShards int `validate:"min=1"` 44 45 // ShardWidth is the amount of time (width on a timeline) that a single 46 // shard should cover. Ideally, NumShards*ShardWidth should be an amount of 47 // time noticeably larger than the current longest certificate lifetime, 48 // but the updater will continue to work if this is not the case (albeit 49 // with more confusing mappings of serials to shards). 50 // WARNING: When this number is changed, revocation entries will move 51 // between shards. 52 ShardWidth config.Duration `validate:"-"` 53 54 // LookbackPeriod is how far back the updater should look for revoked expired 55 // certificates. We are required to include every revoked cert in at least 56 // one CRL, even if it is revoked seconds before it expires, so this must 57 // always be greater than the UpdatePeriod, and should be increased when 58 // recovering from an outage to ensure continuity of coverage. 59 LookbackPeriod config.Duration `validate:"-"` 60 61 // UpdatePeriod controls how frequently the crl-updater runs and publishes 62 // new versions of every CRL shard. The Baseline Requirements, Section 4.9.7: 63 // "MUST update and publish a new CRL within twenty‐four (24) hours after 64 // recording a Certificate as revoked." 65 UpdatePeriod config.Duration 66 67 // UpdateTimeout controls how long a single CRL shard is allowed to attempt 68 // to update before being timed out. The total CRL updating process may take 69 // significantly longer, since a full update cycle may consist of updating 70 // many shards with varying degrees of parallelism. This value must be 71 // strictly less than the UpdatePeriod. Defaults to 10 minutes, one order 72 // of magnitude greater than our p99 update latency. 73 UpdateTimeout config.Duration `validate:"-"` 74 75 // MaxParallelism controls how many workers may be running in parallel. 76 // A higher value reduces the total time necessary to update all CRL shards 77 // that this updater is responsible for, but also increases the memory used 78 // by this updater. Only relevant in -runOnce mode. 79 MaxParallelism int `validate:"min=0"` 80 81 // MaxAttempts control how many times the updater will attempt to generate 82 // a single CRL shard. A higher number increases the likelihood of a fully 83 // successful run, but also increases the worst-case runtime and db/network 84 // load of said run. The default is 1. 85 MaxAttempts int `validate:"omitempty,min=1"` 86 87 // ExpiresMargin adds a small increment to the CRL's HTTP Expires time. 88 // 89 // When uploading a CRL, its Expires field in S3 is set to the expected time 90 // the next CRL will be uploaded (by this instance). That allows our CDN 91 // instances to cache for that long. However, since the next update might be 92 // slow or delayed, we add a margin of error. 93 // 94 // Tradeoffs: A large ExpiresMargin reduces the chance that a CRL becomes 95 // uncacheable and floods S3 with traffic (which might result in 503s while 96 // S3 scales out). 97 // 98 // A small ExpiresMargin means revocations become visible sooner, including 99 // admin-invoked revocations that may have a time requirement. 100 ExpiresMargin config.Duration 101 102 // CacheControl is a string passed verbatim to the crl-storer to store on 103 // the S3 object. 104 // 105 // Note: if this header contains max-age, it will override 106 // Expires. https://www.rfc-editor.org/rfc/rfc9111.html#name-calculating-freshness-lifet 107 // Cache-Control: max-age has the disadvantage that it caches for a fixed 108 // amount of time, regardless of how close the CRL is to replacement. So 109 // if max-age is used, the worst-case time for a revocation to become visible 110 // is UpdatePeriod + the value of max age. 111 // 112 // The stale-if-error and stale-while-revalidate headers may be useful here: 113 // https://aws.amazon.com/about-aws/whats-new/2023/05/amazon-cloudfront-stale-while-revalidate-stale-if-error-cache-control-directives/ 114 // 115 // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control 116 CacheControl string 117 118 Features features.Config 119 } 120 121 Syslog cmd.SyslogConfig 122 OpenTelemetry cmd.OpenTelemetryConfig 123 } 124 125 func main() { 126 configFile := flag.String("config", "", "File path to the configuration file for this service") 127 debugAddr := flag.String("debug-addr", "", "Debug server address override") 128 runOnce := flag.Bool("runOnce", false, "If true, run once immediately and then exit") 129 flag.Parse() 130 if *configFile == "" { 131 flag.Usage() 132 os.Exit(1) 133 } 134 135 var c Config 136 err := cmd.ReadConfigFile(*configFile, &c) 137 cmd.FailOnError(err, "Reading JSON config file into config structure") 138 139 if *debugAddr != "" { 140 c.CRLUpdater.DebugAddr = *debugAddr 141 } 142 143 features.Set(c.CRLUpdater.Features) 144 145 scope, logger, oTelShutdown := cmd.StatsAndLogging(c.Syslog, c.OpenTelemetry, c.CRLUpdater.DebugAddr) 146 defer oTelShutdown(context.Background()) 147 logger.Info(cmd.VersionString()) 148 clk := clock.New() 149 150 tlsConfig, err := c.CRLUpdater.TLS.Load(scope) 151 cmd.FailOnError(err, "TLS config") 152 153 issuers := make([]*issuance.Certificate, 0, len(c.CRLUpdater.IssuerCerts)) 154 for _, filepath := range c.CRLUpdater.IssuerCerts { 155 cert, err := issuance.LoadCertificate(filepath) 156 cmd.FailOnError(err, "Failed to load issuer cert") 157 issuers = append(issuers, cert) 158 } 159 160 if c.CRLUpdater.ShardWidth.Duration == 0 { 161 c.CRLUpdater.ShardWidth.Duration = 16 * time.Hour 162 } 163 if c.CRLUpdater.LookbackPeriod.Duration == 0 { 164 c.CRLUpdater.LookbackPeriod.Duration = 24 * time.Hour 165 } 166 if c.CRLUpdater.UpdateTimeout.Duration == 0 { 167 c.CRLUpdater.UpdateTimeout.Duration = 10 * time.Minute 168 } 169 170 saConn, err := bgrpc.ClientSetup(c.CRLUpdater.SAService, tlsConfig, scope, clk) 171 cmd.FailOnError(err, "Failed to load credentials and create gRPC connection to SA") 172 sac := sapb.NewStorageAuthorityClient(saConn) 173 174 caConn, err := bgrpc.ClientSetup(c.CRLUpdater.CRLGeneratorService, tlsConfig, scope, clk) 175 cmd.FailOnError(err, "Failed to load credentials and create gRPC connection to CRLGenerator") 176 cac := capb.NewCRLGeneratorClient(caConn) 177 178 csConn, err := bgrpc.ClientSetup(c.CRLUpdater.CRLStorerService, tlsConfig, scope, clk) 179 cmd.FailOnError(err, "Failed to load credentials and create gRPC connection to CRLStorer") 180 csc := cspb.NewCRLStorerClient(csConn) 181 182 u, err := updater.NewUpdater( 183 issuers, 184 c.CRLUpdater.NumShards, 185 c.CRLUpdater.ShardWidth.Duration, 186 c.CRLUpdater.LookbackPeriod.Duration, 187 c.CRLUpdater.UpdatePeriod.Duration, 188 c.CRLUpdater.UpdateTimeout.Duration, 189 c.CRLUpdater.MaxParallelism, 190 c.CRLUpdater.MaxAttempts, 191 c.CRLUpdater.CacheControl, 192 c.CRLUpdater.ExpiresMargin.Duration, 193 sac, 194 cac, 195 csc, 196 scope, 197 logger, 198 clk, 199 ) 200 cmd.FailOnError(err, "Failed to create crl-updater") 201 202 ctx, cancel := context.WithCancel(context.Background()) 203 go cmd.CatchSignals(cancel) 204 205 if *runOnce { 206 err = u.RunOnce(ctx) 207 if err != nil && !errors.Is(err, context.Canceled) { 208 cmd.FailOnError(err, "") 209 } 210 } else { 211 err = u.Run(ctx) 212 if err != nil && !errors.Is(err, context.Canceled) { 213 cmd.FailOnError(err, "") 214 } 215 } 216 } 217 218 func init() { 219 cmd.RegisterCommand("crl-updater", main, &cmd.ConfigValidator{Config: &Config{}}) 220 }