package certificates import ( "net/http" "strings" "sync" ) // httpChallengeProvider implements lego's challenge.Provider for HTTP-01, // backed by an in-memory token map served by our own HTTP listener at // /.well-known/acme-challenge/. This avoids spinning up a second server on // port 80 — the main proxy HTTP listener delegates to ServeHTTP below. type httpChallengeProvider struct { mu sync.RWMutex tokens map[string]string // token -> key authorization } func newHTTPChallengeProvider() *httpChallengeProvider { return &httpChallengeProvider{tokens: map[string]string{}} } func (p *httpChallengeProvider) Present(domain, token, keyAuth string) error { p.mu.Lock() defer p.mu.Unlock() p.tokens[token] = keyAuth return nil } func (p *httpChallengeProvider) CleanUp(domain, token, keyAuth string) error { p.mu.Lock() defer p.mu.Unlock() delete(p.tokens, token) return nil } const acmeChallengePrefix = "/.well-known/acme-challenge/" // ServeHTTP handles ACME HTTP-01 challenge requests. Returns true if the // request was an ACME challenge (handled or not found), false if the // caller should continue normal routing. func (p *httpChallengeProvider) ServeHTTP(w http.ResponseWriter, r *http.Request) bool { if !strings.HasPrefix(r.URL.Path, acmeChallengePrefix) { return false } token := strings.TrimPrefix(r.URL.Path, acmeChallengePrefix) p.mu.RLock() keyAuth, ok := p.tokens[token] p.mu.RUnlock() if !ok { http.NotFound(w, r) return true } w.Header().Set("Content-Type", "text/plain") w.Write([]byte(keyAuth)) return true }