traefik/vendor/github.com/xenolf/lego/acme/http.go

213 lines
6.4 KiB
Go
Raw Normal View History

2018-05-31 07:30:04 +00:00
package acme
2018-03-26 12:12:03 +00:00
import (
2018-07-03 10:44:04 +00:00
"crypto/tls"
"crypto/x509"
2018-03-26 12:12:03 +00:00
"encoding/json"
"errors"
"fmt"
"io"
2018-07-03 10:44:04 +00:00
"io/ioutil"
2018-03-26 12:12:03 +00:00
"net"
"net/http"
2018-07-03 10:44:04 +00:00
"os"
2018-03-26 12:12:03 +00:00
"runtime"
"strings"
"time"
)
2018-07-03 10:44:04 +00:00
var (
// UserAgent (if non-empty) will be tacked onto the User-Agent string in requests.
UserAgent string
2018-03-26 12:12:03 +00:00
2018-07-03 10:44:04 +00:00
// HTTPClient is an HTTP client with a reasonable timeout value and
// potentially a custom *x509.CertPool based on the caCertificatesEnvVar
// environment variable (see the `initCertPool` function)
HTTPClient = http.Client{
Transport: &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}).DialContext,
TLSHandshakeTimeout: 15 * time.Second,
ResponseHeaderTimeout: 15 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
TLSClientConfig: &tls.Config{
ServerName: os.Getenv(caServerNameEnvVar),
RootCAs: initCertPool(),
},
},
}
)
2018-03-26 12:12:03 +00:00
const (
// ourUserAgent is the User-Agent of this underlying library package.
2018-11-19 15:40:03 +00:00
// NOTE: Update this with each tagged release.
ourUserAgent = "xenolf-acme/1.2.1"
// ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package.
// values: detach|release
// NOTE: Update this with each tagged release.
ourUserAgentComment = "detach"
2018-07-03 10:44:04 +00:00
// caCertificatesEnvVar is the environment variable name that can be used to
// specify the path to PEM encoded CA Certificates that can be used to
// authenticate an ACME server with a HTTPS certificate not issued by a CA in
// the system-wide trusted root list.
caCertificatesEnvVar = "LEGO_CA_CERTIFICATES"
// caServerNameEnvVar is the environment variable name that can be used to
// specify the CA server name that can be used to
// authenticate an ACME server with a HTTPS certificate not issued by a CA in
// the system-wide trusted root list.
caServerNameEnvVar = "LEGO_CA_SERVER_NAME"
2018-03-26 12:12:03 +00:00
)
2018-07-03 10:44:04 +00:00
// initCertPool creates a *x509.CertPool populated with the PEM certificates
// found in the filepath specified in the caCertificatesEnvVar OS environment
// variable. If the caCertificatesEnvVar is not set then initCertPool will
// return nil. If there is an error creating a *x509.CertPool from the provided
// caCertificatesEnvVar value then initCertPool will panic.
func initCertPool() *x509.CertPool {
if customCACertsPath := os.Getenv(caCertificatesEnvVar); customCACertsPath != "" {
customCAs, err := ioutil.ReadFile(customCACertsPath)
if err != nil {
panic(fmt.Sprintf("error reading %s=%q: %v",
caCertificatesEnvVar, customCACertsPath, err))
}
certPool := x509.NewCertPool()
if ok := certPool.AppendCertsFromPEM(customCAs); !ok {
panic(fmt.Sprintf("error creating x509 cert pool from %s=%q: %v",
caCertificatesEnvVar, customCACertsPath, err))
}
return certPool
}
return nil
}
2018-03-26 12:12:03 +00:00
// httpHead performs a HEAD request with a proper User-Agent string.
// The response body (resp.Body) is already closed when this function returns.
func httpHead(url string) (resp *http.Response, err error) {
2018-07-03 10:44:04 +00:00
req, err := http.NewRequest(http.MethodHead, url, nil)
2018-03-26 12:12:03 +00:00
if err != nil {
return nil, fmt.Errorf("failed to head %q: %v", url, err)
}
req.Header.Set("User-Agent", userAgent())
resp, err = HTTPClient.Do(req)
if err != nil {
return resp, fmt.Errorf("failed to do head %q: %v", url, err)
}
resp.Body.Close()
return resp, err
}
// httpPost performs a POST request with a proper User-Agent string.
// Callers should close resp.Body when done reading from it.
func httpPost(url string, bodyType string, body io.Reader) (resp *http.Response, err error) {
2018-07-03 10:44:04 +00:00
req, err := http.NewRequest(http.MethodPost, url, body)
2018-03-26 12:12:03 +00:00
if err != nil {
return nil, fmt.Errorf("failed to post %q: %v", url, err)
}
req.Header.Set("Content-Type", bodyType)
req.Header.Set("User-Agent", userAgent())
return HTTPClient.Do(req)
}
// httpGet performs a GET request with a proper User-Agent string.
// Callers should close resp.Body when done reading from it.
func httpGet(url string) (resp *http.Response, err error) {
2018-07-03 10:44:04 +00:00
req, err := http.NewRequest(http.MethodGet, url, nil)
2018-03-26 12:12:03 +00:00
if err != nil {
return nil, fmt.Errorf("failed to get %q: %v", url, err)
}
req.Header.Set("User-Agent", userAgent())
return HTTPClient.Do(req)
}
// getJSON performs an HTTP GET request and parses the response body
// as JSON, into the provided respBody object.
func getJSON(uri string, respBody interface{}) (http.Header, error) {
resp, err := httpGet(uri)
if err != nil {
return nil, fmt.Errorf("failed to get json %q: %v", uri, err)
}
defer resp.Body.Close()
if resp.StatusCode >= http.StatusBadRequest {
return resp.Header, handleHTTPError(resp)
}
return resp.Header, json.NewDecoder(resp.Body).Decode(respBody)
}
// postJSON performs an HTTP POST request and parses the response body
// as JSON, into the provided respBody object.
func postJSON(j *jws, uri string, reqBody, respBody interface{}) (http.Header, error) {
jsonBytes, err := json.Marshal(reqBody)
if err != nil {
2018-10-10 14:28:04 +00:00
return nil, errors.New("failed to marshal network message")
2018-03-26 12:12:03 +00:00
}
2018-11-19 15:40:03 +00:00
resp, err := post(j, uri, jsonBytes, respBody)
if resp == nil {
return nil, err
2018-03-26 12:12:03 +00:00
}
defer resp.Body.Close()
2018-11-19 15:40:03 +00:00
return resp.Header, err
}
func postAsGet(j *jws, uri string, respBody interface{}) (*http.Response, error) {
return post(j, uri, []byte{}, respBody)
}
func post(j *jws, uri string, reqBody []byte, respBody interface{}) (*http.Response, error) {
resp, err := j.post(uri, reqBody)
if err != nil {
return nil, fmt.Errorf("failed to post JWS message. -> %v", err)
}
2018-03-26 12:12:03 +00:00
if resp.StatusCode >= http.StatusBadRequest {
2018-10-10 14:28:04 +00:00
err = handleHTTPError(resp)
2018-03-26 12:12:03 +00:00
switch err.(type) {
case NonceError:
// Retry once if the nonce was invalidated
2018-11-19 15:40:03 +00:00
retryResp, errP := j.post(uri, reqBody)
2018-10-10 14:28:04 +00:00
if errP != nil {
return nil, fmt.Errorf("failed to post JWS message. -> %v", errP)
2018-03-26 12:12:03 +00:00
}
if retryResp.StatusCode >= http.StatusBadRequest {
2018-11-19 15:40:03 +00:00
return retryResp, handleHTTPError(retryResp)
2018-03-26 12:12:03 +00:00
}
if respBody == nil {
2018-11-19 15:40:03 +00:00
return retryResp, nil
2018-03-26 12:12:03 +00:00
}
2018-11-19 15:40:03 +00:00
return retryResp, json.NewDecoder(retryResp.Body).Decode(respBody)
2018-03-26 12:12:03 +00:00
default:
2018-11-19 15:40:03 +00:00
return resp, err
2018-03-26 12:12:03 +00:00
}
}
if respBody == nil {
2018-11-19 15:40:03 +00:00
return resp, nil
2018-03-26 12:12:03 +00:00
}
2018-11-19 15:40:03 +00:00
return resp, json.NewDecoder(resp.Body).Decode(respBody)
2018-03-26 12:12:03 +00:00
}
// userAgent builds and returns the User-Agent string to use in requests.
func userAgent() string {
2018-11-19 15:40:03 +00:00
ua := fmt.Sprintf("%s %s (%s; %s; %s)", UserAgent, ourUserAgent, ourUserAgentComment, runtime.GOOS, runtime.GOARCH)
2018-03-26 12:12:03 +00:00
return strings.TrimSpace(ua)
}