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 }