-
-
Notifications
You must be signed in to change notification settings - Fork 610
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
sfe: Implement self-service frontend for account pausing/unpausing (#…
…7500) Adds a new boulder component named `sfe` aka the Self-service FrontEnd which is dedicated to non-ACME related Subscriber functions. This change implements one such function which is a web interface and handlers for account unpausing. When paused, an ACME client receives a log line URL with a JWT parameter from the WFE. For the observant Subscriber, manually clicking the link opens their web browser and displays a page with a pre-filled HTML form. Upon clicking the form button, the SFE sends an HTTP POST back to itself and either validates the JWT and issues an RA gRPC request to unpause the account, or returns an HTML error page. The SFE and WFE should share a 32 byte seed value e.g. the output of `openssl rand -hex 16` which will be used as a go-jose symmetric signer using the HS256 algorithm. The SFE will check various [RFC 7519](https://datatracker.ietf.org/doc/html/rfc7519) claims on the JWT such as the `iss`, `aud`, `nbf`, `exp`, `iat`, and a custom `apiVersion` claim. The SFE should not yet be relied upon or deployed to staging/production environments. It is very much a work in progress, but this change is big enough as-is. Related to #7406 Part of #7499
- Loading branch information
Showing
30 changed files
with
2,140 additions
and
39 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
package notmain | ||
|
||
import ( | ||
"context" | ||
"flag" | ||
"net/http" | ||
"os" | ||
|
||
"github.com/letsencrypt/boulder/cmd" | ||
"github.com/letsencrypt/boulder/config" | ||
"github.com/letsencrypt/boulder/features" | ||
bgrpc "github.com/letsencrypt/boulder/grpc" | ||
rapb "github.com/letsencrypt/boulder/ra/proto" | ||
sapb "github.com/letsencrypt/boulder/sa/proto" | ||
"github.com/letsencrypt/boulder/sfe" | ||
"github.com/letsencrypt/boulder/web" | ||
) | ||
|
||
type Config struct { | ||
SFE struct { | ||
DebugAddr string `validate:"omitempty,hostname_port"` | ||
|
||
// ListenAddress is the address:port on which to listen for incoming | ||
// HTTP requests. Defaults to ":80". | ||
ListenAddress string `validate:"omitempty,hostname_port"` | ||
|
||
// Timeout is the per-request overall timeout. This should be slightly | ||
// lower than the upstream's timeout when making requests to the SFE. | ||
Timeout config.Duration `validate:"-"` | ||
|
||
// ShutdownStopTimeout is the duration that the SFE will wait before | ||
// shutting down any listening servers. | ||
ShutdownStopTimeout config.Duration | ||
|
||
TLS cmd.TLSConfig | ||
|
||
RAService *cmd.GRPCClientConfig | ||
SAService *cmd.GRPCClientConfig | ||
|
||
Unpause cmd.UnpauseConfig | ||
|
||
Features features.Config | ||
} | ||
|
||
Syslog cmd.SyslogConfig | ||
OpenTelemetry cmd.OpenTelemetryConfig | ||
|
||
// OpenTelemetryHTTPConfig configures tracing on incoming HTTP requests | ||
OpenTelemetryHTTPConfig cmd.OpenTelemetryHTTPConfig | ||
} | ||
|
||
func main() { | ||
listenAddr := flag.String("addr", "", "HTTP listen address override") | ||
debugAddr := flag.String("debug-addr", "", "Debug server address override") | ||
configFile := flag.String("config", "", "File path to the configuration file for this service") | ||
flag.Parse() | ||
if *configFile == "" { | ||
flag.Usage() | ||
os.Exit(1) | ||
} | ||
|
||
var c Config | ||
err := cmd.ReadConfigFile(*configFile, &c) | ||
cmd.FailOnError(err, "Reading JSON config file into config structure") | ||
|
||
features.Set(c.SFE.Features) | ||
|
||
if *listenAddr != "" { | ||
c.SFE.ListenAddress = *listenAddr | ||
} | ||
if c.SFE.ListenAddress == "" { | ||
cmd.Fail("HTTP listen address is not configured") | ||
} | ||
if *debugAddr != "" { | ||
c.SFE.DebugAddr = *debugAddr | ||
} | ||
|
||
stats, logger, oTelShutdown := cmd.StatsAndLogging(c.Syslog, c.OpenTelemetry, c.SFE.DebugAddr) | ||
logger.Info(cmd.VersionString()) | ||
|
||
clk := cmd.Clock() | ||
|
||
unpauseHMACKey, err := c.SFE.Unpause.HMACKey.Pass() | ||
cmd.FailOnError(err, "Failed to load unpauseHMACKey") | ||
|
||
if len(unpauseHMACKey) != 32 { | ||
cmd.Fail("Invalid unpauseHMACKey length, should be 32 alphanumeric characters") | ||
} | ||
|
||
// The jose.SigningKey key interface where this is used can be satisfied by | ||
// a byte slice, not a string. | ||
unpauseHMACKeyBytes := []byte(unpauseHMACKey) | ||
|
||
tlsConfig, err := c.SFE.TLS.Load(stats) | ||
cmd.FailOnError(err, "TLS config") | ||
|
||
raConn, err := bgrpc.ClientSetup(c.SFE.RAService, tlsConfig, stats, clk) | ||
cmd.FailOnError(err, "Failed to load credentials and create gRPC connection to RA") | ||
rac := rapb.NewRegistrationAuthorityClient(raConn) | ||
|
||
saConn, err := bgrpc.ClientSetup(c.SFE.SAService, tlsConfig, stats, clk) | ||
cmd.FailOnError(err, "Failed to load credentials and create gRPC connection to SA") | ||
sac := sapb.NewStorageAuthorityReadOnlyClient(saConn) | ||
|
||
sfei, err := sfe.NewSelfServiceFrontEndImpl( | ||
stats, | ||
clk, | ||
logger, | ||
c.SFE.Timeout.Duration, | ||
rac, | ||
sac, | ||
unpauseHMACKeyBytes, | ||
) | ||
cmd.FailOnError(err, "Unable to create SFE") | ||
|
||
logger.Infof("Server running, listening on %s....", c.SFE.ListenAddress) | ||
handler := sfei.Handler(stats, c.OpenTelemetryHTTPConfig.Options()...) | ||
|
||
srv := web.NewServer(c.SFE.ListenAddress, handler, logger) | ||
go func() { | ||
err := srv.ListenAndServe() | ||
if err != nil && err != http.ErrServerClosed { | ||
cmd.FailOnError(err, "Running HTTP server") | ||
} | ||
}() | ||
|
||
// When main is ready to exit (because it has received a shutdown signal), | ||
// gracefully shutdown the servers. Calling these shutdown functions causes | ||
// ListenAndServe() and ListenAndServeTLS() to immediately return, then waits | ||
// for any lingering connection-handling goroutines to finish their work. | ||
defer func() { | ||
ctx, cancel := context.WithTimeout(context.Background(), c.SFE.ShutdownStopTimeout.Duration) | ||
defer cancel() | ||
_ = srv.Shutdown(ctx) | ||
oTelShutdown(ctx) | ||
}() | ||
|
||
cmd.WaitForSignal() | ||
} | ||
|
||
func init() { | ||
cmd.RegisterCommand("sfe", main, &cmd.ConfigValidator{Config: &Config{}}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
<!doctype html> | ||
<html dir="ltr" lang="en-US"> | ||
<header> | ||
<title>Self-Service Frontend</title> | ||
{{ template "meta" }} | ||
</header> | ||
<body> | ||
<h1>No Action Required</h1> | ||
<div> | ||
<p> | ||
There is no action for you to take. This page is intended for | ||
Subscribers whose accounts have been temporarily restricted from | ||
requesting new certificates for certain hostnames, following a | ||
significant number of failed validation attempts without any recent | ||
successes. If your account was paused, your <a | ||
href="https://letsencrypt.org/docs/client-options/">ACME client</a> | ||
would provide you with a URL to visit to unpause your account. | ||
</p> | ||
</div> | ||
</body> | ||
|
||
{{template "footer"}} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
<!doctype html> | ||
<html dir="ltr" lang="en-US"> | ||
<header> | ||
<title>Unpause - Self-Service Frontend</title> | ||
{{ template "meta" }} | ||
</header> | ||
<body> | ||
<div> | ||
<h1>Action Required to Unpause Your ACME Account</h1> | ||
|
||
<p> | ||
You have been directed to this page because your Account ID {{ .AccountID }} | ||
is temporarily restricted from requesting new certificates for certain | ||
hostnames including, but potentially not limited to, the following: | ||
<ul> | ||
{{ range $domain := .PausedDomains }}<li>{{ $domain }}</li>{{ end }} | ||
</ul> | ||
</p> | ||
|
||
<h2>Why Did This Happen?</h2> | ||
<p> | ||
This often happens when domain names expire, point to new hosts, or if | ||
there are issues with the DNS configuration or web server settings. | ||
These problems prevent your ACME client from successfully <a | ||
href="https://letsencrypt.org/how-it-works/">validating control over the | ||
domain</a>, which is necessary for issuing TLS certificates. | ||
</p> | ||
|
||
<h2>What Can You Do?</h2> | ||
<p> | ||
Please check the DNS configuration and web server settings for the | ||
affected hostnames. Ensure they are properly set up to respond to ACME | ||
challenges. This might involve updating DNS records, renewing domain | ||
registrations, or adjusting web server configurations. If you use a | ||
hosting provider or third-party service for domain management, you may | ||
need to coordinate with them. If you believe you've fixed the underlying | ||
issue, consider attempting issuance against our <a | ||
href="https://letsencrypt.org/docs/staging-environment/">staging | ||
environment</a> to verify your fix. | ||
</p> | ||
|
||
<h2>Ready to Unpause?</h2> | ||
<p> | ||
Once you have addressed these issues, click the button below to remove | ||
the pause on your account. This action will allow you to resume | ||
requesting certificates for all affected hostnames associated with your | ||
account. | ||
</p> | ||
<p> | ||
<strong>Note:</strong> If you face difficulties unpausing your account or | ||
need more guidance, our <a | ||
href="https://community.letsencrypt.org">community support forum</a> is | ||
a great resource for troubleshooting and advice. | ||
</p> | ||
<div> | ||
<form action="{{ .UnpauseFormRedirectionPath }}?jwt={{ .JWT }}" method="POST"> | ||
<button class="primary" id="submit">Please Unpause My Account</button> | ||
</form> | ||
</div> | ||
|
||
</div> | ||
</body> | ||
|
||
{{template "footer"}} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
<!doctype html> | ||
<html dir="ltr" lang="en-US"> | ||
<header> | ||
<title>Unpause - Self-Service Frontend</title> | ||
{{ template "meta" }} | ||
</header> | ||
<body> | ||
<div> | ||
<h1>Invalid Request To Unpause Account</h1> | ||
<p> | ||
Your unpause request was invalid meaning that we could not find all of | ||
the data required in the URL. Please verify you copied the log line from | ||
your client correctly. You may visit our <a | ||
href="https://community.letsencrypt.org">community forum</a> and request | ||
assistance if the problem persists. | ||
</p> | ||
</div> | ||
</body> | ||
|
||
{{template "footer"}} |
Oops, something went wrong.