github.com/letsencrypt/boulder@v0.20251208.0/cmd/sfe/main.go (about)

     1  package notmain
     2  
     3  import (
     4  	"context"
     5  	"flag"
     6  	"net/http"
     7  	"os"
     8  	"sync"
     9  
    10  	"github.com/jmhodges/clock"
    11  
    12  	"github.com/letsencrypt/boulder/cmd"
    13  	"github.com/letsencrypt/boulder/config"
    14  	emailpb "github.com/letsencrypt/boulder/email/proto"
    15  	"github.com/letsencrypt/boulder/features"
    16  	bgrpc "github.com/letsencrypt/boulder/grpc"
    17  	rapb "github.com/letsencrypt/boulder/ra/proto"
    18  	"github.com/letsencrypt/boulder/ratelimits"
    19  	bredis "github.com/letsencrypt/boulder/redis"
    20  	sapb "github.com/letsencrypt/boulder/sa/proto"
    21  	"github.com/letsencrypt/boulder/sfe"
    22  	"github.com/letsencrypt/boulder/sfe/zendesk"
    23  	"github.com/letsencrypt/boulder/web"
    24  )
    25  
    26  type Config struct {
    27  	SFE struct {
    28  		DebugAddr string `validate:"omitempty,hostname_port"`
    29  
    30  		// ListenAddress is the address:port on which to listen for incoming
    31  		// HTTP requests. Defaults to ":80".
    32  		ListenAddress string `validate:"omitempty,hostname_port"`
    33  
    34  		// Timeout is the per-request overall timeout. This should be slightly
    35  		// lower than the upstream's timeout when making requests to this service.
    36  		Timeout config.Duration `validate:"-"`
    37  
    38  		// ShutdownStopTimeout determines the maximum amount of time to wait
    39  		// for extant request handlers to complete before exiting. It should be
    40  		// greater than Timeout.
    41  		ShutdownStopTimeout config.Duration
    42  
    43  		TLS cmd.TLSConfig
    44  
    45  		RAService     *cmd.GRPCClientConfig
    46  		SAService     *cmd.GRPCClientConfig
    47  		EmailExporter *cmd.GRPCClientConfig
    48  
    49  		// UnpauseHMACKey validates incoming JWT signatures at the unpause
    50  		// endpoint. This key must be the same as the one configured for all
    51  		// WFEs. This field is required to enable the pausing feature.
    52  		UnpauseHMACKey cmd.HMACKeyConfig
    53  
    54  		Zendesk *struct {
    55  			BaseURL      string             `validate:"required,url"`
    56  			TokenEmail   string             `validate:"required,email"`
    57  			Token        cmd.PasswordConfig `validate:"required,dive"`
    58  			CustomFields struct {
    59  				Organization     int64 `validate:"required"`
    60  				Tier             int64 `validate:"required"`
    61  				RateLimit        int64 `validate:"required"`
    62  				ReviewStatus     int64 `validate:"required"`
    63  				AccountURI       int64 `validate:"required"`
    64  				RegisteredDomain int64 `validate:"required"`
    65  				IPAddress        int64 `validate:"required"`
    66  			} `validate:"required,dive"`
    67  		} `validate:"omitempty,dive"`
    68  
    69  		Limiter struct {
    70  			// Redis contains the configuration necessary to connect to Redis
    71  			// for rate limiting. This field is required to enable rate
    72  			// limiting.
    73  			Redis *bredis.Config `validate:"required_with=Defaults"`
    74  
    75  			// Defaults is a path to a YAML file containing default rate limits.
    76  			// See: ratelimits/README.md for details. This field is required to
    77  			// enable rate limiting. If any individual rate limit is not set,
    78  			// that limit will be disabled. Failed Authorizations limits passed
    79  			// in this file must be identical to those in the RA.
    80  			Defaults string `validate:"required_with=Redis"`
    81  		}
    82  
    83  		// OverridesImporter configures the periodic import of approved rate
    84  		// limit override requests from Zendesk.
    85  		OverridesImporter struct {
    86  			// Mode controls which tickets are processed. Valid values are:
    87  			//   - "all": process all tickets
    88  			//   - "even": process only tickets with even IDs
    89  			//   - "odd": process only tickets with odd IDs
    90  			// If unspecified or empty, defaults to "all".
    91  			Mode string `validate:"omitempty,required_with=Interval,oneof=all even odd"`
    92  			// Interval is the amount of time between runs of the importer. If
    93  			// zero or unspecified, the importer is disabled. Minimum value is
    94  			// 20 minutes.
    95  			Interval config.Duration `validate:"omitempty,required_with=Mode,min=1200s"`
    96  		} `validate:"omitempty,dive"`
    97  
    98  		// AutoApproveOverrides enables automatic approval of override requests
    99  		// for the following limits and tiers:
   100  		//   - NewOrdersPerAccount: 1000
   101  		//   - CertificatesPerDomain: 300
   102  		//   - CertificatesPerDomainPerAccount: 300
   103  		AutoApproveOverrides bool `validate:"-"`
   104  		Features             features.Config
   105  	}
   106  
   107  	Syslog        cmd.SyslogConfig
   108  	OpenTelemetry cmd.OpenTelemetryConfig
   109  
   110  	// OpenTelemetryHTTPConfig configures tracing on incoming HTTP requests
   111  	OpenTelemetryHTTPConfig cmd.OpenTelemetryHTTPConfig
   112  }
   113  
   114  func main() {
   115  	listenAddr := flag.String("addr", "", "HTTP listen address override")
   116  	debugAddr := flag.String("debug-addr", "", "Debug server address override")
   117  	configFile := flag.String("config", "", "File path to the configuration file for this service")
   118  	flag.Parse()
   119  	if *configFile == "" {
   120  		flag.Usage()
   121  		os.Exit(1)
   122  	}
   123  
   124  	var c Config
   125  	err := cmd.ReadConfigFile(*configFile, &c)
   126  	cmd.FailOnError(err, "Reading JSON config file into config structure")
   127  
   128  	features.Set(c.SFE.Features)
   129  
   130  	if *listenAddr != "" {
   131  		c.SFE.ListenAddress = *listenAddr
   132  	}
   133  	if c.SFE.ListenAddress == "" {
   134  		cmd.Fail("HTTP listen address is not configured")
   135  	}
   136  	if *debugAddr != "" {
   137  		c.SFE.DebugAddr = *debugAddr
   138  	}
   139  
   140  	stats, logger, oTelShutdown := cmd.StatsAndLogging(c.Syslog, c.OpenTelemetry, c.SFE.DebugAddr)
   141  	logger.Info(cmd.VersionString())
   142  
   143  	clk := clock.New()
   144  
   145  	unpauseHMACKey, err := c.SFE.UnpauseHMACKey.Load()
   146  	cmd.FailOnError(err, "Failed to load unpauseHMACKey")
   147  
   148  	tlsConfig, err := c.SFE.TLS.Load(stats)
   149  	cmd.FailOnError(err, "TLS config")
   150  
   151  	raConn, err := bgrpc.ClientSetup(c.SFE.RAService, tlsConfig, stats, clk)
   152  	cmd.FailOnError(err, "Failed to load credentials and create gRPC connection to RA")
   153  	rac := rapb.NewRegistrationAuthorityClient(raConn)
   154  
   155  	saConn, err := bgrpc.ClientSetup(c.SFE.SAService, tlsConfig, stats, clk)
   156  	cmd.FailOnError(err, "Failed to load credentials and create gRPC connection to SA")
   157  	sac := sapb.NewStorageAuthorityReadOnlyClient(saConn)
   158  
   159  	var eec emailpb.ExporterClient
   160  	if c.SFE.EmailExporter != nil {
   161  		emailExporterConn, err := bgrpc.ClientSetup(c.SFE.EmailExporter, tlsConfig, stats, clk)
   162  		cmd.FailOnError(err, "Failed to load credentials and create gRPC connection to email-exporter")
   163  		eec = emailpb.NewExporterClient(emailExporterConn)
   164  	}
   165  
   166  	var zendeskClient *zendesk.Client
   167  	var overridesImporterShutdown func()
   168  	var overridesImporterWG sync.WaitGroup
   169  	if c.SFE.Zendesk != nil {
   170  		zendeskToken, err := c.SFE.Zendesk.Token.Pass()
   171  		cmd.FailOnError(err, "Failed to load Zendesk token")
   172  
   173  		zendeskClient, err = zendesk.NewClient(
   174  			c.SFE.Zendesk.BaseURL,
   175  			c.SFE.Zendesk.TokenEmail,
   176  			zendeskToken,
   177  			map[string]int64{
   178  				sfe.OrganizationFieldName:     c.SFE.Zendesk.CustomFields.Organization,
   179  				sfe.TierFieldName:             c.SFE.Zendesk.CustomFields.Tier,
   180  				sfe.RateLimitFieldName:        c.SFE.Zendesk.CustomFields.RateLimit,
   181  				sfe.ReviewStatusFieldName:     c.SFE.Zendesk.CustomFields.ReviewStatus,
   182  				sfe.AccountURIFieldName:       c.SFE.Zendesk.CustomFields.AccountURI,
   183  				sfe.RegisteredDomainFieldName: c.SFE.Zendesk.CustomFields.RegisteredDomain,
   184  				sfe.IPAddressFieldName:        c.SFE.Zendesk.CustomFields.IPAddress,
   185  			},
   186  		)
   187  		if err != nil {
   188  			cmd.FailOnError(err, "Failed to create Zendesk client")
   189  		}
   190  
   191  		if c.SFE.OverridesImporter.Interval.Duration > 0 {
   192  			mode := sfe.ProcessMode(c.SFE.OverridesImporter.Mode)
   193  			if mode == "" {
   194  				mode = sfe.ProcessAll
   195  			}
   196  
   197  			importer, err := sfe.NewOverridesImporter(
   198  				mode,
   199  				c.SFE.OverridesImporter.Interval.Duration,
   200  				zendeskClient,
   201  				rac,
   202  				clk,
   203  				logger,
   204  			)
   205  			cmd.FailOnError(err, "Creating overrides importer")
   206  
   207  			var ctx context.Context
   208  			ctx, overridesImporterShutdown = context.WithCancel(context.Background())
   209  			overridesImporterWG.Go(func() {
   210  				importer.Start(ctx)
   211  			})
   212  			logger.Infof("Overrides importer started with mode=%s interval=%s", mode, c.SFE.OverridesImporter.Interval.Duration)
   213  		}
   214  	}
   215  
   216  	var limiter *ratelimits.Limiter
   217  	var txnBuilder *ratelimits.TransactionBuilder
   218  	var limiterRedis *bredis.Ring
   219  	if c.SFE.Limiter.Defaults != "" {
   220  		limiterRedis, err = bredis.NewRingFromConfig(*c.SFE.Limiter.Redis, stats, logger)
   221  		cmd.FailOnError(err, "Failed to create Redis ring")
   222  
   223  		source := ratelimits.NewRedisSource(limiterRedis.Ring, clk, stats)
   224  		limiter, err = ratelimits.NewLimiter(clk, source, stats)
   225  		cmd.FailOnError(err, "Failed to create rate limiter")
   226  		txnBuilder, err = ratelimits.NewTransactionBuilderFromFiles(c.SFE.Limiter.Defaults, "", stats, logger)
   227  		cmd.FailOnError(err, "Failed to create rate limits transaction builder")
   228  	}
   229  
   230  	sfei, err := sfe.NewSelfServiceFrontEndImpl(
   231  		stats,
   232  		clk,
   233  		logger,
   234  		c.SFE.Timeout.Duration,
   235  		rac,
   236  		sac,
   237  		eec,
   238  		unpauseHMACKey,
   239  		zendeskClient,
   240  		limiter,
   241  		txnBuilder,
   242  		c.SFE.AutoApproveOverrides,
   243  	)
   244  	cmd.FailOnError(err, "Unable to create SFE")
   245  
   246  	logger.Infof("Server running, listening on %s....", c.SFE.ListenAddress)
   247  	handler := sfei.Handler(stats, c.OpenTelemetryHTTPConfig.Options()...)
   248  
   249  	srv := web.NewServer(c.SFE.ListenAddress, handler, logger)
   250  	go func() {
   251  		err := srv.ListenAndServe()
   252  		if err != nil && err != http.ErrServerClosed {
   253  			cmd.FailOnError(err, "Running HTTP server")
   254  		}
   255  	}()
   256  
   257  	// When main is ready to exit (because it has received a shutdown signal),
   258  	// gracefully shutdown the servers. Calling these shutdown functions causes
   259  	// ListenAndServe() and ListenAndServeTLS() to immediately return, then waits
   260  	// for any lingering connection-handling goroutines to finish their work.
   261  	defer func() {
   262  		ctx, cancel := context.WithTimeout(context.Background(), c.SFE.ShutdownStopTimeout.Duration)
   263  		defer cancel()
   264  		if overridesImporterShutdown != nil {
   265  			overridesImporterShutdown()
   266  			overridesImporterWG.Wait()
   267  		}
   268  		_ = srv.Shutdown(ctx)
   269  		oTelShutdown(ctx)
   270  	}()
   271  
   272  	cmd.WaitForSignal()
   273  }
   274  
   275  func init() {
   276  	cmd.RegisterCommand("sfe", main, &cmd.ConfigValidator{Config: &Config{}})
   277  }