mirror of
https://github.com/caddyserver/caddy.git
synced 2024-12-01 21:24:23 +08:00
letsencrypt: Support for http-01, awkwardly straddling that and SimpleHTTP for now
This commit is contained in:
parent
659df6967e
commit
580b50ea20
|
@ -2,62 +2,73 @@ package letsencrypt
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httputil"
|
"net/http/httputil"
|
||||||
"net/url"
|
"net/url"
|
||||||
"sync"
|
"strings"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
"github.com/mholt/caddy/middleware"
|
"github.com/mholt/caddy/middleware"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Handler is a Caddy middleware that can proxy ACME requests
|
const challengeBasePath = "/.well-known/acme-challenge"
|
||||||
// to the real ACME endpoint. This is necessary to renew certificates
|
|
||||||
// while the server is running. Obviously, a site served on port
|
// Handler is a Caddy middleware that can proxy ACME challenge
|
||||||
// 443 (HTTPS) binds to that port, so another listener created by
|
// requests to the real ACME client endpoint. This is necessary
|
||||||
// our acme client can't bind successfully and solve the challenge.
|
// to renew certificates while the server is running.
|
||||||
// Thus, we chain this handler in so that it can, when activated,
|
|
||||||
// proxy ACME requests to an ACME client listening on an alternate
|
|
||||||
// port.
|
|
||||||
type Handler struct {
|
type Handler struct {
|
||||||
sync.Mutex // protects the ChallengePath property
|
|
||||||
Next middleware.Handler
|
Next middleware.Handler
|
||||||
ChallengeActive int32 // use sync/atomic for speed to set/get this flag
|
ChallengeActive int32 // TODO: use sync/atomic to set/get this flag safely and efficiently
|
||||||
ChallengePath string // the exact request path to match before proxying
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServeHTTP is basically a no-op unless an ACME challenge is active on this host
|
// ServeHTTP is basically a no-op unless an ACME challenge is active on this host
|
||||||
// and the request path matches the expected path exactly.
|
// and the request path matches the expected path exactly.
|
||||||
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
|
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
// Only if challenge is active
|
// Only if challenge is active
|
||||||
if atomic.LoadInt32(&h.ChallengeActive) == 1 {
|
// TODO: this won't work until the global challenge hook in the acme package is ready
|
||||||
h.Lock()
|
//if atomic.LoadInt32(&h.ChallengeActive) == 1 {
|
||||||
path := h.ChallengePath
|
|
||||||
h.Unlock()
|
|
||||||
|
|
||||||
// Request path must be correct; if so, proxy to ACME client
|
// Proxy challenge requests to ACME client
|
||||||
if r.URL.Path == path {
|
if strings.HasPrefix(r.URL.Path, challengeBasePath) {
|
||||||
upstream, err := url.Parse("https://" + r.Host + ":" + alternatePort)
|
scheme := "http"
|
||||||
|
if r.TLS != nil {
|
||||||
|
scheme = "https"
|
||||||
|
}
|
||||||
|
|
||||||
|
hostname, _, err := net.SplitHostPort(r.URL.Host)
|
||||||
|
if err != nil {
|
||||||
|
hostname = r.URL.Host
|
||||||
|
}
|
||||||
|
|
||||||
|
upstream, err := url.Parse(scheme + "://" + hostname + ":" + alternatePort)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return http.StatusInternalServerError, err
|
return http.StatusInternalServerError, err
|
||||||
}
|
}
|
||||||
|
|
||||||
proxy := httputil.NewSingleHostReverseProxy(upstream)
|
proxy := httputil.NewSingleHostReverseProxy(upstream)
|
||||||
proxy.Transport = &http.Transport{
|
proxy.Transport = &http.Transport{
|
||||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, // client uses self-signed cert
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, // client uses self-signed cert
|
||||||
}
|
}
|
||||||
proxy.ServeHTTP(w, r)
|
proxy.ServeHTTP(w, r)
|
||||||
|
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
//}
|
||||||
|
|
||||||
return h.Next.ServeHTTP(w, r)
|
return h.Next.ServeHTTP(w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: SimpleHTTP deprecation imminent!! meaning these
|
||||||
|
// challenge handlers will go away and be replaced with
|
||||||
|
// something else.
|
||||||
|
|
||||||
// ChallengeOn enables h to proxy ACME requests.
|
// ChallengeOn enables h to proxy ACME requests.
|
||||||
func (h *Handler) ChallengeOn(challengePath string) {
|
func (h *Handler) ChallengeOn(challengePath string) {
|
||||||
h.Lock()
|
// h.Lock()
|
||||||
h.ChallengePath = challengePath
|
// h.ChallengePath = challengePath
|
||||||
h.Unlock()
|
// h.Unlock()
|
||||||
atomic.StoreInt32(&h.ChallengeActive, 1)
|
atomic.StoreInt32(&h.ChallengeActive, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -344,22 +344,32 @@ func autoConfigure(allConfigs []server.Config, cfgIndex int) []server.Config {
|
||||||
cfg.Port = "https"
|
cfg.Port = "https"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Chain in ACME middleware proxy if we use up the SSL port
|
// Set up http->https redirect as long as there isn't already a http counterpart
|
||||||
if cfg.Port == "https" || cfg.Port == "443" {
|
// in the configs and this isn't, for some reason, already on port 80.
|
||||||
|
// Also, the port 80 variant of this config is necessary for proxying challenge requests.
|
||||||
|
if !otherHostHasScheme(allConfigs, cfgIndex, "http") &&
|
||||||
|
cfg.Port != "80" && cfg.Port != "http" { // (would not be http port with current program flow, but just in case)
|
||||||
|
allConfigs = append(allConfigs, redirPlaintextHost(*cfg))
|
||||||
|
}
|
||||||
|
|
||||||
|
// To support renewals, we need handlers at ports 80 and 443,
|
||||||
|
// depending on the challenge type that is used to complete renewal.
|
||||||
|
// Every proxy for this host can share the handler.
|
||||||
handler := new(Handler)
|
handler := new(Handler)
|
||||||
mid := func(next middleware.Handler) middleware.Handler {
|
mid := func(next middleware.Handler) middleware.Handler {
|
||||||
handler.Next = next
|
handler.Next = next
|
||||||
return handler
|
return handler
|
||||||
}
|
}
|
||||||
cfg.Middleware["/"] = append(cfg.Middleware["/"], mid)
|
|
||||||
acmeHandlers[cfg.Host] = handler
|
acmeHandlers[cfg.Host] = handler
|
||||||
}
|
|
||||||
|
|
||||||
// Set up http->https redirect as long as there isn't already a http counterpart
|
// Handler needs to go in 80 and 443
|
||||||
// in the configs and this isn't, for some reason, already on port 80
|
for i, c := range allConfigs {
|
||||||
if !otherHostHasScheme(allConfigs, cfgIndex, "http") &&
|
if c.Address() == cfg.Host+":80" ||
|
||||||
cfg.Port != "80" && cfg.Port != "http" { // (would not be http port with current program flow, but just in case)
|
c.Address() == cfg.Host+":443" ||
|
||||||
allConfigs = append(allConfigs, redirPlaintextHost(*cfg))
|
c.Address() == cfg.Host+":http" ||
|
||||||
|
c.Address() == cfg.Host+":https" {
|
||||||
|
allConfigs[i].Middleware["/"] = append(allConfigs[i].Middleware["/"], mid)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return allConfigs
|
return allConfigs
|
||||||
|
|
|
@ -144,9 +144,7 @@ func renewCertificates(configs []server.Config, useCustomPort bool) (int, []erro
|
||||||
acme.OnSimpleHTTPStart = acmeHandlers[cfg.Host].ChallengeOn
|
acme.OnSimpleHTTPStart = acmeHandlers[cfg.Host].ChallengeOn
|
||||||
acme.OnSimpleHTTPEnd = acmeHandlers[cfg.Host].ChallengeOff
|
acme.OnSimpleHTTPEnd = acmeHandlers[cfg.Host].ChallengeOff
|
||||||
|
|
||||||
// Renew certificate.
|
// Renew certificate
|
||||||
// TODO: revokeOld should be an option in the caddyfile
|
|
||||||
// TODO: bundle should be an option in the caddyfile as well :)
|
|
||||||
Renew:
|
Renew:
|
||||||
newCertMeta, err := client.RenewCertificate(certMeta, true, true)
|
newCertMeta, err := client.RenewCertificate(certMeta, true, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user