github.com/letsencrypt/boulder@v0.20251208.0/cmd/boulder-wfe2/main.go (about) 1 package notmain 2 3 import ( 4 "bytes" 5 "context" 6 "encoding/pem" 7 "flag" 8 "fmt" 9 "net/http" 10 "os" 11 "time" 12 13 "github.com/jmhodges/clock" 14 15 "github.com/letsencrypt/boulder/cmd" 16 "github.com/letsencrypt/boulder/config" 17 emailpb "github.com/letsencrypt/boulder/email/proto" 18 "github.com/letsencrypt/boulder/features" 19 "github.com/letsencrypt/boulder/goodkey" 20 "github.com/letsencrypt/boulder/goodkey/sagoodkey" 21 bgrpc "github.com/letsencrypt/boulder/grpc" 22 "github.com/letsencrypt/boulder/grpc/noncebalancer" 23 "github.com/letsencrypt/boulder/issuance" 24 "github.com/letsencrypt/boulder/nonce" 25 rapb "github.com/letsencrypt/boulder/ra/proto" 26 "github.com/letsencrypt/boulder/ratelimits" 27 bredis "github.com/letsencrypt/boulder/redis" 28 sapb "github.com/letsencrypt/boulder/sa/proto" 29 "github.com/letsencrypt/boulder/unpause" 30 "github.com/letsencrypt/boulder/web" 31 "github.com/letsencrypt/boulder/wfe2" 32 ) 33 34 type Config struct { 35 WFE struct { 36 DebugAddr string `validate:"omitempty,hostname_port"` 37 38 // ListenAddress is the address:port on which to listen for incoming 39 // HTTP requests. Defaults to ":80". 40 ListenAddress string `validate:"omitempty,hostname_port"` 41 42 // TLSListenAddress is the address:port on which to listen for incoming 43 // HTTPS requests. If none is provided the WFE will not listen for HTTPS 44 // requests. 45 TLSListenAddress string `validate:"omitempty,hostname_port"` 46 47 // Timeout is the per-request overall timeout. This should be slightly 48 // lower than the upstream's timeout when making requests to this service. 49 Timeout config.Duration `validate:"-"` 50 51 // ShutdownStopTimeout determines the maximum amount of time to wait 52 // for extant request handlers to complete before exiting. It should be 53 // greater than Timeout. 54 ShutdownStopTimeout config.Duration 55 56 ServerCertificatePath string `validate:"required_with=TLSListenAddress"` 57 ServerKeyPath string `validate:"required_with=TLSListenAddress"` 58 59 AllowOrigins []string 60 61 SubscriberAgreementURL string 62 63 TLS cmd.TLSConfig 64 65 RAService *cmd.GRPCClientConfig 66 SAService *cmd.GRPCClientConfig 67 EmailExporter *cmd.GRPCClientConfig 68 69 // GetNonceService is a gRPC config which contains a single SRV name 70 // used to lookup nonce-service instances used exclusively for nonce 71 // creation. In a multi-DC deployment this should refer to local 72 // nonce-service instances only. 73 GetNonceService *cmd.GRPCClientConfig `validate:"required"` 74 75 // RedeemNonceService is a gRPC config which contains a list of SRV 76 // names used to lookup nonce-service instances used exclusively for 77 // nonce redemption. In a multi-DC deployment this should contain both 78 // local and remote nonce-service instances. 79 RedeemNonceService *cmd.GRPCClientConfig `validate:"required"` 80 81 // NonceHMACKey is a path to a file containing an HMAC key which is a 82 // secret used for deriving the prefix of each nonce instance. It should 83 // contain 256 bits (32 bytes) of random data to be suitable as an 84 // HMAC-SHA256 key (e.g. the output of `openssl rand -hex 32`). In a 85 // multi-DC deployment this value should be the same across all 86 // boulder-wfe and nonce-service instances. 87 NonceHMACKey cmd.HMACKeyConfig `validate:"-"` 88 89 // Chains is a list of lists of certificate filenames. Each inner list is 90 // a chain (starting with the issuing intermediate, followed by one or 91 // more additional certificates, up to and including a root) which we are 92 // willing to serve. Chains that start with a given intermediate will only 93 // be offered for certificates which were issued by the key pair represented 94 // by that intermediate. The first chain representing any given issuing 95 // key pair will be the default for that issuer, served if the client does 96 // not request a specific chain. 97 Chains [][]string `validate:"required,min=1,dive,min=2,dive,required"` 98 99 Features features.Config 100 101 // DirectoryCAAIdentity is used for the /directory response's "meta" 102 // element's "caaIdentities" field. It should match the VA's "issuerDomain" 103 // configuration value (this value is the one used to enforce CAA) 104 DirectoryCAAIdentity string `validate:"required,fqdn"` 105 // DirectoryWebsite is used for the /directory response's "meta" element's 106 // "website" field. 107 DirectoryWebsite string `validate:"required,url"` 108 109 // ACMEv2 requests (outside some registration/revocation messages) use a JWS with 110 // a KeyID header containing the full account URL. For new accounts this 111 // will be a KeyID based on the HTTP request's Host header and the ACMEv2 112 // account path. For legacy ACMEv1 accounts we need to whitelist the account 113 // ID prefix that legacy accounts would have been using based on the Host 114 // header of the WFE1 instance and the legacy 'reg' path component. This 115 // will differ in configuration for production and staging. 116 LegacyKeyIDPrefix string `validate:"required,url"` 117 118 // GoodKey is an embedded config stanza for the goodkey library. 119 GoodKey goodkey.Config 120 121 // StaleTimeout determines how old should data be to be accessed via Boulder-specific GET-able APIs 122 StaleTimeout config.Duration `validate:"-"` 123 124 // AuthorizationLifetimeDays duplicates the RA's config of the same name. 125 // Deprecated: This field no longer has any effect. 126 AuthorizationLifetimeDays int `validate:"-"` 127 128 // PendingAuthorizationLifetimeDays duplicates the RA's config of the same name. 129 // Deprecated: This field no longer has any effect. 130 PendingAuthorizationLifetimeDays int `validate:"-"` 131 132 // MaxContactsPerRegistration limits the number of contact addresses which 133 // can be provided in a single NewAccount request. Requests containing more 134 // contacts than this are rejected. Default: 10. 135 MaxContactsPerRegistration int `validate:"omitempty,min=1"` 136 137 AccountCache *CacheConfig 138 139 Limiter struct { 140 // Redis contains the configuration necessary to connect to Redis 141 // for rate limiting. This field is required to enable rate 142 // limiting. 143 Redis *bredis.Config `validate:"required_with=Defaults"` 144 145 // Defaults is a path to a YAML file containing default rate limits. 146 // See: ratelimits/README.md for details. This field is required to 147 // enable rate limiting. If any individual rate limit is not set, 148 // that limit will be disabled. Failed Authorizations limits passed 149 // in this file must be identical to those in the RA. 150 Defaults string `validate:"required_with=Redis"` 151 152 // Overrides is a path to a YAML file containing overrides for the 153 // default rate limits. See: ratelimits/README.md for details. If 154 // neither this field nor OverridesFromDB is set, all requesters 155 // will be subject to the default rate limits. Overrides for the 156 // Failed Authorizations overrides passed in this file must be 157 // identical to those in the RA. 158 Overrides string 159 160 // OverridesFromDB causes the WFE and RA to retrieve rate limit 161 // overrides from the database, instead of from a file. 162 OverridesFromDB bool 163 } 164 165 // CertProfiles is a map of acceptable certificate profile names to 166 // descriptions (perhaps including URLs) of those profiles. NewOrder 167 // Requests with a profile name not present in this map will be rejected. 168 // This field is optional; if unset, no profile names are accepted. 169 CertProfiles map[string]string `validate:"omitempty,dive,keys,alphanum,min=1,max=32,endkeys"` 170 171 Unpause struct { 172 // HMACKey signs outgoing JWTs for redemption at the unpause 173 // endpoint. This key must match the one configured for all SFEs. 174 // This field is required to enable the pausing feature. 175 HMACKey cmd.HMACKeyConfig `validate:"required_with=JWTLifetime URL,structonly"` 176 177 // JWTLifetime is the lifetime of the unpause JWTs generated by the 178 // WFE for redemption at the SFE. The minimum value for this field 179 // is 336h (14 days). This field is required to enable the pausing 180 // feature. 181 JWTLifetime config.Duration `validate:"omitempty,required_with=HMACKey URL,min=336h"` 182 183 // URL is the URL of the Self-Service Frontend (SFE). This is used 184 // to build URLs sent to end-users in error messages. This field 185 // must be a URL with a scheme of 'https://' This field is required 186 // to enable the pausing feature. 187 URL string `validate:"omitempty,required_with=HMACKey JWTLifetime,url,startswith=https://,endsnotwith=/"` 188 } 189 } 190 191 Syslog cmd.SyslogConfig 192 OpenTelemetry cmd.OpenTelemetryConfig 193 194 // OpenTelemetryHTTPConfig configures tracing on incoming HTTP requests 195 OpenTelemetryHTTPConfig cmd.OpenTelemetryHTTPConfig 196 } 197 198 type CacheConfig struct { 199 Size int 200 TTL config.Duration 201 } 202 203 // loadChain takes a list of filenames containing pem-formatted certificates, 204 // and returns a chain representing all of those certificates in order. It 205 // ensures that the resulting chain is valid. The final file is expected to be 206 // a root certificate, which the chain will be verified against, but which will 207 // not be included in the resulting chain. 208 func loadChain(certFiles []string) (*issuance.Certificate, []byte, error) { 209 certs, err := issuance.LoadChain(certFiles) 210 if err != nil { 211 return nil, nil, err 212 } 213 214 // Iterate over all certs appending their pem to the buf. 215 var buf bytes.Buffer 216 for _, cert := range certs { 217 buf.Write([]byte("\n")) 218 buf.Write(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw})) 219 } 220 221 return certs[0], buf.Bytes(), nil 222 } 223 224 func main() { 225 listenAddr := flag.String("addr", "", "HTTP listen address override") 226 tlsAddr := flag.String("tls-addr", "", "HTTPS listen address override") 227 debugAddr := flag.String("debug-addr", "", "Debug server address override") 228 configFile := flag.String("config", "", "File path to the configuration file for this service") 229 flag.Parse() 230 if *configFile == "" { 231 flag.Usage() 232 os.Exit(1) 233 } 234 235 var c Config 236 err := cmd.ReadConfigFile(*configFile, &c) 237 cmd.FailOnError(err, "Reading JSON config file into config structure") 238 239 features.Set(c.WFE.Features) 240 241 if *listenAddr != "" { 242 c.WFE.ListenAddress = *listenAddr 243 } 244 if *tlsAddr != "" { 245 c.WFE.TLSListenAddress = *tlsAddr 246 } 247 if *debugAddr != "" { 248 c.WFE.DebugAddr = *debugAddr 249 } 250 251 certChains := map[issuance.NameID][][]byte{} 252 issuerCerts := map[issuance.NameID]*issuance.Certificate{} 253 for _, files := range c.WFE.Chains { 254 issuer, chain, err := loadChain(files) 255 cmd.FailOnError(err, "Failed to load chain") 256 257 id := issuer.NameID() 258 certChains[id] = append(certChains[id], chain) 259 // This may overwrite a previously-set issuerCert (e.g. if there are two 260 // chains for the same issuer, but with different versions of the same 261 // same intermediate issued by different roots). This is okay, as the 262 // only truly important content here is the public key to verify other 263 // certs. 264 issuerCerts[id] = issuer 265 } 266 267 stats, logger, oTelShutdown := cmd.StatsAndLogging(c.Syslog, c.OpenTelemetry, c.WFE.DebugAddr) 268 logger.Info(cmd.VersionString()) 269 270 clk := clock.New() 271 272 var unpauseSigner unpause.JWTSigner 273 if features.Get().CheckIdentifiersPaused { 274 unpauseSigner, err = unpause.NewJWTSigner(c.WFE.Unpause.HMACKey) 275 cmd.FailOnError(err, "Failed to create unpause signer from HMACKey") 276 } 277 278 tlsConfig, err := c.WFE.TLS.Load(stats) 279 cmd.FailOnError(err, "TLS config") 280 281 raConn, err := bgrpc.ClientSetup(c.WFE.RAService, tlsConfig, stats, clk) 282 cmd.FailOnError(err, "Failed to load credentials and create gRPC connection to RA") 283 rac := rapb.NewRegistrationAuthorityClient(raConn) 284 285 saConn, err := bgrpc.ClientSetup(c.WFE.SAService, tlsConfig, stats, clk) 286 cmd.FailOnError(err, "Failed to load credentials and create gRPC connection to SA") 287 sac := sapb.NewStorageAuthorityReadOnlyClient(saConn) 288 289 var eec emailpb.ExporterClient 290 if c.WFE.EmailExporter != nil { 291 emailExporterConn, err := bgrpc.ClientSetup(c.WFE.EmailExporter, tlsConfig, stats, clk) 292 cmd.FailOnError(err, "Failed to load credentials and create gRPC connection to email-exporter") 293 eec = emailpb.NewExporterClient(emailExporterConn) 294 } 295 296 if c.WFE.RedeemNonceService == nil { 297 cmd.Fail("'redeemNonceService' must be configured.") 298 } 299 if c.WFE.GetNonceService == nil { 300 cmd.Fail("'getNonceService' must be configured") 301 } 302 303 noncePrefixKey, err := c.WFE.NonceHMACKey.Load() 304 cmd.FailOnError(err, "Failed to load nonceHMACKey file") 305 306 getNonceConn, err := bgrpc.ClientSetup(c.WFE.GetNonceService, tlsConfig, stats, clk) 307 cmd.FailOnError(err, "Failed to load credentials and create gRPC connection to get nonce service") 308 gnc := nonce.NewGetter(getNonceConn) 309 310 if c.WFE.RedeemNonceService.SRVResolver != noncebalancer.SRVResolverScheme { 311 cmd.Fail(fmt.Sprintf( 312 "'redeemNonceService.SRVResolver' must be set to %q", noncebalancer.SRVResolverScheme), 313 ) 314 } 315 redeemNonceConn, err := bgrpc.ClientSetup(c.WFE.RedeemNonceService, tlsConfig, stats, clk) 316 cmd.FailOnError(err, "Failed to load credentials and create gRPC connection to redeem nonce service") 317 rnc := nonce.NewRedeemer(redeemNonceConn) 318 319 kp, err := sagoodkey.NewPolicy(&c.WFE.GoodKey, sac.KeyBlocked) 320 cmd.FailOnError(err, "Unable to create key policy") 321 322 if c.WFE.StaleTimeout.Duration == 0 { 323 c.WFE.StaleTimeout.Duration = time.Minute * 10 324 } 325 326 if c.WFE.MaxContactsPerRegistration == 0 { 327 c.WFE.MaxContactsPerRegistration = 10 328 } 329 330 var limiter *ratelimits.Limiter 331 var txnBuilder *ratelimits.TransactionBuilder 332 var limiterRedis *bredis.Ring 333 overridesRefresherShutdown := func() {} 334 if c.WFE.Limiter.Defaults != "" { 335 // Setup rate limiting. 336 limiterRedis, err = bredis.NewRingFromConfig(*c.WFE.Limiter.Redis, stats, logger) 337 cmd.FailOnError(err, "Failed to create Redis ring") 338 339 source := ratelimits.NewRedisSource(limiterRedis.Ring, clk, stats) 340 limiter, err = ratelimits.NewLimiter(clk, source, stats) 341 cmd.FailOnError(err, "Failed to create rate limiter") 342 if c.WFE.Limiter.OverridesFromDB { 343 if c.WFE.Limiter.Overrides != "" { 344 cmd.Fail("OverridesFromDB and an overrides file were both defined, but are mutually exclusive") 345 } 346 txnBuilder, err = ratelimits.NewTransactionBuilderFromDatabase(c.WFE.Limiter.Defaults, sac.GetEnabledRateLimitOverrides, stats, logger) 347 } else { 348 txnBuilder, err = ratelimits.NewTransactionBuilderFromFiles(c.WFE.Limiter.Defaults, c.WFE.Limiter.Overrides, stats, logger) 349 } 350 cmd.FailOnError(err, "Failed to create rate limits transaction builder") 351 352 // The 30 minute period here must be kept in sync with the promise 353 // (successCommentBody) made to requesters in sfe/overridesimporter.go 354 overridesRefresherShutdown = txnBuilder.NewRefresher(30 * time.Minute) 355 } 356 357 var accountGetter wfe2.AccountGetter 358 if c.WFE.AccountCache != nil { 359 accountGetter = wfe2.NewAccountCache(sac, 360 c.WFE.AccountCache.Size, 361 c.WFE.AccountCache.TTL.Duration, 362 clk, 363 stats) 364 } else { 365 accountGetter = sac 366 } 367 wfe, err := wfe2.NewWebFrontEndImpl( 368 stats, 369 clk, 370 kp, 371 certChains, 372 issuerCerts, 373 logger, 374 c.WFE.Timeout.Duration, 375 c.WFE.StaleTimeout.Duration, 376 c.WFE.MaxContactsPerRegistration, 377 rac, 378 sac, 379 eec, 380 gnc, 381 rnc, 382 noncePrefixKey, 383 accountGetter, 384 limiter, 385 txnBuilder, 386 c.WFE.CertProfiles, 387 unpauseSigner, 388 c.WFE.Unpause.JWTLifetime.Duration, 389 c.WFE.Unpause.URL, 390 ) 391 cmd.FailOnError(err, "Unable to create WFE") 392 393 wfe.SubscriberAgreementURL = c.WFE.SubscriberAgreementURL 394 wfe.AllowOrigins = c.WFE.AllowOrigins 395 wfe.DirectoryCAAIdentity = c.WFE.DirectoryCAAIdentity 396 wfe.DirectoryWebsite = c.WFE.DirectoryWebsite 397 wfe.LegacyKeyIDPrefix = c.WFE.LegacyKeyIDPrefix 398 399 if c.WFE.ListenAddress == "" { 400 cmd.Fail("HTTP listen address is not configured") 401 } 402 403 logger.Infof("Server running, listening on %s....", c.WFE.ListenAddress) 404 handler := wfe.Handler(stats, c.OpenTelemetryHTTPConfig.Options()...) 405 406 srv := web.NewServer(c.WFE.ListenAddress, handler, logger) 407 go func() { 408 err := srv.ListenAndServe() 409 if err != nil && err != http.ErrServerClosed { 410 cmd.FailOnError(err, "Running HTTP server") 411 } 412 }() 413 414 tlsSrv := web.NewServer(c.WFE.TLSListenAddress, handler, logger) 415 if tlsSrv.Addr != "" { 416 go func() { 417 logger.Infof("TLS server listening on %s", tlsSrv.Addr) 418 err := tlsSrv.ListenAndServeTLS(c.WFE.ServerCertificatePath, c.WFE.ServerKeyPath) 419 if err != nil && err != http.ErrServerClosed { 420 cmd.FailOnError(err, "Running TLS server") 421 } 422 }() 423 } 424 425 // When main is ready to exit (because it has received a shutdown signal), 426 // gracefully shutdown the servers. Calling these shutdown functions causes 427 // ListenAndServe() and ListenAndServeTLS() to immediately return, then waits 428 // for any lingering connection-handling goroutines to finish their work. 429 defer func() { 430 ctx, cancel := context.WithTimeout(context.Background(), c.WFE.ShutdownStopTimeout.Duration) 431 defer cancel() 432 overridesRefresherShutdown() 433 _ = srv.Shutdown(ctx) 434 _ = tlsSrv.Shutdown(ctx) 435 limiterRedis.StopLookups() 436 oTelShutdown(ctx) 437 }() 438 439 cmd.WaitForSignal() 440 } 441 442 func init() { 443 cmd.RegisterCommand("boulder-wfe2", main, &cmd.ConfigValidator{Config: &Config{}}) 444 }