...

Source file src/github.com/letsencrypt/boulder/cmd/boulder-wfe2/main.go

Documentation: github.com/letsencrypt/boulder/cmd/boulder-wfe2

     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  		// 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 request to the WFE.
    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  		// GetNonceService is a gRPC config which contains a single SRV name
    66  		// used to lookup nonce-service instances used exclusively for nonce
    67  		// creation. In a multi-DC deployment this should refer to local
    68  		// nonce-service instances only.
    69  		GetNonceService *cmd.GRPCClientConfig
    70  
    71  		// RedeemNonceServices contains a map of nonce-service prefixes to
    72  		// gRPC configs we want to use to redeem nonces. In a multi-DC deployment
    73  		// this should contain all nonce-services from all DCs as we want to be
    74  		// able to redeem nonces generated at any DC.
    75  		//
    76  		// DEPRECATED: See RedeemNonceService, below.
    77  		// TODO (#6610) Remove this after all configs have migrated to
    78  		// `RedeemNonceService`.
    79  		RedeemNonceServices map[string]cmd.GRPCClientConfig `validate:"required_without=RedeemNonceService,omitempty,min=1,dive"`
    80  
    81  		// RedeemNonceService is a gRPC config which contains a list of SRV
    82  		// names used to lookup nonce-service instances used exclusively for
    83  		// nonce redemption. In a multi-DC deployment this should contain both
    84  		// local and remote nonce-service instances.
    85  		RedeemNonceService *cmd.GRPCClientConfig `validate:"required_without=RedeemNonceServices"`
    86  
    87  		// NoncePrefixKey is a secret used for deriving the prefix of each nonce
    88  		// instance. It should contain 256 bits of random data to be suitable as
    89  		// an HMAC-SHA256 key (e.g. the output of `openssl rand -hex 32`). In a
    90  		// multi-DC deployment this value should be the same across all
    91  		// boulder-wfe and nonce-service instances.
    92  		NoncePrefixKey cmd.PasswordConfig `validate:"-"`
    93  
    94  		// Chains is a list of lists of certificate filenames. Each inner list is
    95  		// a chain (starting with the issuing intermediate, followed by one or
    96  		// more additional certificates, up to and including a root) which we are
    97  		// willing to serve. Chains that start with a given intermediate will only
    98  		// be offered for certificates which were issued by the key pair represented
    99  		// by that intermediate. The first chain representing any given issuing
   100  		// key pair will be the default for that issuer, served if the client does
   101  		// not request a specific chain.
   102  		Chains [][]string `validate:"required,min=1,dive,min=2,dive,required"`
   103  
   104  		Features map[string]bool
   105  
   106  		// DirectoryCAAIdentity is used for the /directory response's "meta"
   107  		// element's "caaIdentities" field. It should match the VA's "issuerDomain"
   108  		// configuration value (this value is the one used to enforce CAA)
   109  		DirectoryCAAIdentity string `validate:"required,fqdn"`
   110  		// DirectoryWebsite is used for the /directory response's "meta" element's
   111  		// "website" field.
   112  		DirectoryWebsite string `validate:"required,url"`
   113  
   114  		// ACMEv2 requests (outside some registration/revocation messages) use a JWS with
   115  		// a KeyID header containing the full account URL. For new accounts this
   116  		// will be a KeyID based on the HTTP request's Host header and the ACMEv2
   117  		// account path. For legacy ACMEv1 accounts we need to whitelist the account
   118  		// ID prefix that legacy accounts would have been using based on the Host
   119  		// header of the WFE1 instance and the legacy 'reg' path component. This
   120  		// will differ in configuration for production and staging.
   121  		LegacyKeyIDPrefix string `validate:"required,url"`
   122  
   123  		// GoodKey is an embedded config stanza for the goodkey library.
   124  		GoodKey goodkey.Config
   125  
   126  		// StaleTimeout determines how old should data be to be accessed via Boulder-specific GET-able APIs
   127  		StaleTimeout config.Duration `validate:"-"`
   128  
   129  		// AuthorizationLifetimeDays defines how long authorizations will be
   130  		// considered valid for. The WFE uses this to find the creation date of
   131  		// authorizations by subtracing this value from the expiry. It should match
   132  		// the value configured in the RA.
   133  		AuthorizationLifetimeDays int `validate:"required,min=1,max=397"`
   134  
   135  		// PendingAuthorizationLifetimeDays defines how long authorizations may be in
   136  		// the pending state before expiry. The WFE uses this to find the creation
   137  		// date of pending authorizations by subtracting this value from the expiry.
   138  		// It should match the value configured in the RA.
   139  		PendingAuthorizationLifetimeDays int `validate:"required,min=1,max=29"`
   140  
   141  		AccountCache *CacheConfig
   142  
   143  		Limiter struct {
   144  			// Redis contains the configuration necessary to connect to Redis
   145  			// for rate limiting. This field is required to enable rate
   146  			// limiting.
   147  			Redis *bredis.Config `validate:"required_with=Defaults"`
   148  
   149  			// Defaults is a path to a YAML file containing default rate limits.
   150  			// See: ratelimits/README.md for details. This field is required to
   151  			// enable rate limiting. If any individual rate limit is not set,
   152  			// that limit will be disabled.
   153  			Defaults string `validate:"required_with=Redis"`
   154  
   155  			// Overrides is a path to a YAML file containing overrides for the
   156  			// default rate limits. See: ratelimits/README.md for details. If
   157  			// this field is not set, all requesters will be subject to the
   158  			// default rate limits.
   159  			Overrides string
   160  		}
   161  	}
   162  
   163  	Syslog        cmd.SyslogConfig
   164  	OpenTelemetry cmd.OpenTelemetryConfig
   165  
   166  	// OpenTelemetryHTTPConfig configures tracing on incoming HTTP requests
   167  	OpenTelemetryHTTPConfig cmd.OpenTelemetryHTTPConfig
   168  }
   169  
   170  type CacheConfig struct {
   171  	Size int
   172  	TTL  config.Duration
   173  }
   174  
   175  // loadChain takes a list of filenames containing pem-formatted certificates,
   176  // and returns a chain representing all of those certificates in order. It
   177  // ensures that the resulting chain is valid. The final file is expected to be
   178  // a root certificate, which the chain will be verified against, but which will
   179  // not be included in the resulting chain.
   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  	// Iterate over all certs appending their pem to the buf.
   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  	// TODO(#6610) Refactor these checks.
   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  		// Dispatch nonce redemption RPCs dynamically.
   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  		// Dispatch nonce redpemption RPCs using a static mapping.
   246  		//
   247  		// TODO(#6610) Remove code below and the `npm` mapping.
   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  	// log.Logger will append a newline to all messages before calling
   266  	// Write. Our log checksum checker doesn't like newlines, because
   267  	// syslog will strip them out so the calculated checksums will
   268  	// differ. So that we don't hit this corner case for every line
   269  	// logged from inside net/http.Server we strip the newline before
   270  	// we get to the checksum generator.
   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  		// This may overwrite a previously-set issuerCert (e.g. if there are two
   303  		// chains for the same issuer, but with different versions of the same
   304  		// same intermediate issued by different roots). This is okay, as the
   305  		// only truly important content here is the public key to verify other
   306  		// certs.
   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  	// Baseline Requirements v1.8.1 section 4.2.1: "any reused data, document,
   325  	// or completed validation MUST be obtained no more than 398 days prior
   326  	// to issuing the Certificate". If unconfigured or the configured value is
   327  	// greater than 397 days, bail out.
   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  	// The Baseline Requirements v1.8.1 state that validation tokens "MUST
   334  	// NOT be used for more than 30 days from its creation". If unconfigured
   335  	// or the configured value pendingAuthorizationLifetimeDays is greater
   336  	// than 29 days, bail out.
   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  		// Setup rate limiting.
   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  	// When main is ready to exit (because it has received a shutdown signal),
   431  	// gracefully shutdown the servers. Calling these shutdown functions causes
   432  	// ListenAndServe() and ListenAndServeTLS() to immediately return, then waits
   433  	// for any lingering connection-handling goroutines to finish their work.
   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