diff --git a/README.md b/README.md index 81f12c7..85d47ad 100644 --- a/README.md +++ b/README.md @@ -11,5 +11,6 @@ - `ACME_EMAIL` (default: `noreply@example.email`): Set this to "true" to accept the Terms of Service of your ACME provider. - `ACME_EAB_KID` & `ACME_EAB_HMAC` (default: don't use EAB): EAB credentials, for example for ZeroSSL. - `ACME_ACCEPT_TERMS` (default: use self-signed certificate): Set this to "true" to accept the Terms of Service of your ACME provider. +- `ENABLE_HTTP_SERVER` (default: false): Set this to true to enable the HTTP-01 challenge and redirect all other HTTP requests to HTTPS. Currently only works with port 80. - `DNS_PROVIDER` (default: use self-signed certificate): Code of the ACME DNS provider for the main domain wildcard. See https://go-acme.github.io/lego/dns/ for available values & additional environment variables. diff --git a/certificates.go b/certificates.go index 501ae37..89961b1 100644 --- a/certificates.go +++ b/certificates.go @@ -131,7 +131,6 @@ var tlsConfig = &tls.Config{ }, } -var challengeCache = mcache.New() var keyCache = mcache.New() var keyDatabase *pogreb.DB @@ -189,6 +188,7 @@ var acmeClientOrderLimit = equalizer.NewTokenBucket(25, 15 * time.Minute) // rate limit is 20 / second, we want 10 / second var acmeClientRequestLimit = equalizer.NewTokenBucket(10, 1 * time.Second) +var challengeCache = mcache.New() type AcmeTLSChallengeProvider struct{} var _ challenge.Provider = AcmeTLSChallengeProvider{} func (a AcmeTLSChallengeProvider) Present(domain, _, keyAuth string) error { @@ -198,6 +198,15 @@ func (a AcmeTLSChallengeProvider) CleanUp(domain, _, _ string) error { challengeCache.Remove(domain) return nil } +type AcmeHTTPChallengeProvider struct{} +var _ challenge.Provider = AcmeHTTPChallengeProvider{} +func (a AcmeHTTPChallengeProvider) Present(domain, token, keyAuth string) error { + return challengeCache.Set(domain + "/" + token, keyAuth, 1*time.Hour) +} +func (a AcmeHTTPChallengeProvider) CleanUp(domain, token, _ string) error { + challengeCache.Remove(domain + "/" + token) + return nil +} func retrieveCertFromDB(sni []byte) (tls.Certificate, bool) { // parse certificate from database @@ -383,7 +392,14 @@ func setupCertificates() { } acmeClient = newAcmeClient(func(challenge *resolver.SolverManager) error { - return challenge.SetTLSALPN01Provider(AcmeTLSChallengeProvider{}) + err = challenge.SetTLSALPN01Provider(AcmeTLSChallengeProvider{}) + if err != nil { + return err + } + if os.Getenv("ENABLE_HTTP_SERVER") == "true" { + return challenge.SetHTTP01Provider(AcmeHTTPChallengeProvider{}) + } + return err }) mainDomainAcmeClient = newAcmeClient(func(challenge *resolver.SolverManager) error { if os.Getenv("DNS_PROVIDER") == "" { diff --git a/main.go b/main.go index c795f1f..c181977 100644 --- a/main.go +++ b/main.go @@ -22,6 +22,7 @@ import ( "fmt" "log" "net" + "net/http" "os" "time" @@ -103,6 +104,26 @@ func main() { listener = tls.NewListener(listener, tlsConfig) setupCertificates() + if os.Getenv("ENABLE_HTTP_SERVER") == "true" { + go (func() { + challengePath := []byte("/.well-known/acme-challenge/") + err := fasthttp.ListenAndServe("[::]:80", func(ctx *fasthttp.RequestCtx) { + if bytes.HasPrefix(ctx.Path(), challengePath) { + challenge, ok := challengeCache.Get(string(TrimHostPort(ctx.Host())) + "/" + string(bytes.TrimPrefix(ctx.Path(), challengePath))) + if !ok { + ctx.SetStatusCode(http.StatusNotFound) + ctx.SetBodyString("no challenge for this token") + } + ctx.SetBodyString(challenge.(string)) + } else { + ctx.Redirect("https://" + string(ctx.Host()) + string(ctx.RequestURI()), http.StatusMovedPermanently) + } + }) + if err != nil { + log.Fatalf("Couldn't start HTTP server: %s", err) + } + })() + } // Start the web server err = server.Serve(listener)