1 package notmain
2
3 import (
4 "bytes"
5 "context"
6 "encoding/pem"
7 "flag"
8 "fmt"
9 "log"
10 "net/http"
11 "os"
12 "time"
13
14 "github.com/jmhodges/clock"
15 "github.com/prometheus/client_golang/prometheus"
16
17 "github.com/letsencrypt/boulder/cmd"
18 "github.com/letsencrypt/boulder/config"
19 "github.com/letsencrypt/boulder/features"
20 "github.com/letsencrypt/boulder/goodkey"
21 "github.com/letsencrypt/boulder/goodkey/sagoodkey"
22 bgrpc "github.com/letsencrypt/boulder/grpc"
23 "github.com/letsencrypt/boulder/grpc/noncebalancer"
24 "github.com/letsencrypt/boulder/issuance"
25 blog "github.com/letsencrypt/boulder/log"
26 "github.com/letsencrypt/boulder/nonce"
27 rapb "github.com/letsencrypt/boulder/ra/proto"
28 "github.com/letsencrypt/boulder/ratelimits"
29 bredis "github.com/letsencrypt/boulder/redis"
30 sapb "github.com/letsencrypt/boulder/sa/proto"
31 "github.com/letsencrypt/boulder/wfe2"
32 )
33
34 type Config struct {
35 WFE struct {
36 DebugAddr string `validate:"required,hostname_port"`
37
38
39
40 ListenAddress string `validate:"omitempty,hostname_port"`
41
42
43
44
45 TLSListenAddress string `validate:"omitempty,hostname_port"`
46
47
48
49 Timeout config.Duration `validate:"-"`
50
51 ServerCertificatePath string `validate:"required_with=TLSListenAddress"`
52 ServerKeyPath string `validate:"required_with=TLSListenAddress"`
53
54 AllowOrigins []string
55
56 ShutdownStopTimeout config.Duration
57
58 SubscriberAgreementURL string
59
60 TLS cmd.TLSConfig
61
62 RAService *cmd.GRPCClientConfig
63 SAService *cmd.GRPCClientConfig
64
65
66
67
68
69 GetNonceService *cmd.GRPCClientConfig
70
71
72
73
74
75
76
77
78
79 RedeemNonceServices map[string]cmd.GRPCClientConfig `validate:"required_without=RedeemNonceService,omitempty,min=1,dive"`
80
81
82
83
84
85 RedeemNonceService *cmd.GRPCClientConfig `validate:"required_without=RedeemNonceServices"`
86
87
88
89
90
91
92 NoncePrefixKey cmd.PasswordConfig `validate:"-"`
93
94
95
96
97
98
99
100
101
102 Chains [][]string `validate:"required,min=1,dive,min=2,dive,required"`
103
104 Features map[string]bool
105
106
107
108
109 DirectoryCAAIdentity string `validate:"required,fqdn"`
110
111
112 DirectoryWebsite string `validate:"required,url"`
113
114
115
116
117
118
119
120
121 LegacyKeyIDPrefix string `validate:"required,url"`
122
123
124 GoodKey goodkey.Config
125
126
127 StaleTimeout config.Duration `validate:"-"`
128
129
130
131
132
133 AuthorizationLifetimeDays int `validate:"required,min=1,max=397"`
134
135
136
137
138
139 PendingAuthorizationLifetimeDays int `validate:"required,min=1,max=29"`
140
141 AccountCache *CacheConfig
142
143 Limiter struct {
144
145
146
147 Redis *bredis.Config `validate:"required_with=Defaults"`
148
149
150
151
152
153 Defaults string `validate:"required_with=Redis"`
154
155
156
157
158
159 Overrides string
160 }
161 }
162
163 Syslog cmd.SyslogConfig
164 OpenTelemetry cmd.OpenTelemetryConfig
165
166
167 OpenTelemetryHTTPConfig cmd.OpenTelemetryHTTPConfig
168 }
169
170 type CacheConfig struct {
171 Size int
172 TTL config.Duration
173 }
174
175
176
177
178
179
180 func loadChain(certFiles []string) (*issuance.Certificate, []byte, error) {
181 certs, err := issuance.LoadChain(certFiles)
182 if err != nil {
183 return nil, nil, err
184 }
185
186
187 var buf bytes.Buffer
188 for _, cert := range certs {
189 buf.Write([]byte("\n"))
190 buf.Write(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}))
191 }
192
193 return certs[0], buf.Bytes(), nil
194 }
195
196 func setupWFE(c Config, scope prometheus.Registerer, clk clock.Clock) (rapb.RegistrationAuthorityClient, sapb.StorageAuthorityReadOnlyClient, nonce.Getter, map[string]nonce.Redeemer, nonce.Redeemer, string) {
197 tlsConfig, err := c.WFE.TLS.Load(scope)
198 cmd.FailOnError(err, "TLS config")
199
200 raConn, err := bgrpc.ClientSetup(c.WFE.RAService, tlsConfig, scope, clk)
201 cmd.FailOnError(err, "Failed to load credentials and create gRPC connection to RA")
202 rac := rapb.NewRegistrationAuthorityClient(raConn)
203
204 saConn, err := bgrpc.ClientSetup(c.WFE.SAService, tlsConfig, scope, clk)
205 cmd.FailOnError(err, "Failed to load credentials and create gRPC connection to SA")
206 sac := sapb.NewStorageAuthorityReadOnlyClient(saConn)
207
208
209 if c.WFE.RedeemNonceService != nil && c.WFE.RedeemNonceServices != nil {
210 cmd.Fail("Only one of 'redeemNonceService' or 'redeemNonceServices' should be configured.")
211 }
212 if c.WFE.RedeemNonceService == nil && c.WFE.RedeemNonceServices == nil {
213 cmd.Fail("One of 'redeemNonceService' or 'redeemNonceServices' must be configured.")
214 }
215 if c.WFE.RedeemNonceService != nil && c.WFE.NoncePrefixKey.PasswordFile == "" {
216 cmd.Fail("'noncePrefixKey' must be configured if 'redeemNonceService' is configured.")
217 }
218 if c.WFE.GetNonceService == nil {
219 cmd.Fail("'getNonceService' must be configured")
220 }
221
222 var rncKey string
223 if c.WFE.NoncePrefixKey.PasswordFile != "" {
224 rncKey, err = c.WFE.NoncePrefixKey.Pass()
225 cmd.FailOnError(err, "Failed to load noncePrefixKey")
226 }
227
228 getNonceConn, err := bgrpc.ClientSetup(c.WFE.GetNonceService, tlsConfig, scope, clk)
229 cmd.FailOnError(err, "Failed to load credentials and create gRPC connection to get nonce service")
230 gnc := nonce.NewGetter(getNonceConn)
231
232 var rnc nonce.Redeemer
233 var npm map[string]nonce.Redeemer
234 if c.WFE.RedeemNonceService != nil {
235
236 if c.WFE.RedeemNonceService.SRVResolver != noncebalancer.SRVResolverScheme {
237 cmd.Fail(fmt.Sprintf(
238 "'redeemNonceService.SRVResolver' must be set to %q", noncebalancer.SRVResolverScheme),
239 )
240 }
241 redeemNonceConn, err := bgrpc.ClientSetup(c.WFE.RedeemNonceService, tlsConfig, scope, clk)
242 cmd.FailOnError(err, "Failed to load credentials and create gRPC connection to redeem nonce service")
243 rnc = nonce.NewRedeemer(redeemNonceConn)
244 } else {
245
246
247
248 npm = make(map[string]nonce.Redeemer)
249 for prefix, serviceConfig := range c.WFE.RedeemNonceServices {
250 serviceConfig := serviceConfig
251 conn, err := bgrpc.ClientSetup(&serviceConfig, tlsConfig, scope, clk)
252 cmd.FailOnError(err, "Failed to load credentials and create gRPC connection to redeem nonce service")
253 npm[prefix] = nonce.NewRedeemer(conn)
254 }
255 }
256
257 return rac, sac, gnc, npm, rnc, rncKey
258 }
259
260 type errorWriter struct {
261 blog.Logger
262 }
263
264 func (ew errorWriter) Write(p []byte) (n int, err error) {
265
266
267
268
269
270
271 p = bytes.TrimRight(p, "\n")
272 ew.Logger.Err(fmt.Sprintf("net/http.Server: %s", string(p)))
273 return
274 }
275
276 func main() {
277 configFile := flag.String("config", "", "File path to the configuration file for this service")
278 flag.Parse()
279 if *configFile == "" {
280 flag.Usage()
281 os.Exit(1)
282 }
283
284 var c Config
285 err := cmd.ReadConfigFile(*configFile, &c)
286 cmd.FailOnError(err, "Reading JSON config file into config structure")
287
288 err = features.Set(c.WFE.Features)
289 cmd.FailOnError(err, "Failed to set feature flags")
290
291 certChains := map[issuance.IssuerNameID][][]byte{}
292 issuerCerts := map[issuance.IssuerNameID]*issuance.Certificate{}
293 if c.WFE.Chains == nil {
294 cmd.Fail("'chains' must be configured")
295 }
296 for _, files := range c.WFE.Chains {
297 issuer, chain, err := loadChain(files)
298 cmd.FailOnError(err, "Failed to load chain")
299
300 id := issuer.NameID()
301 certChains[id] = append(certChains[id], chain)
302
303
304
305
306
307 issuerCerts[id] = issuer
308 }
309
310 stats, logger, oTelShutdown := cmd.StatsAndLogging(c.Syslog, c.OpenTelemetry, c.WFE.DebugAddr)
311 logger.Info(cmd.VersionString())
312
313 clk := cmd.Clock()
314
315 rac, sac, gnc, npm, rnc, npKey := setupWFE(c, stats, clk)
316
317 kp, err := sagoodkey.NewKeyPolicy(&c.WFE.GoodKey, sac.KeyBlocked)
318 cmd.FailOnError(err, "Unable to create key policy")
319
320 if c.WFE.StaleTimeout.Duration == 0 {
321 c.WFE.StaleTimeout.Duration = time.Minute * 10
322 }
323
324
325
326
327
328 if c.WFE.AuthorizationLifetimeDays <= 0 || c.WFE.AuthorizationLifetimeDays > 397 {
329 cmd.Fail("authorizationLifetimeDays value must be greater than 0 and less than 398")
330 }
331 authorizationLifetime := time.Duration(c.WFE.AuthorizationLifetimeDays) * 24 * time.Hour
332
333
334
335
336
337 if c.WFE.PendingAuthorizationLifetimeDays <= 0 || c.WFE.PendingAuthorizationLifetimeDays > 29 {
338 cmd.Fail("pendingAuthorizationLifetimeDays value must be greater than 0 and less than 30")
339 }
340 pendingAuthorizationLifetime := time.Duration(c.WFE.PendingAuthorizationLifetimeDays) * 24 * time.Hour
341
342 var limiter *ratelimits.Limiter
343 var limiterRedis *bredis.Ring
344 if c.WFE.Limiter.Defaults != "" {
345
346 limiterRedis, err = bredis.NewRingFromConfig(*c.WFE.Limiter.Redis, stats, logger)
347 cmd.FailOnError(err, "Failed to create Redis ring")
348
349 source := ratelimits.NewRedisSource(limiterRedis.Ring, clk, stats)
350 limiter, err = ratelimits.NewLimiter(clk, source, c.WFE.Limiter.Defaults, c.WFE.Limiter.Overrides, stats)
351 cmd.FailOnError(err, "Failed to create rate limiter")
352 }
353
354 var accountGetter wfe2.AccountGetter
355 if c.WFE.AccountCache != nil {
356 accountGetter = wfe2.NewAccountCache(sac,
357 c.WFE.AccountCache.Size,
358 c.WFE.AccountCache.TTL.Duration,
359 clk,
360 stats)
361 } else {
362 accountGetter = sac
363 }
364 wfe, err := wfe2.NewWebFrontEndImpl(
365 stats,
366 clk,
367 kp,
368 certChains,
369 issuerCerts,
370 logger,
371 c.WFE.Timeout.Duration,
372 c.WFE.StaleTimeout.Duration,
373 authorizationLifetime,
374 pendingAuthorizationLifetime,
375 rac,
376 sac,
377 gnc,
378 npm,
379 rnc,
380 npKey,
381 accountGetter,
382 limiter,
383 )
384 cmd.FailOnError(err, "Unable to create WFE")
385
386 wfe.SubscriberAgreementURL = c.WFE.SubscriberAgreementURL
387 wfe.AllowOrigins = c.WFE.AllowOrigins
388 wfe.DirectoryCAAIdentity = c.WFE.DirectoryCAAIdentity
389 wfe.DirectoryWebsite = c.WFE.DirectoryWebsite
390 wfe.LegacyKeyIDPrefix = c.WFE.LegacyKeyIDPrefix
391
392 logger.Infof("WFE using key policy: %#v", kp)
393
394 logger.Infof("Server running, listening on %s....", c.WFE.ListenAddress)
395 handler := wfe.Handler(stats, c.OpenTelemetryHTTPConfig.Options()...)
396
397 srv := http.Server{
398 ReadTimeout: 30 * time.Second,
399 WriteTimeout: 120 * time.Second,
400 IdleTimeout: 120 * time.Second,
401 Addr: c.WFE.ListenAddress,
402 ErrorLog: log.New(errorWriter{logger}, "", 0),
403 Handler: handler,
404 }
405
406 go func() {
407 err := srv.ListenAndServe()
408 if err != nil && err != http.ErrServerClosed {
409 cmd.FailOnError(err, "Running HTTP server")
410 }
411 }()
412
413 tlsSrv := http.Server{
414 ReadTimeout: 30 * time.Second,
415 WriteTimeout: 120 * time.Second,
416 IdleTimeout: 120 * time.Second,
417 Addr: c.WFE.TLSListenAddress,
418 ErrorLog: log.New(errorWriter{logger}, "", 0),
419 Handler: handler,
420 }
421 if tlsSrv.Addr != "" {
422 go func() {
423 err := tlsSrv.ListenAndServeTLS(c.WFE.ServerCertificatePath, c.WFE.ServerKeyPath)
424 if err != nil && err != http.ErrServerClosed {
425 cmd.FailOnError(err, "Running TLS server")
426 }
427 }()
428 }
429
430
431
432
433
434 defer func() {
435 ctx, cancel := context.WithTimeout(context.Background(), c.WFE.ShutdownStopTimeout.Duration)
436 defer cancel()
437 _ = srv.Shutdown(ctx)
438 _ = tlsSrv.Shutdown(ctx)
439 limiterRedis.StopLookups()
440 oTelShutdown(ctx)
441 }()
442
443 cmd.WaitForSignal()
444 }
445
446 func init() {
447 cmd.RegisterCommand("boulder-wfe2", main, &cmd.ConfigValidator{Config: &Config{}})
448 }
449
View as plain text