2019-03-11 16:56:48 +00:00
package http01
2016-01-15 04:06:25 +00:00
import (
"fmt"
"net"
"net/http"
2019-10-05 11:44:38 +00:00
"net/textproto"
2016-01-15 04:06:25 +00:00
"strings"
2018-05-30 17:53:04 +00:00
2019-07-30 19:19:32 +00:00
"github.com/go-acme/lego/v3/log"
2016-01-15 04:06:25 +00:00
)
2018-12-06 21:50:17 +00:00
// ProviderServer implements ChallengeProvider for `http-01` challenge
// It may be instantiated without using the NewProviderServer function if
2016-02-14 15:56:14 +00:00
// you want only to use the default values.
2018-12-06 21:50:17 +00:00
type ProviderServer struct {
2016-01-15 04:06:25 +00:00
iface string
port string
2019-10-05 11:44:38 +00:00
matcher domainMatcher
2016-01-15 04:06:25 +00:00
done chan bool
listener net . Listener
}
2018-12-06 21:50:17 +00:00
// NewProviderServer creates a new ProviderServer on the selected interface and port.
2016-02-14 15:56:14 +00:00
// Setting iface and / or port to an empty string will make the server fall back to
// the "any" interface and port 80 respectively.
2018-12-06 21:50:17 +00:00
func NewProviderServer ( iface , port string ) * ProviderServer {
2019-10-05 11:44:38 +00:00
if port == "" {
port = "80"
}
return & ProviderServer { iface : iface , port : port , matcher : & hostMatcher { } }
2016-02-14 15:56:14 +00:00
}
2018-12-06 21:50:17 +00:00
// Present starts a web server and makes the token available at `ChallengePath(token)` for web requests.
func ( s * ProviderServer ) Present ( domain , token , keyAuth string ) error {
2016-01-15 04:06:25 +00:00
var err error
2018-12-06 21:50:17 +00:00
s . listener , err = net . Listen ( "tcp" , s . GetAddress ( ) )
2016-01-15 04:06:25 +00:00
if err != nil {
2020-02-27 18:14:46 +00:00
return fmt . Errorf ( "could not start HTTP server for challenge -> %w" , err )
2016-01-15 04:06:25 +00:00
}
s . done = make ( chan bool )
go s . serve ( domain , token , keyAuth )
return nil
}
2018-12-06 21:50:17 +00:00
func ( s * ProviderServer ) GetAddress ( ) string {
return net . JoinHostPort ( s . iface , s . port )
}
// CleanUp closes the HTTP server and removes the token from `ChallengePath(token)`
func ( s * ProviderServer ) CleanUp ( domain , token , keyAuth string ) error {
2016-01-15 04:06:25 +00:00
if s . listener == nil {
return nil
}
s . listener . Close ( )
<- s . done
return nil
}
2019-10-05 11:44:38 +00:00
// SetProxyHeader changes the validation of incoming requests.
// By default, s matches the "Host" header value to the domain name.
//
// When the server runs behind a proxy server, this is not the correct place to look at;
// Apache and NGINX have traditionally moved the original Host header into a new header named "X-Forwarded-Host".
// Other webservers might use different names;
// and RFC7239 has standadized a new header named "Forwarded" (with slightly different semantics).
//
// The exact behavior depends on the value of headerName:
// - "" (the empty string) and "Host" will restore the default and only check the Host header
// - "Forwarded" will look for a Forwarded header, and inspect it according to https://tools.ietf.org/html/rfc7239
// - any other value will check the header value with the same name
func ( s * ProviderServer ) SetProxyHeader ( headerName string ) {
switch h := textproto . CanonicalMIMEHeaderKey ( headerName ) ; h {
case "" , "Host" :
s . matcher = & hostMatcher { }
case "Forwarded" :
s . matcher = & forwardedMatcher { }
default :
s . matcher = arbitraryMatcher ( h )
}
}
2018-12-06 21:50:17 +00:00
func ( s * ProviderServer ) serve ( domain , token , keyAuth string ) {
path := ChallengePath ( token )
2016-01-15 04:06:25 +00:00
2019-10-05 11:44:38 +00:00
// The incoming request must will be validated to prevent DNS rebind attacks.
// We only respond with the keyAuth, when we're receiving a GET requests with
// the "Host" header matching the domain (the latter is configurable though SetProxyHeader).
2016-01-15 04:06:25 +00:00
mux := http . NewServeMux ( )
mux . HandleFunc ( path , func ( w http . ResponseWriter , r * http . Request ) {
2019-10-05 11:44:38 +00:00
if r . Method == http . MethodGet && s . matcher . matches ( r , domain ) {
2016-01-15 04:06:25 +00:00
w . Header ( ) . Add ( "Content-Type" , "text/plain" )
2018-09-24 19:07:20 +00:00
_ , err := w . Write ( [ ] byte ( keyAuth ) )
if err != nil {
http . Error ( w , err . Error ( ) , http . StatusInternalServerError )
return
}
2018-06-21 17:06:16 +00:00
log . Infof ( "[%s] Served key authentication" , domain )
2016-01-15 04:06:25 +00:00
} else {
2019-10-05 11:44:38 +00:00
log . Warnf ( "Received request for domain %s with method %s but the domain did not match any challenge. Please ensure your are passing the %s header properly." , r . Host , r . Method , s . matcher . name ( ) )
2018-09-24 19:07:20 +00:00
_ , err := w . Write ( [ ] byte ( "TEST" ) )
if err != nil {
http . Error ( w , err . Error ( ) , http . StatusInternalServerError )
return
}
2016-01-15 04:06:25 +00:00
}
} )
2018-09-24 19:07:20 +00:00
httpServer := & http . Server { Handler : mux }
2018-12-06 21:50:17 +00:00
// Once httpServer is shut down
// we don't want any lingering connections, so disable KeepAlives.
2016-02-07 13:25:31 +00:00
httpServer . SetKeepAlivesEnabled ( false )
2018-09-24 19:07:20 +00:00
err := httpServer . Serve ( s . listener )
2018-12-06 21:50:17 +00:00
if err != nil && ! strings . Contains ( err . Error ( ) , "use of closed network connection" ) {
2018-09-24 19:07:20 +00:00
log . Println ( err )
}
2016-01-15 04:06:25 +00:00
s . done <- true
}